зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
02bd3e02b7
|
@ -157,6 +157,7 @@ media/libvorbis/.*
|
|||
media/libvpx/.*
|
||||
media/libwebp/.*
|
||||
media/libyuv/.*
|
||||
media/mozva/va/.*
|
||||
media/openmax_dl/.*
|
||||
media/openmax_il/.*
|
||||
media/webrtc/signaling/src/sdp/sipcc/.*
|
||||
|
|
|
@ -202,14 +202,6 @@ panelview[mainview] > .panel-header {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tab-icon-image[fadein],
|
||||
.tab-close-button[fadein],
|
||||
.tabbrowser-tab[fadein]::after,
|
||||
.tab-background[fadein] {
|
||||
/* This transition is only wanted for opening tabs. */
|
||||
transition: visibility 0ms 25ms;
|
||||
}
|
||||
|
||||
.tab-icon-pending:not([fadein]),
|
||||
.tab-icon-image:not([fadein]),
|
||||
.tab-close-button:not([fadein]),
|
||||
|
|
|
@ -2616,6 +2616,9 @@ var gMainPane = {
|
|||
* Sort the list when the user clicks on a column header.
|
||||
*/
|
||||
sort(event) {
|
||||
if (event.button != 0) {
|
||||
return;
|
||||
}
|
||||
var column = event.target;
|
||||
|
||||
// If the user clicked on a new sort column, remove the direction indicator
|
||||
|
|
|
@ -604,21 +604,6 @@ const AVAILABLE_INJECTIONS = [
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bug1746883",
|
||||
platform: "all",
|
||||
domain: "zoom.us",
|
||||
bug: "1746883",
|
||||
contentScripts: {
|
||||
matches: ["*://*.zoom.us/*"],
|
||||
js: [
|
||||
{
|
||||
file: "injections/js/bug1746883-zoom.us-OffscreenCanvas.js",
|
||||
},
|
||||
],
|
||||
allFrames: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = AVAILABLE_INJECTIONS;
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 1746883 - disable OffscreenCanvas for Zoom
|
||||
*
|
||||
* When OffscreenCanvas is enabled, Zoom breaks due to Canvas2D not being
|
||||
* supported yet. As such, we disable OffscreenCanvas for now on Zoom.
|
||||
*/
|
||||
|
||||
if (window.OffscreenCanvas) {
|
||||
console.info(
|
||||
"OffscreenCanvas has been disabled for compatibility reasons. See https://bugzilla.mozilla.org/show_bug.cgi?id=1746883 for details."
|
||||
);
|
||||
|
||||
Object.defineProperty(window.wrappedJSObject, "OffscreenCanvas", {
|
||||
get: undefined,
|
||||
set: undefined,
|
||||
});
|
||||
|
||||
Object.defineProperty(
|
||||
HTMLCanvasElement.wrappedJSObject.prototype,
|
||||
"transferControlToOffscreen",
|
||||
{
|
||||
get: undefined,
|
||||
set: undefined,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
"name": "Web Compatibility Interventions",
|
||||
"description": "Urgent post-release fixes for web compatibility.",
|
||||
"version": "29.8.1",
|
||||
"version": "29.9.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "webcompat@mozilla.org",
|
||||
|
|
|
@ -84,7 +84,6 @@ FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["injections"]["js"] += [
|
|||
"injections/js/bug1724764-amextravel.com-window-print.js",
|
||||
"injections/js/bug1724868-news.yahoo.co.jp-ua-override.js",
|
||||
"injections/js/bug1731825-office365-email-handling-prompt-autohide.js",
|
||||
"injections/js/bug1746883-zoom.us-OffscreenCanvas.js",
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["shims"] += [
|
||||
|
|
|
@ -65,6 +65,7 @@ included_inclnames_to_ignore = set(
|
|||
"frontend/smoosh_generated.h", # generated in $OBJDIR
|
||||
"gc/StatsPhasesGenerated.h", # generated in $OBJDIR
|
||||
"gc/StatsPhasesGenerated.inc", # generated in $OBJDIR
|
||||
"jit/AtomicOperationsGenerated.h", # generated in $OBJDIR
|
||||
"jit/CacheIROpsGenerated.h", # generated in $OBJDIR
|
||||
"jit/LIROpsGenerated.h", # generated in $OBJDIR
|
||||
"jit/MIROpsGenerated.h", # generated in $OBJDIR
|
||||
|
|
|
@ -57,6 +57,7 @@ const {
|
|||
|
||||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
const POPUP_DEBUG_PREF = "devtools.popups.debug";
|
||||
|
||||
/**
|
||||
* DevTools is a class that represents a set of developer tools, it holds a
|
||||
|
@ -585,7 +586,10 @@ DevTools.prototype = {
|
|||
) {
|
||||
// Popups are debugged via the toolbox of their opener document/tab.
|
||||
// So avoid opening dedicated toolbox for them.
|
||||
if (tab.linkedBrowser.browsingContext.opener) {
|
||||
if (
|
||||
tab.linkedBrowser.browsingContext.opener &&
|
||||
Services.prefs.getBoolPref(POPUP_DEBUG_PREF)
|
||||
) {
|
||||
const openerTab = tab.ownerGlobal.gBrowser.getTabForBrowser(
|
||||
tab.linkedBrowser.browsingContext.opener.embedderElement
|
||||
);
|
||||
|
|
|
@ -124,6 +124,7 @@ skip-if =
|
|||
[browser_toolbox_options_enable_serviceworkers_testing.js]
|
||||
[browser_toolbox_options_frames_button.js]
|
||||
[browser_toolbox_options_panel_toggle.js]
|
||||
[browser_toolbox_popups_debugging.js]
|
||||
[browser_toolbox_raise.js]
|
||||
disabled=Bug 962258
|
||||
[browser_toolbox_races.js]
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test opening toolboxes against a tab and its popup
|
||||
|
||||
const TEST_URL = "data:text/html,test for debugging popups";
|
||||
const POPUP_URL = "data:text/html,popup";
|
||||
|
||||
const POPUP_DEBUG_PREF = "devtools.popups.debug";
|
||||
|
||||
add_task(async function() {
|
||||
const isPopupDebuggingEnabled = Services.prefs.getBoolPref(POPUP_DEBUG_PREF);
|
||||
|
||||
info("Open a tab and debug it");
|
||||
const tab = await addTab(TEST_URL);
|
||||
const toolbox = await gDevTools.showToolboxForTab(tab, {
|
||||
toolId: "webconsole",
|
||||
});
|
||||
|
||||
info("Open a popup");
|
||||
const onTabOpened = once(gBrowser.tabContainer, "TabOpen");
|
||||
const onToolboxSwitchedToTab = toolbox.once("switched-host-to-tab");
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [POPUP_URL], url => {
|
||||
content.open(url);
|
||||
});
|
||||
const tabOpenEvent = await onTabOpened;
|
||||
const popupTab = tabOpenEvent.target;
|
||||
|
||||
const popupToolbox = await gDevTools.showToolboxForTab(popupTab);
|
||||
if (isPopupDebuggingEnabled) {
|
||||
ok(
|
||||
!popupToolbox,
|
||||
"When popup debugging is enabled, the popup should be debugged via the same toolbox as the original tab"
|
||||
);
|
||||
info("Wait for internal event notifying about the toolbox being moved");
|
||||
await onToolboxSwitchedToTab;
|
||||
const browserContainer = gBrowser.getBrowserContainer(
|
||||
popupTab.linkedBrowser
|
||||
);
|
||||
const iframe = browserContainer.querySelector(
|
||||
".devtools-toolbox-bottom-iframe"
|
||||
);
|
||||
ok(iframe, "The original tab's toolbox moved to the popup tab");
|
||||
} else {
|
||||
ok(popupToolbox, "We were able to spawn a toolbox for the popup");
|
||||
info("Close the popup toolbox and its tab");
|
||||
await popupToolbox.destroy();
|
||||
}
|
||||
|
||||
info("Close the popup tab");
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
info("Close the original tab toolbox and itself");
|
||||
await toolbox.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
|
@ -3639,6 +3639,8 @@ Toolbox.prototype = {
|
|||
);
|
||||
|
||||
this.commands.targetCommand.selectTarget(target);
|
||||
|
||||
this.emit("switched-host-to-tab");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,12 +21,15 @@ const TEST_URI = `
|
|||
.shift {
|
||||
margin-left: 300px;
|
||||
}
|
||||
.has-before::before {
|
||||
content: "-";
|
||||
}
|
||||
</style>
|
||||
<div id="top" class="parent">
|
||||
<div id="child1" class="fixed shift">
|
||||
<div id="child2" class="fixed"></div>
|
||||
</div>
|
||||
<div id="child3" class="shift">
|
||||
<div id="child3" class="shift has-before">
|
||||
<div id="child4" class="fixed"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,9 +3,22 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/* import-globals-from ../../../debugger/test/mochitest/helpers.js */
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers.js",
|
||||
this
|
||||
);
|
||||
|
||||
/* import-globals-from ../../../debugger/test/mochitest/helpers/context.js */
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers/context.js",
|
||||
this
|
||||
);
|
||||
|
||||
const DOCUMENT_SRC = `
|
||||
<body>
|
||||
<button id="foo">Button</button>
|
||||
<button id="btn-eval">Eval</button>
|
||||
<button id="btn-dom0" onclick="console.info('bloup')">DOM0</button>
|
||||
<script>
|
||||
var script = \`
|
||||
function foo() {
|
||||
|
@ -14,7 +27,7 @@ var script = \`
|
|||
\`;
|
||||
eval(script);
|
||||
|
||||
var button = document.getElementById("foo");
|
||||
var button = document.getElementById("btn-eval");
|
||||
button.addEventListener("click", foo, false);
|
||||
</script>
|
||||
</body>`;
|
||||
|
@ -22,36 +35,94 @@ button.addEventListener("click", foo, false);
|
|||
const TEST_URI = "data:text/html;charset=utf-8," + DOCUMENT_SRC;
|
||||
|
||||
add_task(async function() {
|
||||
// Test that event handler links go to the right debugger source when it
|
||||
// came from an eval().
|
||||
const { inspector, tab, toolbox } = await openInspectorForURL(TEST_URI);
|
||||
const { inspector, toolbox } = await openInspectorForURL(TEST_URI);
|
||||
|
||||
const nodeFront = await getNodeFront("#foo", inspector);
|
||||
info(
|
||||
"Test that event handler links go to the right debugger source when it came from an eval()"
|
||||
);
|
||||
const evaledSource = await clickOnJumpToDebuggerIconForNode(
|
||||
inspector,
|
||||
toolbox,
|
||||
"#btn-eval"
|
||||
);
|
||||
is(evaledSource.url, null, "no expected url for eval source");
|
||||
|
||||
info("Add a breakpoint in opened source");
|
||||
const debuggerContext = createDebuggerContext(toolbox);
|
||||
await addBreakpoint(
|
||||
debuggerContext,
|
||||
debuggerContext.selectors.getSelectedSource(),
|
||||
1
|
||||
);
|
||||
await safeSynthesizeMouseEventAtCenterInContentPage("#btn-eval");
|
||||
|
||||
await waitForPaused(debuggerContext);
|
||||
ok(true, "The debugger paused on the evaled source breakpoint");
|
||||
await resume(debuggerContext);
|
||||
|
||||
info(
|
||||
"Test that event handler links go to the right debugger source when it's a dom0 event listener."
|
||||
);
|
||||
await toolbox.selectTool("inspector");
|
||||
const dom0Source = await clickOnJumpToDebuggerIconForNode(
|
||||
inspector,
|
||||
toolbox,
|
||||
"#btn-dom0"
|
||||
);
|
||||
is(dom0Source.url, null, "no expected url for dom0 event listener source");
|
||||
await addBreakpoint(
|
||||
debuggerContext,
|
||||
debuggerContext.selectors.getSelectedSource(),
|
||||
1
|
||||
);
|
||||
await safeSynthesizeMouseEventAtCenterInContentPage("#btn-dom0");
|
||||
await waitForPaused(debuggerContext);
|
||||
ok(true, "The debugger paused on the dom0 source breakpoint");
|
||||
await resume(debuggerContext);
|
||||
});
|
||||
|
||||
async function clickOnJumpToDebuggerIconForNode(
|
||||
inspector,
|
||||
toolbox,
|
||||
nodeSelector
|
||||
) {
|
||||
const nodeFront = await getNodeFront(nodeSelector, inspector);
|
||||
const container = getContainerForNodeFront(nodeFront, inspector);
|
||||
|
||||
const evHolder = container.elt.querySelector(
|
||||
".inspector-badge.interactive[data-event]"
|
||||
);
|
||||
|
||||
evHolder.scrollIntoView();
|
||||
info(`Display event tooltip for node "${nodeSelector}"`);
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
evHolder,
|
||||
{},
|
||||
inspector.markup.doc.defaultView
|
||||
);
|
||||
|
||||
const tooltip = inspector.markup.eventDetailsTooltip;
|
||||
await tooltip.once("shown");
|
||||
|
||||
info(`Tooltip displayed, click on the "jump to debugger" icon`);
|
||||
const debuggerIcon = tooltip.panel.querySelector(
|
||||
".event-tooltip-debugger-icon"
|
||||
);
|
||||
|
||||
if (!debuggerIcon) {
|
||||
ok(
|
||||
false,
|
||||
`There is no jump to debugger icon in event tooltip for node "${nodeSelector}"`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const onDebuggerSelected = toolbox.once(`jsdebugger-selected`);
|
||||
EventUtils.synthesizeMouse(debuggerIcon, 2, 2, {}, debuggerIcon.ownerGlobal);
|
||||
|
||||
await gDevTools.showToolboxForTab(tab, { toolId: "jsdebugger" });
|
||||
const dbg = toolbox.getPanel("jsdebugger");
|
||||
const dbg = await onDebuggerSelected;
|
||||
ok(true, "The debugger was opened");
|
||||
|
||||
let source;
|
||||
info("Wait for source to be opened");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => {
|
||||
source = dbg._selectors.getSelectedSource(dbg._getState());
|
||||
|
@ -61,6 +132,5 @@ add_task(async function() {
|
|||
100,
|
||||
20
|
||||
);
|
||||
|
||||
is(source.url, null, "expected no url for eval source");
|
||||
});
|
||||
return source;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,18 @@ ReadOnlyEditor.prototype = {
|
|||
this.tag = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show overflow highlight if showOverflowHighlight is true, otherwise hide it.
|
||||
*
|
||||
* @param {Boolean} showOverflowHighlight
|
||||
*/
|
||||
setOverflowHighlight: function(showOverflowHighlight) {
|
||||
this.container.tagState.classList.toggle(
|
||||
"overflow-causing-highlighted",
|
||||
showOverflowHighlight
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stub method for consistency with ElementEditor.
|
||||
*/
|
||||
|
|
|
@ -41,17 +41,21 @@ add_task(async function() {
|
|||
await selectNode("#testid", inspector);
|
||||
|
||||
let linkText = getRuleViewLinkTextByIndex(view, 1);
|
||||
is(linkText, "inline:3", "link text at index 1 has expected content.");
|
||||
|
||||
const mediaText = getRuleViewMediaTextByIndex(view, 1);
|
||||
is(
|
||||
linkText,
|
||||
"inline:3 @screen and (min-width: 10px)",
|
||||
"link text at index 1 contains media query text."
|
||||
mediaText,
|
||||
"@media screen and (min-width: 10px)",
|
||||
"media text at index 1 has expected content"
|
||||
);
|
||||
|
||||
linkText = getRuleViewLinkTextByIndex(view, 2);
|
||||
is(linkText, "inline:7", "link text at index 2 has expected content.");
|
||||
is(
|
||||
linkText,
|
||||
"inline:7",
|
||||
"link text at index 2 contains no media query text."
|
||||
getRuleViewMediaElementByIndex(view, 2),
|
||||
null,
|
||||
"There is no media text element for rule at index 2"
|
||||
);
|
||||
|
||||
const selector = getRuleViewRuleEditor(view, 2).selectorText;
|
||||
|
@ -66,3 +70,13 @@ add_task(async function() {
|
|||
".unmatched should not be matched."
|
||||
);
|
||||
});
|
||||
|
||||
function getRuleViewMediaElementByIndex(view, ruleIndex) {
|
||||
return view.styleDocument.querySelector(
|
||||
`.ruleview-rule:nth-of-type(${ruleIndex + 1}) .ruleview-rule-parent-data`
|
||||
);
|
||||
}
|
||||
|
||||
function getRuleViewMediaTextByIndex(view, ruleIndex) {
|
||||
return getRuleViewMediaElementByIndex(view, ruleIndex)?.textContent;
|
||||
}
|
||||
|
|
|
@ -135,6 +135,18 @@ RuleEditor.prototype = {
|
|||
|
||||
this.updateSourceLink();
|
||||
|
||||
if (this.rule.mediaText) {
|
||||
const text = `@media ${this.rule.mediaText}`;
|
||||
createChild(this.element, "span", {
|
||||
class: "ruleview-rule-parent-data theme-link",
|
||||
// We force the string to be LTR in CSS, but for some reason, the `@` char is seen
|
||||
// as not part of the string in the tooltip, and is displayed "at the end" of the
|
||||
// string in RTL locales. To workaround this, we force LTR with \u202D
|
||||
title: `\u202A${text}`,
|
||||
textContent: text,
|
||||
});
|
||||
}
|
||||
|
||||
const code = createChild(this.element, "div", {
|
||||
class: "ruleview-code",
|
||||
});
|
||||
|
@ -302,10 +314,6 @@ RuleEditor.prototype = {
|
|||
sourceTextContent += ":" + line;
|
||||
title += ":" + line;
|
||||
}
|
||||
if (this.rule.mediaText) {
|
||||
sourceTextContent += " @" + this.rule.mediaText;
|
||||
title += " @" + this.rule.mediaText;
|
||||
}
|
||||
|
||||
const sourceLabel = this.element.querySelector(
|
||||
".ruleview-rule-source-label"
|
||||
|
|
|
@ -148,10 +148,6 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.ruleview-code {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.ruleview-property:not(:hover) > .ruleview-enableproperty {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -267,6 +263,7 @@
|
|||
.ruleview-rule {
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
padding: 2px 4px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
#ruleview-container-focusable > .ruleview-rule:last-child {
|
||||
|
@ -696,6 +693,17 @@
|
|||
border-bottom-color: hsl(0,0%,50%);
|
||||
}
|
||||
|
||||
/* @media and @layer rule info element */
|
||||
.ruleview-rule-parent-data {
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* we only want to have it displayed on a single line. The whole string is available on
|
||||
the title attribute of the element anyway */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ruleview-selectorcontainer {
|
||||
word-wrap: break-word;
|
||||
cursor: text;
|
||||
|
|
|
@ -931,7 +931,6 @@ class EventCollector {
|
|||
const override = listener.override || {};
|
||||
const tags = listener.tags || "";
|
||||
const type = listener.type || "";
|
||||
let isScriptBoundToNonScriptElement = false;
|
||||
const enabled = !!listener.enabled;
|
||||
let functionSource = handler.toString();
|
||||
let line = 0;
|
||||
|
@ -968,13 +967,6 @@ class EventCollector {
|
|||
if (script) {
|
||||
const scriptSource = script.source.text;
|
||||
|
||||
// Scripts are provided via script tags. If it wasn't provided by a
|
||||
// script tag it must be a DOM0 event.
|
||||
if (script.source.element) {
|
||||
isScriptBoundToNonScriptElement =
|
||||
script.source.element.class !== "HTMLScriptElement";
|
||||
}
|
||||
|
||||
line = script.startLine;
|
||||
column = script.startColumn;
|
||||
url = script.url;
|
||||
|
@ -1032,9 +1024,7 @@ class EventCollector {
|
|||
} else {
|
||||
origin =
|
||||
url +
|
||||
(isScriptBoundToNonScriptElement || line === 0
|
||||
? ""
|
||||
: ":" + line + (column === null ? "" : ":" + column));
|
||||
(line ? ":" + line + (column === null ? "" : ":" + column) : "");
|
||||
}
|
||||
|
||||
eventObj = {
|
||||
|
@ -1056,7 +1046,7 @@ class EventCollector {
|
|||
// Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
|
||||
// generated dynamically from e.g. an onclick="" attribute so the script
|
||||
// doesn't actually exist.
|
||||
if (native || isScriptBoundToNonScriptElement) {
|
||||
if (!sourceActor) {
|
||||
eventObj.hide.debugger = true;
|
||||
}
|
||||
} finally {
|
||||
|
|
|
@ -594,7 +594,13 @@ const windowGlobalTargetPrototype = {
|
|||
originalBrowsingContext.currentWindowContext.innerWindowId;
|
||||
const parentInnerWindowId =
|
||||
originalBrowsingContext.parent?.currentWindowContext.innerWindowId;
|
||||
const isPopup = !!originalBrowsingContext.opener;
|
||||
// Doesn't only check `!!opener` as some iframe might have an opener
|
||||
// if their location was loaded via `window.open(url, "iframe-name")`.
|
||||
// So also ensure that the document is opened in a distinct tab.
|
||||
const isPopup =
|
||||
!!originalBrowsingContext.opener &&
|
||||
originalBrowsingContext.browserId !=
|
||||
originalBrowsingContext.opener.browserId;
|
||||
|
||||
const response = {
|
||||
actor: this.actorID,
|
||||
|
|
|
@ -3,12 +3,40 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* !! AFTER MOVING OR RENAMING THIS METHOD, UPDATE `EXPECTED` CONSTANTS BELOW !!
|
||||
*/
|
||||
const createParentProcessRequests = async () => {
|
||||
info("Do some requests from the parent process");
|
||||
// The line:column for `fetch` should be EXPECTED_REQUEST_LINE_1/COL_1
|
||||
await fetch(FETCH_URI);
|
||||
|
||||
const img = new Image();
|
||||
const onLoad = new Promise(r => img.addEventListener("load", r));
|
||||
// The line:column for `img` below should be EXPECTED_REQUEST_LINE_2/COL_2
|
||||
img.src = IMAGE_URI;
|
||||
await onLoad;
|
||||
};
|
||||
|
||||
const EXPECTED_METHOD_NAME = "createParentProcessRequests";
|
||||
const EXPECTED_REQUEST_LINE_1 = 12;
|
||||
const EXPECTED_REQUEST_COL_1 = 9;
|
||||
const EXPECTED_REQUEST_LINE_2 = 17;
|
||||
const EXPECTED_REQUEST_COL_2 = 3;
|
||||
|
||||
// Test the ResourceCommand API around NETWORK_EVENT for the parent process
|
||||
|
||||
const FETCH_URI = "https://example.com/document-builder.sjs?html=foo";
|
||||
const IMAGE_URI = URL_ROOT_SSL + "test_image.png";
|
||||
// The img.src request gets cached regardless of `devtools.cache.disabled`.
|
||||
// Add a random parameter to the request to bypass the cache.
|
||||
const uuid = `${Date.now()}-${Math.random()}`;
|
||||
const IMAGE_URI = URL_ROOT_SSL + "test_image.png?" + uuid;
|
||||
|
||||
add_task(async function testParentProcessRequests() {
|
||||
// The test expects the main process commands instance to receive resources
|
||||
// for content process requests.
|
||||
await pushPref("devtools.browsertoolbox.fission", true);
|
||||
|
||||
const commands = await CommandsFactory.forMainProcess();
|
||||
await commands.targetCommand.startListening();
|
||||
const { resourceCommand } = commands;
|
||||
|
@ -48,13 +76,7 @@ add_task(async function testParentProcessRequests() {
|
|||
}
|
||||
);
|
||||
|
||||
info("Do some requests from the parent process");
|
||||
await fetch(FETCH_URI);
|
||||
|
||||
const img = new Image();
|
||||
const onLoad = new Promise(r => img.addEventListener("load", r));
|
||||
img.src = IMAGE_URI;
|
||||
await onLoad;
|
||||
await createParentProcessRequests();
|
||||
|
||||
const img2 = new Image();
|
||||
img2.src = IMAGE_URI;
|
||||
|
@ -75,9 +97,9 @@ add_task(async function testParentProcessRequests() {
|
|||
const fetchStacktrace = receivedStacktraces[0].lastFrame;
|
||||
is(receivedStacktraces[0].resourceId, fetchRequest.stacktraceResourceId);
|
||||
is(fetchStacktrace.filename, gTestPath);
|
||||
is(fetchStacktrace.lineNumber, 52);
|
||||
is(fetchStacktrace.columnNumber, 9);
|
||||
is(fetchStacktrace.functionName, "testParentProcessRequests");
|
||||
is(fetchStacktrace.lineNumber, EXPECTED_REQUEST_LINE_1);
|
||||
is(fetchStacktrace.columnNumber, EXPECTED_REQUEST_COL_1);
|
||||
is(fetchStacktrace.functionName, EXPECTED_METHOD_NAME);
|
||||
is(fetchStacktrace.asyncCause, null);
|
||||
|
||||
async function getResponseContent(networkEvent) {
|
||||
|
@ -103,9 +125,9 @@ add_task(async function testParentProcessRequests() {
|
|||
const firstImageStacktrace = receivedStacktraces[1].lastFrame;
|
||||
is(receivedStacktraces[1].resourceId, firstImageRequest.stacktraceResourceId);
|
||||
is(firstImageStacktrace.filename, gTestPath);
|
||||
is(firstImageStacktrace.lineNumber, 56);
|
||||
is(firstImageStacktrace.columnNumber, 3);
|
||||
is(firstImageStacktrace.functionName, "testParentProcessRequests");
|
||||
is(firstImageStacktrace.lineNumber, EXPECTED_REQUEST_LINE_2);
|
||||
is(firstImageStacktrace.columnNumber, EXPECTED_REQUEST_COL_2);
|
||||
is(firstImageStacktrace.functionName, EXPECTED_METHOD_NAME);
|
||||
is(firstImageStacktrace.asyncCause, null);
|
||||
|
||||
info("Assert the second image request");
|
||||
|
|
|
@ -12,6 +12,9 @@ const POPUP_SECOND_URL =
|
|||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.popups.debug", true);
|
||||
// We expect to create a target for a same-process iframe
|
||||
// in the test against window.open to load a document in an iframe.
|
||||
await pushPref("devtools.every-frame-target.enabled", true);
|
||||
|
||||
// Create a TargetCommand for a given test tab
|
||||
const tab = await addTab(TEST_URL);
|
||||
|
@ -140,7 +143,23 @@ add_task(async function() {
|
|||
ok(!targets[3].isDestroyed(), "The about:blank popup target is still alive");
|
||||
|
||||
info("Call about:blank popup method to ensure it really is functional");
|
||||
await targets[3].focus();
|
||||
await targets[3].logInPage("foo");
|
||||
|
||||
info(
|
||||
"Ensure that iframe using window.open to load their document aren't considered as popups"
|
||||
);
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
const iframe = content.document.createElement("iframe");
|
||||
iframe.setAttribute("name", "test-iframe");
|
||||
content.document.documentElement.appendChild(iframe);
|
||||
content.open("data:text/html,iframe", "test-iframe");
|
||||
});
|
||||
await waitFor(() => targets.length === 6);
|
||||
is(
|
||||
targets[5].targetForm.isPopup,
|
||||
false,
|
||||
"The iframe target isn't considered as a popup"
|
||||
);
|
||||
|
||||
targetCommand.unwatchTargets({
|
||||
types: [TYPES.FRAME],
|
||||
|
|
|
@ -38,8 +38,6 @@ class AbortFollower : public nsISupports {
|
|||
AbortSignalImpl* Signal() const { return mFollowingSignal; }
|
||||
|
||||
protected:
|
||||
static void Unlink(AbortFollower* aFollower) { aFollower->Unfollow(); }
|
||||
|
||||
virtual ~AbortFollower();
|
||||
|
||||
friend class AbortSignalImpl;
|
||||
|
|
|
@ -110,7 +110,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal,
|
||||
DOMEventTargetHelper)
|
||||
AbortSignalImpl::Unlink(static_cast<AbortSignalImpl*>(tmp));
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignal)
|
||||
|
|
|
@ -284,6 +284,10 @@ DOMInterfaces = {
|
|||
'concrete': True,
|
||||
},
|
||||
|
||||
'FileSystemHandle': {
|
||||
'concrete': True,
|
||||
},
|
||||
|
||||
'FluentBundle': {
|
||||
'nativeType': 'mozilla::intl::FluentBundle',
|
||||
},
|
||||
|
|
|
@ -977,7 +977,8 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(
|
|||
mHasPendingStableStateCallback(false),
|
||||
mIsEntireFrameInvalid(false),
|
||||
mPredictManyRedrawCalls(false),
|
||||
mIsCapturedFrameInvalid(false),
|
||||
mFrameCaptureState(FrameCaptureState::CLEAN,
|
||||
"CanvasRenderingContext2D::mFrameCaptureState"),
|
||||
mPathTransformWillUpdate(false),
|
||||
mInvalidateCount(0),
|
||||
mWriteOnly(false) {
|
||||
|
@ -1054,7 +1055,7 @@ nsresult CanvasRenderingContext2D::Reset() {
|
|||
// no longer be valid.
|
||||
mIsEntireFrameInvalid = false;
|
||||
mPredictManyRedrawCalls = false;
|
||||
mIsCapturedFrameInvalid = false;
|
||||
mFrameCaptureState = FrameCaptureState::CLEAN;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1122,7 +1123,7 @@ void CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor,
|
|||
}
|
||||
|
||||
nsresult CanvasRenderingContext2D::Redraw() {
|
||||
mIsCapturedFrameInvalid = true;
|
||||
mFrameCaptureState = FrameCaptureState::DIRTY;
|
||||
|
||||
if (mIsEntireFrameInvalid) {
|
||||
return NS_OK;
|
||||
|
@ -1143,7 +1144,7 @@ nsresult CanvasRenderingContext2D::Redraw() {
|
|||
}
|
||||
|
||||
void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
|
||||
mIsCapturedFrameInvalid = true;
|
||||
mFrameCaptureState = FrameCaptureState::DIRTY;
|
||||
|
||||
++mInvalidateCount;
|
||||
|
||||
|
@ -1169,7 +1170,7 @@ void CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) {
|
|||
void CanvasRenderingContext2D::DidRefresh() {}
|
||||
|
||||
void CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) {
|
||||
mIsCapturedFrameInvalid = true;
|
||||
mFrameCaptureState = FrameCaptureState::DIRTY;
|
||||
|
||||
if (mIsEntireFrameInvalid) {
|
||||
++mInvalidateCount;
|
||||
|
@ -1360,12 +1361,12 @@ bool CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
|
|||
if (mCanvasElement) {
|
||||
mCanvasElement->InvalidateCanvas();
|
||||
}
|
||||
// EnsureTarget hasn't drawn anything. Preserve mIsCapturedFrameInvalid.
|
||||
bool capturedFrameInvalid = mIsCapturedFrameInvalid;
|
||||
// EnsureTarget hasn't drawn anything. Preserve mFrameCaptureState.
|
||||
FrameCaptureState captureState = mFrameCaptureState;
|
||||
// Calling Redraw() tells our invalidation machinery that the entire
|
||||
// canvas is already invalid, which can speed up future drawing.
|
||||
Redraw();
|
||||
mIsCapturedFrameInvalid = capturedFrameInvalid;
|
||||
mFrameCaptureState = captureState;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -5506,14 +5507,6 @@ void CanvasRenderingContext2D::MarkContextClean() {
|
|||
mInvalidateCount = 0;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::MarkContextCleanForFrameCapture() {
|
||||
mIsCapturedFrameInvalid = false;
|
||||
}
|
||||
|
||||
bool CanvasRenderingContext2D::IsContextCleanForFrameCapture() {
|
||||
return !mIsCapturedFrameInvalid;
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::GetAppUnitsValues(int32_t* aPerDevPixel,
|
||||
int32_t* aPerCSSPixel) {
|
||||
// If we don't have a canvas element, we just return something generic.
|
||||
|
|
|
@ -430,8 +430,12 @@ class CanvasRenderingContext2D final : public nsICanvasRenderingContextInternal,
|
|||
bool InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder,
|
||||
CanvasRenderer* aRenderer) override;
|
||||
void MarkContextClean() override;
|
||||
void MarkContextCleanForFrameCapture() override;
|
||||
bool IsContextCleanForFrameCapture() override;
|
||||
void MarkContextCleanForFrameCapture() override {
|
||||
mFrameCaptureState = FrameCaptureState::CLEAN;
|
||||
}
|
||||
Watchable<FrameCaptureState>* GetFrameCaptureState() override {
|
||||
return &mFrameCaptureState;
|
||||
}
|
||||
NS_IMETHOD SetIsIPC(bool aIsIPC) override;
|
||||
// this rect is in canvas device space
|
||||
void Redraw(const mozilla::gfx::Rect& aR);
|
||||
|
@ -750,7 +754,7 @@ class CanvasRenderingContext2D final : public nsICanvasRenderingContextInternal,
|
|||
* case when the canvas is not currently being drawn into and not rendered
|
||||
* but canvas capturing is still ongoing.
|
||||
*/
|
||||
bool mIsCapturedFrameInvalid;
|
||||
Watchable<FrameCaptureState> mFrameCaptureState;
|
||||
|
||||
/**
|
||||
* We also have a device space pathbuilder. The reason for this is as
|
||||
|
|
|
@ -12,8 +12,12 @@
|
|||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/dom/UserActivation.h"
|
||||
#include "mozilla/dom/WorkerCommon.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/gfx/gfxVars.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/StaticPrefs_gfx.h"
|
||||
#include "mozilla/StaticPrefs_privacy.h"
|
||||
#include "mozilla/StaticPrefs_webgl.h"
|
||||
#include "nsIPrincipal.h"
|
||||
|
@ -285,6 +289,35 @@ bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) {
|
|||
nsGkAtoms::all_urlsPermission);
|
||||
}
|
||||
|
||||
bool IsOffscreenCanvasEnabled(JSContext* aCx, JSObject* /* unused */) {
|
||||
if (StaticPrefs::gfx_offscreencanvas_enabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!StaticPrefs::gfx_offscreencanvas_domain_enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& allowlist = gfxVars::OffscreenCanvasDomainAllowlist();
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
dom::WorkerPrivate* workerPrivate = dom::GetWorkerPrivateFromContext(aCx);
|
||||
if (workerPrivate->UsesSystemPrincipal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nsContentUtils::IsURIInList(workerPrivate->GetBaseURI(), allowlist);
|
||||
}
|
||||
|
||||
nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
|
||||
if (principal->IsSystemPrincipal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = principal->GetURI();
|
||||
return nsContentUtils::IsURIInList(uri, allowlist);
|
||||
}
|
||||
|
||||
bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal,
|
||||
bool aHadCrossOriginRedirects) {
|
||||
if (!aPrincipal) {
|
||||
|
|
|
@ -51,6 +51,9 @@ void DoDrawImageSecurityCheck(dom::HTMLCanvasElement* aCanvasElement,
|
|||
// Check if the context is chrome or has the permission to drawWindow
|
||||
bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
// Check if the context has permission to use OffscreenCanvas.
|
||||
bool IsOffscreenCanvasEnabled(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
// Check site-specific permission and display prompt if appropriate.
|
||||
bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
|
||||
nsIPrincipal& aPrincipal);
|
||||
|
|
|
@ -917,13 +917,14 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
|
|||
public:
|
||||
bool InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder,
|
||||
layers::CanvasRenderer* aRenderer) override;
|
||||
|
||||
void MarkContextCleanForFrameCapture() override {
|
||||
mFrameCaptureState = FrameCaptureState::CLEAN;
|
||||
}
|
||||
// Note that 'clean' here refers to its invalidation state, not the
|
||||
// contents of the buffer.
|
||||
bool IsContextCleanForFrameCapture() override {
|
||||
return !mCapturedFrameInvalidated;
|
||||
}
|
||||
void MarkContextCleanForFrameCapture() override {
|
||||
mCapturedFrameInvalidated = false;
|
||||
Watchable<FrameCaptureState>* GetFrameCaptureState() override {
|
||||
return &mFrameCaptureState;
|
||||
}
|
||||
|
||||
void OnMemoryPressure() override;
|
||||
|
@ -986,7 +987,8 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
|
|||
protected:
|
||||
layers::LayersBackend GetCompositorBackendType() const;
|
||||
|
||||
bool mCapturedFrameInvalidated = false;
|
||||
Watchable<FrameCaptureState> mFrameCaptureState = {
|
||||
FrameCaptureState::CLEAN, "ClientWebGLContext::mFrameCaptureState"};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// WebGLRenderingContext Basic Properties and Methods
|
||||
|
|
|
@ -1256,11 +1256,9 @@ bool PathCacheEntry::MatchesPath(const SkPath& aPath, const Pattern* aPattern,
|
|||
const StrokeOptions* aStrokeOptions,
|
||||
const Matrix& aTransform,
|
||||
const IntRect& aBounds, HashNumber aHash) {
|
||||
if (aHash != mHash || !HasMatchingScale(aTransform, mTransform) ||
|
||||
aBounds.Size() != mBounds.Size() || aPath == mPath) {
|
||||
return false;
|
||||
}
|
||||
return (!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) &&
|
||||
return aHash == mHash && HasMatchingScale(aTransform, mTransform) &&
|
||||
aBounds.Size() == mBounds.Size() && aPath == mPath &&
|
||||
(!aPattern ? !mPattern : mPattern && *aPattern == *mPattern) &&
|
||||
(!aStrokeOptions
|
||||
? !mStrokeOptions
|
||||
: mStrokeOptions && *aStrokeOptions == *mStrokeOptions);
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
namespace mozilla::dom {
|
||||
|
||||
ImageBitmapRenderingContext::ImageBitmapRenderingContext()
|
||||
: mWidth(0), mHeight(0), mIsCapturedFrameInvalid(false) {}
|
||||
: mWidth(0),
|
||||
mHeight(0),
|
||||
mFrameCaptureState(FrameCaptureState::CLEAN,
|
||||
"ImageBitmapRenderingContext::mFrameCaptureState") {}
|
||||
|
||||
ImageBitmapRenderingContext::~ImageBitmapRenderingContext() {
|
||||
RemovePostRefreshObserver();
|
||||
|
@ -213,7 +216,7 @@ ImageBitmapRenderingContext::Reset() {
|
|||
}
|
||||
|
||||
mImage = nullptr;
|
||||
mIsCapturedFrameInvalid = false;
|
||||
mFrameCaptureState = FrameCaptureState::CLEAN;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -243,7 +246,7 @@ void ImageBitmapRenderingContext::MarkContextClean() {}
|
|||
|
||||
NS_IMETHODIMP
|
||||
ImageBitmapRenderingContext::Redraw(const gfxRect& aDirty) {
|
||||
mIsCapturedFrameInvalid = true;
|
||||
mFrameCaptureState = FrameCaptureState::DIRTY;
|
||||
|
||||
if (mOffscreenCanvas) {
|
||||
mOffscreenCanvas->CommitFrameToCompositor();
|
||||
|
@ -260,14 +263,6 @@ ImageBitmapRenderingContext::SetIsIPC(bool aIsIPC) { return NS_OK; }
|
|||
|
||||
void ImageBitmapRenderingContext::DidRefresh() {}
|
||||
|
||||
void ImageBitmapRenderingContext::MarkContextCleanForFrameCapture() {
|
||||
mIsCapturedFrameInvalid = false;
|
||||
}
|
||||
|
||||
bool ImageBitmapRenderingContext::IsContextCleanForFrameCapture() {
|
||||
return !mIsCapturedFrameInvalid;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmapRenderingContext)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmapRenderingContext)
|
||||
|
||||
|
|
|
@ -93,8 +93,12 @@ class ImageBitmapRenderingContext final
|
|||
|
||||
virtual void DidRefresh() override;
|
||||
|
||||
virtual void MarkContextCleanForFrameCapture() override;
|
||||
virtual bool IsContextCleanForFrameCapture() override;
|
||||
void MarkContextCleanForFrameCapture() override {
|
||||
mFrameCaptureState = FrameCaptureState::CLEAN;
|
||||
}
|
||||
Watchable<FrameCaptureState>* GetFrameCaptureState() override {
|
||||
return &mFrameCaptureState;
|
||||
}
|
||||
|
||||
protected:
|
||||
already_AddRefed<gfx::DataSourceSurface> MatchWithIntrinsicSize();
|
||||
|
@ -109,7 +113,7 @@ class ImageBitmapRenderingContext final
|
|||
* case when the canvas is not currently being drawn into and not rendered
|
||||
* but canvas capturing is still ongoing.
|
||||
*/
|
||||
bool mIsCapturedFrameInvalid;
|
||||
Watchable<FrameCaptureState> mFrameCaptureState;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -390,7 +390,7 @@ bool OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx,
|
|||
return true;
|
||||
}
|
||||
|
||||
return StaticPrefs::gfx_offscreencanvas_enabled();
|
||||
return CanvasUtils::IsOffscreenCanvasEnabled(aCx, aObj);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper,
|
||||
|
|
|
@ -222,7 +222,7 @@ void WebGLContext::DestroyResourcesAndContext() {
|
|||
void ClientWebGLContext::MarkCanvasDirty() {
|
||||
if (!mCanvasElement && !mOffscreenCanvas) return;
|
||||
|
||||
mCapturedFrameInvalidated = true;
|
||||
mFrameCaptureState = FrameCaptureState::DIRTY;
|
||||
|
||||
if (mIsCanvasDirty) return;
|
||||
mIsCanvasDirty = true;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/StateWatching.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
|
@ -52,6 +53,8 @@ class SourceSurface;
|
|||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
enum class FrameCaptureState : uint8_t { CLEAN, DIRTY };
|
||||
|
||||
class nsICanvasRenderingContextInternal : public nsISupports,
|
||||
public mozilla::SupportsWeakPtr,
|
||||
public nsAPostRefreshObserver {
|
||||
|
@ -160,9 +163,10 @@ class nsICanvasRenderingContextInternal : public nsISupports,
|
|||
// Called when a frame is captured.
|
||||
virtual void MarkContextCleanForFrameCapture() = 0;
|
||||
|
||||
// Whether the context is clean or has been invalidated since the last frame
|
||||
// was captured.
|
||||
virtual bool IsContextCleanForFrameCapture() = 0;
|
||||
// Whether the context is clean or has been invalidated (dirty) since the last
|
||||
// frame was captured. The Watchable allows the caller to get notified of
|
||||
// state changes.
|
||||
virtual mozilla::Watchable<FrameCaptureState>* GetFrameCaptureState() = 0;
|
||||
|
||||
// Redraw the dirty rectangle of this canvas.
|
||||
NS_IMETHOD Redraw(const gfxRect& dirty) = 0;
|
||||
|
|
|
@ -128,6 +128,8 @@ skip-if = (toolkit == 'windows') # bug 1464173
|
|||
[test_canvas_strokeStyle_getter.html]
|
||||
[test_capture.html]
|
||||
support-files = captureStream_common.js
|
||||
[test_capture_throttled.html]
|
||||
support-files = captureStream_common.js
|
||||
[test_drawImageIncomplete.html]
|
||||
[test_drawImage_document_domain.html]
|
||||
[test_drawImage_edge_cases.html]
|
||||
|
|
|
@ -8,13 +8,21 @@
|
|||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<body>
|
||||
<script>
|
||||
var c; // Canvas element captured by streams.
|
||||
var h; // CaptureStreamTestHelper holding utility test functions.
|
||||
var vauto; // Video element with captureStream stream in automatic mode.
|
||||
var vmanual; // Video element with captureStream stream in manual (fps 0) mode.
|
||||
var vrate; // Video element with captureStream stream with fixed frame rate.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin");
|
||||
|
||||
function checkDrawColorInitialRed() {
|
||||
// CaptureStreamTestHelper holding utility test functions.
|
||||
const h = new CaptureStreamTestHelper2D();
|
||||
// Canvas element captured by streams.
|
||||
const c = h.createAndAppendElement('canvas', 'c');
|
||||
// Video element with captureStream stream in automatic mode.
|
||||
const vauto = h.createAndAppendElement('video', 'vauto');
|
||||
// Video element with captureStream stream in manual (fps 0) mode.
|
||||
const vmanual = h.createAndAppendElement('video', 'vmanual');
|
||||
// Video element with captureStream stream with fixed frame rate.
|
||||
const vrate = h.createAndAppendElement('video', 'vrate');
|
||||
|
||||
async function checkDrawColorInitialRed() {
|
||||
info("Checking that all video elements become red when initiated just after the first drawColor(red).");
|
||||
|
||||
h.drawColor(c, h.red);
|
||||
|
@ -30,90 +38,94 @@ function checkDrawColorInitialRed() {
|
|||
ok(h.isPixel(h.getPixel(vmanual), h.blackTransparent, 0),
|
||||
"vmanual should not be drawn to before stable state");
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => h.pixelMustBecome(vauto, h.red, {
|
||||
infoString: "should become red automatically",
|
||||
}))
|
||||
.then(() => h.pixelMustBecome(vrate, h.red, {
|
||||
infoString: "should become red automatically",
|
||||
}))
|
||||
.then(() => h.pixelMustBecome(vmanual, h.red, {
|
||||
infoString: "should become red when we get to stable state (first frame)",
|
||||
}));
|
||||
await h.pixelMustBecome(vauto, h.red, {
|
||||
infoString: "should become red automatically",
|
||||
});
|
||||
await h.pixelMustBecome(vrate, h.red, {
|
||||
infoString: "should become red automatically",
|
||||
});
|
||||
await h.pixelMustBecome(vmanual, h.red, {
|
||||
infoString: "should become red when we get to stable state (first frame)",
|
||||
});
|
||||
}
|
||||
|
||||
function checkDrawColorGreen() {
|
||||
async function checkDrawColorGreen() {
|
||||
info("Checking that drawing green propagates properly to video elements.");
|
||||
|
||||
var drawing = h.startDrawing(() => h.drawColor(c, h.green));
|
||||
const drawing = h.startDrawing(() => h.drawColor(c, h.green));
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => h.pixelMustBecome(vauto, h.green, {
|
||||
try {
|
||||
await h.pixelMustBecome(vauto, h.green, {
|
||||
infoString: "should become green automatically",
|
||||
}))
|
||||
.then(() => h.pixelMustBecome(vrate, h.green, {
|
||||
});
|
||||
await h.pixelMustBecome(vrate, h.green, {
|
||||
infoString: "should become green automatically",
|
||||
}))
|
||||
.then(() => h.pixelMustBecome(vmanual, h.red, {
|
||||
});
|
||||
await h.pixelMustBecome(vmanual, h.red, {
|
||||
infoString: "should still be red",
|
||||
}))
|
||||
.then(() => h.requestFrame(vmanual))
|
||||
.then(() => h.pixelMustBecome(vmanual, h.green, {
|
||||
});
|
||||
h.requestFrame(vmanual);
|
||||
await h.pixelMustBecome(vmanual, h.green, {
|
||||
infoString: "should become green after requstFrame()",
|
||||
}))
|
||||
.catch(err => ok(false, "checkDrawColorGreen failed: ", err))
|
||||
.then(() => drawing.stop());
|
||||
});
|
||||
}
|
||||
catch(err) {
|
||||
ok(false, "checkDrawColorGreen failed: ", err);
|
||||
}
|
||||
drawing.stop();
|
||||
}
|
||||
|
||||
function checkRequestFrameOrderGuarantee() {
|
||||
async function checkRequestFrameOrderGuarantee() {
|
||||
info("Checking that requestFrame() immediately after a drawColor() " +
|
||||
"call results in the expected frame seen in the stream.");
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => h.pixelMustBecome(vmanual, h.green, {
|
||||
infoString: "should still be green",
|
||||
}))
|
||||
.then(() => h.drawColor(c, h.red)) // 1. Draw canvas red
|
||||
.then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame
|
||||
.then(() => h.pixelMustBecome(vmanual, h.red, {
|
||||
infoString: "should become red after call order test",
|
||||
}));
|
||||
await h.pixelMustBecome(vmanual, h.green, {
|
||||
infoString: "should still be green",
|
||||
});
|
||||
h.drawColor(c, h.red); // 1. Draw canvas red
|
||||
h.requestFrame(vmanual); // 2. Immediately request a frame
|
||||
await h.pixelMustBecome(vmanual, h.red, {
|
||||
infoString: "should become red after call order test",
|
||||
});
|
||||
}
|
||||
|
||||
function checkDrawImageNotCleanRed() {
|
||||
async function checkDrawImageNotCleanRed() {
|
||||
info("Checking that drawImage with not origin-clean image renders streams useless.");
|
||||
var ctx = c.getContext('2d');
|
||||
var notCleanRed = new Image();
|
||||
var drawing;
|
||||
const ctx = c.getContext('2d');
|
||||
const notCleanRed = new Image();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
notCleanRed.onload = resolve;
|
||||
notCleanRed.onerror = () => reject(new Error("Failed to load tainted image."));
|
||||
notCleanRed.src = "http://example.com/tests/dom/canvas/test/image_red_crossorigin_credentials.png";
|
||||
document.body.appendChild(notCleanRed);
|
||||
})
|
||||
.then(() => drawing = h.startDrawing(() => ctx.drawImage(notCleanRed, 0, 0, c.width, c.height)))
|
||||
.then(() => h.testNotClean(c))
|
||||
.then(() => h.pixelMustNotBecome(vauto, h.red, {
|
||||
});
|
||||
const drawing = h.startDrawing(
|
||||
() => ctx.drawImage(notCleanRed, 0, 0, c.width, c.height));
|
||||
h.testNotClean(c);
|
||||
try {
|
||||
await h.pixelMustNotBecome(vauto, h.red, {
|
||||
timeout: 1000,
|
||||
infoString: "should not become red",
|
||||
}))
|
||||
.then(() => ok(h.isPixelNot(h.getPixel(vrate), h.red, 250),
|
||||
"should not have become red"))
|
||||
.then(() => h.pixelMustBecome(vmanual, h.green, {
|
||||
});
|
||||
ok(h.isPixelNot(h.getPixel(vrate), h.red, 250),
|
||||
"should not have become red");
|
||||
await h.pixelMustBecome(vmanual, h.green, {
|
||||
infoString: "should still be green",
|
||||
}))
|
||||
.then(() => h.requestFrame(vmanual))
|
||||
.then(() => h.pixelMustNotBecome(vmanual, h.red, {
|
||||
});
|
||||
h.requestFrame(vmanual);
|
||||
await h.pixelMustNotBecome(vmanual, h.red, {
|
||||
timeout: 1000,
|
||||
infoString: "should not become red",
|
||||
}))
|
||||
.catch(err => ok(false, "checkDrawImageNotCleanRed failed: ", err))
|
||||
.then(() => drawing.stop());
|
||||
});
|
||||
} catch(err) {
|
||||
ok(false, "checkDrawImageNotCleanRed failed: ", err);
|
||||
}
|
||||
drawing.stop();
|
||||
}
|
||||
|
||||
function checkEndedOnStop() {
|
||||
let promises = [vauto, vmanual, vrate].map(elem => {
|
||||
async function checkEndedOnStop() {
|
||||
const promises = [vauto, vmanual, vrate].map(elem => {
|
||||
elem.srcObject.getTracks()[0].stop();
|
||||
return new Promise(resolve =>
|
||||
elem.addEventListener("ended", function endedListener(event) {
|
||||
|
@ -122,7 +134,7 @@ function checkEndedOnStop() {
|
|||
elem.removeEventListener("ended", endedListener);
|
||||
}));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
function finish() {
|
||||
|
@ -130,27 +142,14 @@ function finish() {
|
|||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function beginTest() {
|
||||
SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin");
|
||||
h = new CaptureStreamTestHelper2D();
|
||||
|
||||
c = h.createAndAppendElement('canvas', 'c');
|
||||
vauto = h.createAndAppendElement('video', 'vauto');
|
||||
vmanual = h.createAndAppendElement('video', 'vmanual');
|
||||
vrate = h.createAndAppendElement('video', 'vrate');
|
||||
|
||||
Promise.resolve()
|
||||
.then(checkDrawColorInitialRed)
|
||||
.then(checkDrawColorGreen)
|
||||
.then(checkRequestFrameOrderGuarantee)
|
||||
.then(checkDrawColorGreen) // Restore video elements to green.
|
||||
.then(checkDrawImageNotCleanRed)
|
||||
.then(checkEndedOnStop)
|
||||
.then(finish);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
beginTest();
|
||||
(async () => {
|
||||
await checkDrawColorInitialRed();
|
||||
await checkDrawColorGreen();
|
||||
await checkRequestFrameOrderGuarantee();
|
||||
await checkDrawColorGreen(); // Restore video elements to green.
|
||||
await checkDrawImageNotCleanRed();
|
||||
await checkEndedOnStop();
|
||||
finish();
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
|
||||
<title>Canvas2D test: CaptureStream() with throttled rAF</title>
|
||||
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="captureStream_common.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<body>
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin");
|
||||
|
||||
const THROTTLED_THRESHOLD = 5;
|
||||
|
||||
// CaptureStreamTestHelper holding utility test functions.
|
||||
const h = new CaptureStreamTestHelper2D();
|
||||
// Canvas element captured by streams.
|
||||
const c = h.createAndAppendElement('canvas', 'c');
|
||||
// Video element with captureStream stream in automatic mode.
|
||||
const v = h.createAndAppendElement('video', 'v');
|
||||
|
||||
async function checkRequestAnimationFrameThrottled() {
|
||||
const frameRate = await new Promise(resolve => {
|
||||
let start;
|
||||
let count;
|
||||
const tick = time => {
|
||||
if (!start) {
|
||||
start = time;
|
||||
count = 0;
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
if (time - start > 1000) {
|
||||
// One second has passed, break.
|
||||
resolve(count / ((time - start) / 1000));
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(tick);
|
||||
};
|
||||
window.requestAnimationFrame(tick);
|
||||
});
|
||||
ok(frameRate < THROTTLED_THRESHOLD, `rAF framerate is at ${frameRate} fps`);
|
||||
}
|
||||
|
||||
async function checkSetTimeoutNotThrottled() {
|
||||
const start = performance.now();
|
||||
const COUNT = 5;
|
||||
for(let i = 0; i < COUNT; ++i) {
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
}
|
||||
const rate = COUNT / ((performance.now() - start) / 1000);
|
||||
ok(rate > 30, `setTimeout rate is at ${rate} ticks per second`);
|
||||
}
|
||||
|
||||
async function checkCanvasCaptureNotThrottled() {
|
||||
const intervalMillis = 1000 / 60;
|
||||
const ctx = c.getContext('2d');
|
||||
v.srcObject = c.captureStream();
|
||||
v.play();
|
||||
const start = performance.now();
|
||||
let end;
|
||||
is(v.mozPaintedFrames, 0, "mozPaintedFrames starts at 0");
|
||||
while(true) {
|
||||
h.drawColor(c, h.green);
|
||||
await new Promise(resolve => setTimeout(resolve, intervalMillis));
|
||||
end = performance.now();
|
||||
if (end - start > 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const frameRate = v.mozPaintedFrames / ((end - start) / 1000);
|
||||
ok(frameRate > THROTTLED_THRESHOLD,
|
||||
`captureStream() framerate is at ${frameRate} fps`);
|
||||
}
|
||||
|
||||
function finish() {
|
||||
ok(true, 'Test complete.');
|
||||
SpecialPowers.wrap(window).browsingContext.isActive = true;
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// Disable background timer throttling so we can use setTimeout to draw to the
|
||||
// canvas while the refresh driver is throttled.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.timeout.enable_budget_timer_throttling", false],
|
||||
["dom.min_background_timeout_value", 0],
|
||||
["dom.min_background_timeout_value_without_budget_throttling", 0],
|
||||
],
|
||||
});
|
||||
// Throttle the canvas' refresh driver
|
||||
SpecialPowers.wrap(window).browsingContext.isActive = false;
|
||||
await checkRequestAnimationFrameThrottled();
|
||||
await checkSetTimeoutNotThrottled();
|
||||
await checkCanvasCaptureNotThrottled();
|
||||
finish();
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -89,6 +89,15 @@ namespace PathUtils {
|
|||
[Throws]
|
||||
UTF8String toFileURI(DOMString path);
|
||||
|
||||
/**
|
||||
* Determine if the given path is an absolute or relative path.
|
||||
*
|
||||
* @param path A file path that is either relative or absolute.
|
||||
*
|
||||
* @return Whether or not the path is absolute.
|
||||
*/
|
||||
boolean isAbsolute(DOMString path);
|
||||
|
||||
/**
|
||||
* The profile directory.
|
||||
*/
|
||||
|
|
|
@ -2149,7 +2149,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
|
||||
EventListenerManager::ListenerSignalFollower)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListener)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
tmp->mListenerManager = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
|
|
|
@ -140,47 +140,18 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalMainThread)
|
|||
|
||||
class AbortSignalProxy;
|
||||
|
||||
class WorkerSignalFollower final : public AbortFollower {
|
||||
public:
|
||||
// This runnable propagates changes from the AbortSignalImpl on workers to the
|
||||
// AbortSignalImpl on main-thread.
|
||||
class AbortSignalProxyRunnable final : public Runnable {
|
||||
RefPtr<AbortSignalProxy> mProxy;
|
||||
|
||||
public:
|
||||
explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy)
|
||||
: Runnable("dom::WorkerSignalFollower::AbortSignalProxyRunnable"),
|
||||
mProxy(aProxy) {}
|
||||
|
||||
NS_IMETHOD Run() override;
|
||||
};
|
||||
// This runnable propagates changes from the AbortSignalImpl on workers to the
|
||||
// AbortSignalImpl on main-thread.
|
||||
class AbortSignalProxyRunnable final : public Runnable {
|
||||
RefPtr<AbortSignalProxy> mProxy;
|
||||
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(WorkerSignalFollower)
|
||||
explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy)
|
||||
: Runnable("dom::AbortSignalProxyRunnable"), mProxy(aProxy) {}
|
||||
|
||||
void RunAbortAlgorithm() override {}
|
||||
|
||||
private:
|
||||
~WorkerSignalFollower() = default;
|
||||
NS_IMETHOD Run() override;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerSignalFollower)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerSignalFollower)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerSignalFollower)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkerSignalFollower)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkerSignalFollower)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerSignalFollower)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
// This class orchestrates the proxying of AbortSignal operations between the
|
||||
// main thread and a worker thread.
|
||||
class AbortSignalProxy final : public AbortFollower {
|
||||
|
@ -240,7 +211,7 @@ class AbortSignalProxy final : public AbortFollower {
|
|||
|
||||
NS_IMPL_ISUPPORTS0(AbortSignalProxy)
|
||||
|
||||
NS_IMETHODIMP WorkerSignalFollower::AbortSignalProxyRunnable::Run() {
|
||||
NS_IMETHODIMP AbortSignalProxyRunnable::Run() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
AbortSignalImpl* signalImpl = mProxy->GetOrCreateSignalImplForMainThread();
|
||||
signalImpl->SignalAbort(JS::UndefinedHandleValue);
|
||||
|
@ -249,8 +220,6 @@ NS_IMETHODIMP WorkerSignalFollower::AbortSignalProxyRunnable::Run() {
|
|||
|
||||
void AbortSignalProxy::RunAbortAlgorithm() {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
using AbortSignalProxyRunnable =
|
||||
WorkerSignalFollower::AbortSignalProxyRunnable;
|
||||
RefPtr<AbortSignalProxyRunnable> runnable =
|
||||
new AbortSignalProxyRunnable(this);
|
||||
MainThreadEventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
||||
|
@ -1803,7 +1772,6 @@ NS_IMPL_RELEASE_INHERITED(EmptyBody, FetchBody<EmptyBody>)
|
|||
NS_IMPL_CYCLE_COLLECTION_CLASS(EmptyBody)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EmptyBody, FetchBody<EmptyBody>)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortSignalImpl)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
|
||||
|
|
|
@ -18,7 +18,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchObserver,
|
||||
DOMEventTargetHelper)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchObserver)
|
||||
|
|
|
@ -41,7 +41,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Request, FetchBody<Request>)
|
|||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignal)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ NS_IMPL_RELEASE_INHERITED(Response, FetchBody<Response>)
|
|||
NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response, FetchBody<Response>)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl)
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileSystemDirectoryHandle.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FileSystemDirectoryHandleBinding.h"
|
||||
#include "mozilla/dom/FileSystemDirectoryIterator.h"
|
||||
#include "mozilla/dom/FileSystemHandleBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemDirectoryHandle,
|
||||
FileSystemHandle)
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemDirectoryHandle, FileSystemHandle)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
|
||||
JSObject* FileSystemDirectoryHandle::WrapObject(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
|
||||
return FileSystemDirectoryHandle_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
// WebIDL Interface
|
||||
|
||||
FileSystemHandleKind FileSystemDirectoryHandle::Kind() {
|
||||
return FileSystemHandleKind::Directory;
|
||||
}
|
||||
|
||||
already_AddRefed<FileSystemDirectoryIterator>
|
||||
FileSystemDirectoryHandle::Entries() {
|
||||
return MakeRefPtr<FileSystemDirectoryIterator>(GetParentObject()).forget();
|
||||
}
|
||||
|
||||
already_AddRefed<FileSystemDirectoryIterator>
|
||||
FileSystemDirectoryHandle::Keys() {
|
||||
return MakeRefPtr<FileSystemDirectoryIterator>(GetParentObject()).forget();
|
||||
}
|
||||
|
||||
already_AddRefed<FileSystemDirectoryIterator>
|
||||
FileSystemDirectoryHandle::Values() {
|
||||
return MakeRefPtr<FileSystemDirectoryIterator>(GetParentObject()).forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemDirectoryHandle::GetFileHandle(
|
||||
const nsAString& aName, const FileSystemGetFileOptions& aOptions) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemDirectoryHandle::GetDirectoryHandle(
|
||||
const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemDirectoryHandle::RemoveEntry(
|
||||
const nsAString& aName, const FileSystemRemoveOptions& aOptions) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemDirectoryHandle::Resolve(
|
||||
FileSystemHandle& aPossibleDescendant) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
|
||||
#define DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
|
||||
|
||||
#include "mozilla/dom/FileSystemHandle.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class FileSystemDirectoryIterator;
|
||||
struct FileSystemGetFileOptions;
|
||||
struct FileSystemGetDirectoryOptions;
|
||||
struct FileSystemRemoveOptions;
|
||||
|
||||
class FileSystemDirectoryHandle final : public FileSystemHandle {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemDirectoryHandle,
|
||||
FileSystemHandle)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL Interface
|
||||
FileSystemHandleKind Kind() override;
|
||||
|
||||
[[nodiscard]] already_AddRefed<FileSystemDirectoryIterator> Entries();
|
||||
|
||||
[[nodiscard]] already_AddRefed<FileSystemDirectoryIterator> Keys();
|
||||
|
||||
[[nodiscard]] already_AddRefed<FileSystemDirectoryIterator> Values();
|
||||
|
||||
already_AddRefed<Promise> GetFileHandle(
|
||||
const nsAString& aName, const FileSystemGetFileOptions& aOptions);
|
||||
|
||||
already_AddRefed<Promise> GetDirectoryHandle(
|
||||
const nsAString& aName, const FileSystemGetDirectoryOptions& aOptions);
|
||||
|
||||
already_AddRefed<Promise> RemoveEntry(
|
||||
const nsAString& aName, const FileSystemRemoveOptions& aOptions);
|
||||
|
||||
already_AddRefed<Promise> Resolve(FileSystemHandle& aPossibleDescendant);
|
||||
|
||||
private:
|
||||
~FileSystemDirectoryHandle() = default;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FS_FILESYSTEMDIRECTORYHANDLE_H_
|
|
@ -0,0 +1,53 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileSystemDirectoryIterator.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FileSystemDirectoryIteratorBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
FileSystemDirectoryIterator::FileSystemDirectoryIterator(
|
||||
nsIGlobalObject* aGlobal)
|
||||
: mGlobal(aGlobal) {}
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemDirectoryIterator)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemDirectoryIterator);
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemDirectoryIterator);
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemDirectoryIterator, mGlobal);
|
||||
|
||||
// WebIDL Boilerplate
|
||||
|
||||
nsIGlobalObject* FileSystemDirectoryIterator::GetParentObject() const {
|
||||
return mGlobal;
|
||||
}
|
||||
|
||||
JSObject* FileSystemDirectoryIterator::WrapObject(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
|
||||
return FileSystemDirectoryIterator_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
// WebIDL Interface
|
||||
|
||||
already_AddRefed<Promise> FileSystemDirectoryIterator::Next() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
|
||||
#define DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class Promise;
|
||||
|
||||
class FileSystemDirectoryIterator : public nsISupports, public nsWrapperCache {
|
||||
public:
|
||||
explicit FileSystemDirectoryIterator(nsIGlobalObject* aGlobal);
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemDirectoryIterator)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
nsIGlobalObject* GetParentObject() const;
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL Interface
|
||||
already_AddRefed<Promise> Next();
|
||||
|
||||
protected:
|
||||
virtual ~FileSystemDirectoryIterator() = default;
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FS_FILESYSTEMDIRECTORYITERATOR_H_
|
|
@ -0,0 +1,75 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileSystemFileHandle.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FileSystemFileHandleBinding.h"
|
||||
#include "mozilla/dom/FileSystemHandleBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemFileHandle,
|
||||
FileSystemHandle)
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemFileHandle, FileSystemHandle)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
|
||||
JSObject* FileSystemFileHandle::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return FileSystemFileHandle_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
// WebIDL Interface
|
||||
|
||||
FileSystemHandleKind FileSystemFileHandle::Kind() {
|
||||
return FileSystemHandleKind::File;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemFileHandle::GetFile() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
#ifdef MOZ_DOM_STREAMS
|
||||
already_AddRefed<Promise> FileSystemFileHandle::CreateWritable(
|
||||
const FileSystemCreateWritableOptions& aOptions) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
#endif
|
||||
|
||||
already_AddRefed<Promise> FileSystemFileHandle::CreateSyncAccessHandle() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FS_FILESYSTEMFILEHANDLE_H_
|
||||
#define DOM_FS_FILESYSTEMFILEHANDLE_H_
|
||||
|
||||
#include "mozilla/dom/FileSystemHandle.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
struct FileSystemCreateWritableOptions;
|
||||
|
||||
class FileSystemFileHandle final : public FileSystemHandle {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemFileHandle,
|
||||
FileSystemHandle)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL interface
|
||||
FileSystemHandleKind Kind() override;
|
||||
|
||||
already_AddRefed<Promise> GetFile();
|
||||
|
||||
#ifdef MOZ_DOM_STREAMS
|
||||
already_AddRefed<Promise> CreateWritable(
|
||||
const FileSystemCreateWritableOptions& aOptions);
|
||||
#endif
|
||||
|
||||
already_AddRefed<Promise> CreateSyncAccessHandle();
|
||||
|
||||
private:
|
||||
~FileSystemFileHandle() = default;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FS_FILESYSTEMFILEHANDLE_H_
|
|
@ -0,0 +1,50 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileSystemHandle.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FileSystemHandleBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemHandle)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemHandle);
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemHandle);
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemHandle, mGlobal);
|
||||
|
||||
// WebIDL Boilerplate
|
||||
|
||||
nsIGlobalObject* FileSystemHandle::GetParentObject() const { return mGlobal; }
|
||||
|
||||
JSObject* FileSystemHandle::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return FileSystemHandle_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
// WebIDL Interface
|
||||
|
||||
void FileSystemHandle::GetName(DOMString& aResult) { aResult.SetNull(); }
|
||||
|
||||
already_AddRefed<Promise> FileSystemHandle::IsSameEntry(
|
||||
FileSystemHandle& aOther) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FS_FILESYSTEMHANDLE_H_
|
||||
#define DOM_FS_FILESYSTEMHANDLE_H_
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class DOMString;
|
||||
enum class FileSystemHandleKind : uint8_t;
|
||||
class Promise;
|
||||
|
||||
class FileSystemHandle : public nsISupports, public nsWrapperCache {
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemHandle)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
nsIGlobalObject* GetParentObject() const;
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL Interface
|
||||
virtual FileSystemHandleKind Kind() = 0;
|
||||
|
||||
void GetName(DOMString& aResult);
|
||||
|
||||
already_AddRefed<Promise> IsSameEntry(FileSystemHandle& aOther);
|
||||
|
||||
protected:
|
||||
virtual ~FileSystemHandle() = default;
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FS_FILESYSTEMHANDLE_H_
|
|
@ -0,0 +1,100 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileSystemSyncAccessHandle.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FileSystemSyncAccessHandleBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemSyncAccessHandle)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemSyncAccessHandle);
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemSyncAccessHandle);
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileSystemSyncAccessHandle, mGlobal);
|
||||
|
||||
// WebIDL Boilerplate
|
||||
|
||||
nsIGlobalObject* FileSystemSyncAccessHandle::GetParentObject() const {
|
||||
return mGlobal;
|
||||
}
|
||||
|
||||
JSObject* FileSystemSyncAccessHandle::WrapObject(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
|
||||
return FileSystemSyncAccessHandle_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
// WebIDL Interface
|
||||
|
||||
uint64_t FileSystemSyncAccessHandle::Read(
|
||||
const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
|
||||
const FileSystemReadWriteOptions& aOptions) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t FileSystemSyncAccessHandle::Write(
|
||||
const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
|
||||
const FileSystemReadWriteOptions& aOptions) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemSyncAccessHandle::Truncate(uint64_t aSize) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemSyncAccessHandle::GetSize() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemSyncAccessHandle::Flush() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemSyncAccessHandle::Close() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,59 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_
|
||||
#define DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
struct FileSystemReadWriteOptions;
|
||||
class MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer;
|
||||
class Promise;
|
||||
|
||||
class FileSystemSyncAccessHandle final : public nsISupports,
|
||||
public nsWrapperCache {
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemSyncAccessHandle)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
nsIGlobalObject* GetParentObject() const;
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL Interface
|
||||
uint64_t Read(
|
||||
const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
|
||||
const FileSystemReadWriteOptions& aOptions);
|
||||
|
||||
uint64_t Write(
|
||||
const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
|
||||
const FileSystemReadWriteOptions& aOptions);
|
||||
|
||||
already_AddRefed<Promise> Truncate(uint64_t aSize);
|
||||
|
||||
already_AddRefed<Promise> GetSize();
|
||||
|
||||
already_AddRefed<Promise> Flush();
|
||||
|
||||
already_AddRefed<Promise> Close();
|
||||
|
||||
private:
|
||||
virtual ~FileSystemSyncAccessHandle() = default;
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FS_FILESYSTEMSYNCACCESSHANDLE_H_
|
|
@ -0,0 +1,70 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileSystemWritableFileStream.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FileSystemWritableFileStreamBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemWritableFileStream,
|
||||
WritableStream)
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(FileSystemWritableFileStream, WritableStream)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
|
||||
JSObject* FileSystemWritableFileStream::WrapObject(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
|
||||
return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
// WebIDL Interface
|
||||
|
||||
already_AddRefed<Promise> FileSystemWritableFileStream::Write(
|
||||
const ArrayBufferViewOrArrayBufferOrBlobOrUSVStringOrWriteParams& aData) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemWritableFileStream::Seek(
|
||||
uint64_t aPosition) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemWritableFileStream::Truncate(
|
||||
uint64_t aSize) {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,40 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
|
||||
#define DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
|
||||
|
||||
#include "mozilla/dom/WritableStream.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class ArrayBufferViewOrArrayBufferOrBlobOrUSVStringOrWriteParams;
|
||||
|
||||
class FileSystemWritableFileStream final : public WritableStream {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FileSystemWritableFileStream,
|
||||
WritableStream)
|
||||
|
||||
// WebIDL Boilerplate
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// WebIDL Interface
|
||||
already_AddRefed<Promise> Write(
|
||||
const ArrayBufferViewOrArrayBufferOrBlobOrUSVStringOrWriteParams& aData);
|
||||
|
||||
already_AddRefed<Promise> Seek(uint64_t aPosition);
|
||||
|
||||
already_AddRefed<Promise> Truncate(uint64_t aSize);
|
||||
|
||||
private:
|
||||
~FileSystemWritableFileStream() = default;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_FS_FILESYSTEMWRITABLEFILESTREAM_H_
|
|
@ -0,0 +1,33 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
"FileSystemDirectoryHandle.h",
|
||||
"FileSystemDirectoryIterator.h",
|
||||
"FileSystemFileHandle.h",
|
||||
"FileSystemHandle.h",
|
||||
"FileSystemSyncAccessHandle.h",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"FileSystemDirectoryHandle.cpp",
|
||||
"FileSystemDirectoryIterator.cpp",
|
||||
"FileSystemFileHandle.cpp",
|
||||
"FileSystemHandle.cpp",
|
||||
"FileSystemSyncAccessHandle.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_DOM_STREAMS"]:
|
||||
EXPORTS.mozilla.dom += [
|
||||
"FileSystemWritableFileStream.h",
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
"FileSystemWritableFileStream.cpp",
|
||||
]
|
||||
|
||||
include("/ipc/chromium/chromium-config.mozbuild")
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
|
@ -71,7 +71,9 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
: mRegistered(false),
|
||||
mReturnPlaceholderData(aReturnPlaceholderData),
|
||||
mOwningElement(aOwningElement),
|
||||
mRefreshDriver(aRefreshDriver) {
|
||||
mRefreshDriver(aRefreshDriver),
|
||||
mWatchManager(this, AbstractThread::MainThread()),
|
||||
mPendingThrottledCapture(false) {
|
||||
MOZ_ASSERT(mOwningElement);
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,57 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
mReturnPlaceholderData = aReturnPlaceholderData;
|
||||
}
|
||||
|
||||
void WillRefresh(TimeStamp aTime) override {
|
||||
void NotifyCaptureStateChange() {
|
||||
if (mPendingThrottledCapture) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mOwningElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
Watchable<FrameCaptureState>* captureState =
|
||||
mOwningElement->GetFrameCaptureState();
|
||||
if (!captureState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (captureState->Ref() == FrameCaptureState::CLEAN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mRefreshDriver) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mRefreshDriver->IsThrottled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TimeStamp next = mLastCaptureTime + TimeDuration::FromMilliseconds(
|
||||
nsRefreshDriver::DefaultInterval());
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
if (mLastCaptureTime.IsNull() || next < now) {
|
||||
CaptureFrame(now);
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingThrottledCapture = true;
|
||||
AbstractThread::MainThread()->DelayedDispatch(
|
||||
NS_NewRunnableFunction(
|
||||
__func__,
|
||||
[this, self = RefPtr<RequestedFrameRefreshObserver>(this), next] {
|
||||
mPendingThrottledCapture = false;
|
||||
CaptureFrame(next);
|
||||
}),
|
||||
// next >= now, so this is a guard for (next - now) flooring to 0.
|
||||
std::max<uint32_t>(
|
||||
1, static_cast<uint32_t>((next - now).ToMilliseconds())));
|
||||
}
|
||||
|
||||
void WillRefresh(TimeStamp aTime) override { CaptureFrame(aTime); }
|
||||
|
||||
void CaptureFrame(TimeStamp aTime) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
AUTO_PROFILER_LABEL("RequestedFrameRefreshObserver::WillRefresh", OTHER);
|
||||
|
@ -131,7 +183,9 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
return;
|
||||
}
|
||||
|
||||
if (mOwningElement->IsContextCleanForFrameCapture()) {
|
||||
if (auto* captureStateWatchable = mOwningElement->GetFrameCaptureState();
|
||||
captureStateWatchable &&
|
||||
*captureStateWatchable == FrameCaptureState::CLEAN) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -161,6 +215,11 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
}
|
||||
}
|
||||
|
||||
if (!mLastCaptureTime.IsNull() && aTime <= mLastCaptureTime) {
|
||||
aTime = mLastCaptureTime + TimeDuration::FromMilliseconds(1);
|
||||
}
|
||||
mLastCaptureTime = aTime;
|
||||
|
||||
{
|
||||
AUTO_PROFILER_LABEL("RequestedFrameRefreshObserver::WillRefresh:SetFrame",
|
||||
OTHER);
|
||||
|
@ -175,6 +234,7 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
|
||||
Unregister();
|
||||
mRefreshDriver = nullptr;
|
||||
mWatchManager.Shutdown();
|
||||
}
|
||||
|
||||
void Register() {
|
||||
|
@ -188,6 +248,17 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
"Canvas frame capture listeners");
|
||||
mRegistered = true;
|
||||
}
|
||||
|
||||
if (!mOwningElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Watchable<FrameCaptureState>* captureState =
|
||||
mOwningElement->GetFrameCaptureState()) {
|
||||
mWatchManager.Watch(
|
||||
*captureState,
|
||||
&RequestedFrameRefreshObserver::NotifyCaptureStateChange);
|
||||
}
|
||||
}
|
||||
|
||||
void Unregister() {
|
||||
|
@ -200,6 +271,17 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
mRefreshDriver->RemoveRefreshObserver(this, FlushType::Display);
|
||||
mRegistered = false;
|
||||
}
|
||||
|
||||
if (!mOwningElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Watchable<FrameCaptureState>* captureState =
|
||||
mOwningElement->GetFrameCaptureState()) {
|
||||
mWatchManager.Unwatch(
|
||||
*captureState,
|
||||
&RequestedFrameRefreshObserver::NotifyCaptureStateChange);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -210,8 +292,11 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver {
|
|||
|
||||
bool mRegistered;
|
||||
bool mReturnPlaceholderData;
|
||||
HTMLCanvasElement* const mOwningElement;
|
||||
const WeakPtr<HTMLCanvasElement> mOwningElement;
|
||||
RefPtr<nsRefreshDriver> mRefreshDriver;
|
||||
WatchManager<RequestedFrameRefreshObserver> mWatchManager;
|
||||
TimeStamp mLastCaptureTime;
|
||||
bool mPendingThrottledCapture;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -1191,8 +1276,11 @@ void HTMLCanvasElement::MarkContextCleanForFrameCapture() {
|
|||
mCurrentContext->MarkContextCleanForFrameCapture();
|
||||
}
|
||||
|
||||
bool HTMLCanvasElement::IsContextCleanForFrameCapture() {
|
||||
return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
|
||||
Watchable<FrameCaptureState>* HTMLCanvasElement::GetFrameCaptureState() {
|
||||
if (!mCurrentContext) {
|
||||
return nullptr;
|
||||
}
|
||||
return mCurrentContext->GetFrameCaptureState();
|
||||
}
|
||||
|
||||
nsresult HTMLCanvasElement::RegisterFrameCaptureListener(
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
# define mozilla_dom_HTMLCanvasElement_h
|
||||
|
||||
# include "mozilla/Attributes.h"
|
||||
# include "mozilla/StateWatching.h"
|
||||
# include "mozilla/WeakPtr.h"
|
||||
# include "nsIDOMEventListener.h"
|
||||
# include "nsIObserver.h"
|
||||
|
@ -23,6 +24,7 @@ class nsICanvasRenderingContextInternal;
|
|||
class nsIInputStream;
|
||||
class nsITimerCallback;
|
||||
enum class gfxAlphaType;
|
||||
enum class FrameCaptureState : uint8_t;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -300,8 +302,9 @@ class HTMLCanvasElement final : public nsGenericHTMLElement,
|
|||
// copies for future frames when no drawing has occurred.
|
||||
void MarkContextCleanForFrameCapture();
|
||||
|
||||
// Starts returning false when something is drawn.
|
||||
bool IsContextCleanForFrameCapture();
|
||||
// Returns non-null when the current context supports captureStream().
|
||||
// The FrameCaptureState gets set to DIRTY when something is drawn.
|
||||
Watchable<FrameCaptureState>* GetFrameCaptureState();
|
||||
|
||||
nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
|
||||
|
||||
|
|
|
@ -785,12 +785,12 @@ MediaResult FFmpegVideoDecoder<LIBAV_VER>::CreateImageVAAPI(
|
|||
}
|
||||
|
||||
MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
|
||||
auto surface = mVideoFramePool->GetVideoFrameSurface(vaDesc);
|
||||
auto surface = mVideoFramePool->GetVideoFrameSurface(vaDesc, mCodecContext,
|
||||
mFrame, mLib);
|
||||
if (!surface) {
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("VAAPI dmabuf allocation error"));
|
||||
}
|
||||
surface->LockVAAPIData(mCodecContext, mFrame, mLib);
|
||||
surface->SetYUVColorSpace(GetFrameColorSpace());
|
||||
|
||||
if (mLib->av_frame_get_color_range) {
|
||||
|
|
|
@ -23,6 +23,7 @@ VideoFrameSurfaceDMABuf::VideoFrameSurfaceDMABuf(DMABufSurface* aSurface)
|
|||
MOZ_ASSERT(mSurface);
|
||||
MOZ_RELEASE_ASSERT(mSurface->GetAsDMABufSurfaceYUV());
|
||||
mSurface->GlobalRefCountCreate();
|
||||
mSurface->GlobalRefAdd();
|
||||
FFMPEG_LOG("VideoFrameSurfaceDMABuf: creating surface UID = %d",
|
||||
mSurface->GetUID());
|
||||
}
|
||||
|
@ -68,17 +69,23 @@ VideoFrameSurfaceVAAPI::~VideoFrameSurfaceVAAPI() {
|
|||
ReleaseVAAPIData(/* aForFrameRecycle */ false);
|
||||
}
|
||||
|
||||
VideoFramePool::VideoFramePool(bool aUseVAAPI) : mUseVAAPI(aUseVAAPI) {}
|
||||
VideoFramePool::VideoFramePool(bool aUseVAAPI)
|
||||
: mUseVAAPI(aUseVAAPI), mSurfaceLock("VideoFramePoolSurfaceLock") {}
|
||||
|
||||
VideoFramePool::~VideoFramePool() { mDMABufSurfaces.Clear(); }
|
||||
VideoFramePool::~VideoFramePool() {
|
||||
MutexAutoLock lock(mSurfaceLock);
|
||||
mDMABufSurfaces.Clear();
|
||||
}
|
||||
|
||||
void VideoFramePool::ReleaseUnusedVAAPIFrames() {
|
||||
if (!mUseVAAPI) {
|
||||
return;
|
||||
}
|
||||
MutexAutoLock lock(mSurfaceLock);
|
||||
for (const auto& surface : mDMABufSurfaces) {
|
||||
if (!surface->IsUsed()) {
|
||||
surface->ReleaseVAAPIData();
|
||||
auto* dmabufSurface = surface->AsVideoFrameSurfaceVAAPI();
|
||||
if (!dmabufSurface->IsUsed()) {
|
||||
dmabufSurface->ReleaseVAAPIData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +93,13 @@ void VideoFramePool::ReleaseUnusedVAAPIFrames() {
|
|||
RefPtr<VideoFrameSurface> VideoFramePool::GetFreeVideoFrameSurface() {
|
||||
int len = mDMABufSurfaces.Length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!mDMABufSurfaces[i]->IsUsed()) {
|
||||
auto* dmabufSurface = mDMABufSurfaces[i]->AsVideoFrameSurfaceDMABuf();
|
||||
if (!dmabufSurface->IsUsed()) {
|
||||
auto* vaapiSurface = dmabufSurface->AsVideoFrameSurfaceVAAPI();
|
||||
if (vaapiSurface) {
|
||||
vaapiSurface->ReleaseVAAPIData();
|
||||
}
|
||||
dmabufSurface->MarkAsUsed();
|
||||
return mDMABufSurfaces[i];
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +107,8 @@ RefPtr<VideoFrameSurface> VideoFramePool::GetFreeVideoFrameSurface() {
|
|||
}
|
||||
|
||||
RefPtr<VideoFrameSurface> VideoFramePool::GetVideoFrameSurface(
|
||||
VADRMPRIMESurfaceDescriptor& aVaDesc) {
|
||||
VADRMPRIMESurfaceDescriptor& aVaDesc, AVCodecContext* aAVCodecContext,
|
||||
AVFrame* aAVFrame, FFmpegLibWrapper* aLib) {
|
||||
// VADRMPRIMESurfaceDescriptor can be used with VA-API only.
|
||||
MOZ_ASSERT(mUseVAAPI);
|
||||
|
||||
|
@ -104,6 +118,7 @@ RefPtr<VideoFrameSurface> VideoFramePool::GetVideoFrameSurface(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mSurfaceLock);
|
||||
auto videoSurface = GetFreeVideoFrameSurface();
|
||||
if (!videoSurface) {
|
||||
RefPtr<DMABufSurfaceYUV> surface =
|
||||
|
@ -114,17 +129,18 @@ RefPtr<VideoFrameSurface> VideoFramePool::GetVideoFrameSurface(
|
|||
FFMPEG_LOG("Created new VA-API DMABufSurface UID = %d", surface->GetUID());
|
||||
videoSurface = new VideoFrameSurfaceVAAPI(surface);
|
||||
mDMABufSurfaces.AppendElement(videoSurface);
|
||||
return videoSurface;
|
||||
} else {
|
||||
RefPtr<DMABufSurfaceYUV> surface = videoSurface->GetDMABufSurface();
|
||||
if (!surface->UpdateYUVData(aVaDesc)) {
|
||||
return nullptr;
|
||||
}
|
||||
FFMPEG_LOG("Reusing VA-API DMABufSurface UID = %d", surface->GetUID());
|
||||
}
|
||||
|
||||
// Release VAAPI surface data before we reuse it.
|
||||
videoSurface->ReleaseVAAPIData();
|
||||
|
||||
RefPtr<DMABufSurfaceYUV> surface = videoSurface->GetDMABufSurface();
|
||||
if (!surface->UpdateYUVData(aVaDesc)) {
|
||||
return nullptr;
|
||||
auto vaapiSurface = videoSurface->AsVideoFrameSurfaceVAAPI();
|
||||
if (vaapiSurface) {
|
||||
vaapiSurface->LockVAAPIData(aAVCodecContext, aAVFrame, aLib);
|
||||
}
|
||||
FFMPEG_LOG("Reusing VA-API DMABufSurface UID = %d", surface->GetUID());
|
||||
return videoSurface;
|
||||
}
|
||||
|
||||
|
@ -139,6 +155,7 @@ RefPtr<VideoFrameSurface> VideoFramePool::GetVideoFrameSurface(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mSurfaceLock);
|
||||
auto videoSurface = GetFreeVideoFrameSurface();
|
||||
if (!videoSurface) {
|
||||
RefPtr<DMABufSurfaceYUV> surface = DMABufSurfaceYUV::CreateYUVSurface(
|
||||
|
|
|
@ -16,22 +16,25 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class VideoFramePool;
|
||||
|
||||
class VideoFrameSurface {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoFrameSurface)
|
||||
|
||||
VideoFrameSurface(){};
|
||||
VideoFrameSurface() = default;
|
||||
|
||||
virtual void LockVAAPIData(AVCodecContext* aAVCodecContext, AVFrame* aAVFrame,
|
||||
FFmpegLibWrapper* aLib){};
|
||||
virtual void ReleaseVAAPIData(bool aForFrameRecycle = true){};
|
||||
virtual bool IsUsed() const = 0;
|
||||
virtual class VideoFrameSurfaceDMABuf* AsVideoFrameSurfaceDMABuf() {
|
||||
return nullptr;
|
||||
}
|
||||
virtual class VideoFrameSurfaceVAAPI* AsVideoFrameSurfaceVAAPI() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void SetYUVColorSpace(mozilla::gfx::YUVColorSpace aColorSpace) = 0;
|
||||
virtual void SetColorRange(mozilla::gfx::ColorRange aColorRange) = 0;
|
||||
|
||||
virtual RefPtr<DMABufSurfaceYUV> GetDMABufSurface() { return nullptr; };
|
||||
|
||||
virtual RefPtr<layers::Image> GetAsImage() = 0;
|
||||
|
||||
// Don't allow VideoFrameSurface plain copy as it leads to
|
||||
|
@ -47,17 +50,18 @@ class VideoFrameSurface {
|
|||
// VideoFrameSurfaceDMABuf is YUV dmabuf surface used for SW video decoding.
|
||||
// Stores decoded video data in GPU memory.
|
||||
class VideoFrameSurfaceDMABuf : public VideoFrameSurface {
|
||||
friend class VideoFramePool;
|
||||
|
||||
public:
|
||||
explicit VideoFrameSurfaceDMABuf(DMABufSurface* aSurface);
|
||||
|
||||
// Check if DMABufSurface is used by any gecko rendering process
|
||||
// (WebRender or GL compositor) or by DMABUFSurfaceImage/VideoData.
|
||||
bool IsUsed() const { return mSurface->IsGlobalRefSet(); }
|
||||
class VideoFrameSurfaceDMABuf* AsVideoFrameSurfaceDMABuf() {
|
||||
return this;
|
||||
}
|
||||
|
||||
void SetYUVColorSpace(mozilla::gfx::YUVColorSpace aColorSpace) {
|
||||
mSurface->GetAsDMABufSurfaceYUV()->SetYUVColorSpace(aColorSpace);
|
||||
}
|
||||
|
||||
void SetColorRange(mozilla::gfx::ColorRange aColorRange) {
|
||||
mSurface->GetAsDMABufSurfaceYUV()->SetColorRange(aColorRange);
|
||||
}
|
||||
|
@ -69,9 +73,14 @@ class VideoFrameSurfaceDMABuf : public VideoFrameSurface {
|
|||
RefPtr<layers::Image> GetAsImage();
|
||||
|
||||
protected:
|
||||
const RefPtr<DMABufSurface> mSurface;
|
||||
// Check if DMABufSurface is used by any gecko rendering process
|
||||
// (WebRender or GL compositor) or by DMABUFSurfaceImage/VideoData.
|
||||
bool IsUsed() const { return mSurface->IsGlobalRefSet(); }
|
||||
void MarkAsUsed() { mSurface->GlobalRefAdd(); }
|
||||
|
||||
protected:
|
||||
const RefPtr<DMABufSurface> mSurface;
|
||||
|
||||
~VideoFrameSurfaceDMABuf(){};
|
||||
};
|
||||
|
||||
|
@ -104,18 +113,23 @@ class VideoFrameSurfaceDMABuf : public VideoFrameSurface {
|
|||
// Unfortunately there isn't any obvious way how to mark particular VASurface
|
||||
// as used. The best we can do is to hold a reference to particular AVBuffer
|
||||
// from decoded AVFrame and AVHWFramesContext which owns the AVBuffer.
|
||||
|
||||
class VideoFrameSurfaceVAAPI : public VideoFrameSurfaceDMABuf {
|
||||
friend class VideoFramePool;
|
||||
|
||||
public:
|
||||
explicit VideoFrameSurfaceVAAPI(DMABufSurface* aSurface);
|
||||
|
||||
virtual class VideoFrameSurfaceVAAPI* AsVideoFrameSurfaceVAAPI() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Lock VAAPI related data
|
||||
void LockVAAPIData(AVCodecContext* aAVCodecContext, AVFrame* aAVFrame,
|
||||
FFmpegLibWrapper* aLib);
|
||||
|
||||
// Release VAAPI related data, DMABufSurface can be reused
|
||||
// for another frame.
|
||||
void ReleaseVAAPIData(bool aForFrameRecycle);
|
||||
void ReleaseVAAPIData(bool aForFrameRecycle = true);
|
||||
|
||||
private:
|
||||
~VideoFrameSurfaceVAAPI();
|
||||
|
@ -125,13 +139,15 @@ class VideoFrameSurfaceVAAPI : public VideoFrameSurfaceDMABuf {
|
|||
AVBufferRef* mHWAVBuffer;
|
||||
};
|
||||
|
||||
// VideoFramePool class is thread-safe.
|
||||
class VideoFramePool final {
|
||||
public:
|
||||
explicit VideoFramePool(bool aUseVAAPI);
|
||||
~VideoFramePool();
|
||||
|
||||
RefPtr<VideoFrameSurface> GetVideoFrameSurface(
|
||||
VADRMPRIMESurfaceDescriptor& aVaDesc);
|
||||
VADRMPRIMESurfaceDescriptor& aVaDesc, AVCodecContext* aAVCodecContext,
|
||||
AVFrame* aAVFrame, FFmpegLibWrapper* aLib);
|
||||
RefPtr<VideoFrameSurface> GetVideoFrameSurface(AVPixelFormat aPixelFormat,
|
||||
AVFrame* aFrame);
|
||||
void ReleaseUnusedVAAPIFrames();
|
||||
|
@ -141,6 +157,8 @@ class VideoFramePool final {
|
|||
|
||||
private:
|
||||
const bool mUseVAAPI;
|
||||
// Protect mDMABufSurfaces pool access
|
||||
mozilla::Mutex mSurfaceLock;
|
||||
nsTArray<RefPtr<VideoFrameSurface>> mDMABufSurfaces;
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ UNIFIED_SOURCES += [
|
|||
]
|
||||
LOCAL_INCLUDES += [
|
||||
'..',
|
||||
'/media/ffvpx',
|
||||
'/media/mozva',
|
||||
'include',
|
||||
]
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ SOURCES += [
|
|||
LOCAL_INCLUDES += [
|
||||
"..",
|
||||
"../ffmpeg58/include",
|
||||
"/media/ffvpx",
|
||||
"/media/mozva",
|
||||
]
|
||||
|
||||
CXXFLAGS += ["-Wno-deprecated-declarations"]
|
||||
|
|
|
@ -52,6 +52,7 @@ DIRS += [
|
|||
"filehandle",
|
||||
"filesystem",
|
||||
"flex",
|
||||
"fs",
|
||||
"gamepad",
|
||||
"geolocation",
|
||||
"grid",
|
||||
|
|
|
@ -752,6 +752,19 @@ already_AddRefed<Promise> StorageManager::Estimate(ErrorResult& aRv) {
|
|||
aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> StorageManager::GetDirectory() {
|
||||
IgnoredErrorResult rv;
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(StorageManager, mOwner)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(StorageManager)
|
||||
|
|
|
@ -42,6 +42,8 @@ class StorageManager final : public nsISupports, public nsWrapperCache {
|
|||
|
||||
already_AddRefed<Promise> Estimate(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> GetDirectory();
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StorageManager)
|
||||
|
||||
|
|
|
@ -29,13 +29,13 @@ class Promise;
|
|||
class WritableStreamDefaultController;
|
||||
class WritableStreamDefaultWriter;
|
||||
|
||||
class WritableStream final : public nsISupports, public nsWrapperCache {
|
||||
class WritableStream : public nsISupports, public nsWrapperCache {
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WritableStream)
|
||||
|
||||
protected:
|
||||
~WritableStream();
|
||||
virtual ~WritableStream();
|
||||
|
||||
public:
|
||||
explicit WritableStream(const GlobalObject& aGlobal);
|
||||
|
|
|
@ -331,6 +331,12 @@ void PathUtils::ToFileURI(const GlobalObject&, const nsAString& aPath,
|
|||
}
|
||||
}
|
||||
|
||||
bool PathUtils::IsAbsolute(const GlobalObject&, const nsAString& aPath) {
|
||||
nsCOMPtr<nsIFile> path = new nsLocalFile();
|
||||
nsresult rv = InitFileWithPath(path, aPath);
|
||||
return NS_SUCCEEDED(rv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> PathUtils::GetProfileDir(const GlobalObject& aGlobal,
|
||||
ErrorResult& aErr) {
|
||||
auto guard = sDirCache.Lock();
|
||||
|
|
|
@ -62,6 +62,8 @@ class PathUtils final {
|
|||
static void ToFileURI(const GlobalObject&, const nsAString& aPath,
|
||||
nsCString& aResult, ErrorResult& aErr);
|
||||
|
||||
static bool IsAbsolute(const GlobalObject&, const nsAString& aPath);
|
||||
|
||||
static already_AddRefed<Promise> GetProfileDir(const GlobalObject& aGlobal,
|
||||
ErrorResult& aErr);
|
||||
|
||||
|
|
|
@ -392,6 +392,22 @@
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_isAbsolute() {
|
||||
if (Services.appinfo.OS === "WINNT") {
|
||||
ok(PathUtils.isAbsolute("C:"), "Drive paths are absolute paths on Windows");
|
||||
ok(PathUtils.isAbsolute("C:\\Windows"), "Paths from the root are absolute paths on Windows");
|
||||
ok(!PathUtils.isAbsolute("foo"), "Paths containing a single item are not absolute paths on Windows");
|
||||
ok(!PathUtils.isAbsolute(".\\foo"), "Paths relative to the current working directory are not absolute paths on Windows");
|
||||
ok(!PathUtils.isAbsolute("..\\foo"), "Paths relative to the parent directory are not absolute paths on Windows");
|
||||
} else {
|
||||
ok(PathUtils.isAbsolute("/"), "Root paths are absolute paths");
|
||||
ok(PathUtils.isAbsolute("/home"), "Paths with a root stem are absolute paths");
|
||||
ok(!PathUtils.isAbsolute("foo"), "Paths containing a single non-root item are not absolute paths");
|
||||
ok(!PathUtils.isAbsolute("./foo"), "Paths relative to the current working directory are not absolute paths");
|
||||
ok(!PathUtils.isAbsolute("../foo"), "Paths relative to the parent directory are not absolute paths");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_getDirectories() {
|
||||
const profile = await PathUtils.getProfileDir();
|
||||
is(
|
||||
|
|
|
@ -105,8 +105,9 @@ function runTest()
|
|||
}
|
||||
}
|
||||
|
||||
function testMacFocusesFormControl()
|
||||
async function testMacFocusesFormControl()
|
||||
{
|
||||
await SimpleTest.promiseFocus(window);
|
||||
testHTMLElements(htmlElementsMacPrefSet, false);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
|
|
@ -46,7 +46,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(WebAuthnManager)
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebAuthnManager,
|
||||
WebAuthnManagerBase)
|
||||
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
|
||||
tmp->mTransaction.reset();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
|
|
@ -91,7 +91,9 @@ class CanvasContext final : public nsICanvasRenderingContextInternal,
|
|||
void DidRefresh() override {}
|
||||
|
||||
void MarkContextCleanForFrameCapture() override {}
|
||||
bool IsContextCleanForFrameCapture() override { return false; }
|
||||
Watchable<FrameCaptureState>* GetFrameCaptureState() override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
void Configure(const dom::GPUCanvasConfiguration& aDesc);
|
||||
|
|
|
@ -31,9 +31,9 @@ interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
|
|||
// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frames
|
||||
// Ideally we would just include AnimationFrameProvider to add the interface,
|
||||
// but we cannot make an include conditional.
|
||||
[Pref="gfx.offscreencanvas.enabled", Throws]
|
||||
[Pref="dom.workers.requestAnimationFrame", Throws]
|
||||
long requestAnimationFrame(FrameRequestCallback callback);
|
||||
|
||||
[Pref="gfx.offscreencanvas.enabled", Throws]
|
||||
[Pref="dom.workers.requestAnimationFrame", Throws]
|
||||
void cancelAnimationFrame(long handle);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
dictionary FileSystemGetFileOptions {
|
||||
boolean create = false;
|
||||
};
|
||||
|
||||
dictionary FileSystemGetDirectoryOptions {
|
||||
boolean create = false;
|
||||
};
|
||||
|
||||
dictionary FileSystemRemoveOptions {
|
||||
boolean recursive = false;
|
||||
};
|
||||
|
||||
// TODO: Add Serializzable
|
||||
[Exposed=(Window,Worker), SecureContext, Pref="dom.fs.enabled"]
|
||||
interface FileSystemDirectoryHandle : FileSystemHandle {
|
||||
// This interface defines an async iterable, however that isn't supported yet
|
||||
// by the bindings. So for now just explicitly define what an async iterable
|
||||
// definition implies.
|
||||
//async iterable<USVString, FileSystemHandle>;
|
||||
FileSystemDirectoryIterator entries();
|
||||
FileSystemDirectoryIterator keys();
|
||||
FileSystemDirectoryIterator values();
|
||||
|
||||
Promise<FileSystemFileHandle> getFileHandle(USVString name, optional FileSystemGetFileOptions options = {});
|
||||
Promise<FileSystemDirectoryHandle> getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {});
|
||||
|
||||
Promise<void> removeEntry(USVString name, optional FileSystemRemoveOptions options = {});
|
||||
|
||||
Promise<sequence<USVString>?> resolve(FileSystemHandle possibleDescendant);
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// To implement FileSystemDirectoryHandle's async iteration until we can use
|
||||
// a natively supported `async iterable`.
|
||||
[Exposed=(Window,Worker), SecureContext, LegacyNoInterfaceObject]
|
||||
interface FileSystemDirectoryIterator {
|
||||
Promise<any> next();
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifdef MOZ_DOM_STREAMS
|
||||
dictionary FileSystemCreateWritableOptions {
|
||||
boolean keepExistingData = false;
|
||||
};
|
||||
#endif
|
||||
|
||||
// TODO: Add Serializable
|
||||
[Exposed=(Window,Worker), SecureContext, Pref="dom.fs.enabled"]
|
||||
interface FileSystemFileHandle : FileSystemHandle {
|
||||
Promise<File> getFile();
|
||||
#ifdef MOZ_DOM_STREAMS
|
||||
Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {});
|
||||
#endif
|
||||
|
||||
[Exposed=DedicatedWorker]
|
||||
Promise<FileSystemSyncAccessHandle> createSyncAccessHandle();
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
enum FileSystemHandleKind {
|
||||
"file",
|
||||
"directory",
|
||||
};
|
||||
|
||||
// TODO: Add Serializable
|
||||
[Exposed=(Window,Worker), SecureContext, Pref="dom.fs.enabled"]
|
||||
interface FileSystemHandle {
|
||||
readonly attribute FileSystemHandleKind kind;
|
||||
readonly attribute USVString name;
|
||||
|
||||
Promise<boolean> isSameEntry(FileSystemHandle other);
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
dictionary FileSystemReadWriteOptions {
|
||||
[EnforceRange] unsigned long long at;
|
||||
};
|
||||
|
||||
[Exposed=(DedicatedWorker), SecureContext, Pref="dom.fs.enabled"]
|
||||
interface FileSystemSyncAccessHandle {
|
||||
// TODO: Use `[AllowShared] BufferSource data` once it works (bug 1696216)
|
||||
unsigned long long read(([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) buffer, optional FileSystemReadWriteOptions options = {});
|
||||
unsigned long long write(([AllowShared] ArrayBufferView or [AllowShared] ArrayBuffer) buffer, optional FileSystemReadWriteOptions options = {});
|
||||
|
||||
Promise<void> truncate([EnforceRange] unsigned long long size);
|
||||
Promise<unsigned long long> getSize();
|
||||
Promise<void> flush();
|
||||
Promise<void> close();
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
enum WriteCommandType {
|
||||
"write",
|
||||
"seek",
|
||||
"truncate",
|
||||
};
|
||||
|
||||
dictionary WriteParams {
|
||||
required WriteCommandType type;
|
||||
unsigned long long? size;
|
||||
unsigned long long? position;
|
||||
(BufferSource or Blob or USVString)? data;
|
||||
};
|
||||
|
||||
typedef (BufferSource or Blob or USVString or WriteParams) FileSystemWriteChunkType;
|
||||
|
||||
[Exposed=(Window,Worker), SecureContext, Pref="dom.fs.enabled"]
|
||||
interface FileSystemWritableFileStream : WritableStream {
|
||||
Promise<void> write(FileSystemWriteChunkType data);
|
||||
Promise<void> seek(unsigned long long position);
|
||||
Promise<void> truncate(unsigned long long size);
|
||||
};
|
|
@ -56,7 +56,7 @@ partial interface HTMLCanvasElement {
|
|||
// For OffscreenCanvas
|
||||
// Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas
|
||||
partial interface HTMLCanvasElement {
|
||||
[Pref="gfx.offscreencanvas.enabled", Throws]
|
||||
[Func="CanvasUtils::IsOffscreenCanvasEnabled", Throws]
|
||||
OffscreenCanvas transferControlToOffscreen();
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ dictionary ImageEncodeOptions {
|
|||
enum OffscreenRenderingContextId { /* "2d", */ "bitmaprenderer", "webgl", "webgl2", "webgpu" };
|
||||
|
||||
[Exposed=(Window,Worker),
|
||||
Pref="gfx.offscreencanvas.enabled"]
|
||||
Func="CanvasUtils::IsOffscreenCanvasEnabled"]
|
||||
interface OffscreenCanvas : EventTarget {
|
||||
constructor([EnforceRange] unsigned long width, [EnforceRange] unsigned long height);
|
||||
|
||||
|
|
|
@ -26,3 +26,9 @@ dictionary StorageEstimate {
|
|||
unsigned long long usage;
|
||||
unsigned long long quota;
|
||||
};
|
||||
|
||||
[SecureContext]
|
||||
partial interface StorageManager {
|
||||
[Pref="dom.fs.enabled"]
|
||||
Promise<FileSystemDirectoryHandle> getDirectory();
|
||||
};
|
||||
|
|
|
@ -388,6 +388,7 @@ GENERATED_WEBIDL_FILES = [
|
|||
|
||||
PREPROCESSED_WEBIDL_FILES = [
|
||||
"Animation.webidl",
|
||||
"FileSystemFileHandle.webidl",
|
||||
"Node.webidl",
|
||||
"Window.webidl",
|
||||
]
|
||||
|
@ -545,9 +546,13 @@ WEBIDL_FILES = [
|
|||
"FileReaderSync.webidl",
|
||||
"FileSystem.webidl",
|
||||
"FileSystemDirectoryEntry.webidl",
|
||||
"FileSystemDirectoryHandle.webidl",
|
||||
"FileSystemDirectoryIterator.webidl",
|
||||
"FileSystemDirectoryReader.webidl",
|
||||
"FileSystemEntry.webidl",
|
||||
"FileSystemFileEntry.webidl",
|
||||
"FileSystemHandle.webidl",
|
||||
"FileSystemSyncAccessHandle.webidl",
|
||||
"FinalizationRegistry.webidl",
|
||||
"FocusEvent.webidl",
|
||||
"FontFace.webidl",
|
||||
|
@ -1005,6 +1010,7 @@ WEBIDL_FILES = [
|
|||
|
||||
if CONFIG["MOZ_DOM_STREAMS"]:
|
||||
WEBIDL_FILES += [
|
||||
"FileSystemWritableFileStream.webidl",
|
||||
"QueuingStrategy.webidl",
|
||||
"ReadableByteStreamController.webidl",
|
||||
"ReadableStream.webidl",
|
||||
|
|
|
@ -85,7 +85,8 @@ class gfxVarReceiver;
|
|||
_(DrmRenderDevice, nsCString, nsCString()) \
|
||||
_(UseDMABuf, bool, false) \
|
||||
_(WebRenderRequiresHardwareDriver, bool, false) \
|
||||
_(SupportsThreadsafeGL, bool, false)
|
||||
_(SupportsThreadsafeGL, bool, false) \
|
||||
_(OffscreenCanvasDomainAllowlist, nsCString, nsCString())
|
||||
|
||||
/* Add new entries above this line. */
|
||||
|
||||
|
|
|
@ -28,10 +28,14 @@ using namespace mozilla::gl;
|
|||
|
||||
DMABUFSurfaceImage::DMABUFSurfaceImage(DMABufSurface* aSurface)
|
||||
: Image(nullptr, ImageFormat::DMABUF), mSurface(aSurface) {
|
||||
mSurface->GlobalRefAdd();
|
||||
MOZ_DIAGNOSTIC_ASSERT(mSurface->IsGlobalRefSet(),
|
||||
"DMABufSurface must be marked as used!");
|
||||
}
|
||||
|
||||
DMABUFSurfaceImage::~DMABUFSurfaceImage() { mSurface->GlobalRefRelease(); }
|
||||
DMABUFSurfaceImage::~DMABUFSurfaceImage() {
|
||||
// Unref as we're done with this surface.
|
||||
mSurface->GlobalRefRelease();
|
||||
}
|
||||
|
||||
StaticRefPtr<GLContext> sSnapshotContext;
|
||||
static StaticMutex sSnapshotContextMutex;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPrefs_accessibility.h"
|
||||
#include "mozilla/StaticPrefs_apz.h"
|
||||
#include "mozilla/StaticPrefs_canvas.h"
|
||||
#include "mozilla/StaticPrefs_gfx.h"
|
||||
#include "mozilla/StaticPrefs_layout.h"
|
||||
#include "mozilla/StaticPrefs_layers.h"
|
||||
|
@ -937,6 +938,12 @@ void gfxPlatform::Init() {
|
|||
gpu->LaunchGPUProcess();
|
||||
}
|
||||
|
||||
if (XRE_IsParentProcess()) {
|
||||
nsAutoCString allowlist;
|
||||
Preferences::GetCString("gfx.offscreencavas.domain-allowlist", allowlist);
|
||||
gfxVars::SetOffscreenCanvasDomainAllowlist(allowlist);
|
||||
}
|
||||
|
||||
gLastUsedFrameRate = ForceSoftwareVsync() ? GetSoftwareVsyncRate() : -1;
|
||||
Preferences::RegisterCallback(
|
||||
FrameRatePrefChanged,
|
||||
|
|
|
@ -210,7 +210,7 @@ js::Nursery::Nursery(GCRuntime* gc)
|
|||
reportPretenuringThreshold_(0),
|
||||
minorGCTriggerReason_(JS::GCReason::NO_REASON),
|
||||
hasRecentGrowthData(false),
|
||||
smoothedGrowthFactor(1.0),
|
||||
smoothedTargetSize(0.0),
|
||||
decommitTask(gc)
|
||||
#ifdef JS_GC_ZEAL
|
||||
,
|
||||
|
@ -1626,19 +1626,21 @@ void js::Nursery::maybeResizeNursery(JS::GCOptions options,
|
|||
}
|
||||
}
|
||||
|
||||
static inline double ClampDouble(double value, double min, double max) {
|
||||
MOZ_ASSERT(!std::isnan(value) && !std::isnan(min) && !std::isnan(max));
|
||||
static inline bool ClampDouble(double* value, double min, double max) {
|
||||
MOZ_ASSERT(!std::isnan(*value) && !std::isnan(min) && !std::isnan(max));
|
||||
MOZ_ASSERT(max >= min);
|
||||
|
||||
if (value <= min) {
|
||||
return min;
|
||||
if (*value <= min) {
|
||||
*value = min;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value >= max) {
|
||||
return max;
|
||||
if (*value >= max) {
|
||||
*value = max;
|
||||
return true;
|
||||
}
|
||||
|
||||
return value;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t js::Nursery::targetSize(JS::GCOptions options, JS::GCReason reason) {
|
||||
|
@ -1692,31 +1694,33 @@ size_t js::Nursery::targetSize(JS::GCOptions options, JS::GCReason reason) {
|
|||
// Limit the range of the growth factor to prevent transient high promotion
|
||||
// rates from affecting the nursery size too far into the future.
|
||||
static const double GrowthRange = 2.0;
|
||||
growthFactor = ClampDouble(growthFactor, 1.0 / GrowthRange, GrowthRange);
|
||||
bool wasClamped = ClampDouble(&growthFactor, 1.0 / GrowthRange, GrowthRange);
|
||||
|
||||
// Use exponential smoothing on the desired growth rate to take into account
|
||||
// the promotion rate from recent previous collections.
|
||||
// Calculate the target size based on data from this collection.
|
||||
double target = double(capacity()) * growthFactor;
|
||||
|
||||
// Use exponential smoothing on the target size to take into account data from
|
||||
// recent previous collections.
|
||||
if (hasRecentGrowthData &&
|
||||
now - lastCollectionEndTime() < TimeDuration::FromMilliseconds(200) &&
|
||||
!js::SupportDifferentialTesting()) {
|
||||
growthFactor = 0.75 * smoothedGrowthFactor + 0.25 * growthFactor;
|
||||
// Pay more attention to large changes.
|
||||
double fraction = wasClamped ? 0.5 : 0.25;
|
||||
smoothedTargetSize =
|
||||
(1 - fraction) * smoothedTargetSize + fraction * target;
|
||||
} else {
|
||||
smoothedTargetSize = target;
|
||||
}
|
||||
|
||||
hasRecentGrowthData = true;
|
||||
smoothedGrowthFactor = growthFactor;
|
||||
|
||||
// Leave size untouched if we are close to the promotion goal.
|
||||
// Leave size untouched if we are close to the target.
|
||||
static const double GoalWidth = 1.5;
|
||||
growthFactor = smoothedTargetSize / double(capacity());
|
||||
if (growthFactor > (1.0 / GoalWidth) && growthFactor < GoalWidth) {
|
||||
return capacity();
|
||||
}
|
||||
|
||||
// The multiplication below cannot overflow because growthFactor is at
|
||||
// most two.
|
||||
MOZ_ASSERT(growthFactor <= 2.0);
|
||||
MOZ_ASSERT(capacity() < SIZE_MAX / 2);
|
||||
|
||||
return roundSize(size_t(double(capacity()) * growthFactor));
|
||||
return roundSize(size_t(smoothedTargetSize));
|
||||
}
|
||||
|
||||
void js::Nursery::clearRecentGrowthData() {
|
||||
|
@ -1725,7 +1729,7 @@ void js::Nursery::clearRecentGrowthData() {
|
|||
}
|
||||
|
||||
hasRecentGrowthData = false;
|
||||
smoothedGrowthFactor = 1.0;
|
||||
smoothedTargetSize = 0.0;
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -479,7 +479,7 @@ class Nursery {
|
|||
PreviousGC previousGC;
|
||||
|
||||
bool hasRecentGrowthData;
|
||||
double smoothedGrowthFactor;
|
||||
double smoothedTargetSize;
|
||||
|
||||
// Calculate the promotion rate of the most recent minor GC.
|
||||
// The valid_for_tenuring parameter is used to return whether this
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include "jit/AtomicOperationsGenerated.h"
|
||||
#include "vm/SharedMem.h"
|
||||
|
||||
namespace js {
|
||||
|
@ -64,7 +65,7 @@ namespace jit {
|
|||
*
|
||||
* It's not a requirement that these functions be inlined; performance
|
||||
* is not a great concern. On some platforms these functions may call
|
||||
* out to code that's generated at run time.
|
||||
* functions that use inline assembly. See GenerateAtomicOperations.py.
|
||||
*
|
||||
* In principle these functions will not be written in C++, thus
|
||||
* making races defined behavior if all racy accesses from C++ go via
|
||||
|
@ -149,13 +150,6 @@ class AtomicOperations {
|
|||
size_t nbytes);
|
||||
|
||||
public:
|
||||
// On some platforms we generate code for the atomics at run-time; that
|
||||
// happens here.
|
||||
static bool Initialize();
|
||||
|
||||
// Deallocate the code segment for generated atomics functions.
|
||||
static void ShutDown();
|
||||
|
||||
// Test lock-freedom for any int32 value. This implements the
|
||||
// Atomics::isLockFree() operation in the ECMAScript Shared Memory and
|
||||
// Atomics specification, as follows:
|
||||
|
@ -347,45 +341,12 @@ constexpr inline bool AtomicOperations::isLockfreeJS(int32_t size) {
|
|||
// participate in the memory exclusivity monitors implemented by the simulator.
|
||||
// Such a solution is likely to be difficult.
|
||||
|
||||
#if defined(JS_SIMULATOR_MIPS32)
|
||||
# if defined(__clang__) || defined(__GNUC__)
|
||||
# include "jit/mips-shared/AtomicOperations-mips-shared.h"
|
||||
# else
|
||||
# error "AtomicOperations on MIPS-32 for unknown compiler"
|
||||
# endif
|
||||
#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || \
|
||||
defined(_M_IX86)
|
||||
# if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
|
||||
# include "jit/shared/AtomicOperations-shared-jit.h"
|
||||
# else
|
||||
# include "jit/shared/AtomicOperations-feeling-lucky.h"
|
||||
# endif
|
||||
#elif defined(__arm__)
|
||||
# if defined(JS_CODEGEN_ARM)
|
||||
# include "jit/shared/AtomicOperations-shared-jit.h"
|
||||
# else
|
||||
# include "jit/shared/AtomicOperations-feeling-lucky.h"
|
||||
# endif
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
# if defined(JS_CODEGEN_ARM64)
|
||||
# include "jit/shared/AtomicOperations-shared-jit.h"
|
||||
# else
|
||||
# include "jit/shared/AtomicOperations-feeling-lucky.h"
|
||||
# endif
|
||||
#elif defined(__mips__)
|
||||
# if defined(__clang__) || defined(__GNUC__)
|
||||
# include "jit/mips-shared/AtomicOperations-mips-shared.h"
|
||||
# else
|
||||
# error "AtomicOperations on MIPS for an unknown compiler"
|
||||
# endif
|
||||
#elif defined(__ppc__) || defined(__PPC__) || defined(__sparc__) || \
|
||||
defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \
|
||||
defined(__PPC64LE__) || defined(__alpha__) || defined(__hppa__) || \
|
||||
defined(__sh__) || defined(__s390__) || defined(__s390x__) || \
|
||||
defined(__m68k__) || defined(__riscv) || defined(__wasi__)
|
||||
# include "jit/shared/AtomicOperations-feeling-lucky.h"
|
||||
#ifdef JS_HAVE_GENERATED_ATOMIC_OPS
|
||||
# include "jit/shared/AtomicOperations-shared-jit.h"
|
||||
#elif defined(JS_SIMULATOR_MIPS32) || defined(__mips__)
|
||||
# include "jit/mips-shared/AtomicOperations-mips-shared.h"
|
||||
#else
|
||||
# error "No AtomicOperations support provided for this platform"
|
||||
# include "jit/shared/AtomicOperations-feeling-lucky.h"
|
||||
#endif
|
||||
|
||||
#endif // jit_AtomicOperations_h
|
||||
|
|
|
@ -8118,6 +8118,8 @@ bool CacheIRCompiler::emitAtomicsLoadResult(ObjOperandId objId,
|
|||
// Load the value.
|
||||
BaseIndex source(scratch, index, ScaleFromScalarType(elementType));
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_load in
|
||||
// GenerateAtomicOperations.py
|
||||
auto sync = Synchronization::Load();
|
||||
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
@ -8168,6 +8170,8 @@ bool CacheIRCompiler::emitAtomicsStoreResult(ObjOperandId objId,
|
|||
// Store the value.
|
||||
BaseIndex dest(scratch, index, ScaleFromScalarType(elementType));
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_store in
|
||||
// GenerateAtomicOperations.py
|
||||
auto sync = Synchronization::Store();
|
||||
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
|
|
@ -0,0 +1,861 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This script generates jit/AtomicOperationsGenerated.h
|
||||
#
|
||||
# See the big comment in jit/AtomicOperations.h for an explanation.
|
||||
|
||||
import buildconfig
|
||||
|
||||
is_64bit = "JS_64BIT" in buildconfig.defines
|
||||
cpu_arch = buildconfig.substs["CPU_ARCH"]
|
||||
|
||||
|
||||
def fmt_insn(s):
|
||||
return '"' + s + '\\n\\t"\n'
|
||||
|
||||
|
||||
def gen_seqcst(fun_name):
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
return r"""
|
||||
inline void %(fun_name)s() {
|
||||
asm volatile ("mfence\n\t" ::: "memory");
|
||||
}""" % {
|
||||
"fun_name": fun_name,
|
||||
}
|
||||
if cpu_arch == "aarch64":
|
||||
return r"""
|
||||
inline void %(fun_name)s() {
|
||||
asm volatile ("dmb ish\n\t" ::: "memory");
|
||||
}""" % {
|
||||
"fun_name": fun_name,
|
||||
}
|
||||
if cpu_arch == "arm":
|
||||
return r"""
|
||||
inline void %(fun_name)s() {
|
||||
asm volatile ("dmb sy\n\t" ::: "memory");
|
||||
}""" % {
|
||||
"fun_name": fun_name,
|
||||
}
|
||||
raise Exception("Unexpected arch")
|
||||
|
||||
|
||||
def gen_load(fun_name, cpp_type, size, barrier):
|
||||
# NOTE: the assembly code must match the generated code in:
|
||||
# - CacheIRCompiler::emitAtomicsLoadResult
|
||||
# - LIRGenerator::visitLoadUnboxedScalar
|
||||
# - CodeGenerator::visitAtomicLoad64 (on 64-bit platforms)
|
||||
# - MacroAssembler::wasmLoad
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
insns = ""
|
||||
if barrier:
|
||||
insns += fmt_insn("mfence")
|
||||
if size == 8:
|
||||
insns += fmt_insn("movb (%[arg]), %[res]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("movw (%[arg]), %[res]")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("movl (%[arg]), %[res]")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("movq (%[arg]), %[res]")
|
||||
if barrier:
|
||||
insns += fmt_insn("mfence")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(const %(cpp_type)s* arg) {
|
||||
%(cpp_type)s res;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=r" (res)
|
||||
: [arg] "r" (arg)
|
||||
: "memory");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "aarch64":
|
||||
insns = ""
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb ish")
|
||||
if size == 8:
|
||||
insns += fmt_insn("ldrb %w[res], [%x[arg]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("ldrh %w[res], [%x[arg]]")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("ldr %w[res], [%x[arg]]")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("ldr %x[res], [%x[arg]]")
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb ish")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(const %(cpp_type)s* arg) {
|
||||
%(cpp_type)s res;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=r" (res)
|
||||
: [arg] "r" (arg)
|
||||
: "memory");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "arm":
|
||||
insns = ""
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb sy")
|
||||
if size == 8:
|
||||
insns += fmt_insn("ldrb %[res], [%[arg]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("ldrh %[res], [%[arg]]")
|
||||
else:
|
||||
assert size == 32
|
||||
insns += fmt_insn("ldr %[res], [%[arg]]")
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb sy")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(const %(cpp_type)s* arg) {
|
||||
%(cpp_type)s res;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=r" (res)
|
||||
: [arg] "r" (arg)
|
||||
: "memory");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
raise Exception("Unexpected arch")
|
||||
|
||||
|
||||
def gen_store(fun_name, cpp_type, size, barrier):
|
||||
# NOTE: the assembly code must match the generated code in:
|
||||
# - CacheIRCompiler::emitAtomicsStoreResult
|
||||
# - LIRGenerator::visitStoreUnboxedScalar
|
||||
# - CodeGenerator::visitAtomicStore64 (on 64-bit platforms)
|
||||
# - MacroAssembler::wasmStore
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
insns = ""
|
||||
if barrier:
|
||||
insns += fmt_insn("mfence")
|
||||
if size == 8:
|
||||
insns += fmt_insn("movb %[val], (%[addr])")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("movw %[val], (%[addr])")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("movl %[val], (%[addr])")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("movq %[val], (%[addr])")
|
||||
if barrier:
|
||||
insns += fmt_insn("mfence")
|
||||
return """
|
||||
inline void %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
asm volatile (%(insns)s
|
||||
:
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory");
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "aarch64":
|
||||
insns = ""
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb ish")
|
||||
if size == 8:
|
||||
insns += fmt_insn("strb %w[val], [%x[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("strh %w[val], [%x[addr]]")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("str %w[val], [%x[addr]]")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("str %x[val], [%x[addr]]")
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb ish")
|
||||
return """
|
||||
inline void %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
asm volatile (%(insns)s
|
||||
:
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory");
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "arm":
|
||||
insns = ""
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb sy")
|
||||
if size == 8:
|
||||
insns += fmt_insn("strb %[val], [%[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("strh %[val], [%[addr]]")
|
||||
else:
|
||||
assert size == 32
|
||||
insns += fmt_insn("str %[val], [%[addr]]")
|
||||
if barrier:
|
||||
insns += fmt_insn("dmb sy")
|
||||
return """
|
||||
inline void %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
asm volatile (%(insns)s
|
||||
:
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory");
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
raise Exception("Unexpected arch")
|
||||
|
||||
|
||||
def gen_exchange(fun_name, cpp_type, size):
|
||||
# NOTE: the assembly code must match the generated code in:
|
||||
# - MacroAssembler::atomicExchange
|
||||
# - MacroAssembler::atomicExchange64 (on 64-bit platforms)
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
# Request an input/output register for `val` so that we can simply XCHG it
|
||||
# with *addr.
|
||||
insns = ""
|
||||
if size == 8:
|
||||
insns += fmt_insn("xchgb %[val], (%[addr])")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("xchgw %[val], (%[addr])")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("xchgl %[val], (%[addr])")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("xchgq %[val], (%[addr])")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
asm volatile (%(insns)s
|
||||
: [val] "+r" (val)
|
||||
: [addr] "r" (addr)
|
||||
: "memory");
|
||||
return val;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "aarch64":
|
||||
insns = ""
|
||||
insns += fmt_insn("dmb ish")
|
||||
insns += fmt_insn("0:")
|
||||
if size == 8:
|
||||
insns += fmt_insn("ldxrb %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("stxrb %w[scratch], %w[val], [%x[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("ldxrh %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("stxrh %w[scratch], %w[val], [%x[addr]]")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("ldxr %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("stxr %w[scratch], %w[val], [%x[addr]]")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("ldxr %x[res], [%x[addr]]")
|
||||
insns += fmt_insn("stxr %w[scratch], %x[val], [%x[addr]]")
|
||||
insns += fmt_insn("cbnz %w[scratch], 0b")
|
||||
insns += fmt_insn("dmb ish")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
%(cpp_type)s res;
|
||||
uint32_t scratch;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&r"(res), [scratch] "=&r"(scratch)
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "arm":
|
||||
insns = ""
|
||||
insns += fmt_insn("dmb sy")
|
||||
insns += fmt_insn("0:")
|
||||
if size == 8:
|
||||
insns += fmt_insn("ldrexb %[res], [%[addr]]")
|
||||
insns += fmt_insn("strexb %[scratch], %[val], [%[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("ldrexh %[res], [%[addr]]")
|
||||
insns += fmt_insn("strexh %[scratch], %[val], [%[addr]]")
|
||||
else:
|
||||
assert size == 32
|
||||
insns += fmt_insn("ldrex %[res], [%[addr]]")
|
||||
insns += fmt_insn("strex %[scratch], %[val], [%[addr]]")
|
||||
insns += fmt_insn("cmp %[scratch], #1")
|
||||
insns += fmt_insn("beq 0b")
|
||||
insns += fmt_insn("dmb sy")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
%(cpp_type)s res;
|
||||
uint32_t scratch;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&r"(res), [scratch] "=&r"(scratch)
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
raise Exception("Unexpected arch")
|
||||
|
||||
|
||||
def gen_cmpxchg(fun_name, cpp_type, size):
|
||||
# NOTE: the assembly code must match the generated code in:
|
||||
# - MacroAssembler::compareExchange
|
||||
# - MacroAssembler::compareExchange64
|
||||
if cpu_arch == "x86" and size == 64:
|
||||
# Use a +A constraint to load `oldval` into EDX:EAX as input/output.
|
||||
# `newval` is loaded into ECX:EBX.
|
||||
return r"""
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr,
|
||||
%(cpp_type)s oldval,
|
||||
%(cpp_type)s newval) {
|
||||
asm volatile ("lock; cmpxchg8b (%%[addr])\n\t"
|
||||
: "+A" (oldval)
|
||||
: [addr] "r" (addr),
|
||||
"b" (uint32_t(newval & 0xffff'ffff)),
|
||||
"c" (uint32_t(newval >> 32))
|
||||
: "memory", "cc");
|
||||
return oldval;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
}
|
||||
if cpu_arch == "arm" and size == 64:
|
||||
return r"""
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr,
|
||||
%(cpp_type)s oldval,
|
||||
%(cpp_type)s newval) {
|
||||
uint32_t oldval0 = oldval & 0xffff'ffff;
|
||||
uint32_t oldval1 = oldval >> 32;
|
||||
uint32_t newval0 = newval & 0xffff'ffff;
|
||||
uint32_t newval1 = newval >> 32;
|
||||
asm volatile (
|
||||
"dmb sy\n\t"
|
||||
"0: ldrexd r0, r1, [%%[addr]]\n\t"
|
||||
"cmp r0, %%[oldval0]\n\t"
|
||||
"bne 1f\n\t"
|
||||
"cmp r1, %%[oldval1]\n\t"
|
||||
"bne 1f\n\t"
|
||||
"mov r2, %%[newval0]\n\t"
|
||||
"mov r3, %%[newval1]\n\t"
|
||||
"strexd r4, r2, r3, [%%[addr]]\n\t"
|
||||
"cmp r4, #1\n\t"
|
||||
"beq 0b\n\t"
|
||||
"1: dmb sy\n\t"
|
||||
"mov %%[oldval0], r0\n\t"
|
||||
"mov %%[oldval1], r1\n\t"
|
||||
: [oldval0] "+&r" (oldval0), [oldval1] "+&r"(oldval1)
|
||||
: [addr] "r" (addr), [newval0] "r" (newval0), [newval1] "r" (newval1)
|
||||
: "memory", "cc", "r0", "r1", "r2", "r3", "r4");
|
||||
return uint64_t(oldval0) | (uint64_t(oldval1) << 32);
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
}
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
# Use a +a constraint to load `oldval` into RAX as input/output register.
|
||||
insns = ""
|
||||
if size == 8:
|
||||
insns += fmt_insn("lock; cmpxchgb %[newval], (%[addr])")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("lock; cmpxchgw %[newval], (%[addr])")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("lock; cmpxchgl %[newval], (%[addr])")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("lock; cmpxchgq %[newval], (%[addr])")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr,
|
||||
%(cpp_type)s oldval,
|
||||
%(cpp_type)s newval) {
|
||||
asm volatile (%(insns)s
|
||||
: [oldval] "+a" (oldval)
|
||||
: [addr] "r" (addr), [newval] "r" (newval)
|
||||
: "memory", "cc");
|
||||
return oldval;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "aarch64":
|
||||
insns = ""
|
||||
insns += fmt_insn("dmb ish")
|
||||
insns += fmt_insn("0:")
|
||||
if size == 8:
|
||||
insns += fmt_insn("uxtb %w[scratch], %w[oldval]")
|
||||
insns += fmt_insn("ldxrb %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("cmp %w[res], %w[scratch]")
|
||||
insns += fmt_insn("b.ne 1f")
|
||||
insns += fmt_insn("stxrb %w[scratch], %w[newval], [%x[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("uxth %w[scratch], %w[oldval]")
|
||||
insns += fmt_insn("ldxrh %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("cmp %w[res], %w[scratch]")
|
||||
insns += fmt_insn("b.ne 1f")
|
||||
insns += fmt_insn("stxrh %w[scratch], %w[newval], [%x[addr]]")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("mov %w[scratch], %w[oldval]")
|
||||
insns += fmt_insn("ldxr %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("cmp %w[res], %w[scratch]")
|
||||
insns += fmt_insn("b.ne 1f")
|
||||
insns += fmt_insn("stxr %w[scratch], %w[newval], [%x[addr]]")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("mov %x[scratch], %x[oldval]")
|
||||
insns += fmt_insn("ldxr %x[res], [%x[addr]]")
|
||||
insns += fmt_insn("cmp %x[res], %x[scratch]")
|
||||
insns += fmt_insn("b.ne 1f")
|
||||
insns += fmt_insn("stxr %w[scratch], %x[newval], [%x[addr]]")
|
||||
insns += fmt_insn("cbnz %w[scratch], 0b")
|
||||
insns += fmt_insn("1: dmb ish")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr,
|
||||
%(cpp_type)s oldval,
|
||||
%(cpp_type)s newval) {
|
||||
%(cpp_type)s res, scratch;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&r" (res), [scratch] "=&r" (scratch)
|
||||
: [addr] "r" (addr), [oldval] "r"(oldval), [newval] "r" (newval)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "arm":
|
||||
insns = ""
|
||||
insns += fmt_insn("dmb sy")
|
||||
insns += fmt_insn("0:")
|
||||
if size == 8:
|
||||
insns += fmt_insn("uxtb %[scratch], %[oldval]")
|
||||
insns += fmt_insn("ldrexb %[res], [%[addr]]")
|
||||
insns += fmt_insn("cmp %[res], %[scratch]")
|
||||
insns += fmt_insn("bne 1f")
|
||||
insns += fmt_insn("strexb %[scratch], %[newval], [%[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("uxth %[scratch], %[oldval]")
|
||||
insns += fmt_insn("ldrexh %[res], [%[addr]]")
|
||||
insns += fmt_insn("cmp %[res], %[scratch]")
|
||||
insns += fmt_insn("bne 1f")
|
||||
insns += fmt_insn("strexh %[scratch], %[newval], [%[addr]]")
|
||||
else:
|
||||
assert size == 32
|
||||
insns += fmt_insn("mov %[scratch], %[oldval]")
|
||||
insns += fmt_insn("ldrex %[res], [%[addr]]")
|
||||
insns += fmt_insn("cmp %[res], %[scratch]")
|
||||
insns += fmt_insn("bne 1f")
|
||||
insns += fmt_insn("strex %[scratch], %[newval], [%[addr]]")
|
||||
insns += fmt_insn("cmp %[scratch], #1")
|
||||
insns += fmt_insn("beq 0b")
|
||||
insns += fmt_insn("1: dmb sy")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr,
|
||||
%(cpp_type)s oldval,
|
||||
%(cpp_type)s newval) {
|
||||
%(cpp_type)s res, scratch;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&r" (res), [scratch] "=&r" (scratch)
|
||||
: [addr] "r" (addr), [oldval] "r"(oldval), [newval] "r" (newval)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
raise Exception("Unexpected arch")
|
||||
|
||||
|
||||
def gen_fetchop(fun_name, cpp_type, size, op):
|
||||
# NOTE: the assembly code must match the generated code in:
|
||||
# - MacroAssembler::atomicFetchOp
|
||||
# - MacroAssembler::atomicFetchOp64 (on 64-bit platforms)
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
# The `add` operation can be optimized with XADD.
|
||||
if op == "add":
|
||||
insns = ""
|
||||
if size == 8:
|
||||
insns += fmt_insn("lock; xaddb %[val], (%[addr])")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("lock; xaddw %[val], (%[addr])")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("lock; xaddl %[val], (%[addr])")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("lock; xaddq %[val], (%[addr])")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
asm volatile (%(insns)s
|
||||
: [val] "+&r" (val)
|
||||
: [addr] "r" (addr)
|
||||
: "memory", "cc");
|
||||
return val;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
# Use a +a constraint to ensure `res` is stored in RAX. This is required
|
||||
# for the CMPXCHG instruction.
|
||||
insns = ""
|
||||
if size == 8:
|
||||
insns += fmt_insn("movb (%[addr]), %[res]")
|
||||
insns += fmt_insn("0: movb %[res], %[scratch]")
|
||||
insns += fmt_insn("OPb %[val], %[scratch]")
|
||||
insns += fmt_insn("lock; cmpxchgb %[scratch], (%[addr])")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("movw (%[addr]), %[res]")
|
||||
insns += fmt_insn("0: movw %[res], %[scratch]")
|
||||
insns += fmt_insn("OPw %[val], %[scratch]")
|
||||
insns += fmt_insn("lock; cmpxchgw %[scratch], (%[addr])")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("movl (%[addr]), %[res]")
|
||||
insns += fmt_insn("0: movl %[res], %[scratch]")
|
||||
insns += fmt_insn("OPl %[val], %[scratch]")
|
||||
insns += fmt_insn("lock; cmpxchgl %[scratch], (%[addr])")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("movq (%[addr]), %[res]")
|
||||
insns += fmt_insn("0: movq %[res], %[scratch]")
|
||||
insns += fmt_insn("OPq %[val], %[scratch]")
|
||||
insns += fmt_insn("lock; cmpxchgq %[scratch], (%[addr])")
|
||||
insns = insns.replace("OP", op)
|
||||
insns += fmt_insn("jnz 0b")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
%(cpp_type)s res, scratch;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&a" (res), [scratch] "=&r" (scratch)
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "aarch64":
|
||||
insns = ""
|
||||
insns += fmt_insn("dmb ish")
|
||||
insns += fmt_insn("0:")
|
||||
if size == 8:
|
||||
insns += fmt_insn("ldxrb %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("OP %x[scratch1], %x[res], %x[val]")
|
||||
insns += fmt_insn("stxrb %w[scratch2], %w[scratch1], [%x[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("ldxrh %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("OP %x[scratch1], %x[res], %x[val]")
|
||||
insns += fmt_insn("stxrh %w[scratch2], %w[scratch1], [%x[addr]]")
|
||||
elif size == 32:
|
||||
insns += fmt_insn("ldxr %w[res], [%x[addr]]")
|
||||
insns += fmt_insn("OP %x[scratch1], %x[res], %x[val]")
|
||||
insns += fmt_insn("stxr %w[scratch2], %w[scratch1], [%x[addr]]")
|
||||
else:
|
||||
assert size == 64
|
||||
insns += fmt_insn("ldxr %x[res], [%x[addr]]")
|
||||
insns += fmt_insn("OP %x[scratch1], %x[res], %x[val]")
|
||||
insns += fmt_insn("stxr %w[scratch2], %x[scratch1], [%x[addr]]")
|
||||
cpu_op = op
|
||||
if cpu_op == "or":
|
||||
cpu_op = "orr"
|
||||
if cpu_op == "xor":
|
||||
cpu_op = "eor"
|
||||
insns = insns.replace("OP", cpu_op)
|
||||
insns += fmt_insn("cbnz %w[scratch2], 0b")
|
||||
insns += fmt_insn("dmb ish")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
%(cpp_type)s res;
|
||||
uintptr_t scratch1, scratch2;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&r" (res), [scratch1] "=&r" (scratch1), [scratch2] "=&r"(scratch2)
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
if cpu_arch == "arm":
|
||||
insns = ""
|
||||
insns += fmt_insn("dmb sy")
|
||||
insns += fmt_insn("0:")
|
||||
if size == 8:
|
||||
insns += fmt_insn("ldrexb %[res], [%[addr]]")
|
||||
insns += fmt_insn("OP %[scratch1], %[res], %[val]")
|
||||
insns += fmt_insn("strexb %[scratch2], %[scratch1], [%[addr]]")
|
||||
elif size == 16:
|
||||
insns += fmt_insn("ldrexh %[res], [%[addr]]")
|
||||
insns += fmt_insn("OP %[scratch1], %[res], %[val]")
|
||||
insns += fmt_insn("strexh %[scratch2], %[scratch1], [%[addr]]")
|
||||
else:
|
||||
assert size == 32
|
||||
insns += fmt_insn("ldrex %[res], [%[addr]]")
|
||||
insns += fmt_insn("OP %[scratch1], %[res], %[val]")
|
||||
insns += fmt_insn("strex %[scratch2], %[scratch1], [%[addr]]")
|
||||
cpu_op = op
|
||||
if cpu_op == "or":
|
||||
cpu_op = "orr"
|
||||
if cpu_op == "xor":
|
||||
cpu_op = "eor"
|
||||
insns = insns.replace("OP", cpu_op)
|
||||
insns += fmt_insn("cmp %[scratch2], #1")
|
||||
insns += fmt_insn("beq 0b")
|
||||
insns += fmt_insn("dmb sy")
|
||||
return """
|
||||
inline %(cpp_type)s %(fun_name)s(%(cpp_type)s* addr, %(cpp_type)s val) {
|
||||
%(cpp_type)s res;
|
||||
uintptr_t scratch1, scratch2;
|
||||
asm volatile (%(insns)s
|
||||
: [res] "=&r" (res), [scratch1] "=&r" (scratch1), [scratch2] "=&r"(scratch2)
|
||||
: [addr] "r" (addr), [val] "r"(val)
|
||||
: "memory", "cc");
|
||||
return res;
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
raise Exception("Unexpected arch")
|
||||
|
||||
|
||||
def gen_copy(fun_name, cpp_type, size, unroll, direction):
|
||||
assert direction in ("down", "up")
|
||||
offset = 0
|
||||
if direction == "up":
|
||||
offset = unroll - 1
|
||||
insns = ""
|
||||
for i in range(unroll):
|
||||
if cpu_arch in ("x86", "x86_64"):
|
||||
if size == 1:
|
||||
insns += fmt_insn("movb OFFSET(%[src]), %[scratch]")
|
||||
insns += fmt_insn("movb %[scratch], OFFSET(%[dst])")
|
||||
elif size == 4:
|
||||
insns += fmt_insn("movl OFFSET(%[src]), %[scratch]")
|
||||
insns += fmt_insn("movl %[scratch], OFFSET(%[dst])")
|
||||
else:
|
||||
assert size == 8
|
||||
insns += fmt_insn("movq OFFSET(%[src]), %[scratch]")
|
||||
insns += fmt_insn("movq %[scratch], OFFSET(%[dst])")
|
||||
elif cpu_arch == "aarch64":
|
||||
if size == 1:
|
||||
insns += fmt_insn("ldrb %w[scratch], [%x[src], OFFSET]")
|
||||
insns += fmt_insn("strb %w[scratch], [%x[dst], OFFSET]")
|
||||
else:
|
||||
assert size == 8
|
||||
insns += fmt_insn("ldr %x[scratch], [%x[src], OFFSET]")
|
||||
insns += fmt_insn("str %x[scratch], [%x[dst], OFFSET]")
|
||||
elif cpu_arch == "arm":
|
||||
if size == 1:
|
||||
insns += fmt_insn("ldrb %[scratch], [%[src], OFFSET]")
|
||||
insns += fmt_insn("strb %[scratch], [%[dst], OFFSET]")
|
||||
else:
|
||||
assert size == 4
|
||||
insns += fmt_insn("ldr %[scratch], [%[src], OFFSET]")
|
||||
insns += fmt_insn("str %[scratch], [%[dst], OFFSET]")
|
||||
else:
|
||||
raise Exception("Unexpected arch")
|
||||
insns = insns.replace("OFFSET", str(offset * size))
|
||||
|
||||
if direction == "down":
|
||||
offset += 1
|
||||
else:
|
||||
offset -= 1
|
||||
|
||||
return """
|
||||
inline void %(fun_name)s(uint8_t* dst, const uint8_t* src) {
|
||||
%(cpp_type)s* dst_ = reinterpret_cast<%(cpp_type)s*>(dst);
|
||||
const %(cpp_type)s* src_ = reinterpret_cast<const %(cpp_type)s*>(src);
|
||||
%(cpp_type)s scratch;
|
||||
asm volatile (%(insns)s
|
||||
: [scratch] "=&r" (scratch)
|
||||
: [dst] "r" (dst_), [src] "r"(src_)
|
||||
: "memory");
|
||||
}""" % {
|
||||
"cpp_type": cpp_type,
|
||||
"fun_name": fun_name,
|
||||
"insns": insns,
|
||||
}
|
||||
|
||||
|
||||
HEADER_TEMPLATE = """\
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef jit_AtomicOperationsGenerated_h
|
||||
#define jit_AtomicOperationsGenerated_h
|
||||
|
||||
/* This file is generated by jit/GenerateAtomicOperations.py. Do not edit! */
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
%(contents)s
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif // jit_AtomicOperationsGenerated_h
|
||||
"""
|
||||
|
||||
|
||||
def generate_atomics_header(c_out):
|
||||
contents = ""
|
||||
if cpu_arch in ("x86", "x86_64", "arm", "aarch64"):
|
||||
contents += "#define JS_HAVE_GENERATED_ATOMIC_OPS 1"
|
||||
|
||||
# `fence` performs a full memory barrier.
|
||||
contents += gen_seqcst("AtomicFenceSeqCst")
|
||||
|
||||
contents += gen_load("AtomicLoad8SeqCst", "uint8_t", 8, True)
|
||||
contents += gen_load("AtomicLoad16SeqCst", "uint16_t", 16, True)
|
||||
contents += gen_load("AtomicLoad32SeqCst", "uint32_t", 32, True)
|
||||
if is_64bit:
|
||||
contents += gen_load("AtomicLoad64SeqCst", "uint64_t", 64, True)
|
||||
|
||||
# These are access-atomic up to sizeof(uintptr_t).
|
||||
contents += gen_load("AtomicLoad8Unsynchronized", "uint8_t", 8, False)
|
||||
contents += gen_load("AtomicLoad16Unsynchronized", "uint16_t", 16, False)
|
||||
contents += gen_load("AtomicLoad32Unsynchronized", "uint32_t", 32, False)
|
||||
if is_64bit:
|
||||
contents += gen_load("AtomicLoad64Unsynchronized", "uint64_t", 64, False)
|
||||
|
||||
contents += gen_store("AtomicStore8SeqCst", "uint8_t", 8, True)
|
||||
contents += gen_store("AtomicStore16SeqCst", "uint16_t", 16, True)
|
||||
contents += gen_store("AtomicStore32SeqCst", "uint32_t", 32, True)
|
||||
if is_64bit:
|
||||
contents += gen_store("AtomicStore64SeqCst", "uint64_t", 64, True)
|
||||
|
||||
# These are access-atomic up to sizeof(uintptr_t).
|
||||
contents += gen_store("AtomicStore8Unsynchronized", "uint8_t", 8, False)
|
||||
contents += gen_store("AtomicStore16Unsynchronized", "uint16_t", 16, False)
|
||||
contents += gen_store("AtomicStore32Unsynchronized", "uint32_t", 32, False)
|
||||
if is_64bit:
|
||||
contents += gen_store("AtomicStore64Unsynchronized", "uint64_t", 64, False)
|
||||
|
||||
# `exchange` takes a cell address and a value. It stores it in the cell and
|
||||
# returns the value previously in the cell.
|
||||
contents += gen_exchange("AtomicExchange8SeqCst", "uint8_t", 8)
|
||||
contents += gen_exchange("AtomicExchange16SeqCst", "uint16_t", 16)
|
||||
contents += gen_exchange("AtomicExchange32SeqCst", "uint32_t", 32)
|
||||
if is_64bit:
|
||||
contents += gen_exchange("AtomicExchange64SeqCst", "uint64_t", 64)
|
||||
|
||||
# `cmpxchg` takes a cell address, an expected value and a replacement value.
|
||||
# If the value in the cell equals the expected value then the replacement value
|
||||
# is stored in the cell. It always returns the value previously in the cell.
|
||||
contents += gen_cmpxchg("AtomicCmpXchg8SeqCst", "uint8_t", 8)
|
||||
contents += gen_cmpxchg("AtomicCmpXchg16SeqCst", "uint16_t", 16)
|
||||
contents += gen_cmpxchg("AtomicCmpXchg32SeqCst", "uint32_t", 32)
|
||||
contents += gen_cmpxchg("AtomicCmpXchg64SeqCst", "uint64_t", 64)
|
||||
|
||||
# `add` adds a value atomically to the cell and returns the old value in the
|
||||
# cell. (There is no `sub`; just add the negated value.)
|
||||
contents += gen_fetchop("AtomicAdd8SeqCst", "uint8_t", 8, "add")
|
||||
contents += gen_fetchop("AtomicAdd16SeqCst", "uint16_t", 16, "add")
|
||||
contents += gen_fetchop("AtomicAdd32SeqCst", "uint32_t", 32, "add")
|
||||
if is_64bit:
|
||||
contents += gen_fetchop("AtomicAdd64SeqCst", "uint64_t", 64, "add")
|
||||
|
||||
# `and` bitwise-ands a value atomically into the cell and returns the old value
|
||||
# in the cell.
|
||||
contents += gen_fetchop("AtomicAnd8SeqCst", "uint8_t", 8, "and")
|
||||
contents += gen_fetchop("AtomicAnd16SeqCst", "uint16_t", 16, "and")
|
||||
contents += gen_fetchop("AtomicAnd32SeqCst", "uint32_t", 32, "and")
|
||||
if is_64bit:
|
||||
contents += gen_fetchop("AtomicAnd64SeqCst", "uint64_t", 64, "and")
|
||||
|
||||
# `or` bitwise-ors a value atomically into the cell and returns the old value
|
||||
# in the cell.
|
||||
contents += gen_fetchop("AtomicOr8SeqCst", "uint8_t", 8, "or")
|
||||
contents += gen_fetchop("AtomicOr16SeqCst", "uint16_t", 16, "or")
|
||||
contents += gen_fetchop("AtomicOr32SeqCst", "uint32_t", 32, "or")
|
||||
if is_64bit:
|
||||
contents += gen_fetchop("AtomicOr64SeqCst", "uint64_t", 64, "or")
|
||||
|
||||
# `xor` bitwise-xors a value atomically into the cell and returns the old value
|
||||
# in the cell.
|
||||
contents += gen_fetchop("AtomicXor8SeqCst", "uint8_t", 8, "xor")
|
||||
contents += gen_fetchop("AtomicXor16SeqCst", "uint16_t", 16, "xor")
|
||||
contents += gen_fetchop("AtomicXor32SeqCst", "uint32_t", 32, "xor")
|
||||
if is_64bit:
|
||||
contents += gen_fetchop("AtomicXor64SeqCst", "uint64_t", 64, "xor")
|
||||
|
||||
# See comment in jit/AtomicOperations-shared-jit.cpp for an explanation.
|
||||
wordsize = 8 if is_64bit else 4
|
||||
words_in_block = 8
|
||||
blocksize = words_in_block * wordsize
|
||||
|
||||
contents += gen_copy(
|
||||
"AtomicCopyUnalignedBlockDownUnsynchronized",
|
||||
"uint8_t",
|
||||
1,
|
||||
blocksize,
|
||||
"down",
|
||||
)
|
||||
contents += gen_copy(
|
||||
"AtomicCopyUnalignedBlockUpUnsynchronized", "uint8_t", 1, blocksize, "up"
|
||||
)
|
||||
|
||||
contents += gen_copy(
|
||||
"AtomicCopyUnalignedWordDownUnsynchronized", "uint8_t", 1, wordsize, "down"
|
||||
)
|
||||
contents += gen_copy(
|
||||
"AtomicCopyUnalignedWordUpUnsynchronized", "uint8_t", 1, wordsize, "up"
|
||||
)
|
||||
|
||||
contents += gen_copy(
|
||||
"AtomicCopyBlockDownUnsynchronized",
|
||||
"uintptr_t",
|
||||
wordsize,
|
||||
words_in_block,
|
||||
"down",
|
||||
)
|
||||
contents += gen_copy(
|
||||
"AtomicCopyBlockUpUnsynchronized",
|
||||
"uintptr_t",
|
||||
wordsize,
|
||||
words_in_block,
|
||||
"up",
|
||||
)
|
||||
|
||||
contents += gen_copy(
|
||||
"AtomicCopyWordUnsynchronized", "uintptr_t", wordsize, 1, "down"
|
||||
)
|
||||
contents += gen_copy("AtomicCopyByteUnsynchronized", "uint8_t", 1, 1, "down")
|
||||
|
||||
contents += "\n"
|
||||
contents += (
|
||||
"constexpr size_t JS_GENERATED_ATOMICS_BLOCKSIZE = "
|
||||
+ str(blocksize)
|
||||
+ ";\n"
|
||||
)
|
||||
contents += (
|
||||
"constexpr size_t JS_GENERATED_ATOMICS_WORDSIZE = " + str(wordsize) + ";\n"
|
||||
)
|
||||
|
||||
c_out.write(
|
||||
HEADER_TEMPLATE
|
||||
% {
|
||||
"contents": contents,
|
||||
}
|
||||
)
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "jit/CacheIRSpewer.h"
|
||||
#include "jit/CompileWrappers.h"
|
||||
#include "jit/Ion.h"
|
||||
#include "jit/JitCode.h"
|
||||
#include "jit/JitOptions.h"
|
||||
#include "jit/JitSpewer.h"
|
||||
|
@ -97,6 +98,11 @@ bool jit::InitializeJit() {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
|
||||
// Compute flags.
|
||||
js::jit::CPUInfo::GetSSEVersion();
|
||||
#endif
|
||||
|
||||
#if defined(JS_CODEGEN_ARM)
|
||||
InitARMFlags();
|
||||
#endif
|
||||
|
@ -105,6 +111,10 @@ bool jit::InitializeJit() {
|
|||
ComputeJitSupportFlags();
|
||||
|
||||
CheckPerf();
|
||||
|
||||
#ifndef JS_CODEGEN_NONE
|
||||
MOZ_ASSERT(js::jit::CPUFlagsHaveBeenComputed());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -3764,6 +3764,8 @@ void LIRGenerator::visitLoadUnboxedScalar(MLoadUnboxedScalar* ins) {
|
|||
const LAllocation index = useRegisterOrIndexConstant(
|
||||
ins->index(), ins->storageType(), ins->offsetAdjustment());
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_load in
|
||||
// GenerateAtomicOperations.py
|
||||
Synchronization sync = Synchronization::Load();
|
||||
if (ins->requiresMemoryBarrier()) {
|
||||
LMemoryBarrier* fence = new (alloc()) LMemoryBarrier(sync.barrierBefore);
|
||||
|
@ -3951,6 +3953,9 @@ void LIRGenerator::visitStoreUnboxedScalar(MStoreUnboxedScalar* ins) {
|
|||
// is a store instruction that incorporates the necessary
|
||||
// barriers, and we could use that instead of separate barrier and
|
||||
// store instructions. See bug #1077027.
|
||||
//
|
||||
// NOTE: the generated code must match the assembly code in gen_store in
|
||||
// GenerateAtomicOperations.py
|
||||
Synchronization sync = Synchronization::Store();
|
||||
if (ins->requiresMemoryBarrier()) {
|
||||
LMemoryBarrier* fence = new (alloc()) LMemoryBarrier(sync.barrierBefore);
|
||||
|
|
|
@ -4935,6 +4935,8 @@ static void CompareExchange(MacroAssembler& masm,
|
|||
|
||||
ScratchRegisterScope scratch(masm);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_cmpxchg in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
masm.bind(&again);
|
||||
|
@ -5038,6 +5040,8 @@ static void AtomicExchange(MacroAssembler& masm,
|
|||
|
||||
ScratchRegisterScope scratch(masm);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_exchange in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
masm.bind(&again);
|
||||
|
@ -5139,6 +5143,8 @@ static void AtomicFetchOp(MacroAssembler& masm,
|
|||
SecondScratchRegisterScope scratch2(masm);
|
||||
Register ptr = ComputePointerForAtomic(masm, mem, scratch2);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_fetchop in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
ScratchRegisterScope scratch(masm);
|
||||
|
@ -5394,6 +5400,8 @@ static void CompareExchange64(MacroAssembler& masm,
|
|||
SecondScratchRegisterScope scratch2(masm);
|
||||
Register ptr = ComputePointerForAtomic(masm, mem, scratch2);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_cmpxchg in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
masm.bind(&again);
|
||||
|
@ -6152,6 +6160,8 @@ void MacroAssemblerARM::wasmLoadImpl(const wasm::MemoryAccessDesc& access,
|
|||
type == Scalar::Int32 || type == Scalar::Int64;
|
||||
unsigned byteSize = access.byteSize();
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_load in
|
||||
// GenerateAtomicOperations.py
|
||||
asMasm().memoryBarrierBefore(access.sync());
|
||||
|
||||
BufferOffset load;
|
||||
|
@ -6267,6 +6277,8 @@ void MacroAssemblerARM::wasmStoreImpl(const wasm::MemoryAccessDesc& access,
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_store in
|
||||
// GenerateAtomicOperations.py
|
||||
asMasm().memoryBarrierAfter(access.sync());
|
||||
|
||||
BufferOffset store;
|
||||
|
|
|
@ -1985,6 +1985,8 @@ void CodeGenerator::visitAtomicLoad64(LAtomicLoad64* lir) {
|
|||
|
||||
Scalar::Type storageType = mir->storageType();
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_load in
|
||||
// GenerateAtomicOperations.py
|
||||
auto sync = Synchronization::Load();
|
||||
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
@ -2011,6 +2013,8 @@ void CodeGenerator::visitAtomicStore64(LAtomicStore64* lir) {
|
|||
|
||||
masm.loadBigInt64(value, temp1);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_store in
|
||||
// GenerateAtomicOperations.py
|
||||
auto sync = Synchronization::Store();
|
||||
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
|
|
@ -474,6 +474,8 @@ void MacroAssemblerCompat::wasmLoadImpl(const wasm::MemoryAccessDesc& access,
|
|||
instructionsExpected++;
|
||||
}
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_load in
|
||||
// GenerateAtomicOperations.py
|
||||
asMasm().memoryBarrierBefore(access.sync());
|
||||
|
||||
{
|
||||
|
@ -625,6 +627,8 @@ void MacroAssemblerCompat::wasmStoreImpl(const wasm::MemoryAccessDesc& access,
|
|||
void MacroAssemblerCompat::wasmStoreImpl(const wasm::MemoryAccessDesc& access,
|
||||
MemOperand dstAddr, AnyRegister valany,
|
||||
Register64 val64) {
|
||||
// NOTE: the generated code must match the assembly code in gen_store in
|
||||
// GenerateAtomicOperations.py
|
||||
asMasm().memoryBarrierBefore(access.sync());
|
||||
|
||||
{
|
||||
|
@ -2334,6 +2338,8 @@ static void CompareExchange(MacroAssembler& masm,
|
|||
|
||||
MOZ_ASSERT(ptr.base().asUnsized() != output);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_cmpxchg in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
Register scratch = temps.AcquireX().asUnsized();
|
||||
|
@ -2365,6 +2371,8 @@ static void AtomicExchange(MacroAssembler& masm,
|
|||
Register scratch2 = temps.AcquireX().asUnsized();
|
||||
MemOperand ptr = ComputePointerForAtomic(masm, mem, scratch2);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_exchange in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
Register scratch = temps.AcquireX().asUnsized();
|
||||
|
@ -2395,6 +2403,8 @@ static void AtomicFetchOp(MacroAssembler& masm,
|
|||
Register scratch2 = temps.AcquireX().asUnsized();
|
||||
MemOperand ptr = ComputePointerForAtomic(masm, mem, scratch2);
|
||||
|
||||
// NOTE: the generated code must match the assembly code in gen_fetchop in
|
||||
// GenerateAtomicOperations.py
|
||||
masm.memoryBarrierBefore(sync);
|
||||
|
||||
Register scratch = temps.AcquireX().asUnsized();
|
||||
|
|
|
@ -75,6 +75,7 @@ UNIFIED_SOURCES += [
|
|||
"Safepoints.cpp",
|
||||
"ScalarReplacement.cpp",
|
||||
"shared/Assembler-shared.cpp",
|
||||
"shared/AtomicOperations-shared-jit.cpp",
|
||||
"shared/CodeGenerator-shared.cpp",
|
||||
"shared/Disassembler-shared.cpp",
|
||||
"shared/Lowering-shared.cpp",
|
||||
|
@ -98,7 +99,6 @@ if CONFIG["JS_CODEGEN_NONE"]:
|
|||
UNIFIED_SOURCES += ["none/Trampoline-none.cpp"]
|
||||
elif CONFIG["JS_CODEGEN_X86"] or CONFIG["JS_CODEGEN_X64"]:
|
||||
UNIFIED_SOURCES += [
|
||||
"shared/AtomicOperations-shared-jit.cpp",
|
||||
"x86-shared/Architecture-x86-shared.cpp",
|
||||
"x86-shared/Assembler-x86-shared.cpp",
|
||||
"x86-shared/AssemblerBuffer-x86-shared.cpp",
|
||||
|
@ -139,7 +139,6 @@ elif CONFIG["JS_CODEGEN_ARM"]:
|
|||
"arm/MacroAssembler-arm.cpp",
|
||||
"arm/MoveEmitter-arm.cpp",
|
||||
"arm/Trampoline-arm.cpp",
|
||||
"shared/AtomicOperations-shared-jit.cpp",
|
||||
]
|
||||
if CONFIG["JS_SIMULATOR_ARM"]:
|
||||
UNIFIED_SOURCES += ["arm/Simulator-arm.cpp"]
|
||||
|
@ -168,7 +167,6 @@ elif CONFIG["JS_CODEGEN_ARM64"]:
|
|||
"arm64/vixl/MozCpu-vixl.cpp",
|
||||
"arm64/vixl/MozInstructions-vixl.cpp",
|
||||
"arm64/vixl/Utils-vixl.cpp",
|
||||
"shared/AtomicOperations-shared-jit.cpp",
|
||||
]
|
||||
vixl_werror_sources = [
|
||||
"arm64/vixl/Disasm-vixl.cpp",
|
||||
|
@ -248,5 +246,12 @@ GeneratedFile(
|
|||
inputs=["CacheIROps.yaml"],
|
||||
)
|
||||
|
||||
GeneratedFile(
|
||||
"AtomicOperationsGenerated.h",
|
||||
script="GenerateAtomicOperations.py",
|
||||
entry_point="generate_atomics_header",
|
||||
inputs=[],
|
||||
)
|
||||
|
||||
if CONFIG["FUZZING_INTERFACES"] or CONFIG["FUZZING_JS_FUZZILLI"]:
|
||||
include("/tools/fuzzing/libfuzzer-config.mozbuild")
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче