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

This commit is contained in:
Oana Pop Rus 2019-07-11 13:00:34 +03:00
commit 1a5e831b42
142 changed files with 4475 additions and 961 deletions

View File

@ -571,6 +571,12 @@ ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
Unused << aChildDoc->SendEmulatedWindow(
reinterpret_cast<uintptr_t>(mEmulatedWindowHandle), nullptr);
}
// We need to fire a reorder event on the outer doc accessible.
// For same-process documents, this is fired by the content process, but
// this isn't possible when the document is in a different process to its
// embedder.
// RecvEvent fires both OS and XPCOM events.
Unused << RecvEvent(aParentID, nsIAccessibleEvent::EVENT_REORDER);
}
}
#endif // defined(XP_WIN)

View File

@ -1379,9 +1379,6 @@ pref("security.insecure_connection_text.pbmode.enabled", false);
pref("security.cert_pinning.enforcement_level", 1);
// Override the Gecko-default value of false for Firefox.
pref("plain_text.wrap_long_lines", true);
// If this turns true, Moz*Gesture events are not called stopPropagation()
// before content.
pref("dom.debug.propagate_gesture_events_through_content", false);

View File

@ -86,6 +86,7 @@ skip-if = (verify && (os == 'win' || os == 'mac'))
[browser_reload_deleted_file.js]
skip-if = (debug && os == 'mac') || (debug && os == 'linux' && bits == 64) #Bug 1421183, disabled on Linux/OSX for leaked windows
[browser_tabCloseSpacer.js]
skip-if = (debug && os == 'linux' && bits == 64) || (asan && os == 'linux' && bits == 64) #Bug 1549985, disabled on Linux64 debug and asan for high failure rate
[browser_tab_label_during_reload.js]
[browser_tabCloseProbes.js]
[browser_tabContextMenu_keyboard.js]

View File

@ -5,16 +5,32 @@
"use strict";
var EXPORTED_SYMBOLS = ["AboutProtectionsHandler"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { RemotePages } = ChromeUtils.import(
"resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
let idToTextMap = new Map([
[Ci.nsITrackingDBService.TRACKERS_ID, "tracker"],
[Ci.nsITrackingDBService.TRACKING_COOKIES_ID, "cookie"],
[Ci.nsITrackingDBService.CRYPTOMINERS_ID, "cryptominer"],
[Ci.nsITrackingDBService.FINGERPRINTERS_ID, "fingerprinter"],
]);
var AboutProtectionsHandler = {
_inited: false,
_topics: ["openContentBlockingPreferences"],
_topics: ["OpenContentBlockingPreferences", "FetchContentBlockingEvents"],
init() {
this.receiveMessage = this.receiveMessage.bind(this);
this.pageListener = new RemotePages("about:protections");
for (let topic of this._topics) {
this.pageListener.addMessageListener(topic, this.receiveMessage);
@ -35,11 +51,40 @@ var AboutProtectionsHandler = {
receiveMessage(aMessage) {
let win = aMessage.target.browser.ownerGlobal;
switch (aMessage.name) {
case "openContentBlockingPreferences":
case "OpenContentBlockingPreferences":
win.openPreferences("privacy-trackingprotection", {
origin: "about-protections",
});
break;
case "FetchContentBlockingEvents":
TrackingDBService.getEventsByDateRange(
aMessage.data.from,
aMessage.data.to
).then(results => {
let dataToSend = {};
let largest = 0;
for (let result of results) {
let count = result.getResultByName("count");
let type = result.getResultByName("type");
let timestamp = result.getResultByName("timestamp");
dataToSend[timestamp] = dataToSend[timestamp] || { total: 0 };
dataToSend[timestamp][idToTextMap.get(type)] = count;
dataToSend[timestamp].total += count;
// Record the largest amount of tracking events found per day,
// to create the tallest column on the graph and compare other days to.
if (largest < dataToSend[timestamp].total) {
largest = dataToSend[timestamp].total;
}
}
dataToSend.largest = largest;
if (aMessage.target.browser) {
aMessage.target.sendAsyncMessage(
"SendContentBlockingRecords",
dataToSend
);
}
});
break;
}
},
};

View File

@ -29,6 +29,9 @@ add_task(async function() {
// about:debugging requires specific wait code for internal pending RDP requests.
"debugging",
"debugging-new",
// about:protections uses RPM to send a message as soon as the page loads,
// the page is destoryed before getting a response.
"protections",
];
for (let cid in Cc) {

View File

@ -71,6 +71,15 @@
</vbox>
</vbox>
</hbox>
<hbox id="browserHomePageExtensionContent"
align="center" hidden="true" class="extension-controlled">
<description control="disableHomePageExtension" flex="1" />
<button id="disableHomePageExtension"
is="highlightable-button"
class="extension-controlled-button accessory-button"
data-l10n-id="disable-extension" />
</hbox>
<hbox id="newTabsOption">
<label control="newTabMode" data-l10n-id="home-newtabs-mode-label" flex="1" />
@ -85,15 +94,6 @@
</menupopup>
</menulist>
</hbox>
<hbox id="browserHomePageExtensionContent"
align="center" hidden="true" class="extension-controlled">
<description control="disableHomePageExtension" flex="1" />
<button id="disableHomePageExtension"
is="highlightable-button"
class="extension-controlled-button accessory-button"
data-l10n-id="disable-extension" />
</hbox>
<hbox id="browserNewTabExtensionContent"
align="center" hidden="true" class="extension-controlled">
<description control="disableNewTabExtension" flex="1" />

View File

@ -11,8 +11,8 @@
--card-padding: 22px;
--social-color: #AB71FF;
--social-color-darker: #7F27FF;
--crossSite-color: #0090F4;
--crossSite-color-darker: #0073C3;
--cookie-color: #0090F4;
--cookie-color-darker: #0073C3;
--tracker-color: #2AC3A2;
--tracker-color-darker: #229C82;
--fingerprinter-color: #FFBD4F;
@ -32,8 +32,8 @@ body[focuseddatatype=social] {
--tab-highlight: var(--social-color);
}
body[focuseddatatype=crossSite] {
--tab-highlight: var(--crossSite-color);
body[focuseddatatype=cookie] {
--tab-highlight: var(--cookie-color);
}
body[focuseddatatype=tracker] {
@ -172,12 +172,12 @@ body[focuseddatatype=cryptominer] {
background-color: var(--social-color-darker);
}
.crossSite-bar {
background-color: var(--crossSite-color);
.cookie-bar {
background-color: var(--cookie-color);
}
.hover-crossSite .crossSite-bar {
background-color: var(--crossSite-color-darker);
.hover-cookie .cookie-bar {
background-color: var(--cookie-color-darker);
}
.tracker-bar {
@ -232,10 +232,11 @@ label[data-type="social"] {
color: var(--social-color);
}
label[data-type="crossSite"] {
label[data-type="cookie"] {
color: var(--cookie-color);
background-image: url(chrome://browser/skin/controlcenter/3rdpartycookies.svg);
fill: var(--crossSite-color);
color: var(--crossSite-color);
fill: var(--cookie-color);
color: var(--cookie-color);
}
label[data-type="tracker"] {
@ -257,7 +258,7 @@ label[data-type="cryptominer"] {
}
.hover-social label[for="tab-social"],
.hover-crossSite label[for="tab-crossSite"],
.hover-cookie label[for="tab-cookie"],
.hover-tracker label[for="tab-tracker"],
.hover-fingerprinter label[for="tab-fingerprinter"],
.hover-cryptominer label[for="tab-cryptominer"],
@ -290,7 +291,7 @@ label:hover {
}
#tab-social:checked ~ #social,
#tab-crossSite:checked ~ #crossSite,
#tab-cookie:checked ~ #cookie,
#tab-tracker:checked ~ #tracker,
#tab-fingerprinter:checked ~ #fingerprinter,
#tab-cryptominer:checked ~ #cryptominer {

View File

@ -43,8 +43,8 @@
<input id="tab-social" data-type="social" type="radio" name="tabs" checked>
<label for="tab-social" data-type="social">345</label>
<input id="tab-crossSite" data-type="crossSite" type="radio" name="tabs">
<label for="tab-crossSite" data-type="crossSite">123</label>
<input id="tab-cookie" data-type="cookie" type="radio" name="tabs">
<label for="tab-cookie" data-type="cookie">123</label>
<input id="tab-tracker" data-type="tracker" type="radio" name="tabs">
<label for="tab-tracker" data-type="tracker">1</label>
@ -59,7 +59,7 @@
<p class="content-title">Social Media Trackers</p>
<p>Social media like, post, and comment buttons on other websites can track you — even if you dont use them. Logging in to sites using your Facebook or Twitter account is another way they can track what you do on those sites. We remove these trackers so Facebook and Twitter see less of what you do online.</p>
</div>
<div id="crossSite" class="tab-content">
<div id="cookie" class="tab-content">
<p class="content-title">Cross-Site Tracking Cookies</p>
<p>Cross-site tracking cookies follow you from site to site to collect data about your browsing habits. Advertisers and analytics companies gather this data to create a profile of your interests across many sites. Blocking them reduces the number of personalized ads that follow you around.</p>
</div>

View File

@ -5,114 +5,74 @@
/* eslint-env mozilla/frame-script */
document.addEventListener("DOMContentLoaded", e => {
let todayInMs = Date.now();
let weekAgoInMs = todayInMs - 7 * 24 * 60 * 60 * 1000;
RPMSendAsyncMessage("FetchContentBlockingEvents", {
from: weekAgoInMs,
to: todayInMs,
});
let dataTypes = [
"cryptominer",
"fingerprinter",
"tracker",
"crossSite",
"cookie",
"social",
];
let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let today = new Date().getDay();
let protectionDetails = document.getElementById("protection-details");
protectionDetails.addEventListener("click", () => {
RPMSendAsyncMessage("openContentBlockingPreferences");
RPMSendAsyncMessage("OpenContentBlockingPreferences");
});
let data = [
{
total: 41,
cryptominer: 1,
fingerprinter: 10,
tracker: 15,
crossSite: 12,
social: 3,
},
{
total: 246,
cryptominer: 5,
fingerprinter: 8,
tracker: 110,
crossSite: 103,
social: 20,
},
{
total: 59,
cryptominer: 0,
fingerprinter: 1,
tracker: 25,
crossSite: 25,
social: 8,
},
{
total: 177,
cryptominer: 0,
fingerprinter: 4,
tracker: 24,
crossSite: 136,
social: 13,
},
{
total: 16,
cryptominer: 1,
fingerprinter: 3,
tracker: 0,
crossSite: 7,
social: 5,
},
{
total: 232,
cryptominer: 0,
fingerprinter: 30,
tracker: 84,
crossSite: 86,
social: 32,
},
{
total: 153,
cryptominer: 0,
fingerprinter: 10,
tracker: 35,
crossSite: 95,
social: 13,
},
];
// Use this to populate the graph with real data in the future.
let createGraph = () => {
let largest = 10;
for (let day of data) {
if (largest < day.total) {
largest = day.total;
}
let createGraph = data => {
// Set a default top size for the height of the graph bars so that small
// numbers don't fill the whole graph.
let largest = 100;
if (largest < data.largest) {
largest = data.largest;
}
let graph = document.getElementById("graph");
for (let i = 0; i < weekdays.length; i++) {
for (let i = weekdays.length - 1; i >= 0; i--) {
// Start 7 days ago and count down to today.
let date = new Date();
date.setDate(date.getDate() - i);
let dateString = date.toISOString().split("T")[0];
let bar = document.createElement("div");
bar.className = "graph-bar";
let barHeight = (data[i].total / largest) * 100;
bar.style.height = `${barHeight}%`;
for (let type of dataTypes) {
let dataHeight = (data[i][type] / data[i].total) * 100;
let div = document.createElement("div");
div.className = `${type}-bar`;
div.setAttribute("data-type", type);
div.style.height = `${dataHeight}%`;
bar.appendChild(div);
if (data[dateString]) {
let content = data[dateString];
let barHeight = (content.total / largest) * 100;
bar.style.height = `${barHeight}%`;
for (let type of dataTypes) {
if (content[type]) {
let dataHeight = (content[type] / content.total) * 100;
let div = document.createElement("div");
div.className = `${type}-bar`;
div.setAttribute("data-type", type);
div.style.height = `${dataHeight}%`;
bar.appendChild(div);
}
}
} else {
// There were no content blocking events on this day.
bar.style.height = `0`;
}
graph.appendChild(bar);
let label = document.createElement("span");
label.className = "column-label";
if (i == 6) {
label.innerText = "Today";
label.textContent = "Today";
} else {
label.innerText = weekdays[(i + today) % 7];
label.textContent = weekdays[(i + 1 + new Date().getDay()) % 7];
}
graph.appendChild(label);
graph.prepend(label);
}
addListeners();
};
let addListeners = () => {
@ -130,7 +90,7 @@ document.addEventListener("DOMContentLoaded", e => {
});
wrapper.addEventListener("click", ev => {
if (ev.originalTarget.dataset) {
if (ev.originalTarget.dataset.type) {
document.getElementById(`tab-${ev.target.dataset.type}`).click();
}
});
@ -143,6 +103,8 @@ document.addEventListener("DOMContentLoaded", e => {
});
}
};
createGraph();
addListeners();
RPMAddMessageListener("SendContentBlockingRecords", message => {
createGraph(message.data);
});
});

View File

@ -4,6 +4,8 @@
# 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/.
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
JAR_MANIFESTS += ['jar.mn']
with Files('**'):

View File

@ -0,0 +1,5 @@
[DEFAULT]
support-files =
!/browser/base/content/test/trackingUI/trackingPage.html
[browser_protections_report_ui.js]

View File

@ -0,0 +1,251 @@
/* 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/. */
// Note: This test may cause intermittents if run at exactly midnight.
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { Sqlite } = ChromeUtils.import("resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
XPCOMUtils.defineLazyGetter(this, "DB_PATH", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "protections.sqlite");
});
const SQL = {
insertCustomTimeEvent:
"INSERT INTO events (type, count, timestamp)" +
"VALUES (:type, :count, date(:timestamp));",
selectAll: "SELECT * FROM events",
};
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["browser.contentblocking.database.enabled", true]],
});
});
add_task(async function test_graph_display() {
// This creates the schema.
await TrackingDBService.saveEvents(JSON.stringify({}));
let db = await Sqlite.openConnection({ path: DB_PATH });
let date = new Date().toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 1,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 4,
timestamp: date,
});
date = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 4,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 4,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 1,
timestamp: date,
});
date = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 1,
timestamp: date,
});
date = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 1,
timestamp: date,
});
date = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.CRYPTOMINERS_ID,
count: 3,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.FINGERPRINTERS_ID,
count: 2,
timestamp: date,
});
await db.execute(SQL.insertCustomTimeEvent, {
type: TrackingDBService.TRACKING_COOKIES_ID,
count: 8,
timestamp: date,
});
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
const DATA_TYPES = ["cryptominer", "fingerprinter", "tracker", "cookie"];
let allBars = null;
await ContentTaskUtils.waitForCondition(() => {
allBars = content.document.querySelectorAll(".graph-bar");
return allBars.length;
}, "The graph has been built");
is(allBars.length, 7, "7 bars have been found on the graph");
// today has each type
// yesterday will have no tracking cookies
// 2 days ago will have no fingerprinters
// 3 days ago will have no cryptominers
// 4 days ago will have no trackers
// 5 days ago will have no social (when we add social)
// 6 days ago will be empty
is(
allBars[6].childNodes.length,
DATA_TYPES.length,
"today has all of the data types shown"
);
is(
allBars[6].querySelector(".tracker-bar").style.height,
"10%",
"trackers take 10%"
);
is(
allBars[6].querySelector(".cryptominer-bar").style.height,
"20%",
"cryptominers take 20%"
);
is(
allBars[6].querySelector(".fingerprinter-bar").style.height,
"30%",
"fingerprinters take 30%"
);
is(
allBars[6].querySelector(".cookie-bar").style.height,
"40%",
"cross site tracking cookies take 40%"
);
is(
allBars[5].childNodes.length,
DATA_TYPES.length - 1,
"1 day ago is missing one type"
);
ok(
!allBars[5].querySelector(".cookie-bar"),
"there is no cross site tracking cookie section 1 day ago."
);
is(
allBars[4].childNodes.length,
DATA_TYPES.length - 1,
"2 days ago is missing one type"
);
ok(
!allBars[4].querySelector(".fingerprinter-bar"),
"there is no fingerprinter section 1 day ago."
);
is(
allBars[3].childNodes.length,
DATA_TYPES.length - 1,
"3 days ago is missing one type"
);
ok(
!allBars[3].querySelector(".cryptominer-bar"),
"there is no cryptominer section 1 day ago."
);
is(
allBars[2].childNodes.length,
DATA_TYPES.length - 1,
"4 days ago is missing one type"
);
ok(
!allBars[2].querySelector(".tracker-bar"),
"there is no tracker section 1 day ago."
);
// TODO test for social missing
is(allBars[0].childNodes.length, 0, "6 days ago has no content");
is(allBars[0].style.height, "0px", "6 days ago has no height");
});
// Use the TrackingDBService API to delete the data.
await TrackingDBService.clearAll();
// Make sure the data was deleted.
let rows = await db.execute(SQL.selectAll);
is(rows.length, 0, "length is 0");
await db.close();
BrowserTestUtils.removeTab(tab);
});

View File

@ -19,7 +19,8 @@
#include <math.h>
#include "js/Equality.h" // JS::SameValueZero
#include "js/Conversions.h" // JS::NumberToString
#include "js/Equality.h" // JS::SameValueZero
namespace mozilla {
namespace dom {
@ -470,65 +471,47 @@ void DOMMatrixReadOnly::ToFloat64Array(JSContext* aCx,
aResult.set(&value.toObject());
}
// Convenient way to append things as floats, not doubles. We use this because
// we only want to output about 6 digits of precision for our matrix()
// functions, to preserve the behavior we used to have when we used
// AppendPrintf.
static void AppendFloat(nsAString& aStr, float f) { aStr.AppendFloat(f); }
void DOMMatrixReadOnly::Stringify(nsAString& aResult) {
void DOMMatrixReadOnly::Stringify(nsAString& aResult, ErrorResult& aRv) {
char cbuf[JS::MaximumNumberToStringLength];
nsAutoString matrixStr;
auto AppendDouble = [&aRv, &cbuf, &matrixStr](double d,
bool isLastItem = false) {
if (!mozilla::IsFinite(d)) {
aRv.ThrowDOMException(
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING(
"Matrix with a non-finite element cannot be stringified."));
return false;
}
JS::NumberToString(d, cbuf);
matrixStr.AppendASCII(cbuf);
if (!isLastItem) {
matrixStr.AppendLiteral(", ");
}
return true;
};
if (mMatrix3D) {
// We can't use AppendPrintf here, because it does locale-specific
// formatting of floating-point values.
matrixStr.AssignLiteral("matrix3d(");
AppendFloat(matrixStr, M11());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M12());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M13());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M14());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M21());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M22());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M23());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M24());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M31());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M32());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M33());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M34());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M41());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M42());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M43());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, M44());
if (!AppendDouble(M11()) || !AppendDouble(M12()) || !AppendDouble(M13()) ||
!AppendDouble(M14()) || !AppendDouble(M21()) || !AppendDouble(M22()) ||
!AppendDouble(M23()) || !AppendDouble(M24()) || !AppendDouble(M31()) ||
!AppendDouble(M32()) || !AppendDouble(M33()) || !AppendDouble(M34()) ||
!AppendDouble(M41()) || !AppendDouble(M42()) || !AppendDouble(M43()) ||
!AppendDouble(M44(), true)) {
return;
}
matrixStr.AppendLiteral(")");
} else {
// We can't use AppendPrintf here, because it does locale-specific
// formatting of floating-point values.
matrixStr.AssignLiteral("matrix(");
AppendFloat(matrixStr, A());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, B());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, C());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, D());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, E());
matrixStr.AppendLiteral(", ");
AppendFloat(matrixStr, F());
if (!AppendDouble(A()) || !AppendDouble(B()) || !AppendDouble(C()) ||
!AppendDouble(D()) || !AppendDouble(E()) || !AppendDouble(F(), true)) {
return;
}
matrixStr.AppendLiteral(")");
}

View File

@ -203,7 +203,7 @@ class DOMMatrixReadOnly : public nsWrapperCache {
ErrorResult& aRv) const;
void ToFloat64Array(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
ErrorResult& aRv) const;
void Stringify(nsAString& aResult);
void Stringify(nsAString& aResult, ErrorResult& aRv);
bool WriteStructuredClone(JSContext* aCx,
JSStructuredCloneWriter* aWriter) const;

View File

@ -4,4 +4,4 @@ HTTP == test_xmlPrettyPrint_csp.xml test_xmlPrettyPrint_csp-ref.xml
# size of the reftest document. This test however needs something more representative
# of a real mobile device, where the desktop viewport width doesn't match the
# width of the device screen.
test-pref(dom.meta-viewport.enabled,true) test-pref(browser.viewport.desktopWidth,1200) test-pref(plain_text.wrap_long_lines,true) == test_bug1525662.txt test_bug1525662-ref.html
test-pref(dom.meta-viewport.enabled,true) test-pref(browser.viewport.desktopWidth,1200) == test_bug1525662.txt test_bug1525662-ref.html

View File

@ -3494,6 +3494,29 @@ NS_IMETHODIMP BrowserChild::OnStateChange(nsIWebProgress* aWebProgress,
MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData,
requestData));
/*
* If
* 1) this is a document,
* 2) the document is top-level,
* 3) the document is completely loaded (STATE_STOP), and
* 4) this is the end of activity for the document
* (STATE_IS_WINDOW, STATE_IS_NETWORK),
* then record the elapsed time that it took to load.
*/
if (document && webProgressData->isTopLevel() &&
(aStateFlags & nsIWebProgressListener::STATE_STOP) &&
(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW) &&
(aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK)) {
RefPtr<nsDOMNavigationTiming> navigationTiming =
document->GetNavigationTiming();
if (navigationTiming) {
TimeDuration elapsedLoadTimeMS =
TimeStamp::Now() - navigationTiming->GetNavigationStartTimeStamp();
requestData.elapsedLoadTimeMS() =
Some(elapsedLoadTimeMS.ToMilliseconds());
}
}
if (webProgressData->isTopLevel()) {
stateChangeData.emplace();

View File

@ -2623,7 +2623,7 @@ void BrowserParent::ReconstructWebProgressAndRequest(
if (aRequestData.requestURI()) {
nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
aRequestData.requestURI(), aRequestData.originalRequestURI(),
aRequestData.matchedList());
aRequestData.matchedList(), aRequestData.elapsedLoadTimeMS());
request.forget(aOutRequest);
} else {
*aOutRequest = nullptr;

View File

@ -2052,8 +2052,8 @@ void ContentParent::LaunchSubprocessInternal(
if (isSync) {
*aRetval.as<bool*>() = false;
} else {
*aRetval.as<RefPtr<LaunchPromise>*>() = LaunchPromise::CreateAndReject(
GeckoChildProcessHost::LaunchError(), __func__);
*aRetval.as<RefPtr<LaunchPromise>*>() =
LaunchPromise::CreateAndReject(LaunchError(), __func__);
}
};
@ -2119,7 +2119,7 @@ void ContentParent::LaunchSubprocessInternal(
RefPtr<ContentParent> self(this);
auto reject = [self, this](GeckoChildProcessHost::LaunchError err) {
auto reject = [self, this](LaunchError err) {
NS_ERROR("failed to launch child in the parent");
MarkAsDead();
return LaunchPromise::CreateAndReject(err, __func__);
@ -2160,7 +2160,13 @@ void ContentParent::LaunchSubprocessInternal(
#endif
mLifecycleState = LifecycleState::ALIVE;
InitInternal(aInitialPriority);
if (!InitInternal(aInitialPriority)) {
NS_ERROR("failed to initialize child in the parent");
// We've already called Open() by this point, so we need to close the
// channel to avoid leaking the process.
ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
return LaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
ContentProcessManager::GetSingleton()->AddContentProcess(this);
@ -2201,19 +2207,19 @@ void ContentParent::LaunchSubprocessInternal(
if (ok) {
Unused << resolve(mSubprocess->GetChildProcessHandle());
} else {
Unused << reject(GeckoChildProcessHost::LaunchError{});
Unused << reject(LaunchError{});
}
*aRetval.as<bool*>() = ok;
} else {
auto* retptr = aRetval.as<RefPtr<LaunchPromise>*>();
if (mSubprocess->AsyncLaunch(std::move(extraArgs))) {
RefPtr<GeckoChildProcessHost::HandlePromise> ready =
RefPtr<ProcessHandlePromise> ready =
mSubprocess->WhenProcessHandleReady();
mLaunchYieldTS = TimeStamp::Now();
*retptr = ready->Then(GetCurrentThreadSerialEventTarget(), __func__,
std::move(resolve), std::move(reject));
} else {
*retptr = reject(GeckoChildProcessHost::LaunchError{});
*retptr = reject(LaunchError{});
}
}
}
@ -2326,7 +2332,7 @@ ContentParent::~ContentParent() {
}
}
void ContentParent::InitInternal(ProcessPriority aInitialPriority) {
bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
XPCOMInitData xpcomInit;
nsCOMPtr<nsIIOService> io(do_GetIOService());
@ -2526,10 +2532,12 @@ void ContentParent::InitInternal(ProcessPriority aInitialPriority) {
Endpoint<PRemoteDecoderManagerChild> videoManager;
AutoTArray<uint32_t, 3> namespaces;
DebugOnly<bool> opened =
gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge,
&vrBridge, &videoManager, &namespaces);
MOZ_ASSERT(opened);
if (!gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge,
&vrBridge, &videoManager, &namespaces)) {
// This can fail if we've already started shutting down the compositor
// thread. See Bug 1562763 comment 8.
return false;
}
Unused << SendInitRendering(std::move(compositor), std::move(imageBridge),
std::move(vrBridge), std::move(videoManager),
@ -2601,7 +2609,7 @@ void ContentParent::InitInternal(ProcessPriority aInitialPriority) {
SandboxBroker::Create(std::move(policy), Pid(), brokerFd.ref());
if (!mSandboxBroker) {
KillHard("SandboxBroker::Create failed");
return;
return false;
}
MOZ_ASSERT(brokerFd.ref().IsValid());
}
@ -2657,6 +2665,8 @@ void ContentParent::InitInternal(ProcessPriority aInitialPriority) {
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
pluginHost->SendPluginsToContent();
MaybeEnableRemoteInputEventQueue();
return true;
}
bool ContentParent::IsAlive() const {
@ -3187,7 +3197,7 @@ ContentParent::GetInterface(const nsIID& aIID, void** aResult) {
mozilla::ipc::IPCResult ContentParent::RecvInitBackground(
Endpoint<PBackgroundParent>&& aEndpoint) {
if (!BackgroundParent::Alloc(this, std::move(aEndpoint))) {
return IPC_FAIL(this, "BackgroundParent::Alloc failed");
NS_WARNING("BackgroundParent::Alloc failed");
}
return IPC_OK();

View File

@ -22,6 +22,7 @@
#include "mozilla/HalTypes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/MozPromise.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Variant.h"
@ -148,9 +149,9 @@ class ContentParent final : public PContentParent,
#endif
public:
using LaunchError = GeckoChildProcessHost::LaunchError;
using LaunchError = mozilla::ipc::LaunchError;
using LaunchPromise =
GeckoChildProcessHost::LaunchPromise<RefPtr<ContentParent>>;
mozilla::MozPromise<RefPtr<ContentParent>, LaunchError, false>;
NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTPARENT_IID)
@ -723,7 +724,7 @@ class ContentParent final : public PContentParent,
mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval);
// Common initialization after sub process launch.
void InitInternal(ProcessPriority aPriority);
bool InitInternal(ProcessPriority aPriority);
// Generate a minidump for the child process and one for the main process
void GeneratePairedMinidump(const char* aReason);

View File

@ -118,6 +118,12 @@ struct RequestData
nsIURI requestURI;
nsIURI originalRequestURI;
nsCString matchedList;
// The elapsedLoadTimeMS is only set when the request has finished loading.
// In other words, this field is set only during and |OnStateChange| event
// where |aStateFlags| contains |nsIWebProgressListener::STATE_STOP| and
// |nsIWebProgressListener::STATE_IS_NETWORK| and
// |nsIWebProgressListener::STATE_IS_WINDOW|, and the document is top level.
uint64_t? elapsedLoadTimeMS;
};
struct WebProgressStateChangeData

View File

@ -18,6 +18,17 @@ NS_IMETHODIMP RemoteWebProgressRequest::Init(nsIURI* aURI,
return NS_OK;
}
NS_IMETHODIMP RemoteWebProgressRequest::GetElapsedLoadTimeMS(
uint64_t* aElapsedLoadTimeMS) {
NS_ENSURE_ARG_POINTER(aElapsedLoadTimeMS);
if (mMaybeElapsedLoadTimeMS) {
*aElapsedLoadTimeMS = *mMaybeElapsedLoadTimeMS;
return NS_OK;
}
*aElapsedLoadTimeMS = 0;
return NS_ERROR_NOT_AVAILABLE;
}
// nsIChannel methods
NS_IMETHODIMP RemoteWebProgressRequest::GetOriginalURI(nsIURI** aOriginalURI) {

View File

@ -26,8 +26,12 @@ class RemoteWebProgressRequest final : public nsIRemoteWebProgressRequest,
: mURI(nullptr), mOriginalURI(nullptr), mMatchedList(VoidCString()) {}
RemoteWebProgressRequest(nsIURI* aURI, nsIURI* aOriginalURI,
const nsACString& aMatchedList)
: mURI(aURI), mOriginalURI(aOriginalURI), mMatchedList(aMatchedList) {}
const nsACString& aMatchedList,
const Maybe<uint64_t>& aMaybeElapsedLoadTimeMS)
: mURI(aURI),
mOriginalURI(aOriginalURI),
mMatchedList(aMatchedList),
mMaybeElapsedLoadTimeMS(aMaybeElapsedLoadTimeMS) {}
protected:
~RemoteWebProgressRequest() = default;
@ -36,6 +40,13 @@ class RemoteWebProgressRequest final : public nsIRemoteWebProgressRequest,
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIURI> mOriginalURI;
nsCString mMatchedList;
// This field is only Some(...) when the RemoteWebProgressRequest
// is created at a time that the document whose progress is being
// described by this request is top level and its status changes
// from loading to completely loaded.
// See BrowserChild::OnStateChange.
Maybe<uint64_t> mMaybeElapsedLoadTimeMS;
};
} // namespace dom

View File

@ -10,4 +10,11 @@ interface nsIURI;
interface nsIRemoteWebProgressRequest : nsISupports
{
void init(in nsIURI aURI, in nsIURI aOriginalURI);
// This field is available to users in |OnStateChange| methods only
// when the document whose progress is being described by this progress
// request is top level and its status has just changed from loading to
// completely loaded; for invocations of |OnStateChange| before or after
// that transition, this field will throw |NS_ERROR_UNAVAILABLE|.
readonly attribute uint64_t elapsedLoadTimeMS;
};

View File

@ -9,3 +9,5 @@ support-files =
skip-if = !e10 # This is an e10s only probe.
[browser_cancel_content_js.js]
skip-if = !e10s # This is an e10s only probe.
[browser_ElapsedTime.js]
support-files = elapsed_time.sjs

View File

@ -0,0 +1,63 @@
"use strict";
/*
* Synchronize DELAY_MS with DELAY_MS from elapsed_time.sjs
*/
const DELAY_MS = 200;
const SLOW_PAGE =
getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://example.com"
) + "elapsed_time.sjs";
add_task(async function testLongElapsedTime() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:blank" },
async function(tabBrowser) {
const flags = Ci.nsIWebProgress.NOTIFY_STATE_NETWORK;
let listener;
let stateChangeWaiter = new Promise(resolve => {
listener = {
onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
if (!aWebProgress.isTopLevel) {
return;
}
const isTopLevel = aWebProgress.isTopLevel;
const isStop = aStateFlags & Ci.nsIWebProgressListener.STATE_STOP;
const isNetwork =
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
const isWindow =
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
const isLoadingDocument = aWebProgress.isLoadingDocument;
if (
isTopLevel &&
isStop &&
isWindow &&
isNetwork &&
!isLoadingDocument
) {
aRequest.QueryInterface(Ci.nsIRemoteWebProgressRequest);
if (aRequest.elapsedLoadTimeMS >= DELAY_MS) {
resolve(true);
}
}
},
};
});
tabBrowser.addProgressListener(listener, flags);
BrowserTestUtils.loadURI(tabBrowser, SLOW_PAGE);
await BrowserTestUtils.browserLoaded(tabBrowser);
let pass = await stateChangeWaiter;
tabBrowser.removeProgressListener(listener);
ok(
pass,
"Bug 1559657: Check that the elapsedLoadTimeMS in RemoteWebProgress meets expectations."
);
}
);
});

View File

@ -0,0 +1,31 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const DELAY_MS = 200;
const HTML = `<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US">
<head>
<meta charset="utf8">
</head>
<body>
</body>
</html>`;
/*
* Keep timer as a global so that it is not GC'd
* between the time that handleRequest() completes
* and it expires.
*/
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
function handleRequest(req, resp) {
resp.processAsync();
resp.setHeader("Cache-Control", "no-cache", false);
resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
resp.write(HTML);
timer.init(() => {
resp.write("");
resp.finish();
}, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
}

View File

@ -635,6 +635,15 @@ bool nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce,
// just a specific scheme, the parser should generate a nsCSPSchemeSource.
NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
// Before we can check if the host matches, we have to
// extract the host part from aUri.
nsAutoCString uriHost;
nsresult rv = aUri->GetAsciiHost(uriHost);
NS_ENSURE_SUCCESS(rv, false);
nsString decodedUriHost;
CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
// 2) host matching: Enforce a single *
if (mHost.EqualsASCII("*")) {
// The single ASTERISK character (*) does not match a URI's scheme of a type
@ -650,24 +659,18 @@ bool nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce,
bool isFileScheme =
(NS_SUCCEEDED(aUri->SchemeIs("filesystem", &isFileScheme)) &&
isFileScheme);
if (isBlobScheme || isDataScheme || isFileScheme) {
return false;
}
return true;
// If no scheme is present there also wont be a port and folder to check
// which means we can return early
if (mScheme.IsEmpty()) {
return true;
}
}
// Before we can check if the host matches, we have to
// extract the host part from aUri.
nsAutoCString uriHost;
nsresult rv = aUri->GetAsciiHost(uriHost);
NS_ENSURE_SUCCESS(rv, false);
nsString decodedUriHost;
CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
// 4.5) host matching: Check if the allowed host starts with a wilcard.
if (mHost.First() == '*') {
else if (mHost.First() == '*') {
NS_ASSERTION(
mHost[1] == '.',
"Second character needs to be '.' whenever host starts with '*'");

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -6,6 +6,7 @@ support-files =
file_connect-src-fetch.html
file_CSP.css
file_CSP.sjs
file_dummy_pixel.png
file_allow_https_schemes.html
file_bug663567.xsl
file_bug663567_allows.xml
@ -238,6 +239,7 @@ prefs =
[test_blob_data_schemes.html]
[test_connect-src.html]
[test_CSP.html]
[test_bug1388015.html]
[test_allow_https_schemes.html]
[test_bug663567.html]
[test_bug802872.html]

View File

@ -0,0 +1,46 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1388015 - Test if Firefox respect Port in Wildcard Host </title>
<meta http-equiv="Content-Security-Policy" content="img-src https://*:443">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<img alt="Should be Blocked">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
let image = document.querySelector("img");
Promise.race([
new Promise((res) => {
window.addEventListener("securitypolicyviolation", () => res(true), {once:true});
}),
new Promise((res) => {
image.addEventListener("load", () => res(false),{once:true});
})])
.then((result) => {
ok(result, " CSP did block Image with wildcard and mismatched Port");
})
.then(()=> Promise.race([
new Promise((res) => {
window.addEventListener("securitypolicyviolation", () => res(false), {once:true});
}),
new Promise((res) => {
image.addEventListener("load", () => res(true),{once:true});
requestIdleCallback(()=>{
image.src = "https://example.com:443/tests/dom/security/test/csp/file_dummy_pixel.png"
})
})]))
.then((result) => {
ok(result, " CSP did load the Image with wildcard and matching Port");
SimpleTest.finish();
})
image.src = "file_dummy_pixel.png" // mochi.test:8888
</script>
</body>
</html>

View File

@ -83,7 +83,7 @@ interface DOMMatrixReadOnly {
DOMPoint transformPoint(optional DOMPointInit point = {});
[Throws] Float32Array toFloat32Array();
[Throws] Float64Array toFloat64Array();
[Exposed=Window] stringifier;
[Exposed=Window, Throws] stringifier;
[Default] object toJSON();
};

View File

@ -138,8 +138,18 @@ already_AddRefed<ImageContainer> LayerManager::CreateImageContainer(
return container.forget();
}
bool LayerManager::LayersComponentAlphaEnabled() {
// If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
// and ignore the preference.
#ifdef MOZ_GFX_OPTIMIZE_MOBILE
return false;
#else
return StaticPrefs::layers_componentalpha_enabled_do_not_use_directly();
#endif
}
bool LayerManager::AreComponentAlphaLayersEnabled() {
return StaticPrefs::layers_componentalpha_enabled();
return LayerManager::LayersComponentAlphaEnabled();
}
/*static*/

View File

@ -388,6 +388,12 @@ class LayerManager : public FrameRecorder {
const nsTArray<nsIWidget::Configuration>& aConfigurations) {}
bool IsSnappingEffectiveTransforms() { return mSnapEffectiveTransforms; }
/**
* Returns true if the underlying platform can properly support layers with
* SurfaceMode::SURFACE_COMPONENT_ALPHA.
*/
static bool LayersComponentAlphaEnabled();
/**
* Returns true if this LayerManager can properly support layers with
* SurfaceMode::SURFACE_COMPONENT_ALPHA. LayerManagers that can't will use

View File

@ -10,6 +10,7 @@
#include "gfxWindowsPlatform.h"
#include "nsIWidget.h"
#include "Layers.h"
#include "mozilla/gfx/D3D11Checks.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/gfx/GPUParent.h"
@ -1050,7 +1051,7 @@ void CompositorD3D11::DrawGeometry(const Geometry& aGeometry,
mContext->PSSetShaderResources(TexSlot::Y, 3, srViews);
} break;
case EffectTypes::COMPONENT_ALPHA: {
MOZ_ASSERT(StaticPrefs::layers_componentalpha_enabled());
MOZ_ASSERT(LayerManager::LayersComponentAlphaEnabled());
MOZ_ASSERT(mAttachments->mComponentBlendState);
EffectComponentAlpha* effectComponentAlpha =
static_cast<EffectComponentAlpha*>(aEffectChain.mPrimaryEffect.get());

View File

@ -9,6 +9,7 @@
#include "mozilla/Telemetry.h"
#include "mozilla/layers/Compositor.h"
#include "CompositorD3D11Shaders.h"
#include "Layers.h"
#include "ShaderDefinitionsD3D11.h"
namespace mozilla {
@ -179,7 +180,7 @@ bool DeviceAttachmentsD3D11::Initialize() {
return false;
}
if (StaticPrefs::layers_componentalpha_enabled()) {
if (LayerManager::LayersComponentAlphaEnabled()) {
D3D11_RENDER_TARGET_BLEND_DESC rtBlendComponent = {
TRUE,
D3D11_BLEND_ONE,
@ -275,7 +276,7 @@ bool DeviceAttachmentsD3D11::CreateShaders() {
InitPixelShader(sYCbCrShaderMask, mYCbCrShader, MaskType::Mask);
InitPixelShader(sNV12Shader, mNV12Shader, MaskType::MaskNone);
InitPixelShader(sNV12ShaderMask, mNV12Shader, MaskType::Mask);
if (StaticPrefs::layers_componentalpha_enabled()) {
if (LayerManager::LayersComponentAlphaEnabled()) {
InitPixelShader(sComponentAlphaShader, mComponentAlphaShader,
MaskType::MaskNone);
InitPixelShader(sComponentAlphaShaderMask, mComponentAlphaShader,

View File

@ -107,6 +107,7 @@ struct OpAddImage {
struct OpAddBlobImage {
ImageDescriptor descriptor;
OffsetRange bytes;
ImageIntRect visibleRect;
uint16_t tiling;
BlobImageKey key;
};
@ -121,10 +122,11 @@ struct OpUpdateBlobImage {
ImageDescriptor descriptor;
OffsetRange bytes;
BlobImageKey key;
ImageIntRect visibleRect;
ImageIntRect dirtyRect;
};
struct OpSetImageVisibleArea {
struct OpSetBlobImageVisibleArea {
ImageIntRect area;
BlobImageKey key;
};
@ -177,7 +179,7 @@ union OpUpdateResource {
OpAddBlobImage;
OpUpdateImage;
OpUpdateBlobImage;
OpSetImageVisibleArea;
OpSetBlobImageVisibleArea;
OpDeleteImage;
OpDeleteBlobImage;
OpAddRawFont;

View File

@ -1550,7 +1550,7 @@ void CompositorOGL::DrawGeometry(const Geometry& aGeometry,
BindAndDrawGeometry(program, aGeometry);
} break;
case EffectTypes::COMPONENT_ALPHA: {
MOZ_ASSERT(StaticPrefs::layers_componentalpha_enabled());
MOZ_ASSERT(LayerManager::LayersComponentAlphaEnabled());
MOZ_ASSERT(blendMode == gfx::CompositionOp::OP_OVER,
"Can't support blend modes with component alpha!");
EffectComponentAlpha* effectComponentAlpha =

View File

@ -297,13 +297,15 @@ bool IpcResourceUpdateQueue::AddImage(ImageKey key,
bool IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key,
const ImageDescriptor& aDescriptor,
Range<uint8_t> aBytes) {
Range<uint8_t> aBytes,
ImageIntRect aVisibleRect) {
MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0);
auto bytes = mWriter.Write(aBytes);
if (!bytes.length()) {
return false;
}
mUpdates.AppendElement(layers::OpAddBlobImage(aDescriptor, bytes, 0, key));
mUpdates.AppendElement(
layers::OpAddBlobImage(aDescriptor, bytes, aVisibleRect, 0, key));
return true;
}
@ -336,13 +338,14 @@ bool IpcResourceUpdateQueue::UpdateImageBuffer(
bool IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey,
const ImageDescriptor& aDescriptor,
Range<uint8_t> aBytes,
ImageIntRect aVisibleRect,
ImageIntRect aDirtyRect) {
auto bytes = mWriter.Write(aBytes);
if (!bytes.length()) {
return false;
}
mUpdates.AppendElement(
layers::OpUpdateBlobImage(aDescriptor, bytes, aKey, aDirtyRect));
mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey,
aVisibleRect, aDirtyRect));
return true;
}
@ -355,7 +358,7 @@ void IpcResourceUpdateQueue::UpdateExternalImage(wr::ExternalImageId aExtId,
void IpcResourceUpdateQueue::SetBlobImageVisibleArea(
wr::BlobImageKey aKey, const ImageIntRect& aArea) {
mUpdates.AppendElement(layers::OpSetImageVisibleArea(aArea, aKey));
mUpdates.AppendElement(layers::OpSetBlobImageVisibleArea(aArea, aKey));
}
void IpcResourceUpdateQueue::DeleteImage(ImageKey aKey) {

View File

@ -137,7 +137,7 @@ class IpcResourceUpdateQueue {
Range<uint8_t> aBytes);
bool AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
Range<uint8_t> aBytes);
Range<uint8_t> aBytes, ImageIntRect aVisibleRect);
void AddExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey);
@ -151,7 +151,8 @@ class IpcResourceUpdateQueue {
bool UpdateBlobImage(wr::BlobImageKey aKey,
const ImageDescriptor& aDescriptor,
Range<uint8_t> aBytes, ImageIntRect aDirtyRect);
Range<uint8_t> aBytes, ImageIntRect aVisibleRect,
ImageIntRect aDirtyRect);
void UpdateExternalImage(ExternalImageId aExtID, ImageKey aKey,
ImageIntRect aDirtyRect);

View File

@ -428,7 +428,8 @@ bool WebRenderBridgeParent::UpdateResources(
if (!reader.Read(op.bytes(), bytes)) {
return false;
}
aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes);
aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes,
wr::ToDeviceIntRect(op.visibleRect()));
break;
}
case OpUpdateResource::TOpUpdateBlobImage: {
@ -438,17 +439,14 @@ bool WebRenderBridgeParent::UpdateResources(
return false;
}
aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes,
wr::ToDeviceIntRect(op.visibleRect()),
wr::ToLayoutIntRect(op.dirtyRect()));
break;
}
case OpUpdateResource::TOpSetImageVisibleArea: {
const auto& op = cmd.get_OpSetImageVisibleArea();
wr::DeviceIntRect area;
area.origin.x = op.area().x;
area.origin.y = op.area().y;
area.size.width = op.area().width;
area.size.height = op.area().height;
aUpdates.SetImageVisibleArea(op.key(), area);
case OpUpdateResource::TOpSetBlobImageVisibleArea: {
const auto& op = cmd.get_OpSetBlobImageVisibleArea();
aUpdates.SetBlobImageVisibleArea(op.key(),
wr::ToDeviceIntRect(op.area()));
break;
}
case OpUpdateResource::TOpAddExternalImage: {

View File

@ -721,7 +721,10 @@ struct DIGroup {
GP("No previous key making new one %d\n", key._0.mHandle);
wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity);
MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
if (!aResources.AddBlobImage(key, descriptor, bytes)) {
if (!aResources.AddBlobImage(
key, descriptor, bytes,
ViewAs<ImagePixel>(mPaintRect,
PixelCastJustification::LayerIsImage))) {
return;
}
mKey = Some(MakePair(aBuilder.GetRenderRoot(), key));
@ -734,8 +737,11 @@ struct DIGroup {
bottomRight.y <= dtSize.height);
GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y,
mInvalidRect.width, mInvalidRect.height);
if (!aResources.UpdateBlobImage(mKey.value().second(), descriptor, bytes,
ViewAs<ImagePixel>(mInvalidRect))) {
if (!aResources.UpdateBlobImage(
mKey.value().second(), descriptor, bytes,
ViewAs<ImagePixel>(mPaintRect,
PixelCastJustification::LayerIsImage),
ViewAs<ImagePixel>(mInvalidRect))) {
return;
}
}
@ -2306,7 +2312,10 @@ WebRenderCommandBuilder::GenerateFallbackData(
wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
wr::ImageDescriptor descriptor(dtSize.ToUnknownSize(), 0,
dt->GetFormat(), opacity);
if (!aResources.AddBlobImage(key, descriptor, bytes)) {
if (!aResources.AddBlobImage(
key, descriptor, bytes,
ViewAs<ImagePixel>(visibleRect,
PixelCastJustification::LayerIsImage))) {
return nullptr;
}
TakeExternalSurfaces(
@ -2321,11 +2330,11 @@ WebRenderCommandBuilder::GenerateFallbackData(
if (!fallbackData->GetBlobImageKey().isSome()) {
return nullptr;
}
aResources.SetBlobImageVisibleArea(
fallbackData->GetBlobImageKey().value(),
ViewAs<ImagePixel>(visibleRect,
PixelCastJustification::LayerIsImage));
}
aResources.SetBlobImageVisibleArea(
fallbackData->GetBlobImageKey().value(),
ViewAs<ImagePixel>(visibleRect,
PixelCastJustification::LayerIsImage));
} else {
WebRenderImageData* imageData = fallbackData->PaintIntoImage();
@ -2526,9 +2535,8 @@ Maybe<wr::ImageMask> WebRenderCommandBuilder::BuildWrMaskImage(
wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()};
wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(),
wr::OpacityType::HasAlphaChannel);
if (!aResources.AddBlobImage(key, descriptor,
bytes)) { // visible area: ImageIntRect(0, 0,
// size.width, size.height)
if (!aResources.AddBlobImage(key, descriptor, bytes,
ImageIntRect(0, 0, size.width, size.height))) {
return Nothing();
}
maskData->ClearImageKey();

View File

@ -1478,7 +1478,8 @@ wr::RenderRoot gfxUtils::GetContentRenderRoot() {
Maybe<wr::RenderRoot> gfxUtils::GetRenderRootForFrame(const nsIFrame* aFrame) {
if (!gfxVars::UseWebRender() ||
!StaticPrefs::gfx_webrender_split_render_roots()) {
!StaticPrefs::gfx_webrender_split_render_roots() ||
!XRE_IsParentProcess()) {
return Nothing();
}
if (!aFrame->GetContent()) {
@ -1487,24 +1488,13 @@ Maybe<wr::RenderRoot> gfxUtils::GetRenderRootForFrame(const nsIFrame* aFrame) {
if (!aFrame->GetContent()->IsElement()) {
return Nothing();
}
return gfxUtils::GetRenderRootForElement(aFrame->GetContent()->AsElement());
}
Maybe<wr::RenderRoot> gfxUtils::GetRenderRootForElement(
const dom::Element* aElement) {
if (!aElement) {
return Nothing();
}
if (!gfxVars::UseWebRender() ||
!StaticPrefs::gfx_webrender_split_render_roots()) {
return Nothing();
}
if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
NS_LITERAL_STRING("content"), eCaseMatters)) {
const dom::Element* element = aFrame->GetContent()->AsElement();
if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
NS_LITERAL_STRING("content"), eCaseMatters)) {
return Some(wr::RenderRoot::Content);
}
if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
NS_LITERAL_STRING("popover"), eCaseMatters)) {
if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::renderroot,
NS_LITERAL_STRING("popover"), eCaseMatters)) {
return Some(wr::RenderRoot::Popover);
}
return Nothing();
@ -1513,7 +1503,8 @@ Maybe<wr::RenderRoot> gfxUtils::GetRenderRootForElement(
wr::RenderRoot gfxUtils::RecursivelyGetRenderRootForFrame(
const nsIFrame* aFrame) {
if (!gfxVars::UseWebRender() ||
!StaticPrefs::gfx_webrender_split_render_roots()) {
!StaticPrefs::gfx_webrender_split_render_roots() ||
!XRE_IsParentProcess()) {
return wr::RenderRoot::Default;
}

View File

@ -316,8 +316,6 @@ class gfxUtils {
static mozilla::Maybe<mozilla::wr::RenderRoot> GetRenderRootForFrame(
const nsIFrame* aFrame);
static mozilla::Maybe<mozilla::wr::RenderRoot> GetRenderRootForElement(
const mozilla::dom::Element* aElement);
static mozilla::wr::RenderRoot RecursivelyGetRenderRootForFrame(
const nsIFrame* aFrame);
};

View File

@ -159,7 +159,8 @@ void VRProcessParent::InitAfterConnect(bool aSucceeded) {
// Make vr-gpu process connection
GPUChild* gpuChild = GPUProcessManager::Get()->GetGPUChild();
MOZ_ASSERT(gpuChild);
// Add logs for Bug 1565000 to figure out why gpuChild can't be access.
MOZ_RELEASE_ASSERT(gpuChild, "gpuChild is null.");
Endpoint<PVRGPUChild> vrGPUBridge;
VRProcessManager* vpm = VRProcessManager::Get();

View File

@ -17,6 +17,12 @@ if CONFIG['OS_TARGET'] != 'Android':
'VRService.cpp',
'VRSession.cpp',
]
# PuppetSession includes MacIOSurface.h which includes Mac headers
# which define Size and Points types in the root namespace that
# often conflict with our own types.
SOURCES += [
'PuppetSession.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
# Build OpenVR on Windows, Linux, and macOS desktop targets
@ -34,7 +40,6 @@ if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
# often conflict with our own types.
SOURCES += [
'OpenVRSession.cpp',
'PuppetSession.cpp',
]
FINAL_LIBRARY = 'xul'

View File

@ -307,6 +307,7 @@ static RefPtr<ScaledFont> GetScaledFont(Translator* aTranslator,
static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
@ -473,14 +474,15 @@ extern "C" {
bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob, int32_t width,
int32_t height, mozilla::wr::ImageFormat aFormat,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
mozilla::wr::MutByteSlice output) {
return mozilla::wr::Moz2DRenderCallback(
mozilla::wr::ByteSliceToRange(blob), mozilla::gfx::IntSize(width, height),
mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aTileSize, aTileOffset,
aDirtyRect, mozilla::wr::MutByteSliceToRange(output));
mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect, aTileSize,
aTileOffset, aDirtyRect, mozilla::wr::MutByteSliceToRange(output));
}
} // extern

View File

@ -622,8 +622,10 @@ void TransactionBuilder::AddImage(ImageKey key,
void TransactionBuilder::AddBlobImage(BlobImageKey key,
const ImageDescriptor& aDescriptor,
wr::Vec<uint8_t>& aBytes) {
wr_resource_updates_add_blob_image(mTxn, key, &aDescriptor, &aBytes.inner);
wr::Vec<uint8_t>& aBytes,
const wr::DeviceIntRect& aVisibleRect) {
wr_resource_updates_add_blob_image(mTxn, key, &aDescriptor, &aBytes.inner,
aVisibleRect);
}
void TransactionBuilder::AddExternalImage(ImageKey key,
@ -652,9 +654,10 @@ void TransactionBuilder::UpdateImageBuffer(ImageKey aKey,
void TransactionBuilder::UpdateBlobImage(BlobImageKey aKey,
const ImageDescriptor& aDescriptor,
wr::Vec<uint8_t>& aBytes,
const wr::DeviceIntRect& aVisibleRect,
const wr::LayoutIntRect& aDirtyRect) {
wr_resource_updates_update_blob_image(mTxn, aKey, &aDescriptor, &aBytes.inner,
aDirtyRect);
aVisibleRect, aDirtyRect);
}
void TransactionBuilder::UpdateExternalImage(ImageKey aKey,
@ -674,8 +677,8 @@ void TransactionBuilder::UpdateExternalImageWithDirtyRect(
mTxn, aKey, &aDescriptor, aExtID, &aImageType, aChannelIndex, aDirtyRect);
}
void TransactionBuilder::SetImageVisibleArea(BlobImageKey aKey,
const wr::DeviceIntRect& aArea) {
void TransactionBuilder::SetBlobImageVisibleArea(
BlobImageKey aKey, const wr::DeviceIntRect& aArea) {
wr_resource_updates_set_blob_image_visible_area(mTxn, aKey, &aArea);
}

View File

@ -125,7 +125,8 @@ class TransactionBuilder final {
wr::Vec<uint8_t>& aBytes);
void AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
wr::Vec<uint8_t>& aBytes);
wr::Vec<uint8_t>& aBytes,
const wr::DeviceIntRect& aVisibleRect);
void AddExternalImageBuffer(ImageKey key, const ImageDescriptor& aDescriptor,
ExternalImageId aHandle);
@ -141,6 +142,7 @@ class TransactionBuilder final {
void UpdateBlobImage(wr::BlobImageKey aKey,
const ImageDescriptor& aDescriptor,
wr::Vec<uint8_t>& aBytes,
const wr::DeviceIntRect& aVisibleRect,
const wr::LayoutIntRect& aDirtyRect);
void UpdateExternalImage(ImageKey aKey, const ImageDescriptor& aDescriptor,
@ -155,7 +157,8 @@ class TransactionBuilder final {
const wr::DeviceIntRect& aDirtyRect,
uint8_t aChannelIndex = 0);
void SetImageVisibleArea(BlobImageKey aKey, const wr::DeviceIntRect& aArea);
void SetBlobImageVisibleArea(BlobImageKey aKey,
const wr::DeviceIntRect& aArea);
void DeleteImage(wr::ImageKey aKey);

View File

@ -1602,11 +1602,13 @@ pub extern "C" fn wr_resource_updates_add_blob_image(
image_key: BlobImageKey,
descriptor: &WrImageDescriptor,
bytes: &mut WrVecU8,
visible_rect: DeviceIntRect,
) {
txn.add_blob_image(
image_key,
descriptor.into(),
Arc::new(bytes.flush_into_vec()),
visible_rect,
if descriptor.format == ImageFormat::BGRA8 { Some(256) } else { None }
);
}
@ -1711,12 +1713,14 @@ pub extern "C" fn wr_resource_updates_update_blob_image(
image_key: BlobImageKey,
descriptor: &WrImageDescriptor,
bytes: &mut WrVecU8,
visible_rect: DeviceIntRect,
dirty_rect: LayoutIntRect,
) {
txn.update_blob_image(
image_key,
descriptor.into(),
Arc::new(bytes.flush_into_vec()),
visible_rect,
&DirtyRect::Partial(dirty_rect)
);
}
@ -2189,6 +2193,7 @@ pub extern "C" fn wr_dp_push_stacking_context(
params.mix_blend_mode,
&filters,
&r_filter_datas,
&[],
glyph_raster_space,
params.cache_tiles);
@ -3159,6 +3164,7 @@ extern "C" {
width: i32,
height: i32,
format: ImageFormat,
visible_rect: &DeviceIntRect,
tile_size: Option<&u16>,
tile_offset: Option<&TileOffset>,
dirty_rect: Option<&LayoutIntRect>,

View File

@ -7,7 +7,7 @@
//! registering fonts found in the blob (see `prepare_request`).
use webrender::api::*;
use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation};
use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, DeviceIntRect};
use bindings::{ByteSlice, MutByteSlice, wr_moz2d_render_cb, ArcVecU8, gecko_profiler_start_marker, gecko_profiler_end_marker};
use rayon::ThreadPool;
use rayon::prelude::*;
@ -445,6 +445,9 @@ struct BlobFont {
struct BlobCommand {
/// The blob.
data: Arc<BlobImageData>,
/// What part of the blob should be rasterized (visible_rect's top-left corresponds to
/// (0,0) in the blob's rasterization)
visible_rect: DeviceIntRect,
/// The size of the tiles to use in rasterization, if tiling should be used.
tile_size: Option<TileSize>,
}
@ -454,6 +457,7 @@ struct BlobCommand {
descriptor: BlobImageDescriptor,
commands: Arc<BlobImageData>,
dirty_rect: BlobDirtyRect,
visible_rect: DeviceIntRect,
tile_size: Option<TileSize>,
}
@ -492,10 +496,12 @@ impl AsyncBlobImageRasterizer for Moz2dBlobRasterizer {
let command = &self.blob_commands[&params.request.key];
let blob = Arc::clone(&command.data);
assert!(params.descriptor.rect.size.width > 0 && params.descriptor.rect.size.height > 0);
Job {
request: params.request,
descriptor: params.descriptor,
commands: blob,
visible_rect: command.visible_rect,
dirty_rect: params.dirty_rect,
tile_size: command.tile_size,
}
@ -545,6 +551,7 @@ fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
descriptor.rect.size.width,
descriptor.rect.size.height,
descriptor.format,
&job.visible_rect,
job.tile_size.as_ref(),
job.request.tile.as_ref(),
dirty_rect.as_ref(),
@ -569,15 +576,15 @@ fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
}
impl BlobImageHandler for Moz2dBlobImageHandler {
fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tile_size: Option<TileSize>) {
fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, tile_size: Option<TileSize>) {
{
let index = BlobReader::new(&data);
assert!(index.reader.has_more());
}
self.blob_commands.insert(key, BlobCommand { data: Arc::clone(&data), tile_size });
self.blob_commands.insert(key, BlobCommand { data: Arc::clone(&data), visible_rect: *visible_rect, tile_size });
}
fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect) {
fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, dirty_rect: &BlobDirtyRect) {
match self.blob_commands.entry(key) {
hash_map::Entry::Occupied(mut e) => {
let command = e.get_mut();
@ -597,6 +604,8 @@ impl BlobImageHandler for Moz2dBlobImageHandler {
}
};
command.data = Arc::new(merge_blob_images(&command.data, &data, dirty_rect));
assert_eq!(command.visible_rect, *visible_rect);
command.visible_rect = *visible_rect;
}
_ => { panic!("missing image key"); }
}

View File

@ -71,6 +71,7 @@ impl App {
true,
&filters,
&[],
&[]
);
let space_and_clip = SpaceAndClipInfo {

View File

@ -19,7 +19,7 @@ use std::sync::Arc;
use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, Transaction};
use webrender::api::{ColorF, CommonItemProperties, SpaceAndClipInfo};
use webrender::api::units::*;
use webrender::euclid::size2;
use webrender::euclid::{size2, rect};
// This example shows how to implement a very basic BlobImageHandler that can only render
// a checkerboard pattern.
@ -136,12 +136,14 @@ impl CheckerboardRenderer {
}
impl api::BlobImageHandler for CheckerboardRenderer {
fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, _: Option<api::TileSize>) {
fn add(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>,
_visible_rect: &DeviceIntRect, _: Option<api::TileSize>) {
self.image_cmds
.insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap()));
}
fn update(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>, _dirty_rect: &BlobDirtyRect) {
fn update(&mut self, key: api::BlobImageKey, cmds: Arc<api::BlobImageData>,
_visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) {
// Here, updating is just replacing the current version of the commands with
// the new one (no incremental updates).
self.image_cmds
@ -209,6 +211,7 @@ impl Example for App {
blob_img1,
api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
serialize_blob(api::ColorU::new(50, 50, 150, 255)),
rect(0, 0, 500, 500),
Some(128),
);
@ -217,6 +220,7 @@ impl Example for App {
blob_img2,
api::ImageDescriptor::new(200, 200, api::ImageFormat::BGRA8, true, false),
serialize_blob(api::ColorU::new(50, 150, 50, 255)),
rect(0, 0, 200, 200),
None,
);

View File

@ -0,0 +1,534 @@
/* 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 shared,prim_shared
varying vec3 vInput1Uv;
varying vec3 vInput2Uv;
flat varying vec4 vInput1UvRect;
flat varying vec4 vInput2UvRect;
flat varying int vFilterInputCount;
flat varying int vFilterKind;
flat varying ivec4 vData;
flat varying vec4 vFilterData0;
flat varying vec4 vFilterData1;
flat varying float vFloat0;
flat varying mat3 vColorMat;
flat varying int vFuncs[4];
#define FILTER_BLEND 0
#define FILTER_FLOOD 1
#define FILTER_LINEAR_TO_SRGB 2
#define FILTER_SRGB_TO_LINEAR 3
#define FILTER_OPACITY 4
#define FILTER_COLOR_MATRIX 5
#define FILTER_DROP_SHADOW 6
#define FILTER_OFFSET 7
#define FILTER_COMPONENT_TRANSFER 8
#define FILTER_IDENTITY 9
#ifdef WR_VERTEX_SHADER
in int aFilterRenderTaskAddress;
in int aFilterInput1TaskAddress;
in int aFilterInput2TaskAddress;
in int aFilterKind;
in int aFilterInputCount;
in int aFilterGenericInt;
in ivec2 aFilterExtraDataAddress;
struct FilterTask {
RenderTaskCommonData common_data;
vec3 user_data;
};
FilterTask fetch_filter_task(int address) {
RenderTaskData task_data = fetch_render_task_data(address);
FilterTask task = FilterTask(
task_data.common_data,
task_data.user_data.xyz
);
return task;
}
vec4 compute_uv_rect(RenderTaskCommonData task, vec2 texture_size) {
RectWithSize task_rect = task.task_rect;
vec4 uvRect = vec4(task_rect.p0 + vec2(0.5),
task_rect.p0 + task_rect.size - vec2(0.5));
uvRect /= texture_size.xyxy;
return uvRect;
}
vec3 compute_uv(RenderTaskCommonData task, vec2 texture_size) {
RectWithSize task_rect = task.task_rect;
vec3 uv = vec3(0.0, 0.0, task.texture_layer_index);
vec2 uv0 = task_rect.p0 / texture_size;
vec2 uv1 = floor(task_rect.p0 + task_rect.size) / texture_size;
uv.xy = mix(uv0, uv1, aPosition.xy);
return uv;
}
void main(void) {
FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress);
RectWithSize target_rect = filter_task.common_data.task_rect;
vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy;
RenderTaskCommonData input_1_task;
if (aFilterInputCount > 0) {
vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
input_1_task = fetch_render_task_common_data(aFilterInput1TaskAddress);
vInput1UvRect = compute_uv_rect(input_1_task, texture_size);
vInput1Uv = compute_uv(input_1_task, texture_size);
}
RenderTaskCommonData input_2_task;
if (aFilterInputCount > 1) {
vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
input_2_task = fetch_render_task_common_data(aFilterInput2TaskAddress);
vInput2UvRect = compute_uv_rect(input_2_task, texture_size);
vInput2Uv = compute_uv(input_2_task, texture_size);
}
vFilterInputCount = aFilterInputCount;
vFilterKind = aFilterKind;
// This assignment is only used for component transfer filters but this
// assignment has to be done here and not in the component transfer case
// below because it doesn't get executed on Windows because of a suspected
// miscompile of this shader on Windows. See
// https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows
// default: just to satisfy angle_shader_validation.rs which needs one
// default: for every switch, even in comments.
vFuncs[0] = (aFilterGenericInt >> 12) & 0xf; // R
vFuncs[1] = (aFilterGenericInt >> 8) & 0xf; // G
vFuncs[2] = (aFilterGenericInt >> 4) & 0xf; // B
vFuncs[3] = (aFilterGenericInt) & 0xf; // A
switch (aFilterKind) {
case FILTER_BLEND:
vData = ivec4(aFilterGenericInt, 0, 0, 0);
break;
case FILTER_FLOOD:
vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
break;
case FILTER_OPACITY:
vFloat0 = filter_task.user_data.x;
break;
case FILTER_COLOR_MATRIX:
vec4 mat_data[3] = fetch_from_gpu_cache_3_direct(aFilterExtraDataAddress);
vColorMat = mat3(mat_data[0].xyz, mat_data[1].xyz, mat_data[2].xyz);
vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress + ivec2(4, 0));
break;
case FILTER_DROP_SHADOW:
vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
break;
case FILTER_OFFSET:
vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0));
RectWithSize task_rect = input_1_task.task_rect;
vec4 clipRect = vec4(task_rect.p0, task_rect.p0 + task_rect.size);
clipRect /= texture_size.xyxy;
vFilterData1 = clipRect;
break;
case FILTER_COMPONENT_TRANSFER:
vData = ivec4(aFilterExtraDataAddress, 0, 0);
break;
default:
break;
}
gl_Position = uTransform * vec4(pos, 0.0, 1.0);
}
#endif
#ifdef WR_FRAGMENT_SHADER
#define COMPONENT_TRANSFER_IDENTITY 0
#define COMPONENT_TRANSFER_TABLE 1
#define COMPONENT_TRANSFER_DISCRETE 2
#define COMPONENT_TRANSFER_LINEAR 3
#define COMPONENT_TRANSFER_GAMMA 4
vec3 Multiply(vec3 Cb, vec3 Cs) {
return Cb * Cs;
}
vec3 Screen(vec3 Cb, vec3 Cs) {
return Cb + Cs - (Cb * Cs);
}
vec3 HardLight(vec3 Cb, vec3 Cs) {
vec3 m = Multiply(Cb, 2.0 * Cs);
vec3 s = Screen(Cb, 2.0 * Cs - 1.0);
vec3 edge = vec3(0.5, 0.5, 0.5);
return mix(m, s, step(edge, Cs));
}
// TODO: Worth doing with mix/step? Check GLSL output.
float ColorDodge(float Cb, float Cs) {
if (Cb == 0.0)
return 0.0;
else if (Cs == 1.0)
return 1.0;
else
return min(1.0, Cb / (1.0 - Cs));
}
// TODO: Worth doing with mix/step? Check GLSL output.
float ColorBurn(float Cb, float Cs) {
if (Cb == 1.0)
return 1.0;
else if (Cs == 0.0)
return 0.0;
else
return 1.0 - min(1.0, (1.0 - Cb) / Cs);
}
float SoftLight(float Cb, float Cs) {
if (Cs <= 0.5) {
return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb);
} else {
float D;
if (Cb <= 0.25)
D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb;
else
D = sqrt(Cb);
return Cb + (2.0 * Cs - 1.0) * (D - Cb);
}
}
vec3 Difference(vec3 Cb, vec3 Cs) {
return abs(Cb - Cs);
}
vec3 Exclusion(vec3 Cb, vec3 Cs) {
return Cb + Cs - 2.0 * Cb * Cs;
}
// These functions below are taken from the spec.
// There's probably a much quicker way to implement
// them in GLSL...
float Sat(vec3 c) {
return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));
}
float Lum(vec3 c) {
vec3 f = vec3(0.3, 0.59, 0.11);
return dot(c, f);
}
vec3 ClipColor(vec3 C) {
float L = Lum(C);
float n = min(C.r, min(C.g, C.b));
float x = max(C.r, max(C.g, C.b));
if (n < 0.0)
C = L + (((C - L) * L) / (L - n));
if (x > 1.0)
C = L + (((C - L) * (1.0 - L)) / (x - L));
return C;
}
vec3 SetLum(vec3 C, float l) {
float d = l - Lum(C);
return ClipColor(C + d);
}
void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) {
if (Cmax > Cmin) {
Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin));
Cmax = s;
} else {
Cmid = 0.0;
Cmax = 0.0;
}
Cmin = 0.0;
}
vec3 SetSat(vec3 C, float s) {
if (C.r <= C.g) {
if (C.g <= C.b) {
SetSatInner(C.r, C.g, C.b, s);
} else {
if (C.r <= C.b) {
SetSatInner(C.r, C.b, C.g, s);
} else {
SetSatInner(C.b, C.r, C.g, s);
}
}
} else {
if (C.r <= C.b) {
SetSatInner(C.g, C.r, C.b, s);
} else {
if (C.g <= C.b) {
SetSatInner(C.g, C.b, C.r, s);
} else {
SetSatInner(C.b, C.g, C.r, s);
}
}
}
return C;
}
vec3 Hue(vec3 Cb, vec3 Cs) {
return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb));
}
vec3 Saturation(vec3 Cb, vec3 Cs) {
return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb));
}
vec3 Color(vec3 Cb, vec3 Cs) {
return SetLum(Cs, Lum(Cb));
}
vec3 Luminosity(vec3 Cb, vec3 Cs) {
return SetLum(Cb, Lum(Cs));
}
const int BlendMode_Normal = 0;
const int BlendMode_Multiply = 1;
const int BlendMode_Screen = 2;
const int BlendMode_Overlay = 3;
const int BlendMode_Darken = 4;
const int BlendMode_Lighten = 5;
const int BlendMode_ColorDodge = 6;
const int BlendMode_ColorBurn = 7;
const int BlendMode_HardLight = 8;
const int BlendMode_SoftLight = 9;
const int BlendMode_Difference = 10;
const int BlendMode_Exclusion = 11;
const int BlendMode_Hue = 12;
const int BlendMode_Saturation = 13;
const int BlendMode_Color = 14;
const int BlendMode_Luminosity = 15;
vec4 blend(vec4 Cs, vec4 Cb, int mode) {
vec4 result = vec4(1.0, 0.0, 0.0, 1.0);
switch (mode) {
case BlendMode_Normal:
result.rgb = Cs.rgb;
break;
case BlendMode_Multiply:
result.rgb = Multiply(Cb.rgb, Cs.rgb);
break;
case BlendMode_Screen:
result.rgb = Screen(Cb.rgb, Cs.rgb);
break;
case BlendMode_Overlay:
// Overlay is inverse of Hardlight
result.rgb = HardLight(Cs.rgb, Cb.rgb);
break;
case BlendMode_Darken:
result.rgb = min(Cs.rgb, Cb.rgb);
break;
case BlendMode_Lighten:
result.rgb = max(Cs.rgb, Cb.rgb);
break;
case BlendMode_ColorDodge:
result.r = ColorDodge(Cb.r, Cs.r);
result.g = ColorDodge(Cb.g, Cs.g);
result.b = ColorDodge(Cb.b, Cs.b);
break;
case BlendMode_ColorBurn:
result.r = ColorBurn(Cb.r, Cs.r);
result.g = ColorBurn(Cb.g, Cs.g);
result.b = ColorBurn(Cb.b, Cs.b);
break;
case BlendMode_HardLight:
result.rgb = HardLight(Cb.rgb, Cs.rgb);
break;
case BlendMode_SoftLight:
result.r = SoftLight(Cb.r, Cs.r);
result.g = SoftLight(Cb.g, Cs.g);
result.b = SoftLight(Cb.b, Cs.b);
break;
case BlendMode_Difference:
result.rgb = Difference(Cb.rgb, Cs.rgb);
break;
case BlendMode_Exclusion:
result.rgb = Exclusion(Cb.rgb, Cs.rgb);
break;
case BlendMode_Hue:
result.rgb = Hue(Cb.rgb, Cs.rgb);
break;
case BlendMode_Saturation:
result.rgb = Saturation(Cb.rgb, Cs.rgb);
break;
case BlendMode_Color:
result.rgb = Color(Cb.rgb, Cs.rgb);
break;
case BlendMode_Luminosity:
result.rgb = Luminosity(Cb.rgb, Cs.rgb);
break;
default: break;
}
vec3 rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb;
result = mix(vec4(Cb.rgb * Cb.a, Cb.a), vec4(rgb, 1.0), Cs.a);
return result;
}
// Based on the Gecko's implementation in
// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24
// These could be made faster by sampling a lookup table stored in a float texture
// with linear interpolation.
vec3 SrgbToLinear(vec3 color) {
vec3 c1 = color / 12.92;
vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4));
return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2);
}
vec3 LinearToSrgb(vec3 color) {
vec3 c1 = color * 12.92;
vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
}
// This function has to be factored out due to the following issue:
// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones
// (and now the words "default: default:" so angle_shader_validation.rs passes)
vec4 ComponentTransfer(vec4 colora) {
// We push a different amount of data to the gpu cache depending on the
// function type.
// Identity => 0 blocks
// Table/Discrete => 64 blocks (256 values)
// Linear => 1 block (2 values)
// Gamma => 1 block (3 values)
// We loop through the color components and increment the offset (for the
// next color component) into the gpu cache based on how many blocks that
// function type put into the gpu cache.
// Table/Discrete use a 256 entry look up table.
// Linear/Gamma are a simple calculation.
int offset = 0;
vec4 texel;
int k;
for (int i = 0; i < 4; i++) {
switch (vFuncs[i]) {
case COMPONENT_TRANSFER_IDENTITY:
break;
case COMPONENT_TRANSFER_TABLE:
case COMPONENT_TRANSFER_DISCRETE:
// fetch value from lookup table
k = int(floor(colora[i]*255.0));
texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset + k/4, 0));
colora[i] = clamp(texel[k % 4], 0.0, 1.0);
// offset plus 256/4 blocks
offset = offset + 64;
break;
case COMPONENT_TRANSFER_LINEAR:
// fetch the two values for use in the linear equation
texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0));
colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
// offset plus 1 block
offset = offset + 1;
break;
case COMPONENT_TRANSFER_GAMMA:
// fetch the three values for use in the gamma equation
texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0));
colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
// offset plus 1 block
offset = offset + 1;
break;
default:
// shouldn't happen
break;
}
}
return colora;
}
vec4 sampleInUvRect(sampler2DArray sampler, vec3 uv, vec4 uvRect) {
vec2 clamped = clamp(uv.xy, uvRect.xy, uvRect.zw);
return texture(sampler, vec3(clamped, uv.z), 0.0);
}
void main(void) {
vec4 Ca = vec4(0.0, 0.0, 0.0, 0.0);
vec4 Cb = vec4(0.0, 0.0, 0.0, 0.0);
if (vFilterInputCount > 0) {
Ca = sampleInUvRect(sColor0, vInput1Uv, vInput1UvRect);
if (Ca.a != 0.0) {
Ca.rgb /= Ca.a;
}
}
if (vFilterInputCount > 1) {
Cb = sampleInUvRect(sColor1, vInput2Uv, vInput2UvRect);
if (Cb.a != 0.0) {
Cb.rgb /= Cb.a;
}
}
vec4 result = vec4(1.0, 0.0, 0.0, 1.0);
bool needsPremul = true;
switch (vFilterKind) {
case FILTER_BLEND:
result = blend(Ca, Cb, vData.x);
needsPremul = false;
break;
case FILTER_FLOOD:
result = vFilterData0;
needsPremul = false;
break;
case FILTER_LINEAR_TO_SRGB:
result.rgb = LinearToSrgb(Ca.rgb);
result.a = Ca.a;
break;
case FILTER_SRGB_TO_LINEAR:
result.rgb = SrgbToLinear(Ca.rgb);
result.a = Ca.a;
break;
case FILTER_OPACITY:
result.rgb = Ca.rgb;
result.a = Ca.a * vFloat0;
break;
case FILTER_COLOR_MATRIX:
result.rgb = vColorMat * Ca.rgb + vFilterData0.rgb;
result.a = Ca.a;
break;
case FILTER_DROP_SHADOW:
vec4 shadow = vec4(vFilterData0.rgb, Cb.a * vFilterData0.a);
// Normal blend + source-over coposite
result = blend(Ca, shadow, BlendMode_Normal);
needsPremul = false;
break;
case FILTER_OFFSET:
vec2 offsetUv = vInput1Uv.xy + vFilterData0.xy;
result = sampleInUvRect(sColor0, vec3(offsetUv, vInput1Uv.z), vInput1UvRect);
result *= point_inside_rect(offsetUv, vFilterData1.xy, vFilterData1.zw);
needsPremul = false;
break;
case FILTER_COMPONENT_TRANSFER:
vec4 colora = Ca.a != 0.0 ? Ca / Ca.a : Ca;
result = ComponentTransfer(Ca);
break;
case FILTER_IDENTITY:
result = Ca;
break;
default:
break;
}
if (needsPremul) {
result.rgb *= result.a;
}
oFragColor = result;
}
#endif

View File

@ -100,6 +100,27 @@ impl BatchTextures {
colors: [texture, texture, TextureSource::Invalid],
}
}
pub fn is_compatible_with(&self, other: &BatchTextures) -> bool {
self.colors.iter().zip(other.colors.iter()).all(|(t1, t2)| textures_compatible(*t1, *t2))
}
pub fn combine_textures(&self, other: BatchTextures) -> Option<BatchTextures> {
if !self.is_compatible_with(&other) {
return None;
}
let mut new_textures = BatchTextures::no_texture();
for (i, (color, other_color)) in self.colors.iter().zip(other.colors.iter()).enumerate() {
// If these textures are compatible, for each source either both sources are invalid or only one is not invalid.
new_textures.colors[i] = if *color == TextureSource::Invalid {
*other_color
} else {
*color
};
}
Some(new_textures)
}
}
#[derive(Copy, Clone, Debug)]
@ -121,10 +142,7 @@ impl BatchKey {
}
pub fn is_compatible_with(&self, other: &BatchKey) -> bool {
self.kind == other.kind && self.blend_mode == other.blend_mode &&
textures_compatible(self.textures.colors[0], other.textures.colors[0]) &&
textures_compatible(self.textures.colors[1], other.textures.colors[1]) &&
textures_compatible(self.textures.colors[2], other.textures.colors[2])
self.kind == other.kind && self.blend_mode == other.blend_mode && self.textures.is_compatible_with(&other.textures)
}
}
@ -1640,6 +1658,40 @@ impl BatchBuilder {
ctx,
);
}
PictureCompositeMode::SvgFilter(..) => {
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
);
let (uv_rect_address, textures) = render_tasks.resolve_surface(
surface_task.expect("bug: surface must be allocated by now"),
gpu_cache,
);
let key = BatchKey::new(
kind,
non_segmented_blend_mode,
textures,
);
let prim_header_index = prim_headers.push(&prim_header, z_id, [
ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
RasterizationSpace::Screen as i32,
get_shader_opacity(1.0),
0,
]);
self.add_brush_instance_to_batches(
key,
batch_features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
EdgeAaSegmentMask::empty(),
clip_task_address,
brush_flags,
prim_header_index,
uv_rect_address.as_int(),
prim_vis_mask,
);
}
}
}
None => {

View File

@ -5,7 +5,7 @@
use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter};
use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, RasterSpace};
use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId};
use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, GradientStop};
use api::{FilterOp, FilterPrimitive, FontInstanceKey, GlyphInstance, GlyphOptions, GradientStop};
use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth};
use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
@ -796,6 +796,7 @@ impl<'a> DisplayListFlattener<'a> {
origin: LayoutPoint,
filters: ItemRange<FilterOp>,
filter_datas: &[TempFilterData],
filter_primitives: ItemRange<FilterPrimitive>,
is_backface_visible: bool,
apply_pipeline_clip: bool,
) {
@ -809,6 +810,7 @@ impl<'a> DisplayListFlattener<'a> {
CompositeOps::new(
stacking_context.filter_ops_for_compositing(filters),
stacking_context.filter_datas_for_compositing(filter_datas),
stacking_context.filter_primitives_for_compositing(filter_primitives),
stacking_context.mix_blend_mode_for_compositing(),
)
};
@ -1180,6 +1182,7 @@ impl<'a> DisplayListFlattener<'a> {
info.origin,
item.filters(),
item.filter_datas(),
item.filter_primitives(),
info.is_backface_visible,
apply_pipeline_clip,
);
@ -1309,9 +1312,10 @@ impl<'a> DisplayListFlattener<'a> {
}
// Do nothing; these are dummy items for the display list parser
DisplayItem::SetGradientStops => {}
DisplayItem::SetFilterOps => {}
DisplayItem::SetFilterData => {}
DisplayItem::SetGradientStops |
DisplayItem::SetFilterOps |
DisplayItem::SetFilterData |
DisplayItem::SetFilterPrimitives => {}
DisplayItem::PopReferenceFrame |
DisplayItem::PopStackingContext => {
@ -1977,6 +1981,71 @@ impl<'a> DisplayListFlattener<'a> {
self.prim_store.optimize_picture_if_possible(current_pic_index);
}
if !stacking_context.composite_ops.filter_primitives.is_empty() {
let filter_datas = stacking_context.composite_ops.filter_datas.iter()
.map(|filter_data| filter_data.sanitize())
.map(|filter_data| {
SFilterData {
r_func: SFilterDataComponent::from_functype_values(
filter_data.func_r_type, &filter_data.r_values),
g_func: SFilterDataComponent::from_functype_values(
filter_data.func_g_type, &filter_data.g_values),
b_func: SFilterDataComponent::from_functype_values(
filter_data.func_b_type, &filter_data.b_values),
a_func: SFilterDataComponent::from_functype_values(
filter_data.func_a_type, &filter_data.a_values),
}
})
.collect();
// Sanitize filter inputs
for primitive in &mut stacking_context.composite_ops.filter_primitives {
primitive.sanitize();
}
let composite_mode = PictureCompositeMode::SvgFilter(
stacking_context.composite_ops.filter_primitives,
filter_datas,
);
let filter_pic_index = PictureIndex(self.prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
Some(composite_mode.clone()),
Picture3DContext::Out,
None,
true,
stacking_context.is_backface_visible,
stacking_context.requested_raster_space,
PrimitiveList::new(
vec![cur_instance.clone()],
&self.interners,
),
stacking_context.spatial_node_index,
None,
PictureOptions::default(),
))
);
current_pic_index = filter_pic_index;
cur_instance = create_prim_instance(
current_pic_index,
Some(composite_mode).into(),
stacking_context.is_backface_visible,
ClipChainId::NONE,
stacking_context.spatial_node_index,
&mut self.interners,
);
if cur_instance.is_chased() {
println!("\tis a composite picture for a stacking context with an SVG filter");
}
// Run the optimize pass on this picture, to see if we can
// collapse opacity and avoid drawing to an off-screen surface.
self.prim_store.optimize_picture_if_possible(current_pic_index);
}
// Same for mix-blend-mode, except we can skip if this primitive is the first in the parent
// stacking context.
// From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is:
@ -3063,6 +3132,11 @@ impl FlattenedStackingContext {
return false;
}
// If there are svg filters
if !self.composite_ops.filter_primitives.is_empty() {
return false;
}
// We can skip mix-blend modes if they are the first primitive in a stacking context,
// see pop_stacking_context for a full explanation.
if !self.composite_ops.mix_blend_mode.is_none() &&

View File

@ -122,6 +122,23 @@ impl From<SFilterDataKey> for SFilterDataTemplate {
}
}
impl SFilterData {
pub fn is_identity(&self) -> bool {
self.r_func == SFilterDataComponent::Identity
&& self.g_func == SFilterDataComponent::Identity
&& self.b_func == SFilterDataComponent::Identity
&& self.a_func == SFilterDataComponent::Identity
}
pub fn update(&self, mut request: GpuDataRequest) {
push_component_transfer_data(&self.r_func, &mut request);
push_component_transfer_data(&self.g_func, &mut request);
push_component_transfer_data(&self.b_func, &mut request);
push_component_transfer_data(&self.a_func, &mut request);
assert!(!self.is_identity());
}
}
impl SFilterDataTemplate {
/// Update the GPU cache for a given filter data template. This may be called multiple
/// times per frame, by each primitive reference that refers to this interned
@ -131,15 +148,8 @@ impl SFilterDataTemplate {
&mut self,
frame_state: &mut FrameBuildingState,
) {
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
push_component_transfer_data(&self.data.r_func, &mut request);
push_component_transfer_data(&self.data.g_func, &mut request);
push_component_transfer_data(&self.data.b_func, &mut request);
push_component_transfer_data(&self.data.a_func, &mut request);
assert!(self.data.r_func != SFilterDataComponent::Identity
|| self.data.g_func != SFilterDataComponent::Identity
|| self.data.b_func != SFilterDataComponent::Identity
|| self.data.a_func != SFilterDataComponent::Identity);
if let Some(request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
self.data.update(request);
}
}
}

View File

@ -109,6 +109,20 @@ pub struct ScalingInstance {
pub src_task_address: RenderTaskAddress,
}
#[derive(Debug)]
#[repr(C)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct SvgFilterInstance {
pub task_address: RenderTaskAddress,
pub input_1_task_address: RenderTaskAddress,
pub input_2_task_address: RenderTaskAddress,
pub kind: u16,
pub input_count: u16,
pub generic_int: u16,
pub extra_data_address: GpuCacheAddress,
}
#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
#[repr(C)]
#[cfg_attr(feature = "capture", derive(Serialize))]

View File

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{MixBlendMode, PipelineId, PremultipliedColorF};
use api::{PropertyBinding, PropertyBindingId, FontRenderMode};
use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
use api::{DebugFlags, RasterSpace, ImageKey, ColorF};
use api::units::*;
use crate::box_shadow::{BLUR_SAMPLE_SCALE};
@ -14,6 +14,7 @@ use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
use crate::debug_colors;
use euclid::{vec3, TypedPoint2D, TypedScale, TypedSize2D, Vector2D, TypedRect};
use euclid::approxeq::ApproxEq;
use crate::filterdata::SFilterData;
use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
use crate::intern::ItemUid;
use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter};
@ -1573,6 +1574,66 @@ pub enum PictureCompositeMode {
/// Used to cache a picture as a series of tiles.
TileCache {
},
/// Apply an SVG filter
SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
}
impl PictureCompositeMode {
pub fn inflate_picture_rect(&self, picture_rect: PictureRect, inflation_factor: f32) -> PictureRect {
let mut result_rect = picture_rect;
match self {
PictureCompositeMode::Filter(filter) => match filter {
Filter::Blur(_) => {
result_rect = picture_rect.inflate(inflation_factor, inflation_factor);
},
Filter::DropShadows(shadows) => {
let mut max_inflation: f32 = 0.0;
for shadow in shadows {
let inflation_factor = shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
max_inflation = max_inflation.max(inflation_factor);
}
result_rect = picture_rect.inflate(max_inflation, max_inflation);
},
_ => {}
}
PictureCompositeMode::SvgFilter(primitives, _) => {
let mut output_rects = Vec::with_capacity(primitives.len());
for (cur_index, primitive) in primitives.iter().enumerate() {
let output_rect = match primitive.kind {
FilterPrimitiveKind::Blur(ref primitive) => {
let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
let inflation_factor = primitive.radius.round() * BLUR_SAMPLE_SCALE;
input.inflate(inflation_factor, inflation_factor)
}
FilterPrimitiveKind::DropShadow(ref primitive) => {
let inflation_factor = primitive.shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
let shadow_rect = input.inflate(inflation_factor, inflation_factor);
input.union(&shadow_rect.translate(&(primitive.shadow.offset * TypedScale::new(1.0))))
}
FilterPrimitiveKind::Blend(ref primitive) => {
primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
.union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
}
FilterPrimitiveKind::Identity(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::Opacity(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::ColorMatrix(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::Flood(..) => picture_rect,
};
output_rects.push(output_rect);
result_rect = result_rect.union(&output_rect);
}
}
_ => {},
}
result_rect
}
}
/// Enum value describing the place of a picture in a 3D context.
@ -1938,7 +1999,8 @@ impl PicturePrimitive {
Some(RasterConfig { composite_mode: PictureCompositeMode::MixBlend(..), .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(..), .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::ComponentTransferFilter(..), .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, ..}) |
Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::SvgFilter(..), .. }) |
None => {
false
}
@ -2442,6 +2504,40 @@ impl PicturePrimitive {
Some((render_task_id, render_task_id))
}
PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
true,
);
let picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
);
let picture_task_id = frame_state.render_tasks.add(picture_task);
let filter_task_id = RenderTask::new_svg_filter(
primitives,
filter_datas,
&mut frame_state.render_tasks,
clipped.size,
uv_rect_kind,
picture_task_id,
device_pixel_scale,
);
Some((filter_task_id, picture_task_id))
}
};
if let Some((root, port)) = dep_info {
@ -2495,7 +2591,8 @@ impl PicturePrimitive {
PictureCompositeMode::Blit(..) |
PictureCompositeMode::ComponentTransferFilter(..) |
PictureCompositeMode::Filter(..) |
PictureCompositeMode::MixBlend(..) => {
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::SvgFilter(..) => {
// TODO(gw): We can take advantage of the same logic that
// exists in the opaque rect detection for tile
// caches, to allow subpixel text on other surfaces
@ -2741,14 +2838,30 @@ impl PicturePrimitive {
0.0
}
}
PictureCompositeMode::SvgFilter(ref primitives, _) if self.options.inflate_if_required => {
let mut max = 0.0;
for primitive in primitives {
if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
max = f32::max(max, blur.radius * BLUR_SAMPLE_SCALE);
}
}
max
}
_ => {
0.0
}
};
// Check if there is perspective, and thus whether a new
// Filters must be applied before transforms, to do this, we can mark this picture as establishing a raster root.
let has_svg_filter = if let PictureCompositeMode::SvgFilter(..) = composite_mode {
true
} else {
false
};
// Check if there is perspective or if an SVG filter is applied, and thus whether a new
// rasterization root should be established.
let establishes_raster_root = frame_context.clip_scroll_tree
let establishes_raster_root = has_svg_filter || frame_context.clip_scroll_tree
.get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
.is_perspective();
@ -2835,26 +2948,12 @@ impl PicturePrimitive {
// rect into the parent surface coordinate space, and propagate that up
// to the parent.
if let Some(ref mut raster_config) = self.raster_config {
let mut surface_rect = {
let surface = state.current_surface_mut();
// Inflate the local bounding rect if required by the filter effect.
// This inflaction factor is to be applied to the surface itsefl.
// TODO: in prepare_for_render we round before multiplying with the
// blur sample scale. Should we do this here as well?
let inflation_size = match raster_config.composite_mode {
PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let mut max = 0.0;
for shadow in shadows {
max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
}
max.ceil()
}
_ => 0.0,
};
surface.rect = surface.rect.inflate(inflation_size, inflation_size);
surface.rect * TypedScale::new(1.0)
};
let surface = state.current_surface_mut();
// Inflate the local bounding rect if required by the filter effect.
// This inflaction factor is to be applied to the surface itself.
surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.inflation_factor);
let mut surface_rect = surface.rect * TypedScale::new(1.0);
// Pop this surface from the stack
let surface_index = state.pop_surface();
@ -2990,7 +3089,8 @@ impl PicturePrimitive {
filter_data.update(frame_state);
}
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::Blit(_) => {}
PictureCompositeMode::Blit(_) |
PictureCompositeMode::SvgFilter(..) => {}
}
true

View File

@ -10,7 +10,6 @@ use api::{PrimitiveKeyKind};
use api::units::*;
use crate::border::{get_max_scale_for_border, build_border_instances};
use crate::border::BorderSegmentCacheKey;
use crate::box_shadow::{BLUR_SAMPLE_SCALE};
use crate::clip::{ClipStore};
use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
use crate::clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
@ -2253,18 +2252,7 @@ impl PrimitiveStore {
if let Some(ref raster_config) = pic.raster_config {
// Inflate the local bounding rect if required by the filter effect.
// This inflaction factor is to be applied to the surface itself.
let inflation_size = match raster_config.composite_mode {
PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let mut max = 0.0;
for shadow in shadows {
max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
}
max.ceil()
}
_ => 0.0,
};
surface_rect = surface_rect.inflate(inflation_size, inflation_size);
surface_rect = raster_config.composite_mode.inflate_picture_rect(surface_rect, surface.inflation_factor);
// Layout space for the picture is picture space from the
// perspective of its child primitives.

View File

@ -3,12 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{
ColorU, MixBlendMode,
ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace,
PropertyBinding, PropertyBindingId,
};
use api::units::{Au, LayoutSize, LayoutVector2D};
use crate::intern::ItemUid;
use crate::display_list_flattener::IsVisible;
use crate::filterdata::SFilterData;
use crate::intern::ItemUid;
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
use crate::internal_types::{LayoutPrimitiveInfo, Filter};
use crate::picture::PictureCompositeMode;
@ -18,6 +19,20 @@ use crate::prim_store::{
InternablePrimitive,
};
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
pub enum FilterPrimitiveKey {
Identity(ColorSpace, FilterPrimitiveInput),
Flood(ColorSpace, ColorU),
Blend(ColorSpace, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveInput),
Blur(ColorSpace, Au, FilterPrimitiveInput),
Opacity(ColorSpace, Au, FilterPrimitiveInput),
ColorMatrix(ColorSpace, [Au; 20], FilterPrimitiveInput),
DropShadow(ColorSpace, (VectorKey, Au, ColorU), FilterPrimitiveInput),
ComponentTransfer(ColorSpace, FilterPrimitiveInput, Vec<SFilterData>),
}
/// Represents a hashable description of how a picture primitive
/// will be composited into its parent.
#[cfg_attr(feature = "capture", derive(Serialize))]
@ -44,6 +59,7 @@ pub enum PictureCompositeKey {
LinearToSrgb,
ComponentTransfer(ItemUid),
Flood(ColorU),
SvgFilter(Vec<FilterPrimitiveKey>),
// MixBlendMode
Multiply,
@ -130,6 +146,38 @@ impl From<Option<PictureCompositeMode>> for PictureCompositeKey {
Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
PictureCompositeKey::ComponentTransfer(handle.uid())
}
Some(PictureCompositeMode::SvgFilter(filter_primitives, filter_data)) => {
PictureCompositeKey::SvgFilter(filter_primitives.into_iter().map(|primitive| {
match primitive.kind {
FilterPrimitiveKind::Identity(identity) => FilterPrimitiveKey::Identity(primitive.color_space, identity.input),
FilterPrimitiveKind::Blend(blend) => FilterPrimitiveKey::Blend(primitive.color_space, blend.mode, blend.input1, blend.input2),
FilterPrimitiveKind::Flood(flood) => FilterPrimitiveKey::Flood(primitive.color_space, flood.color.into()),
FilterPrimitiveKind::Blur(blur) => FilterPrimitiveKey::Blur(primitive.color_space, Au::from_f32_px(blur.radius), blur.input),
FilterPrimitiveKind::Opacity(opacity) =>
FilterPrimitiveKey::Opacity(primitive.color_space, Au::from_f32_px(opacity.opacity), opacity.input),
FilterPrimitiveKind::ColorMatrix(color_matrix) => {
let mut quantized_values: [Au; 20] = [Au(0); 20];
for (value, result) in color_matrix.matrix.iter().zip(quantized_values.iter_mut()) {
*result = Au::from_f32_px(*value);
}
FilterPrimitiveKey::ColorMatrix(primitive.color_space, quantized_values, color_matrix.input)
}
FilterPrimitiveKind::DropShadow(drop_shadow) => {
FilterPrimitiveKey::DropShadow(
primitive.color_space,
(
drop_shadow.shadow.offset.into(),
Au::from_f32_px(drop_shadow.shadow.blur_radius),
drop_shadow.shadow.color.into(),
),
drop_shadow.input,
)
}
FilterPrimitiveKind::ComponentTransfer(component_transfer) =>
FilterPrimitiveKey::ComponentTransfer(primitive.color_space, component_transfer.input, filter_data.clone()),
}
}).collect())
}
Some(PictureCompositeMode::Blit(_)) |
Some(PictureCompositeMode::TileCache { .. }) |
None => {

View File

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ImageDescriptor, ImageFormat};
use api::{LineStyle, LineOrientation, ClipMode, DirtyRect};
use api::{ImageDescriptor, ImageFormat, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind};
use api::{LineStyle, LineOrientation, ClipMode, DirtyRect, MixBlendMode, ColorF, ColorSpace};
use api::units::*;
#[cfg(feature = "pathfinder")]
use api::FontRenderMode;
@ -14,6 +14,7 @@ use crate::clip_scroll_tree::SpatialNodeIndex;
use crate::device::TextureFilter;
#[cfg(feature = "pathfinder")]
use euclid::{TypedPoint2D, TypedVector2D};
use crate::filterdata::SFilterData;
use crate::frame_builder::FrameBuilderConfig;
use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
use crate::glyph_rasterizer::GpuGlyphCacheKey;
@ -354,15 +355,15 @@ impl RenderTaskGraph {
// to insert a blit to ensure we don't read and write from
// the same target.
let task_id = RenderTaskId {
let child_task_id = RenderTaskId {
index: child_task_index as u32,
#[cfg(debug_assertions)]
frame_id: self.frame_id,
};
let mut blit = RenderTask::new_blit(
self.tasks[task_index].location.size(),
BlitSource::RenderTask { task_id },
self.tasks[child_task_index].location.size(),
BlitSource::RenderTask { task_id: child_task_id },
);
// Mark for saving if the blit is more than pass appart from
@ -382,7 +383,7 @@ impl RenderTaskGraph {
passes[child_pass_index as usize + 1].tasks.push(blit_id);
self.tasks[task_index].children[nth_child] = blit_id;
task_redirects[task_index] = Some(blit_id);
task_redirects[child_task_index] = Some(blit_id);
}
}
}
@ -615,6 +616,33 @@ pub struct LineDecorationTask {
pub local_size: LayoutSize,
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum SvgFilterInfo {
Blend(MixBlendMode),
Flood(ColorF),
LinearToSrgb,
SrgbToLinear,
Opacity(f32),
ColorMatrix(Box<[f32; 20]>),
DropShadow(ColorF),
Offset(DeviceVector2D),
ComponentTransfer(SFilterData),
// TODO: This is used as a hack to ensure that a blur task's input is always in the blur's previous pass.
Identity,
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct SvgFilterTask {
pub info: SvgFilterInfo,
pub extra_gpu_cache_handle: Option<GpuCacheHandle>,
pub uv_rect_handle: GpuCacheHandle,
uv_rect_kind: UvRectKind,
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
@ -638,6 +666,7 @@ pub enum RenderTaskKind {
Border(BorderTask),
LineDecoration(LineDecorationTask),
Gradient(GradientTask),
SvgFilter(SvgFilterTask),
#[cfg(test)]
Test(RenderTargetKind),
}
@ -657,6 +686,7 @@ impl RenderTaskKind {
RenderTaskKind::Border(..) => "Border",
RenderTaskKind::LineDecoration(..) => "LineDecoration",
RenderTaskKind::Gradient(..) => "Gradient",
RenderTaskKind::SvgFilter(..) => "SvgFilter",
#[cfg(test)]
RenderTaskKind::Test(..) => "Test",
}
@ -1172,6 +1202,285 @@ impl RenderTask {
)
}
pub fn new_svg_filter(
filter_primitives: &[FilterPrimitive],
filter_datas: &[SFilterData],
render_tasks: &mut RenderTaskGraph,
content_size: DeviceIntSize,
uv_rect_kind: UvRectKind,
original_task_id: RenderTaskId,
device_pixel_scale: DevicePixelScale,
) -> RenderTaskId {
if filter_primitives.is_empty() {
return original_task_id;
}
// Resolves the input to a filter primitive
let get_task_input = |
input: &FilterPrimitiveInput,
filter_primitives: &[FilterPrimitive],
render_tasks: &mut RenderTaskGraph,
cur_index: usize,
outputs: &[RenderTaskId],
original: RenderTaskId,
color_space: ColorSpace,
| {
// TODO(cbrewster): Not sure we can assume that the original input is sRGB.
let (mut task_id, input_color_space) = match input.to_index(cur_index) {
Some(index) => (outputs[index], filter_primitives[index].color_space),
None => (original, ColorSpace::Srgb),
};
match (input_color_space, color_space) {
(ColorSpace::Srgb, ColorSpace::LinearRgb) => {
let task = RenderTask::new_svg_filter_primitive(
vec![task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::SrgbToLinear,
);
task_id = render_tasks.add(task);
},
(ColorSpace::LinearRgb, ColorSpace::Srgb) => {
let task = RenderTask::new_svg_filter_primitive(
vec![task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::LinearToSrgb,
);
task_id = render_tasks.add(task);
},
_ => {},
}
task_id
};
let mut outputs = vec![];
let mut cur_filter_data = 0;
for (cur_index, primitive) in filter_primitives.iter().enumerate() {
let render_task_id = match primitive.kind {
FilterPrimitiveKind::Identity(ref identity) => {
// Identity does not create a task, it provides its input's render task
get_task_input(
&identity.input,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
)
}
FilterPrimitiveKind::Blend(ref blend) => {
let input_1_task_id = get_task_input(
&blend.input1,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
let input_2_task_id = get_task_input(
&blend.input2,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
let task = RenderTask::new_svg_filter_primitive(
vec![input_1_task_id, input_2_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::Blend(blend.mode),
);
render_tasks.add(task)
},
FilterPrimitiveKind::Flood(ref flood) => {
let task = RenderTask::new_svg_filter_primitive(
vec![],
content_size,
uv_rect_kind,
SvgFilterInfo::Flood(flood.color),
);
render_tasks.add(task)
}
FilterPrimitiveKind::Blur(ref blur) => {
let blur_std_deviation = blur.radius * device_pixel_scale.0;
let input_task_id = get_task_input(
&blur.input,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
// TODO: This is a hack to ensure that a blur task's input is always in the blur's previous pass.
let svg_task = RenderTask::new_svg_filter_primitive(
vec![input_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::Identity,
);
RenderTask::new_blur(
DeviceSize::new(blur_std_deviation, blur_std_deviation),
render_tasks.add(svg_task),
render_tasks,
RenderTargetKind::Color,
ClearMode::Transparent,
None,
)
}
FilterPrimitiveKind::Opacity(ref opacity) => {
let input_task_id = get_task_input(
&opacity.input,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
let task = RenderTask::new_svg_filter_primitive(
vec![input_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::Opacity(opacity.opacity),
);
render_tasks.add(task)
}
FilterPrimitiveKind::ColorMatrix(ref color_matrix) => {
let input_task_id = get_task_input(
&color_matrix.input,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
let task = RenderTask::new_svg_filter_primitive(
vec![input_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::ColorMatrix(Box::new(color_matrix.matrix)),
);
render_tasks.add(task)
}
FilterPrimitiveKind::DropShadow(ref drop_shadow) => {
let input_task_id = get_task_input(
&drop_shadow.input,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
let blur_std_deviation = drop_shadow.shadow.blur_radius * device_pixel_scale.0;
let offset = drop_shadow.shadow.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale;
let offset_task = RenderTask::new_svg_filter_primitive(
vec![input_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::Offset(offset),
);
let offset_task_id = render_tasks.add(offset_task);
let blur_task_id = RenderTask::new_blur(
DeviceSize::new(blur_std_deviation, blur_std_deviation),
offset_task_id,
render_tasks,
RenderTargetKind::Color,
ClearMode::Transparent,
None,
);
let task = RenderTask::new_svg_filter_primitive(
vec![input_task_id, blur_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::DropShadow(drop_shadow.shadow.color),
);
render_tasks.add(task)
}
FilterPrimitiveKind::ComponentTransfer(ref component_transfer) => {
let input_task_id = get_task_input(
&component_transfer.input,
filter_primitives,
render_tasks,
cur_index,
&outputs,
original_task_id,
primitive.color_space
);
let filter_data = &filter_datas[cur_filter_data];
cur_filter_data += 1;
if filter_data.is_identity() {
input_task_id
} else {
let task = RenderTask::new_svg_filter_primitive(
vec![input_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::ComponentTransfer(filter_data.clone()),
);
render_tasks.add(task)
}
}
};
outputs.push(render_task_id);
}
// The output of a filter is the output of the last primitive in the chain.
let mut render_task_id = *outputs.last().unwrap();
// Convert to sRGB if needed
if filter_primitives.last().unwrap().color_space == ColorSpace::LinearRgb {
let task = RenderTask::new_svg_filter_primitive(
vec![render_task_id],
content_size,
uv_rect_kind,
SvgFilterInfo::LinearToSrgb,
);
render_task_id = render_tasks.add(task);
}
render_task_id
}
pub fn new_svg_filter_primitive(
tasks: Vec<RenderTaskId>,
target_size: DeviceIntSize,
uv_rect_kind: UvRectKind,
info: SvgFilterInfo,
) -> Self {
RenderTask::with_dynamic_location(
target_size,
tasks,
RenderTaskKind::SvgFilter(SvgFilterTask {
extra_gpu_cache_handle: None,
uv_rect_handle: GpuCacheHandle::new(),
uv_rect_kind,
info,
}),
ClearMode::Transparent,
)
}
#[cfg(feature = "pathfinder")]
pub fn new_glyph(
location: RenderTaskLocation,
@ -1216,6 +1525,10 @@ impl RenderTask {
task.uv_rect_kind
}
RenderTaskKind::SvgFilter(ref task) => {
task.uv_rect_kind
}
RenderTaskKind::ClipRegion(..) |
RenderTaskKind::Glyph(_) |
RenderTaskKind::Border(..) |
@ -1287,6 +1600,15 @@ impl RenderTask {
[0.0; 3]
}
RenderTaskKind::SvgFilter(ref task) => {
match task.info {
SvgFilterInfo::Opacity(opacity) => [opacity, 0.0, 0.0],
SvgFilterInfo::Offset(offset) => [offset.x, offset.y, 0.0],
_ => [0.0; 3]
}
}
#[cfg(test)]
RenderTaskKind::Test(..) => {
unreachable!();
@ -1324,6 +1646,9 @@ impl RenderTask {
RenderTaskKind::HorizontalBlur(ref info) => {
gpu_cache.get_address(&info.uv_rect_handle)
}
RenderTaskKind::SvgFilter(ref info) => {
gpu_cache.get_address(&info.uv_rect_handle)
}
RenderTaskKind::ClipRegion(..) |
RenderTaskKind::Readback(..) |
RenderTaskKind::Scaling(..) |
@ -1393,9 +1718,16 @@ impl RenderTask {
pub fn target_kind(&self) -> RenderTargetKind {
match self.kind {
RenderTaskKind::Readback(..) => RenderTargetKind::Color,
RenderTaskKind::LineDecoration(..) => RenderTargetKind::Color,
RenderTaskKind::LineDecoration(..) |
RenderTaskKind::Readback(..) |
RenderTaskKind::Glyph(..) |
RenderTaskKind::Border(..) |
RenderTaskKind::Gradient(..) |
RenderTaskKind::Picture(..) |
RenderTaskKind::Blit(..) |
RenderTaskKind::SvgFilter(..) => {
RenderTargetKind::Color
}
RenderTaskKind::ClipRegion(..) |
RenderTaskKind::CacheMask(..) => {
@ -1407,24 +1739,10 @@ impl RenderTask {
task_info.target_kind
}
RenderTaskKind::Glyph(..) => {
RenderTargetKind::Color
}
RenderTaskKind::Scaling(ref task_info) => {
task_info.target_kind
}
RenderTaskKind::Border(..) |
RenderTaskKind::Gradient(..) |
RenderTaskKind::Picture(..) => {
RenderTargetKind::Color
}
RenderTaskKind::Blit(..) => {
RenderTargetKind::Color
}
#[cfg(test)]
RenderTaskKind::Test(kind) => kind,
}
@ -1444,6 +1762,9 @@ impl RenderTask {
RenderTaskKind::Picture(ref mut info) => {
(&mut info.uv_rect_handle, info.uv_rect_kind)
}
RenderTaskKind::SvgFilter(ref mut info) => {
(&mut info.uv_rect_handle, info.uv_rect_kind)
}
RenderTaskKind::Readback(..) |
RenderTaskKind::Scaling(..) |
RenderTaskKind::Blit(..) |
@ -1473,6 +1794,33 @@ impl RenderTask {
};
image_source.write_gpu_blocks(&mut request);
}
if let RenderTaskKind::SvgFilter(ref mut filter_task) = self.kind {
match filter_task.info {
SvgFilterInfo::ColorMatrix(ref matrix) => {
let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
if let Some(mut request) = gpu_cache.request(handle) {
for i in 0..5 {
request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
}
}
}
SvgFilterInfo::DropShadow(color) |
SvgFilterInfo::Flood(color) => {
let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
if let Some(mut request) = gpu_cache.request(handle) {
request.push(color.to_array());
}
}
SvgFilterInfo::ComponentTransfer(ref data) => {
let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
if let Some(request) = gpu_cache.request(handle) {
data.update(request);
}
}
_ => {},
}
}
}
#[cfg(feature = "debugger")]
@ -1520,6 +1868,10 @@ impl RenderTask {
RenderTaskKind::Gradient(..) => {
pt.new_level("Gradient".to_owned());
}
RenderTaskKind::SvgFilter(ref task) => {
pt.new_level("SvgFilter".to_owned());
pt.add_item(format!("primitive: {:?}", task.info));
}
#[cfg(test)]
RenderTaskKind::Test(..) => {
pt.new_level("Test".to_owned());
@ -1527,6 +1879,7 @@ impl RenderTask {
}
pt.add_item(format!("clear to: {:?}", self.clear_mode));
pt.add_item(format!("dimensions: {:?}", self.location.size()));
for &child_id in &self.children {
if tree[child_id].print_with(pt, tree) {

View File

@ -63,7 +63,7 @@ use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
#[cfg(feature = "pathfinder")]
use crate::gpu_glyph_renderer::GpuGlyphRenderer;
use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, TransformData, ResolveInstanceData};
use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData, ResolveInstanceData};
use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg};
use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
@ -220,6 +220,10 @@ const GPU_SAMPLER_TAG_TRANSPARENT: GpuProfileTag = GpuProfileTag {
label: "Transparent Pass",
color: debug_colors::BLACK,
};
const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag {
label: "SvgFilter",
color: debug_colors::LEMONCHIFFON,
};
/// The clear color used for the texture cache when the debug display is enabled.
/// We use a shade of blue so that we can still identify completely blue items in
@ -662,6 +666,53 @@ pub(crate) mod desc {
],
};
pub const SVG_FILTER: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[
VertexAttribute {
name: "aPosition",
count: 2,
kind: VertexAttributeKind::F32,
},
],
instance_attributes: &[
VertexAttribute {
name: "aFilterRenderTaskAddress",
count: 1,
kind: VertexAttributeKind::U16,
},
VertexAttribute {
name: "aFilterInput1TaskAddress",
count: 1,
kind: VertexAttributeKind::U16,
},
VertexAttribute {
name: "aFilterInput2TaskAddress",
count: 1,
kind: VertexAttributeKind::U16,
},
VertexAttribute {
name: "aFilterKind",
count: 1,
kind: VertexAttributeKind::U16,
},
VertexAttribute {
name: "aFilterInputCount",
count: 1,
kind: VertexAttributeKind::U16,
},
VertexAttribute {
name: "aFilterGenericInt",
count: 1,
kind: VertexAttributeKind::U16,
},
VertexAttribute {
name: "aFilterExtraDataAddress",
count: 2,
kind: VertexAttributeKind::U16,
},
],
};
pub const VECTOR_STENCIL: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[
VertexAttribute {
@ -759,6 +810,7 @@ pub(crate) enum VertexArrayKind {
LineDecoration,
Gradient,
Resolve,
SvgFilter,
}
#[derive(Clone, Debug, PartialEq)]
@ -1052,8 +1104,25 @@ impl TextureResolver {
device.bind_texture(sampler, texture);
}
TextureSource::RenderTaskCache(saved_index) => {
let texture = &self.saved_targets[saved_index.0];
device.bind_texture(sampler, texture)
if saved_index.0 < self.saved_targets.len() {
let texture = &self.saved_targets[saved_index.0];
device.bind_texture(sampler, texture)
} else {
// Check if this saved index is referring to a the prev pass
if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
let texture = match self.prev_pass_color {
Some(ref at) => &at.texture,
None => &self.dummy_cache_texture,
};
device.bind_texture(sampler, texture);
} else if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
let texture = match self.prev_pass_alpha {
Some(ref at) => &at.texture,
None => &self.dummy_cache_texture,
};
device.bind_texture(sampler, texture);
}
}
}
}
}
@ -1592,6 +1661,7 @@ pub struct RendererVAOs {
scale_vao: VAO,
gradient_vao: VAO,
resolve_vao: VAO,
svg_filter_vao: VAO,
}
@ -1939,6 +2009,7 @@ impl Renderer {
let line_vao = device.create_vao_with_new_instances(&desc::LINE, &prim_vao);
let gradient_vao = device.create_vao_with_new_instances(&desc::GRADIENT, &prim_vao);
let resolve_vao = device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao);
let svg_filter_vao = device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao);
let texture_cache_upload_pbo = device.create_pbo();
let texture_resolver = TextureResolver::new(&mut device);
@ -2169,6 +2240,7 @@ impl Renderer {
gradient_vao,
resolve_vao,
line_vao,
svg_filter_vao,
},
transforms_texture,
prim_header_i_texture,
@ -2511,6 +2583,11 @@ impl Renderer {
"Horizontal Blur",
target.horizontal_blurs.len(),
);
debug_target.add(
debug_server::BatchKind::Cache,
"SVG Filters",
target.svg_filters.iter().map(|(_, batch)| batch.len()).sum(),
);
for alpha_batch_container in &target.alpha_batch_containers {
for batch in alpha_batch_container.opaque_batches.iter().rev() {
@ -3375,6 +3452,33 @@ impl Renderer {
);
}
fn handle_svg_filters(
&mut self,
textures: &BatchTextures,
svg_filters: &[SvgFilterInstance],
projection: &Transform3D<f32>,
stats: &mut RendererStats,
) {
if svg_filters.is_empty() {
return;
}
let _timer = self.gpu_profile.start_timer(GPU_TAG_SVG_FILTER);
self.shaders.borrow_mut().cs_svg_filter.bind(
&mut self.device,
&projection,
&mut self.renderer_errors
);
self.draw_instanced_batch(
&svg_filters,
VertexArrayKind::SvgFilter,
textures,
stats,
);
}
fn draw_picture_cache_target(
&mut self,
target: &PictureCacheTarget,
@ -3743,6 +3847,15 @@ impl Renderer {
stats,
);
for (ref textures, ref filters) in &target.svg_filters {
self.handle_svg_filters(
textures,
filters,
projection,
stats,
);
}
for alpha_batch_container in &target.alpha_batch_containers {
self.draw_alpha_batch_container(
alpha_batch_container,
@ -5114,6 +5227,7 @@ impl Renderer {
self.device.delete_vao(self.vaos.line_vao);
self.device.delete_vao(self.vaos.border_vao);
self.device.delete_vao(self.vaos.scale_vao);
self.device.delete_vao(self.vaos.svg_filter_vao);
self.debug.deinit(&mut self.device);
@ -5937,6 +6051,7 @@ fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
VertexArrayKind::LineDecoration => &vaos.line_vao,
VertexArrayKind::Gradient => &vaos.gradient_vao,
VertexArrayKind::Resolve => &vaos.resolve_vao,
VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
}
}
@ -5955,6 +6070,7 @@ fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
VertexArrayKind::LineDecoration => &vaos.line_vao,
VertexArrayKind::Gradient => &vaos.gradient_vao,
VertexArrayKind::Resolve => &vaos.resolve_vao,
VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
}
}
#[derive(Clone, Copy, PartialEq)]

View File

@ -596,6 +596,7 @@ impl ResourceCache {
&img.dirty_rect
),
);
self.discard_tiles_outside_visible_area(img.key, &img.visible_rect);
}
ResourceUpdate::DeleteImage(img) => {
self.delete_image_template(img);
@ -630,6 +631,7 @@ impl ResourceCache {
&img.descriptor,
img.tiling,
Arc::clone(&img.data),
&img.visible_rect,
);
}
ResourceUpdate::UpdateBlobImage(ref img) => {
@ -638,6 +640,7 @@ impl ResourceCache {
&img.descriptor,
&img.dirty_rect,
Arc::clone(&img.data),
&img.visible_rect,
);
}
ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => {
@ -920,11 +923,14 @@ impl ResourceCache {
descriptor: &ImageDescriptor,
mut tiling: Option<TileSize>,
data: Arc<BlobImageData>,
visible_rect: &DeviceIntRect,
) {
let max_texture_size = self.max_texture_size();
tiling = get_blob_tiling(tiling, descriptor, max_texture_size);
self.blob_image_handler.as_mut().unwrap().add(key, data, tiling);
let viewport_tiles = tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
self.blob_image_handler.as_mut().unwrap().add(key, data, visible_rect, tiling);
self.blob_image_templates.insert(
key,
@ -932,7 +938,7 @@ impl ResourceCache {
descriptor: *descriptor,
tiling,
dirty_rect: DirtyRect::All,
viewport_tiles: None,
viewport_tiles,
},
);
}
@ -944,8 +950,9 @@ impl ResourceCache {
descriptor: &ImageDescriptor,
dirty_rect: &BlobDirtyRect,
data: Arc<BlobImageData>,
visible_rect: &DeviceIntRect,
) {
self.blob_image_handler.as_mut().unwrap().update(key, data, dirty_rect);
self.blob_image_handler.as_mut().unwrap().update(key, data, visible_rect, dirty_rect);
let max_texture_size = self.max_texture_size();
@ -955,11 +962,13 @@ impl ResourceCache {
let tiling = get_blob_tiling(image.tiling, descriptor, max_texture_size);
let viewport_tiles = image.tiling.map(|tile_size| compute_tile_range(&visible_rect, tile_size));
*image = BlobImageTemplate {
descriptor: *descriptor,
tiling,
dirty_rect: dirty_rect.union(&image.dirty_rect),
viewport_tiles: image.viewport_tiles,
viewport_tiles,
};
}

View File

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch};
use api::{FilterOp, TempFilterData, FilterData, ComponentTransferFuncType};
use api::{FilterOp, TempFilterData, FilterData, FilterPrimitive, ComponentTransferFuncType};
use api::{PipelineId, PropertyBinding, PropertyBindingId, ItemRange, MixBlendMode, StackingContext};
use api::units::{LayoutSize, LayoutTransform};
use crate::internal_types::{FastHashMap, Filter};
@ -206,6 +206,10 @@ pub trait StackingContextHelpers {
&self,
input_filter_datas: &[TempFilterData],
) -> Vec<FilterData>;
fn filter_primitives_for_compositing(
&self,
input_filter_primitives: ItemRange<FilterPrimitive>,
) -> Vec<FilterPrimitive>;
}
impl StackingContextHelpers for StackingContext {
@ -254,4 +258,15 @@ impl StackingContextHelpers for StackingContext {
}
filter_datas
}
fn filter_primitives_for_compositing(
&self,
input_filter_primitives: ItemRange<FilterPrimitive>,
) -> Vec<FilterPrimitive> {
// Resolve these in the flattener?
// TODO(gw): Now that we resolve these later on,
// we could probably make it a bit
// more efficient than cloning these here.
input_filter_primitives.iter().map(|primitive| primitive.into()).collect()
}
}

View File

@ -192,6 +192,7 @@ impl LazilyCompiledShader {
VertexArrayKind::Border => &desc::BORDER,
VertexArrayKind::Scale => &desc::SCALE,
VertexArrayKind::Resolve => &desc::RESOLVE,
VertexArrayKind::SvgFilter => &desc::SVG_FILTER,
};
device.link_program(program, vertex_descriptor)?;
@ -511,6 +512,7 @@ pub struct Shaders {
pub cs_scale: LazilyCompiledShader,
pub cs_line_decoration: LazilyCompiledShader,
pub cs_gradient: LazilyCompiledShader,
pub cs_svg_filter: LazilyCompiledShader,
// Brush shaders
brush_solid: BrushShader,
@ -633,6 +635,14 @@ impl Shaders {
options.precache_flags,
)?;
let cs_svg_filter = LazilyCompiledShader::new(
ShaderKind::Cache(VertexArrayKind::SvgFilter),
"cs_svg_filter",
&[],
device,
options.precache_flags,
)?;
let cs_clip_rectangle_slow = LazilyCompiledShader::new(
ShaderKind::ClipCache,
"cs_clip_rectangle",
@ -846,6 +856,7 @@ impl Shaders {
cs_gradient,
cs_border_solid,
cs_scale,
cs_svg_filter,
brush_solid,
brush_image,
brush_fast_image,
@ -930,6 +941,7 @@ impl Shaders {
self.cs_scale.deinit(device);
self.cs_blur_a8.deinit(device);
self.cs_blur_rgba8.deinit(device);
self.cs_svg_filter.deinit(device);
self.brush_solid.deinit(device);
self.brush_blend.deinit(device);
self.brush_mix_blend.deinit(device);

View File

@ -2,12 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ColorF, BorderStyle, MixBlendMode, PipelineId, PremultipliedColorF};
use api::{ColorF, BorderStyle, FilterPrimitive, MixBlendMode, PipelineId, PremultipliedColorF};
use api::{DocumentLayer, FilterData, ImageFormat, LineOrientation};
use api::units::*;
#[cfg(feature = "pathfinder")]
use api::FontRenderMode;
use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image, BatchBuilder};
use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, ClipBatcher, resolve_image, BatchBuilder};
use crate::clip::ClipStore;
use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX};
use crate::debug_render::DebugItem;
@ -15,8 +15,8 @@ use crate::device::{Texture};
#[cfg(feature = "pathfinder")]
use euclid::{TypedPoint2D, TypedVector2D};
use crate::frame_builder::FrameGlobalResources;
use crate::gpu_cache::{GpuCache};
use crate::gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
use crate::gpu_cache::{GpuCache, GpuCacheAddress};
use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
use crate::gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource, Filter};
#[cfg(feature = "pathfinder")]
@ -26,7 +26,7 @@ use crate::prim_store::gradient::GRADIENT_FP_STOPS;
use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer, PrimitiveVisibilityMask};
use crate::profiler::FrameProfileCounters;
use crate::render_backend::{DataStores, FrameId};
use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind, SvgFilterTask, SvgFilterInfo};
use crate::render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskGraph, ScalingTask};
use crate::resource_cache::ResourceCache;
use std::{cmp, usize, f32, i32, mem};
@ -361,6 +361,7 @@ pub struct ColorRenderTarget {
pub horizontal_blurs: Vec<BlurInstance>,
pub readbacks: Vec<DeviceIntRect>,
pub scalings: Vec<ScalingInstance>,
pub svg_filters: Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
pub blits: Vec<BlitJob>,
// List of frame buffer outputs for this render target.
pub outputs: Vec<FrameOutput>,
@ -383,6 +384,7 @@ impl RenderTarget for ColorRenderTarget {
horizontal_blurs: Vec::new(),
readbacks: Vec::new(),
scalings: Vec::new(),
svg_filters: Vec::new(),
blits: Vec::new(),
outputs: Vec::new(),
alpha_tasks: Vec::new(),
@ -533,6 +535,17 @@ impl RenderTarget for ColorRenderTarget {
});
}
}
RenderTaskKind::SvgFilter(ref task_info) => {
task_info.add_instances(
&mut self.svg_filters,
render_tasks,
&task_info.info,
task_id,
task.children.get(0).cloned(),
task.children.get(1).cloned(),
task_info.extra_gpu_cache_handle.map(|handle| gpu_cache.get_address(&handle)),
)
}
RenderTaskKind::ClipRegion(..) |
RenderTaskKind::Border(..) |
RenderTaskKind::CacheMask(..) |
@ -686,7 +699,8 @@ impl RenderTarget for AlphaRenderTarget {
RenderTaskKind::Border(..) |
RenderTaskKind::LineDecoration(..) |
RenderTaskKind::Gradient(..) |
RenderTaskKind::Glyph(..) => {
RenderTaskKind::Glyph(..) |
RenderTaskKind::SvgFilter(..) => {
panic!("BUG: should not be added to alpha target!");
}
RenderTaskKind::VerticalBlur(ref info) => {
@ -896,7 +910,8 @@ impl TextureCacheRenderTarget {
RenderTaskKind::ClipRegion(..) |
RenderTaskKind::CacheMask(..) |
RenderTaskKind::Readback(..) |
RenderTaskKind::Scaling(..) => {
RenderTaskKind::Scaling(..) |
RenderTaskKind::SvgFilter(..) => {
panic!("BUG: unexpected task kind for texture cache target");
}
#[cfg(test)]
@ -1332,18 +1347,23 @@ pub struct CompositeOps {
// Requires only a single texture as input (e.g. most filters)
pub filters: Vec<Filter>,
pub filter_datas: Vec<FilterData>,
pub filter_primitives: Vec<FilterPrimitive>,
// Requires two source textures (e.g. mix-blend-mode)
pub mix_blend_mode: Option<MixBlendMode>,
}
impl CompositeOps {
pub fn new(filters: Vec<Filter>,
filter_datas: Vec<FilterData>,
mix_blend_mode: Option<MixBlendMode>) -> Self {
pub fn new(
filters: Vec<Filter>,
filter_datas: Vec<FilterData>,
filter_primitives: Vec<FilterPrimitive>,
mix_blend_mode: Option<MixBlendMode>
) -> Self {
CompositeOps {
filters,
filter_datas,
filter_primitives,
mix_blend_mode,
}
}
@ -1439,3 +1459,100 @@ impl ScalingTask {
instances.push(instance);
}
}
impl SvgFilterTask {
fn add_instances(
&self,
instances: &mut Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
render_tasks: &RenderTaskGraph,
filter: &SvgFilterInfo,
task_id: RenderTaskId,
input_1_task: Option<RenderTaskId>,
input_2_task: Option<RenderTaskId>,
extra_data_address: Option<GpuCacheAddress>,
) {
let mut textures = BatchTextures::no_texture();
if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) {
textures.colors[0] = match saved_index {
Some(saved_index) => TextureSource::RenderTaskCache(*saved_index),
None => TextureSource::PrevPassColor,
};
}
if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) {
textures.colors[1] = match saved_index {
Some(saved_index) => TextureSource::RenderTaskCache(*saved_index),
None => TextureSource::PrevPassColor,
};
}
let kind = match filter {
SvgFilterInfo::Blend(..) => 0,
SvgFilterInfo::Flood(..) => 1,
SvgFilterInfo::LinearToSrgb => 2,
SvgFilterInfo::SrgbToLinear => 3,
SvgFilterInfo::Opacity(..) => 4,
SvgFilterInfo::ColorMatrix(..) => 5,
SvgFilterInfo::DropShadow(..) => 6,
SvgFilterInfo::Offset(..) => 7,
SvgFilterInfo::ComponentTransfer(..) => 8,
SvgFilterInfo::Identity => 9,
};
let input_count = match filter {
SvgFilterInfo::Flood(..) => 0,
SvgFilterInfo::LinearToSrgb |
SvgFilterInfo::SrgbToLinear |
SvgFilterInfo::Opacity(..) |
SvgFilterInfo::ColorMatrix(..) |
SvgFilterInfo::Offset(..) |
SvgFilterInfo::ComponentTransfer(..) |
SvgFilterInfo::Identity => 1,
// Not techincally a 2 input filter, but we have 2 inputs here: original content & blurred content.
SvgFilterInfo::DropShadow(..) |
SvgFilterInfo::Blend(..) => 2,
};
let generic_int = match filter {
SvgFilterInfo::Blend(mode) => *mode as u16,
SvgFilterInfo::ComponentTransfer(data) =>
((data.r_func.to_int() << 12 |
data.g_func.to_int() << 8 |
data.b_func.to_int() << 4 |
data.a_func.to_int()) as u16),
SvgFilterInfo::LinearToSrgb |
SvgFilterInfo::SrgbToLinear |
SvgFilterInfo::Flood(..) |
SvgFilterInfo::Opacity(..) |
SvgFilterInfo::ColorMatrix(..) |
SvgFilterInfo::DropShadow(..) |
SvgFilterInfo::Offset(..) |
SvgFilterInfo::Identity => 0,
};
let instance = SvgFilterInstance {
task_address: render_tasks.get_task_address(task_id),
input_1_task_address: input_1_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)),
input_2_task_address: input_2_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)),
kind,
input_count,
generic_int,
extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID),
};
for (ref mut batch_textures, ref mut batch) in instances.iter_mut() {
if let Some(combined_textures) = batch_textures.combine_textures(textures) {
batch.push(instance);
// Update the batch textures to the newly combined batch textures
*batch_textures = combined_textures;
return;
}
}
instances.push((textures, vec![instance]));
}
}

View File

@ -64,6 +64,10 @@ const SHADERS: &[Shader] = &[
name: "cs_border_solid",
features: CACHE_FEATURES,
},
Shader {
name: "cs_svg_filter",
features: CACHE_FEATURES,
},
// Prim shaders
Shader {
name: "ps_split_composite",

View File

@ -347,6 +347,7 @@ impl Transaction {
key: BlobImageKey,
descriptor: ImageDescriptor,
data: Arc<BlobImageData>,
visible_rect: DeviceIntRect,
tiling: Option<TileSize>,
) {
self.resource_updates.push(
@ -354,6 +355,7 @@ impl Transaction {
key,
descriptor,
data,
visible_rect,
tiling,
})
);
@ -364,6 +366,7 @@ impl Transaction {
key: BlobImageKey,
descriptor: ImageDescriptor,
data: Arc<BlobImageData>,
visible_rect: DeviceIntRect,
dirty_rect: &BlobDirtyRect,
) {
self.resource_updates.push(
@ -371,6 +374,7 @@ impl Transaction {
key,
descriptor,
data,
visible_rect,
dirty_rect: *dirty_rect,
})
);
@ -523,6 +527,7 @@ pub struct AddBlobImage {
pub descriptor: ImageDescriptor,
//#[serde(with = "serde_image_data_raw")]
pub data: Arc<BlobImageData>,
pub visible_rect: DeviceIntRect,
pub tiling: Option<TileSize>,
}
@ -532,6 +537,7 @@ pub struct UpdateBlobImage {
pub descriptor: ImageDescriptor,
//#[serde(with = "serde_image_data_raw")]
pub data: Arc<BlobImageData>,
pub visible_rect: DeviceIntRect,
pub dirty_rect: BlobDirtyRect,
}

View File

@ -121,6 +121,7 @@ pub enum DisplayItem {
SetGradientStops,
SetFilterOps,
SetFilterData,
SetFilterPrimitives,
// These marker items terminate a scope introduced by a previous item.
PopReferenceFrame,
@ -159,6 +160,7 @@ pub enum DebugDisplayItem {
SetGradientStops(Vec<GradientStop>),
SetFilterOps(Vec<FilterOp>),
SetFilterData(FilterData),
SetFilterPrimitives(Vec<FilterPrimitive>),
PopReferenceFrame,
PopStackingContext,
@ -638,7 +640,7 @@ pub struct StackingContext {
pub raster_space: RasterSpace,
/// True if picture caching should be used on this stacking context.
pub cache_tiles: bool,
} // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>
} // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>, filter_primitives: Vec<FilterPrimitive>
#[repr(u8)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@ -677,7 +679,7 @@ impl RasterSpace {
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum MixBlendMode {
Normal = 0,
Multiply = 1,
@ -697,6 +699,172 @@ pub enum MixBlendMode {
Luminosity = 15,
}
/// An input to a SVG filter primitive.
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum ColorSpace {
Srgb,
LinearRgb,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FilterPrimitiveInput {
/// The input is the original graphic that the filter is being applied to.
Original,
/// The input is the output of the previous filter primitive in the filter primitive chain.
Previous,
/// The input is the output of the filter primitive at the given index in the filter primitive chain.
OutputOfPrimitiveIndex(usize),
}
impl FilterPrimitiveInput {
/// Gets the index of the input.
/// Returns `None` if the source graphic is the input.
pub fn to_index(self, cur_index: usize) -> Option<usize> {
match self {
FilterPrimitiveInput::Previous if cur_index > 0 => Some(cur_index - 1),
FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => Some(index),
_ => None,
}
}
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct BlendPrimitive {
pub input1: FilterPrimitiveInput,
pub input2: FilterPrimitiveInput,
pub mode: MixBlendMode,
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct FloodPrimitive {
pub color: ColorF,
}
impl FloodPrimitive {
pub fn sanitize(&mut self) {
self.color.r = self.color.r.min(1.0).max(0.0);
self.color.g = self.color.g.min(1.0).max(0.0);
self.color.b = self.color.b.min(1.0).max(0.0);
self.color.a = self.color.a.min(1.0).max(0.0);
}
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct BlurPrimitive {
pub input: FilterPrimitiveInput,
pub radius: f32,
}
impl BlurPrimitive {
pub fn sanitize(&mut self) {
self.radius = self.radius.min(MAX_BLUR_RADIUS);
}
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct OpacityPrimitive {
pub input: FilterPrimitiveInput,
pub opacity: f32,
}
impl OpacityPrimitive {
pub fn sanitize(&mut self) {
self.opacity = self.opacity.min(1.0).max(0.0);
}
}
/// cbindgen:derive-eq=false
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ColorMatrixPrimitive {
pub input: FilterPrimitiveInput,
pub matrix: [f32; 20],
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct DropShadowPrimitive {
pub input: FilterPrimitiveInput,
pub shadow: Shadow,
}
impl DropShadowPrimitive {
pub fn sanitize(&mut self) {
self.shadow.blur_radius = self.shadow.blur_radius.min(MAX_BLUR_RADIUS);
}
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ComponentTransferPrimitive {
pub input: FilterPrimitiveInput,
// Component transfer data is stored in FilterData.
}
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct IdentityPrimitive {
pub input: FilterPrimitiveInput,
}
/// See: https://github.com/eqrion/cbindgen/issues/9
/// cbindgen:derive-eq=false
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum FilterPrimitiveKind {
Identity(IdentityPrimitive),
Blend(BlendPrimitive),
Flood(FloodPrimitive),
Blur(BlurPrimitive),
// TODO: Support animated opacity?
Opacity(OpacityPrimitive),
/// cbindgen:derive-eq=false
ColorMatrix(ColorMatrixPrimitive),
DropShadow(DropShadowPrimitive),
ComponentTransfer(ComponentTransferPrimitive),
}
impl FilterPrimitiveKind {
pub fn sanitize(&mut self) {
match self {
FilterPrimitiveKind::Flood(flood) => flood.sanitize(),
FilterPrimitiveKind::Blur(blur) => blur.sanitize(),
FilterPrimitiveKind::Opacity(opacity) => opacity.sanitize(),
FilterPrimitiveKind::DropShadow(drop_shadow) => drop_shadow.sanitize(),
// No sanitization needed.
FilterPrimitiveKind::Identity(..) |
FilterPrimitiveKind::Blend(..) |
FilterPrimitiveKind::ColorMatrix(..) |
// Component transfer's filter data is sanitized separately.
FilterPrimitiveKind::ComponentTransfer(..) => {}
}
}
}
/// SVG Filter Primitive.
/// See: https://github.com/eqrion/cbindgen/issues/9
/// cbindgen:derive-eq=false
#[repr(C)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct FilterPrimitive {
pub kind: FilterPrimitiveKind,
pub color_space: ColorSpace,
}
impl FilterPrimitive {
pub fn sanitize(&mut self) {
self.kind.sanitize();
}
}
/// CSS filter.
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum FilterOp {
@ -1162,6 +1330,7 @@ impl DisplayItem {
DisplayItem::PushStackingContext(..) => "push_stacking_context",
DisplayItem::SetFilterOps => "set_filter_ops",
DisplayItem::SetFilterData => "set_filter_data",
DisplayItem::SetFilterPrimitives => "set_filter_primitives",
DisplayItem::RadialGradient(..) => "radial_gradient",
DisplayItem::Rectangle(..) => "rectangle",
DisplayItem::ScrollFrame(..) => "scroll_frame",

View File

@ -131,6 +131,7 @@ pub struct BuiltDisplayListIter<'a> {
cur_glyphs: ItemRange<'a, GlyphInstance>,
cur_filters: ItemRange<'a, di::FilterOp>,
cur_filter_data: Vec<TempFilterData<'a>>,
cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>,
cur_clip_chain_items: ItemRange<'a, di::ClipId>,
cur_complex_clip: ItemRange<'a, di::ComplexClipRegion>,
peeking: Peek,
@ -297,6 +298,7 @@ impl<'a> BuiltDisplayListIter<'a> {
cur_glyphs: ItemRange::default(),
cur_filters: ItemRange::default(),
cur_filter_data: Vec::new(),
cur_filter_primitives: ItemRange::default(),
cur_clip_chain_items: ItemRange::default(),
cur_complex_clip: ItemRange::default(),
peeking: Peek::NotPeeking,
@ -335,7 +337,8 @@ impl<'a> BuiltDisplayListIter<'a> {
match self.cur_item {
SetGradientStops |
SetFilterOps |
SetFilterData => {
SetFilterData |
SetFilterPrimitives => {
// These are marker items for populating other display items, don't yield them.
continue;
}
@ -391,6 +394,10 @@ impl<'a> BuiltDisplayListIter<'a> {
self.debug_stats.log_slice("set_filter_data.b_values", &data.b_values);
self.debug_stats.log_slice("set_filter_data.a_values", &data.a_values);
}
SetFilterPrimitives => {
self.cur_filter_primitives = skip_slice::<di::FilterPrimitive>(&mut self.data);
self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives);
}
ClipChain(_) => {
self.cur_clip_chain_items = skip_slice::<di::ClipId>(&mut self.data);
self.debug_stats.log_slice("clip_chain.clip_ids", &self.cur_clip_chain_items);
@ -502,6 +509,10 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
&self.iter.cur_filter_data
}
pub fn filter_primitives(&self) -> ItemRange<di::FilterPrimitive> {
self.iter.cur_filter_primitives
}
pub fn clip_chain_items(&self) -> ItemRange<di::ClipId> {
self.iter.cur_clip_chain_items
}
@ -602,6 +613,9 @@ impl Serialize for BuiltDisplayList {
a_values: temp_filter_data.a_values.iter().collect(),
})
},
Real::SetFilterPrimitives => Debug::SetFilterPrimitives(
item.iter.cur_filter_primitives.iter().collect()
),
Real::SetGradientStops => Debug::SetGradientStops(
item.iter.cur_stops.iter().collect()
),
@ -700,6 +714,10 @@ impl<'de> Deserialize<'de> for BuiltDisplayList {
DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values);
Real::SetFilterData
},
Debug::SetFilterPrimitives(filter_primitives) => {
DisplayListBuilder::push_iter_impl(&mut temp, filter_primitives);
Real::SetFilterPrimitives
}
Debug::SetGradientStops(stops) => {
DisplayListBuilder::push_iter_impl(&mut temp, stops);
Real::SetGradientStops
@ -1425,6 +1443,7 @@ impl DisplayListBuilder {
mix_blend_mode: di::MixBlendMode,
filters: &[di::FilterOp],
filter_datas: &[di::FilterData],
filter_primitives: &[di::FilterPrimitive],
raster_space: di::RasterSpace,
cache_tiles: bool,
) {
@ -1445,6 +1464,11 @@ impl DisplayListBuilder {
self.push_iter(&filter_data.a_values);
}
if !filter_primitives.is_empty() {
self.push_item(&di::DisplayItem::SetFilterPrimitives);
self.push_iter(filter_primitives);
}
let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem {
origin,
spatial_id,
@ -1468,7 +1492,7 @@ impl DisplayListBuilder {
spatial_id: di::SpatialId,
is_backface_visible: bool,
) {
self.push_simple_stacking_context_with_filters(origin, spatial_id, is_backface_visible, &[], &[]);
self.push_simple_stacking_context_with_filters(origin, spatial_id, is_backface_visible, &[], &[], &[]);
}
/// Helper for examples/ code.
@ -1479,6 +1503,7 @@ impl DisplayListBuilder {
is_backface_visible: bool,
filters: &[di::FilterOp],
filter_datas: &[di::FilterData],
filter_primitives: &[di::FilterPrimitive],
) {
self.push_stacking_context(
origin,
@ -1489,6 +1514,7 @@ impl DisplayListBuilder {
di::MixBlendMode::Normal,
filters,
filter_datas,
filter_primitives,
di::RasterSpace::Screen,
/* cache_tiles = */ false,
);

View File

@ -309,10 +309,12 @@ pub trait BlobImageHandler: Send {
);
/// Register a blob image.
fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, tiling: Option<TileSize>);
fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect,
tiling: Option<TileSize>);
/// Update an already registered blob image.
fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, dirty_rect: &BlobDirtyRect);
fn update(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect,
dirty_rect: &BlobDirtyRect);
/// Delete an already registered blob image.
fn delete(&mut self, key: BlobImageKey);

View File

@ -44,4 +44,14 @@ skip_on(android) == filter-mix-blend-mode.yaml filter-mix-blend-mode-ref.yaml #
!= filter-drop-shadow-huge.yaml blank.yaml
== filter-blur-scaled.yaml filter-blur-scaled-ref.yaml
skip_on(android) == filter-blur-scaled-xonly.yaml filter-blur-scaled-xonly.png # fails on Android emulator and Pixel2
== filter-flood.yaml filter-flood-ref.yaml
skip_on(android,emulator) == svg-filter-component-transfer.yaml filter-component-transfer-ref.yaml # fails on Android emulator
== svg-filter-flood.yaml svg-filter-flood-ref.yaml
skip_on(android) == svg-filter-blend.yaml svg-filter-blend-ref.yaml
skip_on(android,device) == svg-filter-color-matrix.yaml filter-color-matrix-ref.yaml # fails on Pixel2
platform(linux,mac) == draw_calls(8) color_targets(8) alpha_targets(0) svg-filter-blur.yaml filter-blur.png # Extra draw call is due to render task graph workaround
platform(linux,mac) == svg-filter-drop-shadow.yaml svg-filter-drop-shadow.png
== fuzzy(1,10000) svg-srgb-to-linear.yaml srgb-to-linear-ref.yaml
platform(linux,mac) == fuzzy(4,28250) svg-filter-drop-shadow-rotate.yaml svg-filter-drop-shadow-rotate-ref.yaml
platform(linux,mac) == svg-filter-blur-transforms.yaml svg-filter-blur-transforms.png
platform(linux,mac) == svg-filter-drop-shadow-on-viewport-edge.yaml svg-filter-drop-shadow-on-viewport-edge.png
platform(linux,mac) == svg-filter-drop-shadow-perspective.yaml svg-filter-drop-shadow-perspective.png

View File

@ -0,0 +1,21 @@
---
root:
items:
- type: stacking-context
bounds: [0, 0, 50, 250]
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [0, 255, 0, 1]
- type: rect
bounds: [0, 50, 50, 50]
color: [40, 20, 2, 1]
- type: rect
bounds: [0, 100, 50, 50]
color: [20, 10, 155, 1]
- type: rect
bounds: [0, 150, 50, 50]
color: [255, 222, 156, 1]
- type: rect
bounds: [0, 200, 50, 50]
color: [255, 245, 151, 1]

View File

@ -0,0 +1,83 @@
# Tests various blend modes using the blend filter primitive.
---
root:
items:
- type: stacking-context
bounds: [0, 0, 50, 250]
filter-primitives:
- type: flood
color: [255, 255, 255, 1]
color-space: srgb
- type: blend
in1: original
in2: 0
color-space: srgb
blend-mode: difference
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [255, 0, 255, 1]
- type: stacking-context
bounds: [0, 0, 50, 250]
filter-primitives:
- type: flood
color: [51, 51, 51, 1]
color-space: srgb
- type: blend
in1: original
in2: 0
color-space: srgb
blend-mode: multiply
items:
- type: rect
bounds: [0, 50, 50, 50]
color: [200, 100, 10, 1]
- type: stacking-context
bounds: [0, 0, 50, 250]
filter-primitives:
- type: flood
color: [255, 10, 156, 1]
color-space: srgb
- type: blend
in1: original
in2: 0
color-space: srgb
blend-mode: darken
items:
- type: rect
bounds: [0, 100, 50, 50]
color: [20, 222, 155, 1]
- type: stacking-context
bounds: [0, 0, 50, 250]
filter-primitives:
- type: flood
color: [255, 10, 156, 1]
color-space: srgb
- type: blend
in1: original
in2: 0
color-space: srgb
blend-mode: lighten
items:
- type: rect
bounds: [0, 150, 50, 50]
color: [20, 222, 155, 1]
- type: stacking-context
bounds: [0, 0, 50, 250]
filter-primitives:
- type: flood
color: [255, 10, 156, 1]
color-space: srgb
- type: blend
in1: original
in2: 0
color-space: srgb
blend-mode: exclusion
items:
- type: rect
bounds: [0, 200, 50, 50]
color: [0, 255, 24, 1]

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -0,0 +1,15 @@
---
root:
items:
- type: stacking-context
bounds: [0, 100, 300, 300]
transform: scale-x(0.1) rotate-z(45)
filter-primitives:
- type: blur
radius: 10
in: previous
color-space: srgb
items:
- type: rect
color: red
bounds: 0 0 256 256

View File

@ -0,0 +1,13 @@
---
root:
items:
- type: stacking-context
bounds: [100, 100, 300, 300]
filter-primitives:
- type: blur
radius: 10
in: previous
color-space: srgb
items:
- image: "firefox.png"
bounds: 20 20 256 256

View File

@ -0,0 +1,54 @@
---
root:
items:
- type: stacking-context
bounds: [0, 0, 120, 120]
items:
- type: rect
bounds: [0, 0, 120, 120]
color: [0, 0, 0, 1]
- type: stacking-context
bounds: [10, 10, 50, 50]
filter-primitives:
- type: color-matrix
in: previous
color-space: srgb
matrix: [0.393, 0.686, 0.534, 0,
0.189, 0.168, 0.131, 0,
0.349, 0.272, 0, 0,
0, 0, 0, 1,
0, 0, 0, 0]
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [255, 0, 0, 1]
- type: stacking-context
bounds: [10, 60, 50, 50]
filter-primitives:
- type: color-matrix
in: previous
color-space: srgb
matrix: [-1, 0, 0, 0,
0, -1, 0, 0,
0, 0, -1, 0,
0, 0, 0, 1,
1, 1, 1, 0]
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [0, 255, 0, 1]
- type: stacking-context
bounds: [60, 10, 50, 50]
filter-primitives:
- type: color-matrix
in: previous
color-space: srgb
matrix: [0, 0, 1, 0,
0, 1, 0, 0,
1, 0, 0, 0,
0, 0, 0, 1,
0, 0, 0, 0]
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [0, 0, 255, 1]

View File

@ -0,0 +1,128 @@
---
root:
items:
- type: stacking-context
bounds: [0, 0, 50, 250]
items:
- type: stacking-context
bounds: [0, 0, 50, 50]
filter-primitives:
- type: component-transfer
color-space: srgb
in: previous
filter-datas:
- - - Identity
- Identity
- Identity
- Identity
- []
- []
- []
- []
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [255, 0, 255, 1]
- type: stacking-context
bounds: [0, 50, 50, 50]
filter-primitives:
- type: component-transfer
color-space: srgb
in: previous
filter-datas:
- - - Table
- Table
- Table
- Identity
- - "1"
- "1"
- "0"
- "0"
- - "0"
- "0"
- "1"
- "1"
- - "0"
- "1"
- "1"
- "0"
- []
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [173, 255, 47, 1]
- type: stacking-context
bounds: [0, 100, 50, 50]
filter-primitives:
- type: component-transfer
color-space: srgb
in: previous
filter-datas:
- - - Discrete
- Discrete
- Discrete
- Identity
- - "1"
- "1"
- "0"
- "0"
- - "0"
- "0"
- "1"
- "1"
- - "0"
- "1"
- "1"
- "0"
- []
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [0, 255, 255, 1]
- type: stacking-context
bounds: [0, 150, 50, 50]
filter-primitives:
- type: component-transfer
color-space: srgb
in: previous
filter-datas:
- - - Linear
- Linear
- Linear
- Identity
- - "0.5"
- "0.25"
- - "0.5"
- "0"
- - "0.5"
- "0.5"
- []
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [255, 255, 0, 1]
- type: stacking-context
bounds: [0, 200, 50, 50]
filter-primitives:
- type: component-transfer
color-space: srgb
in: previous
filter-datas:
- - - Gamma
- Gamma
- Gamma
- Identity
- - "2"
- "5"
- "-1"
- - "2"
- "3"
- "0"
- - "2"
- "1"
- "-1.75"
- []
items:
- type: rect
bounds: [0, 0, 50, 50]
color: [135, 206, 235, 1]

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,16 @@
---
root:
items:
- type: stacking-context
bounds: [10, 10, 400, 400]
filter-primitives:
- type: drop-shadow
offset: [10, 10]
radius: 20
color: [255, 0, 0, 1]
in: previous
color-space: srgb
items:
- type: rect
bounds: 0 0 256 256
color: green

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,22 @@
# Tests SVG drop shadows with perspective transforms
---
root:
items:
- type: stacking-context
perspective: 100
perspective-origin: 100 50
items:
- type: "stacking-context"
transform-origin: 0 250
transform: rotate-x(15)
filter-primitives:
- type: drop-shadow
color: red
offset: [20, 20]
radius: 10
in: previous
color-space: srgb
items:
- type: rect
color: blue
bounds: 0 0 200 200

View File

@ -0,0 +1,11 @@
# Tests SVG drop shadows with transforms
---
root:
items:
- type: stacking-context
bounds: [100, 100, 400, 400]
filters: drop-shadow([73, 73], 20, [255, 0, 0, 1])
transform: rotate-z(45)
items:
- image: "firefox.png"
bounds: 0 0 256 256

View File

@ -0,0 +1,17 @@
# Tests SVG drop shadows with transforms
---
root:
items:
- type: stacking-context
bounds: [100, 100, 400, 400]
transform: rotate-z(45)
filter-primitives:
- type: drop-shadow
in: previous
offset: [73, 73]
radius: 20
color: [255, 0, 0, 1]
color-space: srgb
items:
- image: "firefox.png"
bounds: 0 0 256 256

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -0,0 +1,16 @@
# Tests that SVG drop shadows are working properly
---
root:
items:
- type: stacking-context
bounds: [100, 100, 400, 400]
filter-primitives:
- type: drop-shadow
in: previous
offset: [73, 73]
radius: 20
color: [255, 0, 0, 1]
color-space: srgb
items:
- image: "firefox.png"
bounds: 0 0 256 256

View File

@ -0,0 +1,20 @@
# this test ensures that a sRGB -> linear-RGB -> sRGB results in no change (with exception to rounding error)
---
root:
items:
- type: stacking-context
bounds: [0, 0, 300, 100]
filter-primitives:
- type: identity
in: previous
color-space: linear-rgb
items:
- type: rect
bounds: [100, 0, 100, 100]
color: [200, 200, 200, 1.0]
- type: rect
bounds: [100, 0, 100, 100]
color: [100, 100, 100, 1.0]
- type: rect
bounds: [200, 0, 100, 100]
color: [50, 50, 50, 1.0]

View File

@ -10,7 +10,7 @@ platform(linux,mac) == rounded-corners.yaml rounded-corners.png
platform(linux,mac) fuzzy(1,17500) color_targets(3) alpha_targets(1) == mask-atomicity.yaml mask-atomicity-ref.yaml
platform(linux,mac) fuzzy(1,17500) == mask-atomicity-tiling.yaml mask-atomicity-ref.yaml
platform(linux,mac) == mask-perspective.yaml mask-perspective.png
skip_on(android,emulator) == fuzzy(1,6) mask-perspective-tiling.yaml mask-perspective.yaml # Android emulator: GL error 502 at tex_sub_image_3d_pbo, fails on opt
skip_on(android,emulator) == fuzzy(1,7) mask-perspective-tiling.yaml mask-perspective.yaml # Android emulator: GL error 502 at tex_sub_image_3d_pbo, fails on opt
platform(linux,mac) == checkerboard.yaml checkerboard.png
skip_on(android) == checkerboard.yaml checkerboard-tiling.yaml # Android emulator: GL error 502 at blit_framebuffer, fails on opt emulator and on a Pixel2
== missing-mask.yaml missing-mask-ref.yaml

View File

@ -9,6 +9,7 @@ use std::sync::Arc;
use std::sync::Mutex;
use webrender::api::*;
use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, TileOffset};
use webrender::api::units::DeviceIntRect;
// Serialize/deserialize the blob.
@ -129,12 +130,14 @@ impl CheckerboardRenderer {
}
impl BlobImageHandler for CheckerboardRenderer {
fn add(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>, tile_size: Option<TileSize>) {
fn add(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>,
_visible_rect: &DeviceIntRect, tile_size: Option<TileSize>) {
self.image_cmds
.insert(key, (deserialize_blob(&cmds[..]).unwrap(), tile_size));
}
fn update(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>, _dirty_rect: &BlobDirtyRect) {
fn update(&mut self, key: BlobImageKey, cmds: Arc<BlobImageData>,
_visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) {
// Here, updating is just replacing the current version of the commands with
// the new one (no incremental updates).
self.image_cmds.get_mut(&key).unwrap().0 = deserialize_blob(&cmds[..]).unwrap();

View File

@ -213,6 +213,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(151, 56, ImageFormat::BGRA8, true, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 151, 56),
Some(128),
);
@ -269,6 +270,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(1510, 111256, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 15010, 111256),
Some(31),
);
@ -401,6 +403,7 @@ impl<'a> RawtestHarness<'a> {
false
),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, image_size.width as i32, image_size.height as i32),
Some(100),
);
@ -432,14 +435,14 @@ impl<'a> RawtestHarness<'a> {
false
),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
// Set a visible rectangle that is too small.
// This will force sync rasterization of the missing tiles during frame building.
DeviceIntRect {
origin: point2(200, 200),
size: size2(80, 80),
},
Some(100),
);
// Set a visible rectangle that is too small.
// This will force sync rasterization of the missing tiles during frame building.
txn.set_blob_image_visible_area(blob_img2, DeviceIntRect {
origin: point2(200, 200),
size: size2(80, 80),
});
builder.push_image(
&info,
@ -485,6 +488,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 1510, 1510),
None,
);
@ -542,6 +546,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 1510, 1510),
&rect(10, 10, 100, 100).into(),
);
@ -599,6 +604,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 500, 500),
None,
);
}
@ -689,6 +695,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 500, 500),
None,
);
blob_img2 = api.generate_blob_image_key();
@ -696,6 +703,7 @@ impl<'a> RawtestHarness<'a> {
blob_img2,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(80, 50, 150, 255)),
rect(0, 0, 500, 500),
None,
);
(blob_img, blob_img2)
@ -753,19 +761,20 @@ impl<'a> RawtestHarness<'a> {
self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
let _pixels_first = self.render_and_get_pixels(window_rect);
// update and redraw both images
let mut txn = Transaction::new();
txn.update_blob_image(
blob_img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 500, 500),
&rect(100, 100, 100, 100).into(),
);
txn.update_blob_image(
blob_img2,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(59, 50, 150, 255)),
rect(0, 0, 500, 500),
&rect(100, 100, 100, 100).into(),
);
@ -774,13 +783,13 @@ impl<'a> RawtestHarness<'a> {
self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
let _pixels_second = self.render_and_get_pixels(window_rect);
// only update the first image
let mut txn = Transaction::new();
txn.update_blob_image(
blob_img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
rect(0, 0, 500, 500),
&rect(200, 200, 100, 100).into(),
);
@ -816,10 +825,11 @@ impl<'a> RawtestHarness<'a> {
img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 500, 500),
None,
);
img
};
};
// draw the blobs the first time
let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
@ -847,6 +857,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
rect(0, 0, 500, 500),
&rect(100, 100, 100, 100).into(),
);
@ -873,6 +884,7 @@ impl<'a> RawtestHarness<'a> {
blob_img,
ImageDescriptor::new(500, 500, ImageFormat::BGRA8, false, false),
blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
rect(0, 0, 500, 500),
&rect(200, 200, 100, 100).into(),
);

View File

@ -1881,6 +1881,7 @@ impl YamlFrameReader {
let filters = yaml["filters"].as_vec_filter_op().unwrap_or(vec![]);
let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or(vec![]);
let filter_primitives = yaml["filter-primitives"].as_vec_filter_primitive().unwrap_or(vec![]);
dl.push_stacking_context(
bounds.origin,
@ -1891,6 +1892,7 @@ impl YamlFrameReader {
mix_blend_mode,
&filters,
&filter_datas,
&filter_primitives,
raster_space,
cache_tiles,
);

View File

@ -72,6 +72,14 @@ fn color_to_string(value: ColorF) -> String {
}
}
fn filter_input_to_string(input: FilterPrimitiveInput) -> String {
match input {
FilterPrimitiveInput::Original => "original".into(),
FilterPrimitiveInput::Previous => "previous".into(),
FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => index.to_string(),
}
}
fn color_node(parent: &mut Table, key: &str, value: ColorF) {
yaml_node(parent, key, Yaml::String(color_to_string(value)));
}
@ -132,6 +140,10 @@ fn table_node(parent: &mut Table, key: &str, value: Table) {
yaml_node(parent, key, Yaml::Hash(value));
}
fn filter_input_node(parent: &mut Table, key: &str, value: FilterPrimitiveInput) {
yaml_node(parent, key, Yaml::String(filter_input_to_string(value)));
}
fn string_vec_yaml(value: &[String], check_unique: bool) -> Yaml {
if !value.is_empty() && check_unique && array_elements_are_same(value) {
Yaml::String(value[0].clone())
@ -253,6 +265,7 @@ fn write_stacking_context(
properties: &SceneProperties,
filter_iter: impl IntoIterator<Item = FilterOp>,
filter_data_iter: &[TempFilterData],
filter_primitive_iter: impl IntoIterator<Item = FilterPrimitive>,
) {
enum_node(parent, "transform-style", sc.transform_style);
@ -349,6 +362,58 @@ fn write_stacking_context(
}
yaml_node(parent, "filter-datas", Yaml::Array(filter_datas));
// filter primitives
let mut filter_primitives = vec![];
for filter_primitive in filter_primitive_iter {
let mut table = new_table();
match filter_primitive.kind {
FilterPrimitiveKind::Identity(identity_primitive) => {
yaml_node(&mut table, "type", Yaml::String("identity".into()));
filter_input_node(&mut table, "in", identity_primitive.input);
}
FilterPrimitiveKind::Blend(blend_primitive) => {
yaml_node(&mut table, "type", Yaml::String("blend".into()));
filter_input_node(&mut table, "in1", blend_primitive.input1);
filter_input_node(&mut table, "in2", blend_primitive.input2);
enum_node(&mut table, "mode", blend_primitive.mode);
}
FilterPrimitiveKind::Flood(flood_primitive) => {
yaml_node(&mut table, "type", Yaml::String("flood".into()));
color_node(&mut table, "color", flood_primitive.color);
}
FilterPrimitiveKind::Blur(blur_primitive) => {
yaml_node(&mut table, "type", Yaml::String("blur".into()));
filter_input_node(&mut table, "in", blur_primitive.input);
f32_node(&mut table, "radius", blur_primitive.radius);
}
FilterPrimitiveKind::Opacity(opacity_primitive) => {
yaml_node(&mut table, "type", Yaml::String("opacity".into()));
filter_input_node(&mut table, "in", opacity_primitive.input);
f32_node(&mut table, "opacity", opacity_primitive.opacity);
}
FilterPrimitiveKind::ColorMatrix(color_matrix_primitive) => {
yaml_node(&mut table, "type", Yaml::String("color-matrix".into()));
filter_input_node(&mut table, "in", color_matrix_primitive.input);
f32_vec_node(&mut table, "matrix", &color_matrix_primitive.matrix);
}
FilterPrimitiveKind::DropShadow(drop_shadow_primitive) => {
yaml_node(&mut table, "type", Yaml::String("drop-shadow".into()));
filter_input_node(&mut table, "in", drop_shadow_primitive.input);
vector_node(&mut table, "offset", &drop_shadow_primitive.shadow.offset);
color_node(&mut table, "color", drop_shadow_primitive.shadow.color);
f32_node(&mut table, "radius", drop_shadow_primitive.shadow.blur_radius);
}
FilterPrimitiveKind::ComponentTransfer(component_transfer_primitive) => {
yaml_node(&mut table, "type", Yaml::String("component-transfer".into()));
filter_input_node(&mut table, "in", component_transfer_primitive.input);
}
}
enum_node(&mut table, "color-space", filter_primitive.color_space);
filter_primitives.push(Yaml::Hash(table));
}
yaml_node(parent, "filter-primitives", Yaml::Array(filter_primitives));
}
#[cfg(target_os = "macos")]
@ -1165,6 +1230,7 @@ impl YamlFrameWriter {
&scene.properties,
base.filters(),
base.filter_datas(),
base.filter_primitives(),
);
let mut sub_iter = base.sub_iter();
@ -1277,9 +1343,11 @@ impl YamlFrameWriter {
DisplayItem::PopReferenceFrame |
DisplayItem::PopStackingContext => return,
DisplayItem::SetGradientStops => panic!("dummy item yielded?"),
DisplayItem::SetFilterOps => panic!("dummy item yielded?"),
DisplayItem::SetFilterData => panic!("dummy item yielded?"),
DisplayItem::SetGradientStops |
DisplayItem::SetFilterOps |
DisplayItem::SetFilterData |
DisplayItem::SetFilterPrimitives => panic!("dummy item yielded?"),
DisplayItem::PushShadow(item) => {
str_node(&mut v, "type", "shadow");
vector_node(&mut v, "offset", &item.shadow.offset);

View File

@ -38,6 +38,10 @@ pub trait YamlHelper {
fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>>;
fn as_filter_data(&self) -> Option<FilterData>;
fn as_vec_filter_data(&self) -> Option<Vec<FilterData>>;
fn as_filter_input(&self) -> Option<FilterPrimitiveInput>;
fn as_filter_primitive(&self) -> Option<FilterPrimitive>;
fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>>;
fn as_color_space(&self) -> Option<ColorSpace>;
}
fn string_to_color(color: &str) -> Option<ColorF> {
@ -152,6 +156,14 @@ define_string_enum!(
]
);
define_string_enum!(
ColorSpace,
[
Srgb = "srgb",
LinearRgb = "linear-rgb"
]
);
// Rotate around `axis` by `degrees` angle
fn make_rotation(
origin: &LayoutPoint,
@ -674,6 +686,24 @@ impl YamlHelper for Yaml {
None
}
fn as_filter_input(&self) -> Option<FilterPrimitiveInput> {
if let Some(input) = self.as_str() {
match input {
"original" => Some(FilterPrimitiveInput::Original),
"previous" => Some(FilterPrimitiveInput::Previous),
_ => None,
}
} else if let Some(index) = self.as_i64() {
if index >= 0 {
Some(FilterPrimitiveInput::OutputOfPrimitiveIndex(index as usize))
} else {
panic!("Filter input index cannot be negative");
}
} else {
panic!("Invalid filter input");
}
}
fn as_vec_filter_data(&self) -> Option<Vec<FilterData>> {
if let Some(v) = self.as_vec() {
Some(v.iter().map(|x| x.as_filter_data().unwrap()).collect())
@ -681,4 +711,85 @@ impl YamlHelper for Yaml {
self.as_filter_data().map(|data| vec![data])
}
}
fn as_filter_primitive(&self) -> Option<FilterPrimitive> {
if let Some(filter_type) = self["type"].as_str() {
let kind = match filter_type {
"identity" => {
FilterPrimitiveKind::Identity(IdentityPrimitive {
input: self["in"].as_filter_input().unwrap(),
})
}
"blend" => {
FilterPrimitiveKind::Blend(BlendPrimitive {
input1: self["in1"].as_filter_input().unwrap(),
input2: self["in2"].as_filter_input().unwrap(),
mode: self["blend-mode"].as_mix_blend_mode().unwrap(),
})
}
"flood" => {
FilterPrimitiveKind::Flood(FloodPrimitive {
color: self["color"].as_colorf().unwrap(),
})
}
"blur" => {
FilterPrimitiveKind::Blur(BlurPrimitive {
input: self["in"].as_filter_input().unwrap(),
radius: self["radius"].as_f32().unwrap(),
})
}
"opacity" => {
FilterPrimitiveKind::Opacity(OpacityPrimitive {
input: self["in"].as_filter_input().unwrap(),
opacity: self["opacity"].as_f32().unwrap(),
})
}
"color-matrix" => {
let m: Vec<f32> = self["matrix"].as_vec_f32().unwrap();
let mut matrix: [f32; 20] = [0.0; 20];
matrix.clone_from_slice(&m);
FilterPrimitiveKind::ColorMatrix(ColorMatrixPrimitive {
input: self["in"].as_filter_input().unwrap(),
matrix,
})
}
"drop-shadow" => {
FilterPrimitiveKind::DropShadow(DropShadowPrimitive {
input: self["in"].as_filter_input().unwrap(),
shadow: Shadow {
offset: self["offset"].as_vector().unwrap(),
color: self["color"].as_colorf().unwrap(),
blur_radius: self["radius"].as_f32().unwrap(),
}
})
}
"component-transfer" => {
FilterPrimitiveKind::ComponentTransfer(ComponentTransferPrimitive {
input: self["in"].as_filter_input().unwrap(),
})
}
_ => return None,
};
Some(FilterPrimitive {
kind,
color_space: self["color-space"].as_color_space().unwrap_or(ColorSpace::LinearRgb),
})
} else {
None
}
}
fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>> {
if let Some(v) = self.as_vec() {
Some(v.iter().map(|x| x.as_filter_primitive().unwrap()).collect())
} else {
self.as_filter_primitive().map(|data| vec![data])
}
}
fn as_color_space(&self) -> Option<ColorSpace> {
self.as_str().and_then(|x| StringEnum::from_str(x))
}
}

View File

@ -1491,7 +1491,7 @@ PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread(
MOZ_ASSERT(content);
if (!content->SendInitBackground(std::move(aParent))) {
MOZ_CRASH("Failed to create top level actor!");
NS_WARNING("Failed to create top level actor!");
}
});
if (!runnable) {
@ -1597,7 +1597,7 @@ PBackgroundChild* ChildImpl::GetOrCreateSocketActorForCurrentThread(
SocketProcessBridgeChild::GetSingleton();
if (!bridgeChild->SendInitBackground(std::move(aParent))) {
MOZ_CRASH("Failed to create top level actor!");
NS_WARNING("Failed to create top level actor!");
}
});
if (!runnable) {

View File

@ -45,6 +45,7 @@
#include "mozilla/Services.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/Telemetry.h"
#include "ProtocolUtils.h"
#include <sys/stat.h>
@ -84,6 +85,10 @@ using mozilla::MonitorAutoLock;
using mozilla::Preferences;
using mozilla::StaticMutexAutoLock;
using mozilla::ipc::GeckoChildProcessHost;
using mozilla::ipc::LaunchError;
using mozilla::ipc::LaunchResults;
using mozilla::ipc::ProcessHandlePromise;
using mozilla::ipc::ProcessLaunchPromise;
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
@ -103,6 +108,70 @@ static bool ShouldHaveDirectoryService() {
return GeckoProcessType_Default == XRE_GetProcessType();
}
namespace mozilla {
namespace ipc {
class ProcessLauncher {
public:
ProcessLauncher(GeckoChildProcessHost* aHost,
std::vector<std::string>&& aExtraOpts)
: mProcessType(aHost->mProcessType),
mLaunchOptions(std::move(aHost->mLaunchOptions)),
mExtraOpts(std::move(aExtraOpts)),
#ifdef XP_WIN
mGroupId(aHost->mGroupId),
#endif
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
mAllowedFilesRead(aHost->mAllowedFilesRead),
mSandboxLevel(aHost->mSandboxLevel),
mIsFileContent(aHost->mIsFileContent),
mEnableSandboxLogging(aHost->mEnableSandboxLogging),
#endif
mTmpDirName(aHost->mTmpDirName) {
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProcessLauncher);
RefPtr<ProcessLaunchPromise> Launch(GeckoChildProcessHost*);
private:
~ProcessLauncher() {}
RefPtr<ProcessLaunchPromise> PerformAsyncLaunch();
static BinPathType GetPathToBinary(FilePath&, GeckoProcessType);
#if defined(MOZ_WIDGET_ANDROID)
void LaunchAndroidService(
const char* type, const std::vector<std::string>& argv,
const base::file_handle_mapping_vector& fds_to_remap,
base::ProcessHandle* process_handle);
#endif // defined(MOZ_WIDGET_ANDROID)
GeckoProcessType mProcessType;
UniquePtr<base::LaunchOptions> mLaunchOptions;
std::vector<std::string> mExtraOpts;
#ifdef XP_WIN
nsString mGroupId;
#endif
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
std::vector<std::wstring> mAllowedFilesRead;
int32_t mSandboxLevel;
bool mIsFileContent;
bool mEnableSandboxLogging;
#endif
nsCString mTmpDirName;
// Set during launch.
IPC::Channel* mChannel;
std::wstring mChannelId;
};
} // namespace ipc
} // namespace mozilla
using mozilla::ipc::ProcessLauncher;
mozilla::StaticAutoPtr<mozilla::LinkedList<GeckoChildProcessHost>>
GeckoChildProcessHost::sGeckoChildProcessHosts;
@ -192,21 +261,21 @@ void GeckoChildProcessHost::Destroy() {
MOZ_RELEASE_ASSERT(!mDestroying);
// We can remove from the list before it's really destroyed
RemoveFromProcessList();
RefPtr<HandlePromise> whenReady = mHandlePromise;
RefPtr<ProcessHandlePromise> whenReady = mHandlePromise;
if (!whenReady) {
// AsyncLaunch not called yet, so dispatch immediately.
whenReady = HandlePromise::CreateAndReject(LaunchError{}, __func__);
whenReady = ProcessHandlePromise::CreateAndReject(LaunchError{}, __func__);
}
using Value = HandlePromise::ResolveOrRejectValue;
using Value = ProcessHandlePromise::ResolveOrRejectValue;
mDestroying = true;
whenReady->Then(XRE_GetIOMessageLoop()->SerialEventTarget(), __func__,
[this](const Value&) { delete this; });
}
// static
mozilla::BinPathType GeckoChildProcessHost::GetPathToBinary(
mozilla::BinPathType ProcessLauncher::GetPathToBinary(
FilePath& exePath, GeckoProcessType processType) {
BinPathType pathType = XRE_GetChildProcBinPathType(processType);
@ -389,6 +458,10 @@ bool GeckoChildProcessHost::SyncLaunch(std::vector<std::string> aExtraOpts,
return WaitUntilConnected(aTimeoutMs);
}
static inline nsISerialEventTarget* IOThread() {
return XRE_GetIOMessageLoop()->SerialEventTarget();
}
bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) {
PrepareLaunch();
@ -398,18 +471,72 @@ bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) {
}
#endif
MessageLoop* ioLoop = XRE_GetIOMessageLoop();
RefPtr<ProcessLauncher> launcher =
new ProcessLauncher(this, std::move(aExtraOpts));
// Note: Destroy() waits on mHandlePromise to delete |this|. As such, we want
// to be sure that all of our post-launch processing on |this| happens before
// mHandlePromise notifies.
MOZ_ASSERT(mHandlePromise == nullptr);
mHandlePromise = new HandlePromise::Private(__func__);
RefPtr<ProcessHandlePromise::Private> p =
new ProcessHandlePromise::Private(__func__);
mHandlePromise = p;
// Currently this can't fail (see the MOZ_ALWAYS_SUCCEEDS in
// MessageLoop::PostTask_Helper), but in the future it possibly
// could, in which case this method could return false.
ioLoop->PostTask(NewNonOwningRunnableMethod<std::vector<std::string>>(
"ipc::GeckoChildProcessHost::RunPerformAsyncLaunch", this,
&GeckoChildProcessHost::RunPerformAsyncLaunch, aExtraOpts));
mozilla::InvokeAsync<GeckoChildProcessHost*>(
IOThread(), launcher.get(), __func__, &ProcessLauncher::Launch, this)
->Then(
IOThread(), __func__,
[this, p](const LaunchResults aResults) {
{
if (!OpenPrivilegedHandle(base::GetProcId(aResults.mHandle))
#ifdef XP_WIN
// If we failed in opening the process handle, try harder by
// duplicating one.
&& !::DuplicateHandle(::GetCurrentProcess(), aResults.mHandle,
::GetCurrentProcess(),
&mChildProcessHandle,
PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ | SYNCHRONIZE,
FALSE, 0)
#endif // XP_WIN
) {
MOZ_CRASH("cannot open handle to child process");
}
#ifdef XP_MACOSX
this->mChildTask = aResults.mChildTask;
#endif
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
this->mSandboxBroker = aResults.mSandboxBroker;
#endif
MonitorAutoLock lock(mMonitor);
// The OnChannel{Connected,Error} may have already advanced the
// state.
if (mProcessState < PROCESS_CREATED) {
mProcessState = PROCESS_CREATED;
}
lock.Notify();
}
p->Resolve(aResults.mHandle, __func__);
},
[this, p](const LaunchError aError) {
// WaitUntilConnected might be waiting for us to signal.
// If something failed let's set the error state and notify.
CHROMIUM_LOG(ERROR)
<< "Failed to launch "
<< XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
Telemetry::Accumulate(
Telemetry::SUBPROCESS_LAUNCH_FAILURE,
nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
{
MonitorAutoLock lock(mMonitor);
mProcessState = PROCESS_ERROR;
lock.Notify();
}
p->Reject(aError, __func__);
});
return true;
}
@ -492,10 +619,9 @@ void GeckoChildProcessHost::SetAlreadyDead() {
mChildProcessHandle = 0;
}
int32_t GeckoChildProcessHost::mChildCounter = 0;
static int32_t gChildCounter = 0;
void GeckoChildProcessHost::GetChildLogName(const char* origLogName,
nsACString& buffer) {
static void GetChildLogName(const char* origLogName, nsACString& buffer) {
#ifdef XP_WIN
// On Windows we must expand relative paths because sandboxing rules
// bound only to full paths. fopen fowards to NtCreateFile which checks
@ -524,7 +650,7 @@ void GeckoChildProcessHost::GetChildLogName(const char* origLogName,
// Append child-specific postfix to name
buffer.AppendLiteral(".child-");
buffer.AppendInt(mChildCounter);
buffer.AppendInt(gChildCounter);
}
namespace {
@ -581,6 +707,7 @@ static nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
}
nsCOMPtr<nsIEventTarget> thread = gIPCLaunchThread.get();
MOZ_DIAGNOSTIC_ASSERT(thread);
return thread;
}
@ -591,78 +718,13 @@ static nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
static nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
nsCOMPtr<nsIEventTarget> pool =
mozilla::SharedThreadPool::Get(NS_LITERAL_CSTRING("IPC Launch"));
MOZ_DIAGNOSTIC_ASSERT(pool);
return pool;
}
#endif // XP_WIN
} // anonymous namespace
void GeckoChildProcessHost::RunPerformAsyncLaunch(
std::vector<std::string> aExtraOpts) {
// Warning: rejecting the promise allows `this` to be deleted. Do
// not use `this` after calling the `fail` function (including
// destructors of AutoLock objects).
//
// (Deletion happens on the I/O thread, so it's safe to access
// `this` afterwards from RunPerformAsyncLaunch itself, but not from
// the launchWrapper closure. For simplicity, it's just treated
// like `delete this` everywhere.)
auto fail = [this] {
{
MonitorAutoLock lock(mMonitor);
mProcessState = PROCESS_ERROR;
lock.Notify();
}
mHandlePromise->Reject(LaunchError{}, __func__);
};
// This (probably?) needs to happen on the I/O thread.
InitializeChannel();
// But the rest of this doesn't, and shouldn't block IPC messages:
auto launchWrapper = [this, fail, aExtraOpts = std::move(aExtraOpts)]() {
bool ok = PerformAsyncLaunch(aExtraOpts);
if (!ok) {
// WaitUntilConnected might be waiting for us to signal.
// If something failed let's set the error state and notify.
CHROMIUM_LOG(ERROR) << "Failed to launch "
<< XRE_ChildProcessTypeToString(mProcessType)
<< " subprocess";
Telemetry::Accumulate(
Telemetry::SUBPROCESS_LAUNCH_FAILURE,
nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
fail();
}
};
// The Web Replay middleman process launches the actual content
// processes, and doesn't initialize enough of XPCOM to use thread
// pools.
if (!mozilla::recordreplay::IsMiddleman()) {
auto launcher = GetIPCLauncher();
MOZ_DIAGNOSTIC_ASSERT(launcher != nullptr);
// Creating a thread pool shouldn't normally fail, but in case it
// does, use the fallback we already have for the middleman case.
if (launcher != nullptr) {
nsresult rv = launcher->Dispatch(
NS_NewRunnableFunction(
"ipc::GeckoChildProcessHost::PerformAsyncLaunch", launchWrapper),
NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
CHROMIUM_LOG(ERROR) << "Failed to dispatch launch task for "
<< XRE_ChildProcessTypeToString(mProcessType)
<< " process; launching during shutdown?";
fail();
}
return;
}
}
// Fall back to launching on the I/O thread.
launchWrapper();
}
void
#if defined(XP_WIN)
AddAppDirToCommandLine(CommandLine& aCmdLine)
@ -731,11 +793,11 @@ static bool Contains(const std::vector<std::string>& aExtraOpts,
}
#endif // defined(XP_WIN) && (defined(MOZ_SANDBOX) || defined(_ARM64_))
bool GeckoChildProcessHost::PerformAsyncLaunch(
std::vector<std::string> aExtraOpts) {
RefPtr<ProcessLaunchPromise> ProcessLauncher::PerformAsyncLaunch() {
#ifdef MOZ_GECKO_PROFILER
GetProfilerEnvVarsForChildProcess([this](const char* key, const char* value) {
mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
RefPtr<ProcessLauncher> self = this;
GetProfilerEnvVarsForChildProcess([self](const char* key, const char* value) {
self->mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
ENVIRONMENT_STRING(value);
});
#endif
@ -743,8 +805,8 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
const auto startTS = TimeStamp::Now();
// - Note: this code is not called re-entrantly, nor are restoreOrig*LogName
// or mChildCounter touched by any other thread, so this is safe.
++mChildCounter;
// or gChildCounter touched by any other thread, so this is safe.
++gChildCounter;
const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE");
const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE");
@ -781,13 +843,8 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
}
#endif
// We rely on the fact that InitializeChannel() has already been processed
// on the IO thread before this point is reached.
if (!GetChannel()) {
return false;
}
base::ProcessHandle process = 0;
LaunchResults results = LaunchResults();
results.mHandle = 0;
// send the child the PID so that it can open a ProcessHandle back to us.
// probably don't want to do this in the long run
@ -801,7 +858,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
ScopedPRFileDesc crashAnnotationWritePipe;
if (PR_CreatePipe(&crashAnnotationReadPipe.rwget(),
&crashAnnotationWritePipe.rwget()) != PR_SUCCESS) {
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
//--------------------------------------------------
@ -880,7 +937,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
// remap the IPC socket fd to a well-known int, as the OS does for
// STDOUT_FILENO, for example
int srcChannelFd, dstChannelFd;
channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
mChannel->GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
mLaunchOptions->fds_to_remap.push_back(
std::pair<int, int>(srcChannelFd, dstChannelFd));
@ -895,7 +952,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
childArgv.push_back("-contentproc");
}
childArgv.insert(childArgv.end(), aExtraOpts.begin(), aExtraOpts.end());
childArgv.insert(childArgv.end(), mExtraOpts.begin(), mExtraOpts.end());
if (mProcessType != GeckoProcessType_GMPlugin) {
if (Omnijar::IsInitialized()) {
@ -924,7 +981,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
int childCrashFd, childCrashRemapFd;
if (!CrashReporter::CreateNotificationPipeForChild(&childCrashFd,
&childCrashRemapFd)) {
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
if (0 <= childCrashFd) {
@ -968,20 +1025,20 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
# if defined(MOZ_WIDGET_ANDROID)
LaunchAndroidService(childProcessType, childArgv,
mLaunchOptions->fds_to_remap, &process);
if (process == 0) {
return false;
mLaunchOptions->fds_to_remap, &results.mHandle);
if (results.mHandle == 0) {
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
# else // goes with defined(MOZ_WIDGET_ANDROID)
if (!base::LaunchApp(childArgv, *mLaunchOptions, &process)) {
return false;
if (!base::LaunchApp(childArgv, *mLaunchOptions, &results.mHandle)) {
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
# endif // defined(MOZ_WIDGET_ANDROID)
// We're in the parent and the child was launched. Close the child FD in the
// parent as soon as possible, which will allow the parent to detect when the
// child closes its FD (either due to normal exit or due to crash).
GetChannel()->CloseClientFileDescriptor();
mChannel->CloseClientFileDescriptor();
# ifdef MOZ_WIDGET_COCOA
// Wait for the child process to send us its 'task_t' data.
@ -994,18 +1051,18 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
std::string errString =
StringPrintf("0x%x %s", err, mach_error_string(err));
CHROMIUM_LOG(ERROR) << "parent WaitForMessage() failed: " << errString;
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
task_t child_task = child_message.GetTranslatedPort(0);
if (child_task == MACH_PORT_NULL) {
CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(0) failed.";
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
if (child_message.GetTranslatedPort(1) == MACH_PORT_NULL) {
CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(1) failed.";
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
MachPortSender parent_sender(child_message.GetTranslatedPort(1));
@ -1025,7 +1082,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
if (!parent_message.AddDescriptor(MachMsgPortDescriptor(bootstrap_port))) {
CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << bootstrap_port
<< ") failed.";
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
auto* parent_recv_port_memory = new ReceivePort();
@ -1033,7 +1090,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
MachMsgPortDescriptor(parent_recv_port_memory->GetPort()))) {
CHROMIUM_LOG(ERROR) << "parent AddDescriptor("
<< parent_recv_port_memory->GetPort() << ") failed.";
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
auto* parent_send_port_memory_ack = new ReceivePort();
@ -1042,7 +1099,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
CHROMIUM_LOG(ERROR) << "parent AddDescriptor("
<< parent_send_port_memory_ack->GetPort()
<< ") failed.";
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
err = parent_sender.SendMessage(parent_message, kTimeoutMs);
@ -1050,11 +1107,11 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
std::string errString =
StringPrintf("0x%x %s", err, mach_error_string(err));
CHROMIUM_LOG(ERROR) << "parent SendMessage() failed: " << errString;
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
SharedMemoryBasic::SetupMachMemory(
process, parent_recv_port_memory, parent_recv_port_memory_ack,
results.mHandle, parent_recv_port_memory, parent_recv_port_memory_ack,
parent_send_port_memory, parent_send_port_memory_ack, false);
# endif // MOZ_WIDGET_COCOA
@ -1067,9 +1124,9 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
# if defined(MOZ_SANDBOX) || defined(_ARM64_)
const bool isGMP = mProcessType == GeckoProcessType_GMPlugin;
const bool isWidevine = isGMP && Contains(aExtraOpts, "gmp-widevinecdm");
const bool isWidevine = isGMP && Contains(mExtraOpts, "gmp-widevinecdm");
# if defined(_ARM64_)
const bool isClearKey = isGMP && Contains(aExtraOpts, "gmp-clearkey");
const bool isClearKey = isGMP && Contains(mExtraOpts, "gmp-clearkey");
const bool isSandboxBroker =
mProcessType == GeckoProcessType_RemoteSandboxBroker;
if (isClearKey || isWidevine || isSandboxBroker) {
@ -1088,10 +1145,10 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
cmdLine.AppendLooseValue(UTF8ToWide("-contentproc"));
}
cmdLine.AppendSwitchWithValue(switches::kProcessChannelID, channel_id());
cmdLine.AppendSwitchWithValue(switches::kProcessChannelID, mChannelId);
for (std::vector<std::string>::iterator it = aExtraOpts.begin();
it != aExtraOpts.end(); ++it) {
for (std::vector<std::string>::iterator it = mExtraOpts.begin();
it != mExtraOpts.end(); ++it) {
cmdLine.AppendLooseValue(UTF8ToWide(*it));
}
@ -1114,10 +1171,10 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
# if defined(MOZ_SANDBOX)
# if defined(_ARM64_)
if (isClearKey || isWidevine)
mSandboxBroker = new RemoteSandboxBroker();
results.mSandboxBroker = new RemoteSandboxBroker();
else
# endif // if defined(_ARM64_)
mSandboxBroker = new SandboxBroker();
results.mSandboxBroker = new SandboxBroker();
bool shouldSandboxCurrentProcess = false;
@ -1131,17 +1188,17 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
// SetSecurityLevelForContentProcess and just crash there right away.
// Should this change in the future then we should also handle the error
// here.
mSandboxBroker->SetSecurityLevelForContentProcess(mSandboxLevel,
mIsFileContent);
results.mSandboxBroker->SetSecurityLevelForContentProcess(
mSandboxLevel, mIsFileContent);
shouldSandboxCurrentProcess = true;
}
break;
case GeckoProcessType_Plugin:
if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) {
bool ok =
mSandboxBroker->SetSecurityLevelForPluginProcess(mSandboxLevel);
bool ok = results.mSandboxBroker->SetSecurityLevelForPluginProcess(
mSandboxLevel);
if (!ok) {
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
shouldSandboxCurrentProcess = true;
}
@ -1157,9 +1214,9 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
// so use sandbox level USER_RESTRICTED instead of USER_LOCKDOWN.
auto level =
isWidevine ? SandboxBroker::Restricted : SandboxBroker::LockDown;
bool ok = mSandboxBroker->SetSecurityLevelForGMPlugin(level);
bool ok = results.mSandboxBroker->SetSecurityLevelForGMPlugin(level);
if (!ok) {
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
shouldSandboxCurrentProcess = true;
}
@ -1169,7 +1226,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
// For now we treat every failure as fatal in
// SetSecurityLevelForGPUProcess and just crash there right away. Should
// this change in the future then we should also handle the error here.
mSandboxBroker->SetSecurityLevelForGPUProcess(mSandboxLevel);
results.mSandboxBroker->SetSecurityLevelForGPUProcess(mSandboxLevel);
shouldSandboxCurrentProcess = true;
}
break;
@ -1180,8 +1237,8 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
break;
case GeckoProcessType_RDD:
if (!PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
if (!mSandboxBroker->SetSecurityLevelForRDDProcess()) {
return false;
if (!results.mSandboxBroker->SetSecurityLevelForRDDProcess()) {
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
shouldSandboxCurrentProcess = true;
}
@ -1201,7 +1258,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
if (shouldSandboxCurrentProcess) {
for (auto it = mAllowedFilesRead.begin(); it != mAllowedFilesRead.end();
++it) {
mSandboxBroker->AllowReadFile(it->c_str());
results.mSandboxBroker->AllowReadFile(it->c_str());
}
}
# endif // defined(MOZ_SANDBOX)
@ -1236,25 +1293,25 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
if (shouldSandboxCurrentProcess) {
// Mark the handles to inherit as inheritable.
for (HANDLE h : mLaunchOptions->handles_to_inherit) {
mSandboxBroker->AddHandleToShare(h);
results.mSandboxBroker->AddHandleToShare(h);
}
if (mSandboxBroker->LaunchApp(cmdLine.program().c_str(),
cmdLine.command_line_string().c_str(),
mLaunchOptions->env_map, mProcessType,
mEnableSandboxLogging, &process)) {
if (results.mSandboxBroker->LaunchApp(
cmdLine.program().c_str(), cmdLine.command_line_string().c_str(),
mLaunchOptions->env_map, mProcessType, mEnableSandboxLogging,
&results.mHandle)) {
EnvironmentLog("MOZ_PROCESS_LOG")
.print("==> process %d launched child process %d (%S)\n",
base::GetCurrentProcId(), base::GetProcId(process),
base::GetCurrentProcId(), base::GetProcId(results.mHandle),
cmdLine.command_line_string().c_str());
} else {
return false;
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
} else
# endif // defined(MOZ_SANDBOX)
{
if (!base::LaunchApp(cmdLine, *mLaunchOptions, &process)) {
return false;
if (!base::LaunchApp(cmdLine, *mLaunchOptions, &results.mHandle)) {
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
# ifdef MOZ_SANDBOX
@ -1268,7 +1325,7 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
// No handle duplication necessary.
break;
default:
if (!SandboxBroker::AddTargetPeer(process)) {
if (!SandboxBroker::AddTargetPeer(results.mHandle)) {
NS_WARNING("Failed to add child process as target peer.");
}
break;
@ -1280,50 +1337,20 @@ bool GeckoChildProcessHost::PerformAsyncLaunch(
# error Sorry
#endif // defined(OS_POSIX)
MOZ_DIAGNOSTIC_ASSERT(process);
MOZ_DIAGNOSTIC_ASSERT(results.mHandle);
// NB: on OS X, we block much longer than we need to in order to
// reach this call, waiting for the child process's task_t. The
// best way to fix that is to refactor this file, hard.
#if defined(MOZ_WIDGET_COCOA)
mChildTask = child_task;
#endif // defined(MOZ_WIDGET_COCOA)
if (!OpenPrivilegedHandle(base::GetProcId(process))
#ifdef XP_WIN
// If we failed in opening the process handle, try harder by duplicating
// one.
&& !::DuplicateHandle(::GetCurrentProcess(), process,
::GetCurrentProcess(), &mChildProcessHandle,
PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ |
SYNCHRONIZE,
FALSE, 0)
#endif // XP_WIN
) {
MOZ_CRASH("cannot open handle to child process");
}
#ifdef XP_MACOSX
results.mChildTask = child_task;
#endif // XP_MACOSX
CrashReporter::RegisterChildCrashAnnotationFileDescriptor(
base::GetProcId(process), crashAnnotationReadPipe.forget());
{
MonitorAutoLock lock(mMonitor);
// This runs on a launch thread, but the OnChannel{Connected,Error}
// callbacks run on the I/O thread, so it's possible that the state already
// advanced beyond PROCESS_CREATED.
if (mProcessState < PROCESS_CREATED) {
mProcessState = PROCESS_CREATED;
}
lock.Notify();
}
mLaunchOptions = nullptr;
base::GetProcId(results.mHandle), crashAnnotationReadPipe.forget());
Telemetry::AccumulateTimeDelta(Telemetry::CHILD_PROCESS_LAUNCH_MS, startTS);
// Warning: resolving the promise allows `this` to be deleted.
mHandlePromise->Resolve(process, __func__);
return true;
return ProcessLaunchPromise::CreateAndResolve(results, __func__);
}
bool GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid) {
@ -1363,8 +1390,7 @@ void GeckoChildProcessHost::OnChannelError() {
// FIXME/bug 773925: save up this error for the next listener.
}
RefPtr<GeckoChildProcessHost::HandlePromise>
GeckoChildProcessHost::WhenProcessHandleReady() {
RefPtr<ProcessHandlePromise> GeckoChildProcessHost::WhenProcessHandleReady() {
MOZ_ASSERT(mHandlePromise != nullptr);
return mHandlePromise;
}
@ -1377,10 +1403,10 @@ void GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue) {
}
#ifdef MOZ_WIDGET_ANDROID
void GeckoChildProcessHost::LaunchAndroidService(
void ProcessLauncher::LaunchAndroidService(
const char* type, const std::vector<std::string>& argv,
const base::file_handle_mapping_vector& fds_to_remap,
ProcessHandle* process_handle) {
base::ProcessHandle* process_handle) {
MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 5));
JNIEnv* const env = mozilla::jni::GetEnvForThread();
MOZ_ASSERT(env);
@ -1491,3 +1517,41 @@ void GeckoChildProcessHost::GetAll(const GeckoProcessCallback& aCallback) {
aCallback(gp);
}
}
RefPtr<ProcessLaunchPromise> ProcessLauncher::Launch(
GeckoChildProcessHost* aHost) {
AssertIOThread();
// Initializing the channel needs to happen on the I/O thread, but everything
// else can run on the launcher thread (or pool), to avoid blocking IPC
// messages.
//
// We avoid passing the host to the launcher thread to reduce the chances of
// data races with the IO thread (where e.g. OnChannelConnected may run
// concurrently). The pool currently needs access to the channel, which is not
// great.
//
// It's also unfortunate that we need to work with raw pointers to both the
// host and the channel. The assumption here is that the host (and therefore
// the channel) are never torn down until the return promise is resolved or
// rejected.
aHost->InitializeChannel();
mChannel = aHost->GetChannel();
if (!mChannel) {
return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__);
}
mChannelId = aHost->GetChannelId();
nsCOMPtr<nsISerialEventTarget> launchThread;
if (mozilla::recordreplay::IsMiddleman()) {
// During Web Replay, the middleman process launches the actual content
// processes, and doesn't initialize enough of XPCOM to use thread pools.
launchThread = IOThread();
} else {
nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher();
launchThread = new TaskQueue(threadOrPool.forget());
}
return InvokeAsync(launchThread, this, __func__,
&ProcessLauncher::PerformAsyncLaunch);
}

View File

@ -38,6 +38,22 @@ typedef _MacSandboxInfo MacSandboxInfo;
namespace mozilla {
namespace ipc {
struct LaunchError {};
typedef mozilla::MozPromise<base::ProcessHandle, LaunchError, false>
ProcessHandlePromise;
struct LaunchResults {
base::ProcessHandle mHandle = 0;
#ifdef XP_MACOSX
task_t mChildTask = MACH_PORT_NULL;
#endif
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
RefPtr<AbstractSandboxBroker> mSandboxBroker;
#endif
};
typedef mozilla::MozPromise<LaunchResults, LaunchError, false>
ProcessLaunchPromise;
class GeckoChildProcessHost : public ChildProcessHost,
public LinkedListElement<GeckoChildProcessHost> {
protected:
@ -94,20 +110,16 @@ class GeckoChildProcessHost : public ChildProcessHost,
virtual void OnChannelError() override;
virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
struct LaunchError {};
template <typename T>
using LaunchPromise = mozilla::MozPromise<T, LaunchError, /* excl: */ false>;
using HandlePromise = LaunchPromise<base::ProcessHandle>;
// Resolves to the process handle when it's available (see
// LaunchAndWaitForProcessHandle); use with AsyncLaunch.
RefPtr<HandlePromise> WhenProcessHandleReady();
RefPtr<ProcessHandlePromise> WhenProcessHandleReady();
virtual void InitializeChannel();
virtual bool CanShutdown() override { return true; }
IPC::Channel* GetChannel() { return channelp(); }
std::wstring GetChannelId() { return channel_id(); }
// Returns a "borrowed" handle to the child process - the handle returned
// by this function must not be closed by the caller.
@ -162,6 +174,8 @@ class GeckoChildProcessHost : public ChildProcessHost,
// so you need to make sure the callback is as fast as possible.
static void GetAll(const GeckoProcessCallback& aCallback);
friend class ProcessLauncher;
protected:
~GeckoChildProcessHost();
GeckoProcessType mProcessType;
@ -191,8 +205,6 @@ class GeckoChildProcessHost : public ChildProcessHost,
PROCESS_ERROR
} mProcessState;
static int32_t mChildCounter;
void PrepareLaunch();
#ifdef XP_WIN
@ -211,7 +223,7 @@ class GeckoChildProcessHost : public ChildProcessHost,
#if defined(OS_MACOSX)
task_t mChildTask;
#endif
RefPtr<HandlePromise::Private> mHandlePromise;
RefPtr<ProcessHandlePromise> mHandlePromise;
bool OpenPrivilegedHandle(base::ProcessId aPid);
@ -233,21 +245,6 @@ class GeckoChildProcessHost : public ChildProcessHost,
private:
DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost);
// Does the actual work for AsyncLaunch; run in a thread pool
// (or, on Windows, a dedicated thread).
bool PerformAsyncLaunch(StringVector aExtraOpts);
// Called on the I/O thread; creates channel, dispatches
// PerformAsyncLaunch, and consolidates error handling.
void RunPerformAsyncLaunch(StringVector aExtraOpts);
static BinPathType GetPathToBinary(FilePath& exePath,
GeckoProcessType processType);
// The buffer is passed to preserve its lifetime until we are done
// with launching the sub-process.
void GetChildLogName(const char* origLogName, nsACString& buffer);
// Removes the instance from sGeckoChildProcessHosts
void RemoveFromProcessList();
@ -260,10 +257,8 @@ class GeckoChildProcessHost : public ChildProcessHost,
// FIXME/cjones: this strongly indicates bad design. Shame on us.
std::queue<IPC::Message> mQueue;
// Set this up before we're called from a different thread.
#if defined(OS_LINUX)
// Linux-Only. Set this up before we're called from a different thread.
nsCString mTmpDirName;
#endif
mozilla::Atomic<bool> mDestroying;
@ -271,12 +266,6 @@ class GeckoChildProcessHost : public ChildProcessHost,
static StaticAutoPtr<LinkedList<GeckoChildProcessHost>>
sGeckoChildProcessHosts;
static StaticMutex sMutex;
#if defined(MOZ_WIDGET_ANDROID)
void LaunchAndroidService(
const char* type, const std::vector<std::string>& argv,
const base::file_handle_mapping_vector& fds_to_remap,
ProcessHandle* process_handle);
#endif // defined(MOZ_WIDGET_ANDROID)
};
} /* namespace ipc */

View File

@ -17,8 +17,11 @@
#include "mozilla/WrappingOperations.h"
#include <math.h>
#include <stddef.h> // size_t
#include <stdint.h> // {u,}int{8,16,32,64}_t
#include "jspubtd.h"
#include "jstypes.h" // JS_PUBLIC_API
#include "js/RootingAPI.h"
#include "js/Value.h"
@ -554,6 +557,20 @@ inline int64_t ToInt64(double d) { return ToSignedInteger<int64_t>(d); }
/* WEBIDL 4.2.11 */
inline uint64_t ToUint64(double d) { return ToUnsignedInteger<uint64_t>(d); }
/**
* The maximum space needed for the null-terminated result of |ToString| on a
* Number (sign, fractional part, "e+", and exponent of at most three digits,
* e.g. |String(-Number.MAX_VALUE)| as "-1.7976931348623157e+308" is length 24).
*/
static constexpr size_t MaximumNumberToStringLength = 24 + 1;
/**
* Store in |out| the null-terminated, base-10 result of |ToString| applied to
* |d| per <https://tc39.es/ecma262/#sec-tostring-applied-to-the-number-type>.
*/
extern JS_PUBLIC_API void NumberToString(
double d, char (&out)[MaximumNumberToStringLength]);
} // namespace JS
#endif /* js_Conversions_h */

Some files were not shown because too many files have changed in this diff Show More