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

This commit is contained in:
Oana Pop Rus 2019-03-19 18:40:44 +02:00
commit ee7211d66d
216 changed files with 3604 additions and 2115 deletions

View File

@ -161,8 +161,7 @@ static AtkKeyEventStruct *atk_key_event_from_gdk_event_key(GdkEventKey *key) {
event->keyval = key->keyval;
event->length = key->length;
if (key->string && key->string[0] &&
(key->state & GDK_CONTROL_MASK ||
g_unichar_isgraph(g_utf8_get_char(key->string)))) {
g_unichar_isgraph(g_utf8_get_char(key->string))) {
event->string = key->string;
} else if (key->type == GDK_KEY_PRESS || key->type == GDK_KEY_RELEASE) {
event->string = gdk_keyval_name(key->keyval);

View File

@ -1681,6 +1681,8 @@ pref("reader.parse-node-limit", 0);
// and because (normally) these errors are not persisted anywhere.
pref("reader.errors.includeURLs", true);
pref("view_source.tab", true);
pref("dom.serviceWorkers.enabled", true);
// Enable Push API.

View File

@ -2680,6 +2680,8 @@ async function BrowserViewSourceOfDocument(aArgsOrDocument) {
tabBrowser = browserWindow.gBrowser;
}
const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
// `viewSourceInBrowser` will load the source content from the page
// descriptor for the tab (when possible) or fallback to the network if
// that fails. Either way, the view source module will manage the tab's
@ -2687,13 +2689,19 @@ async function BrowserViewSourceOfDocument(aArgsOrDocument) {
// requests.
let tab = tabBrowser.loadOneTab("about:blank", {
relatedToCurrent: true,
inBackground: false,
inBackground: inNewWindow,
skipAnimation: inNewWindow,
preferredRemoteType,
sameProcessAsFrameLoader: args.browser ? args.browser.frameLoader : null,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
top.gViewSourceUtils.viewSourceInBrowser(args);
if (inNewWindow) {
tabBrowser.hideTab(tab);
tabBrowser.replaceTabWithWindow(tab);
}
}
/**

View File

@ -897,6 +897,7 @@ nsContextMenu.prototype = {
let {browser} = this;
let openSelectionFn = function() {
let tabBrowser = gBrowser;
const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
// In the case of popups, we need to find a non-popup browser window.
// We might also not have a tabBrowser reference (if this isn't in a
// a tabbrowser scope) or might have a fake/stub tabbrowser reference
@ -909,10 +910,16 @@ nsContextMenu.prototype = {
let relatedToCurrent = gBrowser && gBrowser.selectedBrowser == browser;
let tab = tabBrowser.loadOneTab("about:blank", {
relatedToCurrent,
inBackground: false,
inBackground: inNewWindow,
skipAnimation: inNewWindow,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
return tabBrowser.getBrowserForTab(tab);
const viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
if (inNewWindow) {
tabBrowser.hideTab(tab);
tabBrowser.replaceTabsWithWindow(tab);
}
return viewSourceBrowser;
};
top.gViewSourceUtils.viewPartialSourceInBrowser(browser, openSelectionFn);

View File

@ -2298,73 +2298,6 @@ BrowserGlue.prototype = {
let xulStore = Services.xulStore;
if (currentUIVersion < 44) {
// Merge the various cosmetic animation prefs into one. If any were set to
// disable animations, we'll disabled cosmetic animations entirely.
let animate = Services.prefs.getBoolPref("browser.tabs.animate", true) &&
Services.prefs.getBoolPref("browser.fullscreen.animate", true) &&
!Services.prefs.getBoolPref("alerts.disableSlidingEffect", false);
Services.prefs.setBoolPref("toolkit.cosmeticAnimations.enabled", animate);
Services.prefs.clearUserPref("browser.tabs.animate");
Services.prefs.clearUserPref("browser.fullscreen.animate");
Services.prefs.clearUserPref("alerts.disableSlidingEffect");
}
if (currentUIVersion < 45) {
const LEGACY_PREF = "browser.shell.skipDefaultBrowserCheck";
if (Services.prefs.prefHasUserValue(LEGACY_PREF)) {
Services.prefs.setBoolPref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun",
!Services.prefs.getBoolPref(LEGACY_PREF));
Services.prefs.clearUserPref(LEGACY_PREF);
}
}
// Version 46 has been replaced by 47
if (currentUIVersion < 47) {
// Search suggestions are now on by default.
// For privacy reasons, we want to respect previously made user's choice
// regarding the feature, so if it's known reflect that choice into the
// current pref.
// Note that in case of downgrade/upgrade we won't guarantee anything.
try {
if (Services.prefs.prefHasUserValue("browser.urlbar.searchSuggestionsChoice")) {
Services.prefs.setBoolPref(
"browser.urlbar.suggest.searches",
Services.prefs.getBoolPref("browser.urlbar.searchSuggestionsChoice")
);
} else if (Services.prefs.getBoolPref("browser.urlbar.userMadeSearchSuggestionsChoice")) {
// If the user made a choice but searchSuggestionsChoice is not set,
// something went wrong in the upgrade path. For example, due to a
// now fixed bug, some profilespicking "no" at the opt-in bar and
// upgrading in the same session wouldn't mirror the pref.
// Users could also lack the mirrored pref due to skipping one version.
// In this case just fallback to the safest side and disable suggestions.
Services.prefs.setBoolPref("browser.urlbar.suggest.searches", false);
}
} catch (ex) {
// A missing pref is not a fatal error.
}
}
if (currentUIVersion < 50) {
try {
// Transform prefs related to old DevTools Console.
// The following prefs might be missing when the old DevTools Console
// front-end is removed.
// See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1381834
if (Services.prefs.getBoolPref("devtools.webconsole.filter.networkinfo")) {
Services.prefs.setBoolPref("devtools.webconsole.filter.net", true);
}
if (Services.prefs.getBoolPref("devtools.webconsole.filter.cssparser")) {
Services.prefs.setBoolPref("devtools.webconsole.filter.css", true);
}
} catch (ex) {
// It's ok if a pref is missing.
}
}
if (currentUIVersion < 51) {
// Switch to compact UI density if the user is using a formerly compact
// dark or light theme.
@ -2396,9 +2329,6 @@ BrowserGlue.prototype = {
}
}
// currentUIVersion < 49 and < 54 were originally used for onboarding prefs and
// have since then been removed and cleared in currentUIVersion < 76
if (currentUIVersion < 55) {
Services.prefs.clearUserPref("browser.customizemode.tip0.shown");
}

View File

@ -25,8 +25,10 @@ var gSetBackground = {
if (AppConstants.platform == "macosx") {
document.documentElement.getButton("accept").hidden = true;
}
if (this._screenWidth / this._screenHeight >= 1.6)
document.getElementById("monitor").setAttribute("aspectratio", "16:10");
// Cap ratio to 4 so the dialog width doesn't get ridiculous. Highest
// regular screens seem to be 32:9 (3.56) according to Wikipedia.
let screenRatio = Math.min(this._screenWidth / this._screenHeight, 4);
this._canvas.width = this._canvas.height * screenRatio;
if (AppConstants.platform == "win") {
// Hide fill + fit options if < Win7 since they don't work.

View File

@ -63,11 +63,9 @@
#endif
<vbox align="center">
<stack>
<!-- if width and height are not present, they default to 300x150 and stretch the stack -->
<html:canvas id="screen" width="1" height="1" role="presentation"/>
<image id="monitor"/>
</stack>
<!-- default to 16:9, will be adjusted to match user's actual screen -->
<html:canvas id="screen" width="202" height="114" role="presentation"/>
<image id="monitor-base"/>
</vbox>
#ifdef XP_MACOSX

View File

@ -39,19 +39,24 @@ add_task(async function() {
const win = await dialogLoad;
/* setDesktopBackground.js does a setTimeout to wait for correct
dimensions. If we don't wait here we could read the monitor image
URL before it's changed to the widescreen version */
dimensions. If we don't wait here we could read the preview dimensions
before they're changed to match the screen */
await TestUtils.waitForTick();
const img = win.document.getElementById("monitor");
const measure = new Image();
const measureLoad = BrowserTestUtils.waitForEvent(measure, "load");
measure.src =
getComputedStyle(img).listStyleImage.slice(4, -1).replace(/"/g, "");
await measureLoad;
const canvas = win.document.getElementById("screen");
const screenRatio = screen.width / screen.height;
const previewRatio = canvas.clientWidth / canvas.clientHeight;
Assert.equal(img.clientWidth, measure.naturalWidth, "Monitor image correct width");
Assert.equal(img.clientHeight, measure.naturalHeight, "Monitor image correct height");
info(`Screen dimensions are ${screen.width}x${screen.height}`);
info(`Screen's raw ratio is ${screenRatio}`);
info(`Preview dimensions are ${canvas.clientWidth}x${canvas.clientHeight}`);
info(`Preview's raw ratio is ${previewRatio}`);
Assert.ok(
(previewRatio < screenRatio + .01) &&
(previewRatio > screenRatio - .01),
"Preview's aspect ratio is within ±.01 of screen's"
);
win.close();

View File

@ -41,6 +41,32 @@ async function checkEndpointPref() {
}
}
function hasFastClickPageScript() {
const win = window.wrappedJSObject;
if (win.FastClick) {
return true;
}
for (const property in win) {
try {
const proto = win[property].prototype;
if (proto && proto.needsClick) {
return true;
}
} catch (_) {
}
}
return false;
}
function checkForFastClick(tabId) {
return browser.tabs.executeScript(tabId, {
code: `${hasFastClickPageScript};hasFastClickPageScript()`,
}).then(([hasFastClick]) => hasFastClick).catch(() => false);
}
function getWebCompatInfoForTab(tab) {
const {id, url} = tab;
return Promise.all([
@ -50,12 +76,13 @@ function getWebCompatInfoForTab(tab) {
browser.browserInfo.getUpdateChannel(),
browser.browserInfo.hasTouchScreen(),
browser.tabExtras.getWebcompatInfo(id),
checkForFastClick(id),
browser.tabs.captureTab(id, Config.screenshotFormat).catch(e => {
console.error("WebCompat Reporter: getting a screenshot failed", e);
return Promise.resolve(undefined);
}),
]).then(([blockList, buildID, graphicsPrefs, channel, hasTouchScreen,
frameInfo, screenshot]) => {
frameInfo, hasFastClick, screenshot]) => {
if (channel !== "linux") {
delete graphicsPrefs["layers.acceleration.force-enabled"];
}
@ -70,6 +97,7 @@ function getWebCompatInfoForTab(tab) {
buildID,
channel,
consoleLog,
hasFastClick,
hasTouchScreen,
"mixed active content blocked": frameInfo.hasMixedActiveContentBlocked,
"mixed passive content blocked": frameInfo.hasMixedDisplayContentBlocked,
@ -104,6 +132,11 @@ async function openWebCompatTab(compatInfo) {
details,
label: [],
};
if (details.hasFastClick) {
params.label.push("type-fastclick");
} else {
delete details.hasFastClick;
}
if (details["gfx.webrender.all"] || details["gfx.webrender.enabled"]) {
params.label.push("type-webrender-enabled");
}

View File

@ -1,5 +1,7 @@
[DEFAULT]
support-files =
fastclick1.html
fastclick2.html
head.js
test.html
webcompat.html

View File

@ -1,20 +1,6 @@
"use strict";
/* Test that clicking on the Report Site Issue button opens a new tab
and sends a postMessaged blob to it. */
add_task(async function test_opened_page() {
requestLongerTimeout(2);
const serverLanding = await startIssueServer();
// ./head.js sets the value for PREF_WC_REPORTER_ENDPOINT
await SpecialPowers.pushPrefEnv({set: [
[PREF_WC_REPORTER_ENABLED, true],
[PREF_WC_REPORTER_ENDPOINT, serverLanding],
]});
let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
async function clickToReportAndAwaitReportTabLoad() {
await openPageActions();
await isPanelItemEnabled();
@ -28,8 +14,28 @@ add_task(async function test_opened_page() {
}, {once: true});
});
document.getElementById(WC_PAGE_ACTION_PANEL_ID).click();
let tab2 = await newTabPromise;
const tab = await newTabPromise;
await screenshotPromise;
return tab;
}
add_task(async function start_issue_server() {
requestLongerTimeout(2);
const serverLanding = await startIssueServer();
// ./head.js sets the value for PREF_WC_REPORTER_ENDPOINT
await SpecialPowers.pushPrefEnv({set: [
[PREF_WC_REPORTER_ENABLED, true],
[PREF_WC_REPORTER_ENDPOINT, serverLanding],
]});
});
/* Test that clicking on the Report Site Issue button opens a new tab
and sends a postMessaged blob to it. */
add_task(async function test_opened_page() {
let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE);
let tab2 = await clickToReportAndAwaitReportTabLoad();
await ContentTask.spawn(tab2.linkedBrowser, {TEST_PAGE}, async function(args) {
async function isGreen(dataUrl) {
@ -67,6 +73,7 @@ add_task(async function test_opened_page() {
ok(typeof details.buildID == "string", "Details has a buildID string.");
ok(typeof details.channel == "string", "Details has a channel string.");
ok(typeof details.hasTouchScreen == "boolean", "Details has a hasTouchScreen flag.");
ok(typeof details.hasFastClick == "undefined", "Details does not have FastClick if not found.");
ok(typeof details["mixed active content blocked"] == "boolean", "Details has a mixed active content blocked flag.");
ok(typeof details["mixed passive content blocked"] == "boolean", "Details has a mixed passive content blocked flag.");
ok(typeof details["tracking content blocked"] == "string", "Details has a tracking content blocked string.");
@ -85,3 +92,35 @@ add_task(async function test_opened_page() {
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(tab1);
});
add_task(async function test_fastclick_detection1() {
let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, FASTCLICK_TEST_PAGE1);
let tab2 = await clickToReportAndAwaitReportTabLoad();
await ContentTask.spawn(tab2.linkedBrowser, {}, async function(args) {
let doc = content.document;
let detailsParam = doc.getElementById("details").innerText;
const details = JSON.parse(detailsParam);
ok(typeof details == "object", "Details param is a stringified JSON object.");
is(details.hasFastClick, true, "FastClick was found.");
});
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(tab1);
});
add_task(async function test_fastclick_detection2() {
let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, FASTCLICK_TEST_PAGE2);
let tab2 = await clickToReportAndAwaitReportTabLoad();
await ContentTask.spawn(tab2.linkedBrowser, {}, async function(args) {
let doc = content.document;
let detailsParam = doc.getElementById("details").innerText;
const details = JSON.parse(detailsParam);
ok(typeof details == "object", "Details param is a stringified JSON object.");
is(details.hasFastClick, true, "FastClick was found.");
});
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(tab1);
});

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script>
"use strict";
function FastClick() {}
</script>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script>
"use strict";
function ObscuredFastClick() {
}
ObscuredFastClick.prototype = {
needsClick: () => {},
};
window.someRandomVar = new ObscuredFastClick();
</script>

View File

@ -10,6 +10,8 @@ const PREF_WC_REPORTER_ENDPOINT = "extensions.webcompat-reporter.newIssueEndpoin
const TEST_ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
const TEST_PAGE = TEST_ROOT + "test.html";
const FASTCLICK_TEST_PAGE1 = TEST_ROOT + "fastclick1.html";
const FASTCLICK_TEST_PAGE2 = TEST_ROOT + "fastclick2.html";
const NEW_ISSUE_PAGE = TEST_ROOT + "webcompat.html";
const WC_ADDON_ID = "webcompat-reporter@mozilla.org";

View File

@ -9,12 +9,11 @@ browser.jar:
* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
* skin/classic/browser/browser.css
* skin/classic/browser/compacttheme.css
skin/classic/browser/monitor.png
skin/classic/browser/monitor_16-10.png
skin/classic/browser/monitor-base.png
skin/classic/browser/monitor-border.png
* skin/classic/browser/pageInfo.css
skin/classic/browser/pageInfo.png
* skin/classic/browser/searchbar.css
skin/classic/browser/setDesktopBackground.css
skin/classic/browser/slowStartup-16.png
skin/classic/browser/webRTC-indicator.css (../shared/webRTC-indicator.css)
* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -1,18 +0,0 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
html|canvas#screen {
margin: 12px 11px 32px;
}
#monitor {
list-style-image: url("chrome://browser/skin/monitor.png");
}
#monitor[aspectratio="16:10"] {
list-style-image: url("chrome://browser/skin/monitor_16-10.png");
}

View File

@ -17,9 +17,8 @@ browser.jar:
* skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
* skin/classic/browser/downloads/allDownloadsView.css (downloads/allDownloadsView.css)
* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
skin/classic/browser/setDesktopBackground.css
skin/classic/browser/monitor.png
skin/classic/browser/monitor_16-10.png
skin/classic/browser/monitor-base.png
skin/classic/browser/monitor-border.png
skin/classic/browser/notification-icons/geo-blocked.svg (notification-icons/geo-blocked.svg)
skin/classic/browser/notification-icons/geo.svg (notification-icons/geo.svg)
skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -16,6 +16,7 @@
skin/classic/browser/aboutLibrary.css (../shared/aboutLibrary.css)
skin/classic/browser/aboutTabCrashed.css (../shared/aboutTabCrashed.css)
skin/classic/browser/aboutWelcomeBack.css (../shared/aboutWelcomeBack.css)
skin/classic/browser/setDesktopBackground.css (../shared/setDesktopBackground.css)
skin/classic/browser/addons/addon-install-blocked.svg (../shared/addons/addon-install-blocked.svg)
skin/classic/browser/addons/addon-install-confirm.svg (../shared/addons/addon-install-confirm.svg)
skin/classic/browser/addons/addon-install-downloading.svg (../shared/addons/addon-install-downloading.svg)

View File

@ -6,13 +6,11 @@
@namespace html url("http://www.w3.org/1999/xhtml");
html|canvas#screen {
margin: 12px 11px 38px;
border-style: solid;
border-width: 12px 11px;
border-image: url("chrome://browser/skin/monitor-border.png") 12 11 stretch;
}
#monitor {
list-style-image: url("chrome://browser/skin/monitor.png");
}
#monitor[aspectratio="16:10"] {
list-style-image: url("chrome://browser/skin/monitor_16-10.png");
#monitor-base {
list-style-image: url("chrome://browser/skin/monitor-base.png");
}

View File

@ -9,12 +9,11 @@ browser.jar:
* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
* skin/classic/browser/browser.css
* skin/classic/browser/compacttheme.css
skin/classic/browser/monitor.png
skin/classic/browser/monitor_16-10.png
skin/classic/browser/monitor-base.png
skin/classic/browser/monitor-border.png
skin/classic/browser/pageInfo.css
skin/classic/browser/pageInfo.png
* skin/classic/browser/searchbar.css
skin/classic/browser/setDesktopBackground.css
skin/classic/browser/slowStartup-16.png
skin/classic/browser/webRTC-indicator.css (../shared/webRTC-indicator.css)
* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -1,18 +0,0 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
html|canvas#screen {
margin: 12px 11px 32px;
}
#monitor {
list-style-image: url("chrome://browser/skin/monitor.png");
}
#monitor[aspectratio="16:10"] {
list-style-image: url("chrome://browser/skin/monitor_16-10.png");
}

View File

@ -23,9 +23,9 @@
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionDetail.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/FieldPair.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionInstallSection.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/shared/Message.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/SidebarFixedItem.css";

View File

@ -35,6 +35,7 @@
--highlight-50: #0a84ff;
--grey-20: #ededf0; /* for ui, no special semantic */
--grey-30: #d7d7db; /* for ui, no special semantic */
--grey-50: #737373; /* for ui, no special semantic */
--grey-90: #0c0c0d; /* for ui, no special semantic */
--grey-90-a10: rgba(12, 12, 13, 0.1);
--grey-90-a20: rgba(12, 12, 13, 0.2);
@ -184,7 +185,7 @@ p, h1 {
* +--------+-------------+
*/
.main-subheading {
margin-block: calc(var(--base-unit) * 4);
margin-block: calc(var(--base-unit) * 4) 0;
font-size: var(--title-20-font-size); /* Note: this is from Photon Title 20 */
font-weight: var(--title-20-font-weight); /* Note: this is from Photon Title 20 */
@ -250,7 +251,7 @@ p, h1 {
/* a series of button-like elements, layed out horizontally */
.toolbar {
display: flex;
column-gap: var(--base-unit);
column-gap: calc(var(--base-unit) * 3);
}
/*
@ -393,6 +394,7 @@ Form controls
background-color: var(--white-100); /* from common.inc.css */
border-radius: var(--base-unit); /* from common.inc.css */
box-shadow: 0 1px 4px var(--grey-90-a10); /* from common.inc.css */
padding-block: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
}
.card__heading {

View File

@ -13,17 +13,12 @@ const Localized = createFactory(FluentReact.Localized);
const ConnectionPromptSetting = createFactory(require("./ConnectionPromptSetting"));
const ExtensionDebugSetting = createFactory(require("./ExtensionDebugSetting"));
const TemporaryExtensionInstaller =
createFactory(require("./debugtarget/TemporaryExtensionInstaller"));
const Actions = require("../actions/index");
const { DEBUG_TARGET_PANE, RUNTIMES } = require("../constants");
const { RUNTIMES } = require("../constants");
const Types = require("../types/index");
const {
isExtensionDebugSettingNeeded,
isSupportedDebugTargetPane,
} = require("../modules/debug-target-support");
const { isExtensionDebugSettingNeeded } = require("../modules/debug-target-support");
class RuntimeActions extends PureComponent {
static get propTypes() {
@ -80,14 +75,6 @@ class RuntimeActions extends PureComponent {
: null;
}
renderTemporaryExtensionInstaller() {
const { dispatch, runtimeDetails } = this.props;
const { type } = runtimeDetails.info;
return isSupportedDebugTargetPane(type, DEBUG_TARGET_PANE.TEMPORARY_EXTENSION)
? TemporaryExtensionInstaller({ dispatch })
: null;
}
render() {
return dom.div(
{},
@ -95,7 +82,6 @@ class RuntimeActions extends PureComponent {
{
className: "runtime-actions__toolbar",
},
this.renderTemporaryExtensionInstaller(),
this.renderProfileButton(),
this.renderConnectionPromptSetting(),
),

View File

@ -17,20 +17,23 @@ const DebugTargetPane = createFactory(require("./debugtarget/DebugTargetPane"));
const ExtensionAction = createFactory(require("./debugtarget/ExtensionAction"));
const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
const InspectAction = createFactory(require("./debugtarget/InspectAction"));
const Message = createFactory(require("./shared/Message"));
const ProfilerDialog = createFactory(require("./ProfilerDialog"));
const RuntimeActions = createFactory(require("./RuntimeActions"));
const RuntimeInfo = createFactory(require("./RuntimeInfo"));
const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction"));
const ServiceWorkerAdditionalActions =
createFactory(require("./debugtarget/ServiceWorkerAdditionalActions"));
const ServiceWorkersWarning = createFactory(require("./ServiceWorkersWarning"));
const TabDetail = createFactory(require("./debugtarget/TabDetail"));
const TemporaryExtensionAction = createFactory(require("./debugtarget/TemporaryExtensionAction"));
const TemporaryExtensionAdditionalActions =
createFactory(require("./debugtarget/TemporaryExtensionAdditionalActions"));
const TemporaryExtensionDetail = createFactory(require("./debugtarget/TemporaryExtensionDetail"));
const TemporaryExtensionInstallSection =
createFactory(require("./debugtarget/TemporaryExtensionInstallSection"));
const WorkerDetail = createFactory(require("./debugtarget/WorkerDetail"));
const Actions = require("../actions/index");
const { DEBUG_TARGETS, DEBUG_TARGET_PANE, MESSAGE_LEVEL, PAGE_TYPES } =
require("../constants");
const { DEBUG_TARGETS, DEBUG_TARGET_PANE, PAGE_TYPES } = require("../constants");
const Types = require("../types/index");
const { getCurrentRuntimeDetails } = require("../modules/runtimes-state-helper");
@ -74,8 +77,9 @@ class RuntimePage extends PureComponent {
throw new Error(`Unsupported type [${ type }]`);
}
renderDebugTargetPane(name, icon, targets, actionComponent,
detailComponent, paneKey, localizationId) {
renderDebugTargetPane(name, icon, targets, children, actionComponent,
additionalActionsComponent, detailComponent,
paneKey, localizationId) {
const { collapsibilities, dispatch, runtimeDetails } = this.props;
if (!isSupportedDebugTargetPane(runtimeDetails.info.type, paneKey)) {
@ -87,65 +91,31 @@ class RuntimePage extends PureComponent {
id: localizationId,
attrs: { name: true },
},
DebugTargetPane({
actionComponent,
collapsibilityKey: paneKey,
detailComponent,
dispatch,
icon,
isCollapsed: collapsibilities.get(paneKey),
name,
targets,
})
DebugTargetPane(
{
actionComponent,
additionalActionsComponent,
collapsibilityKey: paneKey,
detailComponent,
dispatch,
icon,
isCollapsed: collapsibilities.get(paneKey),
name,
targets,
},
children
)
);
}
renderTemporaryExtensionInstallError() {
const { runtimeDetails, temporaryInstallError } = this.props;
const { type } = runtimeDetails.info;
if (!temporaryInstallError ||
!isSupportedDebugTargetPane(type, DEBUG_TARGET_PANE.TEMPORARY_EXTENSION)) {
renderTemporaryExtensionInstallSection() {
if (!isSupportedDebugTargetPane(this.props.runtimeDetails.info.type,
DEBUG_TARGET_PANE.TEMPORARY_EXTENSION)) {
return null;
}
let errorMessages = [temporaryInstallError.message];
// Additional error messages can be found in additionalErrors.
if (Array.isArray(temporaryInstallError.additionalErrors)) {
errorMessages = errorMessages.concat(temporaryInstallError.additionalErrors);
}
const errors = errorMessages.map((message, index) => {
return dom.div(
{
className: "technical-text",
key: "tmp-extension-install-error-" + index,
},
message
);
});
return Message(
{
level: MESSAGE_LEVEL.ERROR,
},
dom.div(
{
className: "js-tmp-extension-install-error",
},
Localized(
{
id: "about-debugging-tmp-extension-install-error",
},
dom.span(
{ },
"There was an error during the temporary add-on installation"
)
),
errors
)
);
const { dispatch, temporaryInstallError } = this.props;
return TemporaryExtensionInstallSection({ dispatch, temporaryInstallError });
}
render() {
@ -178,46 +148,57 @@ class RuntimePage extends PureComponent {
RuntimeActions({ dispatch, runtimeId, runtimeDetails }),
runtimeDetails.serviceWorkersAvailable ? null : ServiceWorkersWarning(),
CompatibilityWarning({ compatibilityReport }),
this.renderTemporaryExtensionInstallError(),
this.renderDebugTargetPane("Temporary Extensions",
this.getIconByType(DEBUG_TARGETS.EXTENSION),
temporaryExtensions,
TemporaryExtensionAction,
this.renderTemporaryExtensionInstallSection(),
ExtensionAction,
TemporaryExtensionAdditionalActions,
TemporaryExtensionDetail,
DEBUG_TARGET_PANE.TEMPORARY_EXTENSION,
"about-debugging-runtime-temporary-extensions"),
this.renderDebugTargetPane("Extensions",
this.getIconByType(DEBUG_TARGETS.EXTENSION),
installedExtensions,
null,
ExtensionAction,
null,
ExtensionDetail,
DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
"about-debugging-runtime-extensions"),
this.renderDebugTargetPane("Tabs",
this.getIconByType(DEBUG_TARGETS.TAB),
tabs,
null,
InspectAction,
null,
TabDetail,
DEBUG_TARGET_PANE.TAB,
"about-debugging-runtime-tabs"),
this.renderDebugTargetPane("Service Workers",
this.getIconByType(DEBUG_TARGETS.WORKER),
serviceWorkers,
null,
ServiceWorkerAction,
ServiceWorkerAdditionalActions,
WorkerDetail,
DEBUG_TARGET_PANE.SERVICE_WORKER,
"about-debugging-runtime-service-workers"),
this.renderDebugTargetPane("Shared Workers",
this.getIconByType(DEBUG_TARGETS.WORKER),
sharedWorkers,
null,
InspectAction,
null,
WorkerDetail,
DEBUG_TARGET_PANE.SHARED_WORKER,
"about-debugging-runtime-shared-workers"),
this.renderDebugTargetPane("Other Workers",
this.getIconByType(DEBUG_TARGETS.WORKER),
otherWorkers,
null,
InspectAction,
null,
WorkerDetail,
DEBUG_TARGET_PANE.OTHER_WORKER,
"about-debugging-runtime-other-workers"),

View File

@ -6,18 +6,26 @@
* The current layout of debug target item is
*
* +--------+-----------------------------+----------------+
* | [Icon] | Name | Action button |
* | | Name | |
* | [Icon] |-----------------------------| Action button |
* | | Subname | |
* +--------+-----------------------------+----------------+
* | | Detail |
* | | |
* +--------+----------------------------------------------+
* | Detail |
* | |
* +-------------------------------------------------------+
* | Additional actions |
* | |
* +-------------------------------------------------------+
*/
.debug-target-item {
display: grid;
grid-template-columns: calc(var(--base-unit) * 8) 1fr max-content;
grid-template-rows: 1fr minmax(0, auto) auto;
grid-column-gap: calc(var(--base-unit) * 2);
grid-template-areas: "icon name action"
". detail detail";
grid-template-areas: "icon name action"
"icon subname action"
"detail detail detail"
"additional_actions additional_actions additional_actions";
margin-block-end: calc(var(--base-unit) * 4);
padding-block: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
@ -25,23 +33,54 @@
}
.debug-target-item__icon {
align-self: center;
grid-area: icon;
margin-inline-start: calc(var(--base-unit) * 3);
width: 100%;
}
.debug-target-item__name {
align-self: center;
grid-area: name;
font-size: var(--body-20-font-size);
font-weight: var(--body-20-font-weight);
line-height: 1.5;
margin-inline-start: calc(var(--base-unit) * 3);
}
.debug-target-item__action {
grid-area: action;
align-self: center;
margin-inline-end: calc(var(--base-unit) * 2);
}
.debug-target-item__additional_actions {
grid-area: additional_actions;
border-top: 1px solid var(--grey-20);
justify-content: end;
margin-block-start: calc(var(--base-unit) * 3);
padding-block-start: calc(var(--base-unit) * 2);
padding-inline-end: calc(var(--base-unit) * 2);
}
.debug-target-item__detail {
grid-area: detail;
margin-block-start: calc(var(--base-unit) * 3);
}
.debug-target-item__detail--empty {
margin-block-start: var(--base-unit);
}
.debug-target-item__messages {
margin-inline: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
}
.debug-target-item__subname {
grid-area: subname;
color: var(--grey-50);
font-size: var(--caption-20-font-size);
font-weight: var(--caption-20-font-weight);
line-height: 1.5;
margin-inline-start: calc(var(--base-unit) * 3);
}

View File

@ -17,6 +17,7 @@ class DebugTargetItem extends PureComponent {
static get propTypes() {
return {
actionComponent: PropTypes.any.isRequired,
additionalActionsComponent: PropTypes.any,
detailComponent: PropTypes.any.isRequired,
dispatch: PropTypes.func.isRequired,
target: Types.debugTarget.isRequired,
@ -33,14 +34,24 @@ class DebugTargetItem extends PureComponent {
);
}
renderAdditionalActions() {
const { additionalActionsComponent, dispatch, target } = this.props;
if (!additionalActionsComponent) {
return null;
}
return dom.section(
{
className: "debug-target-item__additional_actions toolbar",
},
additionalActionsComponent({ dispatch, target }),
);
}
renderDetail() {
const { detailComponent, target } = this.props;
return dom.div(
{
className: "debug-target-item__detail",
},
detailComponent({ target }),
);
return detailComponent({ target });
}
renderIcon() {
@ -69,6 +80,7 @@ class DebugTargetItem extends PureComponent {
this.renderName(),
this.renderAction(),
this.renderDetail(),
this.renderAdditionalActions(),
);
}
}

View File

@ -3,9 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.debug-target-list {
overflow: hidden;
}
.debug-target-list--collapsed {
max-height: 0;
margin-block-start: calc(var(--base-unit) * 4);
}

View File

@ -4,7 +4,7 @@
"use strict";
const { createFactory, createRef, PureComponent } =
const { createFactory, PureComponent } =
require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
@ -23,36 +23,13 @@ class DebugTargetList extends PureComponent {
static get propTypes() {
return {
actionComponent: PropTypes.any.isRequired,
additionalActionsComponent: PropTypes.any,
detailComponent: PropTypes.any.isRequired,
dispatch: PropTypes.func.isRequired,
isCollapsed: PropTypes.bool.isRequired,
targets: PropTypes.arrayOf(Types.debugTarget).isRequired,
};
}
constructor(props) {
super(props);
this.listRef = createRef();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot === null) {
return;
}
const list = this.listRef.current;
list.animate({ maxHeight: [`${ snapshot }px`, `${ list.clientHeight }px`] },
{ duration: 150, easing: "cubic-bezier(.07, .95, 0, 1)" });
}
getSnapshotBeforeUpdate(prevProps) {
if (this.props.isCollapsed !== prevProps.isCollapsed) {
return this.listRef.current.clientHeight;
}
return null;
}
renderEmptyList() {
return Localized(
{
@ -70,22 +47,27 @@ class DebugTargetList extends PureComponent {
render() {
const {
actionComponent,
additionalActionsComponent,
detailComponent,
dispatch,
isCollapsed,
targets,
} = this.props;
return dom.ul(
{
className: "debug-target-list js-debug-target-list" +
(isCollapsed ? " debug-target-list--collapsed" : ""),
ref: this.listRef,
className: "debug-target-list js-debug-target-list",
},
targets.length === 0
? this.renderEmptyList()
: targets.map((target, key) =>
DebugTargetItem({ actionComponent, detailComponent, dispatch, key, target })),
DebugTargetItem({
actionComponent,
additionalActionsComponent,
detailComponent,
dispatch,
key,
target,
})),
);
}
}

View File

@ -25,3 +25,11 @@
.debug-target-pane__title {
cursor: pointer;
}
.debug-target-pane__collapsable {
overflow: hidden;
}
.debug-target-pane__collapsable--collapsed {
max-height: 0;
}

View File

@ -4,7 +4,7 @@
"use strict";
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const { createFactory, createRef, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
@ -22,6 +22,8 @@ class DebugTargetPane extends PureComponent {
static get propTypes() {
return {
actionComponent: PropTypes.any.isRequired,
additionalActionsComponent: PropTypes.any,
children: PropTypes.node,
collapsibilityKey: PropTypes.string.isRequired,
detailComponent: PropTypes.any.isRequired,
dispatch: PropTypes.func.isRequired,
@ -34,6 +36,35 @@ class DebugTargetPane extends PureComponent {
};
}
constructor(props) {
super(props);
this.collapsableRef = createRef();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot === null) {
return;
}
const el = this.collapsableRef.current;
// Cancel existing animation which is collapsing/expanding.
for (const animation of el.getAnimations()) {
animation.cancel();
}
el.animate({ maxHeight: [`${ snapshot }px`, `${ el.clientHeight }px`] },
{ duration: 150, easing: "cubic-bezier(.07, .95, 0, 1)" });
}
getSnapshotBeforeUpdate(prevProps) {
if (this.props.isCollapsed !== prevProps.isCollapsed) {
return this.collapsableRef.current.clientHeight;
}
return null;
}
toggleCollapsibility() {
const { collapsibilityKey, dispatch, isCollapsed } = this.props;
dispatch(Actions.updateDebugTargetCollapsibility(collapsibilityKey, !isCollapsed));
@ -42,6 +73,8 @@ class DebugTargetPane extends PureComponent {
render() {
const {
actionComponent,
additionalActionsComponent,
children,
detailComponent,
dispatch,
getString,
@ -82,13 +115,22 @@ class DebugTargetPane extends PureComponent {
),
)
),
DebugTargetList({
actionComponent,
detailComponent,
dispatch,
isCollapsed,
targets,
}),
dom.div(
{
className: "debug-target-pane__collapsable js-debug-target-pane__collapsable" +
(isCollapsed ? " debug-target-pane__collapsable--collapsed" : ""),
ref: this.collapsableRef,
},
children,
DebugTargetList({
actionComponent,
additionalActionsComponent,
detailComponent,
dispatch,
isCollapsed,
targets,
}),
),
);
}
}

View File

@ -1,25 +0,0 @@
/* 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/. */
/*
* The current layout of extension detail is
*
* +----------------+--------------------+
* | detail name dt | detail value dd |
* | (auto) | |
* +----------------+--------------------+
* | detail name dt | detail value dd |
* +----------------+--------------------+
* | detail name dt | detail value dd |
* +----------------+--------------------+
*/
.extension-detail {
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: calc(var(--base-unit) * 2);
}
.extension-detail__manifest {
margin-inline-start: 1ch;
}

View File

@ -23,6 +23,7 @@ const Types = require("../../types/index");
class ExtensionDetail extends PureComponent {
static get propTypes() {
return {
children: PropTypes.node,
// Provided by wrapping the component with FluentReact.withLocalization.
getString: PropTypes.func.isRequired,
target: Types.debugTarget.isRequired,
@ -31,9 +32,14 @@ class ExtensionDetail extends PureComponent {
renderWarnings() {
const { warnings } = this.props.target.details;
if (!warnings.length) {
return null;
}
return dom.section(
{
key: "extension-warnings",
className: "debug-target-item__messages",
},
warnings.map((warning, index) => {
return Message(
@ -53,29 +59,11 @@ class ExtensionDetail extends PureComponent {
}
renderUUID() {
const { manifestURL, uuid } = this.props.target.details;
const { uuid } = this.props.target.details;
if (!uuid) {
return null;
}
const value = [
uuid,
Localized(
{
id: "about-debugging-extension-manifest-link",
key: "manifest",
},
dom.a(
{
className: "extension-detail__manifest js-manifest-url",
href: manifestURL,
target: "_blank",
},
"Manifest URL",
)
),
];
return Localized(
{
id: "about-debugging-extension-uuid",
@ -83,9 +71,8 @@ class ExtensionDetail extends PureComponent {
},
FieldPair(
{
slug: "uuid",
label: "Internal UUID",
value,
value: uuid,
}
)
);
@ -101,7 +88,6 @@ class ExtensionDetail extends PureComponent {
},
FieldPair(
{
slug: "extension",
label: "Extension ID",
value: id,
}
@ -122,7 +108,6 @@ class ExtensionDetail extends PureComponent {
},
FieldPair(
{
slug: "location",
label: "Location",
value: location,
}
@ -130,19 +115,50 @@ class ExtensionDetail extends PureComponent {
);
}
renderManifest() {
const { manifestURL } = this.props.target.details;
if (!manifestURL) {
return null;
}
const link = dom.a(
{
className: "js-manifest-url",
href: manifestURL,
target: "_blank",
},
manifestURL,
);
return Localized(
{
id: "about-debugging-extension-manifest-link",
attrs: { label: true },
},
FieldPair(
{
label: "Manifest URL",
value: link,
}
)
);
}
render() {
return [
return dom.section(
{
className: "debug-target-item__detail",
},
this.renderWarnings(),
dom.dl(
{
key: "extension-detail",
className: "extension-detail",
},
{},
this.renderLocation(),
this.renderExtensionId(),
this.renderUUID(),
this.renderManifest(),
this.props.children,
),
];
);
}
}

View File

@ -0,0 +1,28 @@
/* 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/. */
.fieldpair {
display: flex;
border-top: 1px solid var(--grey-20);
padding-block: calc(var(--base-unit) * 2);
padding-inline: calc(var(--base-unit) * 4) calc(var(--base-unit) * 2);
}
.fieldpair:last-child {
padding-block-end: 0;
}
.fieldpair__title {
margin-inline-end: var(--base-unit);
font-size: var(--caption-20-font-size);
font-weight: var(--caption-20-font-weight);
}
.fieldpair__description {
color: var(--grey-50);
flex: 1;
font-size: var(--caption-20-font-size);
font-weight: var(--caption-20-font-weight);
text-align: right;
}

View File

@ -8,36 +8,35 @@ const { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
/* Renders a pair of `<dt>` (label) + `<dd>` (value) field.
* It also needs a 'slug' prop, which it's an ID that will be used to generate
* a React key for the dom element */
/* Renders a pair of `<dt>` (label) + `<dd>` (value) field. */
class FieldPair extends PureComponent {
static get propTypes() {
return {
className: PropTypes.string,
label: PropTypes.node.isRequired,
slug: PropTypes.string.isRequired,
value: PropTypes.node,
};
}
render() {
const { slug, label, value } = this.props;
return [
const { label, value } = this.props;
return dom.div(
{
className: "fieldpair",
},
dom.dt(
{
key: `${slug}-dt`,
className: this.props.className ? this.props.className : "",
className: "fieldpair__title " +
(this.props.className ? this.props.className : ""),
},
label
),
value ? dom.dd(
{
key: `${slug}-dd`,
className: "ellipsis-text",
className: "fieldpair__description ellipsis-text",
},
value) : null,
];
);
}
}

View File

@ -0,0 +1,26 @@
/* 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/. */
.service-worker-action {
display: flex;
column-gap: calc(var(--base-unit) * 2);
}
.service-worker-action__status {
display: flex;
align-items: center;
}
.service-worker-action__status::before {
background-color: var(--grey-50);
border-radius: 100%;
content: "";
height: calc(var(--base-unit) * 2);
margin-inline-end: var(--base-unit);
width: calc(var(--base-unit) * 2);
}
.service-worker-action__status--running::before {
background-color: var(--success-50);
}

View File

@ -16,7 +16,6 @@ const { getCurrentRuntimeDetails } = require("../../modules/runtimes-state-helpe
const InspectAction = createFactory(require("./InspectAction"));
const Actions = require("../../actions/index");
const Types = require("../../types/index");
const { SERVICE_WORKER_STATUSES } = require("../../constants");
@ -27,116 +26,55 @@ class ServiceWorkerAction extends PureComponent {
static get propTypes() {
return {
dispatch: PropTypes.func.isRequired,
// Provided by wrapping the component with FluentReact.withLocalization.
getString: PropTypes.func.isRequired,
// Provided by redux state
runtimeDetails: Types.runtimeDetails.isRequired,
target: Types.debugTarget.isRequired,
};
}
push() {
const { dispatch, target } = this.props;
dispatch(Actions.pushServiceWorker(target.id));
}
start() {
const { dispatch, target } = this.props;
dispatch(Actions.startServiceWorker(target.details.registrationFront));
}
unregister() {
const { dispatch, target } = this.props;
dispatch(Actions.unregisterServiceWorker(target.details.registrationFront));
}
_renderButton({ className, disabled, key, labelId, onClick }) {
return Localized(
{
id: labelId,
key,
},
dom.button(
{
className,
disabled,
onClick: e => onClick(),
},
labelId,
)
);
}
_renderInspectAction() {
const { status } = this.props.target.details;
const shallRenderInspectAction = status === SERVICE_WORKER_STATUSES.RUNNING ||
status === SERVICE_WORKER_STATUSES.REGISTERING;
if (!shallRenderInspectAction) {
return null;
}
return InspectAction({
disabled: this.props.runtimeDetails.isMultiE10s,
dispatch: this.props.dispatch,
key: "service-worker-inspect-action",
target: this.props.target,
});
}
_renderPushButton() {
return this._renderButton({
className: "default-button js-push-button",
disabled: this.props.runtimeDetails.isMultiE10s,
key: "service-worker-push-button",
labelId: "about-debugging-worker-action-push",
onClick: this.push.bind(this),
});
}
_renderStatus() {
const status = this.props.target.details.status.toLowerCase();
const statusClassName = status === SERVICE_WORKER_STATUSES.RUNNING.toLowerCase()
? "service-worker-action__status--running" : "";
_renderStartButton() {
return this._renderButton({
className: "default-button js-start-button",
disabled: this.props.runtimeDetails.isMultiE10s,
key: "service-worker-start-button",
labelId: "about-debugging-worker-action-start",
onClick: this.start.bind(this),
});
}
_renderUnregisterButton() {
return this._renderButton({
className: "default-button js-unregister-button",
key: "service-worker-unregister-button",
labelId: "about-debugging-worker-action-unregister",
onClick: this.unregister.bind(this),
});
}
_renderAction() {
const { status } = this.props.target.details;
switch (status) {
case SERVICE_WORKER_STATUSES.RUNNING:
return [
this._renderUnregisterButton(),
this._renderPushButton(),
this._renderInspectAction(),
];
case SERVICE_WORKER_STATUSES.REGISTERING:
// Only inspect is available if the service worker is not active.
return [
this._renderInspectAction(),
];
case SERVICE_WORKER_STATUSES.STOPPED:
return [
this._renderUnregisterButton(),
this._renderStartButton(),
];
default:
console.error("Unexpected service worker status: " + status);
return [];
}
return Localized(
{
id: "about-debugging-worker-status",
$status: status,
},
dom.span(
{
className:
`service-worker-action__status js-worker-status ${ statusClassName }`,
},
status
)
);
}
render() {
return dom.div(
{
className: "toolbar",
className: "service-worker-action",
},
this._renderAction()
this._renderStatus(),
this._renderInspectAction(),
);
}
}
@ -147,5 +85,4 @@ const mapStateToProps = state => {
};
};
module.exports = FluentReact.withLocalization(
connect(mapStateToProps)(ServiceWorkerAction));
module.exports = connect(mapStateToProps)(ServiceWorkerAction);

View File

@ -0,0 +1,127 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const { getCurrentRuntimeDetails } = require("../../modules/runtimes-state-helper");
const Actions = require("../../actions/index");
const Types = require("../../types/index");
const { SERVICE_WORKER_STATUSES } = require("../../constants");
/**
* This component displays buttons for service worker.
*/
class ServiceWorkerAdditionalActions extends PureComponent {
static get propTypes() {
return {
dispatch: PropTypes.func.isRequired,
// Provided by wrapping the component with FluentReact.withLocalization.
getString: PropTypes.func.isRequired,
// Provided by redux state
runtimeDetails: Types.runtimeDetails.isRequired,
target: Types.debugTarget.isRequired,
};
}
push() {
const { dispatch, target } = this.props;
dispatch(Actions.pushServiceWorker(target.id));
}
start() {
const { dispatch, target } = this.props;
dispatch(Actions.startServiceWorker(target.details.registrationFront));
}
unregister() {
const { dispatch, target } = this.props;
dispatch(Actions.unregisterServiceWorker(target.details.registrationFront));
}
_renderButton({ className, disabled, key, labelId, onClick }) {
return Localized(
{
id: labelId,
key,
},
dom.button(
{
className,
disabled,
onClick: e => onClick(),
},
labelId,
)
);
}
_renderPushButton() {
return this._renderButton({
className: "default-button default-button--micro js-push-button",
disabled: this.props.runtimeDetails.isMultiE10s,
key: "service-worker-push-button",
labelId: "about-debugging-worker-action-push",
onClick: this.push.bind(this),
});
}
_renderStartButton() {
return this._renderButton({
className: "default-button default-button--micro js-start-button",
disabled: this.props.runtimeDetails.isMultiE10s,
key: "service-worker-start-button",
labelId: "about-debugging-worker-action-start",
onClick: this.start.bind(this),
});
}
_renderUnregisterButton() {
return this._renderButton({
className: "default-button default-button--micro js-unregister-button",
key: "service-worker-unregister-button",
labelId: "about-debugging-worker-action-unregister",
onClick: this.unregister.bind(this),
});
}
render() {
const { status } = this.props.target.details;
switch (status) {
case SERVICE_WORKER_STATUSES.RUNNING:
return [
this._renderUnregisterButton(),
this._renderPushButton(),
];
case SERVICE_WORKER_STATUSES.REGISTERING:
return null;
case SERVICE_WORKER_STATUSES.STOPPED:
return [
this._renderUnregisterButton(),
this._renderStartButton(),
];
default:
console.error("Unexpected service worker status: " + status);
return null;
}
}
}
const mapStateToProps = state => {
return {
runtimeDetails: getCurrentRuntimeDetails(state.runtimes),
};
};
module.exports = FluentReact.withLocalization(
connect(mapStateToProps)(ServiceWorkerAdditionalActions));

View File

@ -19,7 +19,8 @@ class TabDetail extends PureComponent {
}
render() {
return dom.div({ className: "ellipsis-text" }, this.props.target.details.url);
return dom.div({ className: "debug-target-item__subname ellipsis-text" },
this.props.target.details.url);
}
}

View File

@ -11,15 +11,13 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const ExtensionAction = createFactory(require("./ExtensionAction"));
const Actions = require("../../actions/index");
const Types = require("../../types/index");
/**
* This component provides components that inspect/reload/remove temporary extension.
* This component provides components that reload/remove temporary extension.
*/
class TemporaryExtensionAction extends PureComponent {
class TemporaryExtensionAdditionalActions extends PureComponent {
static get propTypes() {
return {
dispatch: PropTypes.func.isRequired,
@ -38,20 +36,16 @@ class TemporaryExtensionAction extends PureComponent {
}
render() {
const { dispatch, target } = this.props;
return dom.div(
{
className: "toolbar",
},
ExtensionAction({ dispatch, target }),
return [
Localized(
{
id: "about-debugging-tmp-extension-reload-button",
key: "reload-button",
},
dom.button(
{
className: "default-button js-temporary-extension-reload-button",
className: "default-button default-button--micro " +
"js-temporary-extension-reload-button",
onClick: e => this.reload(),
},
"Reload",
@ -60,17 +54,19 @@ class TemporaryExtensionAction extends PureComponent {
Localized(
{
id: "about-debugging-tmp-extension-remove-button",
key: "remove-button",
},
dom.button(
{
className: "default-button js-temporary-extension-remove-button",
className: "default-button default-button--micro " +
"js-temporary-extension-remove-button",
onClick: e => this.remove(),
},
"Remove",
)
),
);
];
}
}
module.exports = TemporaryExtensionAction;
module.exports = TemporaryExtensionAdditionalActions;

View File

@ -12,6 +12,7 @@ const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const ExtensionDetail = createFactory(require("./ExtensionDetail"));
const FieldPair = createFactory(require("./FieldPair"));
const Types = require("../../types/index");
@ -41,17 +42,17 @@ class TemporaryExtensionDetail extends PureComponent {
}),
},
dom.div({
className: "temporary-extension-detail__temporary-id-message " +
"js-temporary-id-message",
className: "js-temporary-id-message",
}),
);
}
render() {
return dom.div(
{},
this.renderTemporaryIdMessage(),
ExtensionDetail({ target: this.props.target }),
return ExtensionDetail(
{
target: this.props.target,
},
FieldPair({ label: this.renderTemporaryIdMessage() }),
);
}
}

View File

@ -2,6 +2,7 @@
* 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/. */
.temporary-extension-detail__temporary-id-message {
padding: calc(var(--base-unit) * 2) 0;
}
.temporary-extension-install-section__toolbar {
display: flex;
justify-content: end;
}

View File

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const Message = createFactory(require("../shared/Message"));
const TemporaryExtensionInstaller =
createFactory(require("./TemporaryExtensionInstaller"));
const { MESSAGE_LEVEL } = require("../../constants");
/**
* This component provides an installer and error message area for temporary extension.
*/
class TemporaryExtensionInstallSection extends PureComponent {
static get propTypes() {
return {
dispatch: PropTypes.func.isRequired,
temporaryInstallError: PropTypes.object,
};
}
renderError() {
const { temporaryInstallError } = this.props;
if (!temporaryInstallError) {
return null;
}
const errorMessages = [
temporaryInstallError.message,
...(temporaryInstallError.additionalErrors || []),
];
const errors = errorMessages.map((message, index) => {
return dom.p(
{
className: "technical-text",
key: "tmp-extension-install-error-" + index,
},
message
);
});
return Message(
{
level: MESSAGE_LEVEL.ERROR,
className: "js-tmp-extension-install-error",
},
Localized(
{
id: "about-debugging-tmp-extension-install-error",
},
dom.p(
{ },
"There was an error during the temporary add-on installation"
)
),
errors,
);
}
render() {
const { dispatch } = this.props;
return dom.section(
{},
dom.div(
{
className: "temporary-extension-install-section__toolbar",
},
TemporaryExtensionInstaller({ dispatch }),
),
this.renderError(),
);
}
}
module.exports = TemporaryExtensionInstallSection;

View File

@ -1,23 +0,0 @@
/* 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/. */
/*
* The current layout of worker detail is
*
* +----------------+--------------------+
* | detail name dt | detail value dd |
* | (auto ) | |
* +----------------+--------------------+
* | detail name dt | detail value dd |
* +----------------+--------------------+
* | detail name dt | detail value dd |
* +----------------+--------------------+
* | [status] |
* +----------------+
*/
.worker-detail {
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: calc(4 * var(--base-unit));
}

View File

@ -99,40 +99,19 @@ class WorkerDetail extends PureComponent {
);
}
renderStatus() {
const status = this.props.target.details.status.toLowerCase();
return FieldPair(
{
slug: "status",
label: Localized(
{
id: "about-debugging-worker-status",
$status: status,
},
dom.span(
{
className: `badge js-worker-status ` +
`${status === "running" ? "badge--success" : ""}`,
},
status
)
),
}
);
}
render() {
const { fetch, pushServiceEndpoint, scope, status } = this.props.target.details;
const { fetch, pushServiceEndpoint, scope } = this.props.target.details;
const isEmptyList = !pushServiceEndpoint && !fetch && !scope && !status;
return dom.dl(
{
className: "worker-detail",
className: "debug-target-item__detail" +
(isEmptyList ? " debug-target-item__detail--empty" : ""),
},
pushServiceEndpoint ? this.renderPushService() : null,
fetch ? this.renderFetch() : null,
scope ? this.renderScope() : null,
status ? this.renderStatus() : null,
);
}
}

View File

@ -10,16 +10,18 @@ DevToolsModules(
'DebugTargetPane.css',
'DebugTargetPane.js',
'ExtensionAction.js',
'ExtensionDetail.css',
'ExtensionDetail.js',
'FieldPair.css',
'FieldPair.js',
'InspectAction.js',
'ServiceWorkerAction.css',
'ServiceWorkerAction.js',
'ServiceWorkerAdditionalActions.js',
'TabDetail.js',
'TemporaryExtensionAction.js',
'TemporaryExtensionDetail.css',
'TemporaryExtensionAdditionalActions.js',
'TemporaryExtensionDetail.js',
'TemporaryExtensionInstaller.js',
'WorkerDetail.css',
'TemporaryExtensionInstallSection.css',
'TemporaryExtensionInstallSection.js',
'WorkerDetail.js',
)

View File

@ -26,17 +26,19 @@ class Message extends PureComponent {
static get propTypes() {
return {
children: PropTypes.node.isRequired,
className: PropTypes.string,
level: PropTypes.oneOf(Object.values(MESSAGE_LEVEL)).isRequired,
};
}
render() {
const { level, children } = this.props;
const { children, className, level } = this.props;
const iconSrc = ICONS[level];
return dom.aside(
{
className: `message message--level-${level} js-message`,
className: `message message--level-${level} js-message` +
(className ? ` ${ className }` : ""),
},
dom.img(
{

View File

@ -33,12 +33,12 @@ async function assertDebugTargetCollapsed(paneEl, title) {
info("Check debug target is collapsed");
// check list height
const listEl = paneEl.querySelector(".js-debug-target-list");
is(listEl.clientHeight, 0, "Height of list element is zero");
const targetEl = paneEl.querySelector(".js-debug-target-pane__collapsable");
is(targetEl.clientHeight, 0, "Height of list element is zero");
// check title
const titleEl = paneEl.querySelector(".js-debug-target-pane-title");
const expectedTitle =
`${ title } (${ listEl.querySelectorAll(".js-debug-target-item").length })`;
`${ title } (${ targetEl.querySelectorAll(".js-debug-target-item").length })`;
is(titleEl.textContent, expectedTitle, "Collapsed title is correct");
}
@ -46,12 +46,12 @@ async function assertDebugTargetExpanded(paneEl, title) {
info("Check debug target is expanded");
// check list height
const listEl = paneEl.querySelector(".js-debug-target-list");
await waitUntil(() => listEl.clientHeight > 0);
const targetEl = paneEl.querySelector(".js-debug-target-pane__collapsable");
await waitUntil(() => targetEl.clientHeight > 0);
ok(true, "Height of list element is greater than zero");
// check title
const titleEl = paneEl.querySelector(".js-debug-target-pane-title");
const expectedTitle =
`${ title } (${ listEl.querySelectorAll(".js-debug-target-item").length })`;
`${ title } (${ targetEl.querySelectorAll(".js-debug-target-item").length })`;
is(titleEl.textContent, expectedTitle, "Expanded title is correct");
}

View File

@ -102,17 +102,23 @@ about-debugging-connect-network-disabled = Network location support currently un
# Below are the titles for the various categories of debug targets that can be found
# on "runtime" pages of about:debugging.
# Title of the temporary extensions category (only available for "This Firefox" runtime).
about-debugging-runtime-temporary-extensions = Temporary Extensions
about-debugging-runtime-temporary-extensions =
.name = Temporary Extensions
# Title of the extensions category.
about-debugging-runtime-extensions = Extensions
about-debugging-runtime-extensions =
.name = Extensions
# Title of the tabs category.
about-debugging-runtime-tabs = Tabs
about-debugging-runtime-tabs =
.name = Tabs
# Title of the service workers category.
about-debugging-runtime-service-workers = Service Workers
about-debugging-runtime-service-workers =
.name = Service Workers
# Title of the shared workers category.
about-debugging-runtime-shared-workers = Shared Workers
about-debugging-runtime-shared-workers =
.name = Shared Workers
# Title of the other workers category.
about-debugging-runtime-other-workers = Other Workers
about-debugging-runtime-other-workers =
.name = Other Workers
# Label of the button opening the performance profiler panel in runtime pages for remote
# runtimes.

View File

@ -378,6 +378,16 @@ charts.time=Time
# time of request.
charts.nonBlockingTime=Non blocking time
# LOCALIZATION NOTE (netRequest.originalFileURL.tooltip): This is the tooltip
# displayed for the file's original URL value displayed in the file column of
# a request.
netRequest.originalFileURL.tooltip=Original: %S
# LOCALIZATION NOTE (netRequest.decodedFileURL.tooltip): This is the tooltip
# displayed for the file's decoded URL value displayed in the file column of
# a request.
netRequest.decodedFileURL.tooltip=Decoded: %S
# LOCALIZATION NOTE (netRequest.headers): A label used for Headers tab
# This tab displays list of HTTP headers
netRequest.headers=Headers

View File

@ -379,8 +379,8 @@ class HeadersPanel extends Component {
);
// not showing #hash in url
const summaryUrl = urlDetails.unicodeUrl ?
this.renderSummary(SUMMARY_URL, urlDetails.unicodeUrl.split("#")[0]) : null;
const summaryUrl = urlDetails.url ?
this.renderSummary(SUMMARY_URL, urlDetails.url.split("#")[0]) : null;
const summaryMethod = method ?
this.renderSummary(SUMMARY_METHOD, method) : null;

View File

@ -6,6 +6,7 @@
const { Component } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { L10N } = require("../utils/l10n");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { propertiesEqual } = require("../utils/request-utils");
@ -29,12 +30,21 @@ class RequestListColumnFile extends Component {
item: { urlDetails },
} = this.props;
const originalFileURL = urlDetails.url;
const decodedFileURL = urlDetails.unicodeUrl;
const ORIGINAL_FILE_URL = L10N.getFormatStr("netRequest.originalFileURL.tooltip",
originalFileURL);
const DECODED_FILE_URL = L10N.getFormatStr("netRequest.decodedFileURL.tooltip",
decodedFileURL);
const fileToolTip = originalFileURL === decodedFileURL ?
originalFileURL : ORIGINAL_FILE_URL + "\n\n" + DECODED_FILE_URL;
return (
dom.td({
className: "requests-list-column requests-list-file",
title: urlDetails.unicodeUrl,
title: fileToolTip,
},
urlDetails.baseNameWithQuery
originalFileURL
)
);
}

View File

@ -300,6 +300,7 @@ function getUrlDetails(url) {
scheme,
unicodeUrl,
isLocal,
url,
};
}

View File

@ -44,7 +44,7 @@ const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.conca
const EXPECTED_REQUESTS = [
{
method: "GET",
url: getSjsURLInUnicodeIdn() + "?fmt=html",
url: getSjsURLInUnicodeIdn() + "?fmt=html&res=undefined&text=Sample",
data: {
fuzzyUrl: true,
status: 200,
@ -55,7 +55,7 @@ const EXPECTED_REQUESTS = [
},
{
method: "GET",
url: CONTENT_TYPE_SJS + "?fmt=css",
url: CONTENT_TYPE_SJS + "?fmt=css&text=sample",
data: {
fuzzyUrl: true,
status: 200,
@ -66,7 +66,7 @@ const EXPECTED_REQUESTS = [
},
{
method: "GET",
url: CONTENT_TYPE_SJS + "?fmt=js",
url: CONTENT_TYPE_SJS + "?fmt=js&text=sample",
data: {
fuzzyUrl: true,
status: 200,
@ -77,7 +77,7 @@ const EXPECTED_REQUESTS = [
},
{
method: "GET",
url: CONTENT_TYPE_SJS + "?fmt=html",
url: CONTENT_TYPE_SJS + `?fmt=html&text=${ENCODED_CHARS_IN_URI_COMP}`,
data: {
fuzzyUrl: true,
status: 200,
@ -88,7 +88,7 @@ const EXPECTED_REQUESTS = [
},
{
method: "GET",
url: CONTENT_TYPE_SJS + "?fmt=css",
url: CONTENT_TYPE_SJS + `?fmt=css&text=${ENCODED_CHARS_IN_URI_COMP}`,
data: {
fuzzyUrl: true,
status: 200,
@ -99,7 +99,7 @@ const EXPECTED_REQUESTS = [
},
{
method: "GET",
url: CONTENT_TYPE_SJS + "?fmt=js",
url: CONTENT_TYPE_SJS + `?fmt=js&text=${ENCODED_CHARS_IN_URI_COMP}`,
data: {
fuzzyUrl: true,
status: 200,

View File

@ -43,7 +43,7 @@ add_task(async function() {
{
// request #1
method: "GET",
uri: STATUS_CODES_SJS + "?sts=200#doh",
uri: STATUS_CODES_SJS + "?sts=200",
correctUri: STATUS_CODES_SJS + "?sts=200",
details: {
status: 202,

View File

@ -30,12 +30,11 @@ const {
} = require("devtools/client/shared/unicode-url");
const {
getFormattedProtocol,
getUrlBaseName,
getUrlHost,
getUrlQuery,
getUrlScheme,
} = require("devtools/client/netmonitor/src/utils/request-utils");
const { EVENTS } = require("devtools/client/netmonitor/src/constants");
const { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
/* eslint-disable no-unused-vars, max-len */
const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
@ -440,8 +439,12 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
const target = document.querySelectorAll(".request-list-item")[visibleIndex];
// Bug 1414981 - Request URL should not show #hash
const unicodeUrl = getUnicodeUrl(url.split("#")[0]);
const name = getUrlBaseName(url);
const query = getUrlQuery(url);
const ORIGINAL_FILE_URL = L10N.getFormatStr("netRequest.originalFileURL.tooltip",
url);
const DECODED_FILE_URL = L10N.getFormatStr("netRequest.decodedFileURL.tooltip",
unicodeUrl);
const fileToolTip = url === unicodeUrl ?
url : ORIGINAL_FILE_URL + "\n\n" + DECODED_FILE_URL;
const host = getUnicodeHostname(getUrlHost(url));
const scheme = getUrlScheme(url);
const {
@ -469,16 +472,16 @@ function verifyRequestItemTarget(document, requestList, requestItem, method,
if (fuzzyUrl) {
ok(target.querySelector(".requests-list-file").textContent.startsWith(
name + (query ? "?" + query : "")), "The displayed file is correct.");
url), "The displayed file is correct.");
ok(target.querySelector(".requests-list-file").getAttribute("title")
.startsWith(unicodeUrl),
.startsWith(fileToolTip),
"The tooltip file is correct.");
} else {
is(target.querySelector(".requests-list-file").textContent,
decodeURIComponent(name + (query ? "?" + query : "")),
url,
"The displayed file is correct.");
is(target.querySelector(".requests-list-file").getAttribute("title"),
unicodeUrl, "The tooltip file is correct.");
fileToolTip, "The tooltip file is correct.");
}
is(target.querySelector(".requests-list-protocol").textContent,

View File

@ -53,14 +53,6 @@ pref("devtools.inspector.showUserAgentShadowRoots", false);
// Enable the new Rules View
pref("devtools.inspector.new-rulesview.enabled", false);
// Flexbox preferences
// Whether or not to show the combined flexbox and box model highlighter.
#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
pref("devtools.inspector.flexboxHighlighter.combine", true);
#else
pref("devtools.inspector.flexboxHighlighter.combine", false);
#endif
// Grid highlighter preferences
pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
pref("devtools.gridinspector.gridOutlineMaxRows", 50);

View File

@ -37,7 +37,8 @@ stubPreparedMessages.set("GET request", new NetworkEventMessage({
"host": "example.com",
"scheme": "http",
"unicodeUrl": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html",
"isLocal": null
"isLocal": null,
"url": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html"
},
"method": "GET",
"cause": {
@ -83,7 +84,8 @@ stubPreparedMessages.set("GET request update", new NetworkEventMessage({
"host": "example.com",
"scheme": "http",
"unicodeUrl": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html",
"isLocal": null
"isLocal": null,
"url": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html"
},
"method": "GET"
}));
@ -112,7 +114,8 @@ stubPreparedMessages.set("XHR GET request", new NetworkEventMessage({
"host": "example.com",
"scheme": "http",
"unicodeUrl": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html",
"isLocal": null
"isLocal": null,
"url": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html"
},
"method": "GET",
"cause": {
@ -158,7 +161,8 @@ stubPreparedMessages.set("XHR GET request update", new NetworkEventMessage({
"host": "example.com",
"scheme": "http",
"unicodeUrl": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html",
"isLocal": null
"isLocal": null,
"url": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html"
},
"method": "GET"
}));
@ -187,7 +191,8 @@ stubPreparedMessages.set("XHR POST request", new NetworkEventMessage({
"host": "example.com",
"scheme": "http",
"unicodeUrl": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html",
"isLocal": null
"isLocal": null,
"url": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html"
},
"method": "POST",
"cause": {
@ -233,7 +238,8 @@ stubPreparedMessages.set("XHR POST request update", new NetworkEventMessage({
"host": "example.com",
"scheme": "http",
"unicodeUrl": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html",
"isLocal": null
"isLocal": null,
"url": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/inexistent.html"
},
"method": "POST"
}));

View File

@ -67,12 +67,6 @@
z-index: 1;
}
:-moz-native-anonymous .highlighter-container.flexbox {
/* Make the flexbox highlighter have a z-index greater than the box-model so
it always sits above it. */
z-index: 2;
}
:-moz-native-anonymous .highlighter-container [hidden] {
display: none;
}
@ -87,10 +81,6 @@
opacity: 0.6;
}
:-moz-native-anonymous .box-model-regions [half-faded] {
opacity: 0.5;
}
/* Box model regions can be faded (see the onlyRegionArea option in
highlighters.js) in order to only display certain regions. */
:-moz-native-anonymous .box-model-regions [faded] {
@ -152,9 +142,6 @@
text-shadow: none;
border: 1px solid var(--highlighter-bubble-border-color);
/* The infobar should always be above every other highlighter when it is visible */
z-index: 2147483647;
}
/* Arrows */

View File

@ -5,7 +5,6 @@
"use strict";
const { AutoRefreshHighlighter } = require("./auto-refresh");
const Services = require("Services");
const {
CanvasFrameAnonymousContentHelper,
createNode,
@ -22,9 +21,6 @@ const {
const { getNodeDisplayName } = require("devtools/server/actors/inspector/utils");
const nodeConstants = require("devtools/shared/dom-node-constants");
loader.lazyRequireGetter(this, "FlexboxHighlighter",
"devtools/server/actors/highlighters/flexbox", true);
// Note that the order of items in this array is important because it is used
// for drawing the BoxModelHighlighter's path elements correctly.
const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
@ -34,8 +30,6 @@ const GUIDE_STROKE_WIDTH = 1;
// FIXME: add ":visited" and ":link" after bug 713106 is fixed
const PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-within"];
const FLEXBOX_HIGHLIGHTER_COMBINE_PREF = "devtools.inspector.flexboxHighlighter.combine";
/**
* The BoxModelHighlighter draws the box model regions on top of a node.
* If the node is a block box, then each region will be displayed as 1 polygon.
@ -119,21 +113,6 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
pageListenerTarget.addEventListener("pagehide", this.onPageHide);
}
/**
* Check whether we should show the combined flexbox highlighter. Because
* checking for prefs is slow and performance is paramount for the highlighter
* we cache the result. Because we cache the result the Toolbox needs to be
* closed and opened again for any pref changes to take affect.
*/
get showCombinedFlexboxHighlighter() {
if (typeof this._showCombinedFlexboxHighlighter === "undefined") {
this._showCombinedFlexboxHighlighter =
Services.prefs.getBoolPref(FLEXBOX_HIGHLIGHTER_COMBINE_PREF);
}
return this._showCombinedFlexboxHighlighter;
}
_buildMarkup() {
const doc = this.win.document;
@ -294,22 +273,9 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
this.markup.destroy();
if (this._flexboxHighlighter) {
this._flexboxHighlighter.destroy();
this._flexboxHighlighter = null;
}
AutoRefreshHighlighter.prototype.destroy.call(this);
}
get flexboxHighlighter() {
if (!this._flexboxHighlighter) {
this._flexboxHighlighter = new FlexboxHighlighter(this.highlighterEnv);
}
return this._flexboxHighlighter;
}
getElement(id) {
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
}
@ -367,10 +333,6 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
let shown = false;
setIgnoreLayoutChanges(true);
// We need to set this option before calling _updateBoxModel().
this.options.isFlexboxContainer =
!!(node && node.getAsFlexContainer && node.getAsFlexContainer());
if (this._updateBoxModel()) {
// Show the infobar only if configured to do so and the node is an element or a text
// node.
@ -388,91 +350,11 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
this._hide();
}
if (this.showCombinedFlexboxHighlighter) {
this._updateFlexboxHighlighter();
}
setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
return shown;
}
/**
* Update the flexbox highlighter on the current highlighted node. Show it
* if the current node is a flexbox container or flexbox item.
*/
_updateFlexboxHighlighter() {
this._hideFlexboxHighlighter();
if (!this.currentNode) {
return;
}
const options = {};
let node = this.currentNode;
let showFlexboxHighlighter = false;
// If the current node is a flexbox container then remove the box model
// content region and make the other regions a little more transparent so
// that the flexbox highlighting is emphasized.
if (this.options.isFlexboxContainer) {
for (const region of BOX_MODEL_REGIONS) {
const box = this.getElement(region);
if (region === "content") {
// Hide the content region.
box.removeAttribute("d");
} else {
// Make the non-content regions a little more transparent.
box.setAttribute("half-faded", "");
}
}
// Stop the flexbox highlighter from showing an outline (the guides do a
// great job of that themselves).
options.noContainerOutline = true;
// Toggle the flag to show the flexbox highlighter.
showFlexboxHighlighter = true;
} else {
// The highlighted element is not a flexbox container so we need to check
// if it is a flex item.
const container = node.parentFlexElement;
if (container) {
for (const region of BOX_MODEL_REGIONS) {
const box = this.getElement(region);
// Ensure that the box model regions are not faded. The content region
// will reappear because it is regenerated by the highlighter.
box.setAttribute("half-faded", "");
}
// Hide the guides because we are only interested in the flex item's
// box model regions in relation to the flexbox overlay (and to make
// things less ugly).
this._hideGuides();
node = container;
// Toggle the flag to show the flexbox highlighter.
showFlexboxHighlighter = true;
}
}
if (showFlexboxHighlighter) {
// If the flag is set then show the flexbox highlighter.
this.flexboxHighlighter.show(node, options);
} else {
// Otherwise ensure that the box model regions are not faded.
for (const region of BOX_MODEL_REGIONS) {
const box = this.getElement(region);
box.removeAttribute("half-faded");
}
}
}
_scrollUpdate() {
this._moveInfobar();
}
@ -486,20 +368,10 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
this._untrackMutations();
this._hideBoxModel();
this._hideInfobar();
this._hideFlexboxHighlighter();
setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
}
/**
* Hide the Flexbox highlighter.
*/
_hideFlexboxHighlighter() {
if (this._flexboxHighlighter) {
this.flexboxHighlighter.hide();
}
}
/**
* Hide the infobar
*/
@ -651,11 +523,9 @@ class BoxModelHighlighter extends AutoRefreshHighlighter {
_getBoxPathCoordinates(boxQuad, nextBoxQuad) {
const {p1, p2, p3, p4} = boxQuad;
const isFlexboxContainer = this.options.isFlexboxContainer;
let path;
if ((isFlexboxContainer && !nextBoxQuad) ||
(!isFlexboxContainer && (!nextBoxQuad || !this.options.onlyRegionArea))) {
if (!nextBoxQuad || !this.options.onlyRegionArea) {
// If this is the content box (inner-most box) or if we're not being asked
// to highlight only region areas, then draw a simple rectangle.
path = "M" + p1.x + "," + p1.y + " " +

View File

@ -62,9 +62,6 @@ const JUSTIFY_CONTENT = "justify-content";
*
* @param {String} options.color
* The color that should be used to draw the highlighter for this flexbox.
* @param {Boolean} options.noCountainerOutline
* Prevent drawing an outline around the flex container.
*
* Structure:
* <div class="highlighter-container">
* <div id="flexbox-root" class="flexbox-root">
@ -111,7 +108,7 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
_buildMarkup() {
const container = createNode(this.win, {
attributes: {
"class": "highlighter-container flexbox",
"class": "highlighter-container",
},
});
@ -432,8 +429,6 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
});
this.ctx.fillStyle = this.getFlexContainerPattern(devicePixelRatio);
this.ctx.strokeStyle =
this.options.noContainerOutline ? "transparent" : this.color;
drawRect(this.ctx, 0, 0, width, height, this.currentMatrix);

View File

@ -84,6 +84,8 @@ const gFunctions = [
[2, (n) => n!=0?1:0],
// 18: Welsh
[6, (n) => n==0?0:n==1?1:n==2?2:n==3?3:n==6?4:5],
// 19: Bosnian, Croatian, Serbian
[3, (n) => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2],
];
const PluralForm = {

View File

@ -1498,13 +1498,6 @@ bool KeyframeEffect::ShouldBlockAsyncTransformAnimations(
}
}
// FIXME: Bug 1425837: drop this hack.
// XXX cku temporarily disable async-animation when this frame has any
// individual transforms before bug 1425837 been fixed.
if (aFrame->StyleDisplay()->HasIndividualTransform()) {
return true;
}
return false;
}
@ -1589,7 +1582,8 @@ void KeyframeEffect::CalculateCumulativeChangeHint(
// on invisible elements because we can't calculate the change hint for
// such properties until we compose it.
if (!segment.HasReplaceableValues()) {
if (property.mProperty != eCSSProperty_transform) {
if (!nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
property.mProperty)) {
mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
return;
}

View File

@ -2,6 +2,7 @@
prefs =
dom.animations-api.compositing.enabled=true
gfx.omta.background-color=true
layout.css.individual-transform.enabled=true
support-files =
testcommon.js
../../imptests/testharness.js

View File

@ -1057,5 +1057,90 @@ promise_test(async t => {
}, 'background-color animation does not run on the compositor if the pref ' +
'is disabled');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ translate: ['0px', '100px'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'translate animation should be running on the compositor');
}, 'translate animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ rotate: ['0deg', '45deg'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'rotate animation should be running on the compositor');
}, 'rotate animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ scale: ['1 1', '2 2'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'scale animation should be running on the compositor');
}, 'scale animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ translate: ['0px', '100px'],
rotate: ['0deg', '45deg'],
transform: ['translate(20px)',
'translate(30px)'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_running_on_compositor(animation,
'multiple transform-like properties animation should be running on the ' +
'compositor');
const properties = animation.effect.getProperties();
properties.forEach(property => {
assert_true(property.runningOnCompositor,
property.property + ' is running on the compositor');
});
}, 'Multiple transform-like properties animation runs on the compositor');
promise_test(async t => {
const div = addDiv(t);
const animation = div.animate({ translate: ['0px', '100px'],
rotate: ['0deg', '45deg'],
transform: ['translate(20px)',
'translate(30px)'] },
100 * MS_PER_SEC);
div.style.setProperty('translate', '50px', 'important');
getComputedStyle(div).translate;
await waitForAnimationReadyToRestyle(animation);
await waitForPaints();
assert_animation_is_not_running_on_compositor(animation,
'Animation overridden by an !important rule reports that it is ' +
'NOT running on the compositor');
const properties = animation.effect.getProperties();
properties.forEach(property => {
assert_true(!property.runningOnCompositor,
property.property + ' is not running on the compositor');
});
}, 'Multiple transform-like properties animation does not runs on the ' +
'compositor because one of the transform-like property is overridden ' +
'by an !important rule');
</script>
</body>

View File

@ -1782,6 +1782,66 @@ waitForAllPaints(() => {
await ensureElementRemoval(div);
});
add_task(async function restyling_translate_animations_on_invisible_element() {
const div = addDiv(null, { style: 'visibility: hidden;' });
const animation =
div.animate([ { translate: '100px' } ],
{ duration: 100 * MS_PER_SEC,
iterations: Infinity });
await waitForAnimationReadyToRestyle(animation);
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
const markers = await observeStyling(5);
is(markers.length, 0,
'Translate animations without 100% keyframe on visibility hidden ' +
'element should be throttled');
await ensureElementRemoval(div);
});
add_task(async function restyling_rotate_animations_on_invisible_element() {
const div = addDiv(null, { style: 'visibility: hidden;' });
const animation =
div.animate([ { rotate: '45deg' } ],
{ duration: 100 * MS_PER_SEC,
iterations: Infinity });
await waitForAnimationReadyToRestyle(animation);
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
const markers = await observeStyling(5);
is(markers.length, 0,
'Rotate animations without 100% keyframe on visibility hidden ' +
'element should be throttled');
await ensureElementRemoval(div);
});
add_task(async function restyling_scale_animations_on_invisible_element() {
const div = addDiv(null, { style: 'visibility: hidden;' });
const animation =
div.animate([ { scale: '2 2' } ],
{ duration: 100 * MS_PER_SEC,
iterations: Infinity });
await waitForAnimationReadyToRestyle(animation);
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
const markers = await observeStyling(5);
is(markers.length, 0,
'Scale animations without 100% keyframe on visibility hidden ' +
'element should be throttled');
await ensureElementRemoval(div);
});
add_task(
async function restyling_transform_animations_having_abs_pos_child_on_invisible_element() {
const div = addDiv(null, { style: 'visibility: hidden;' });

View File

@ -28,7 +28,7 @@
#include "mozilla/layers/APZCCallbackHelper.h"
#include "ClientLayerManager.h"
#include "nsQueryObject.h"
#include "CubebUtils.h"
#include "CubebDeviceEnumerator.h"
#include "nsIScrollableFrame.h"
@ -2149,10 +2149,14 @@ nsDOMWindowUtils::AudioDevices(uint16_t aSide, nsIArray** aDevices) {
do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<CubebDeviceEnumerator> enumerator = Enumerator::GetInstance();
nsTArray<RefPtr<AudioDeviceInfo>> collection;
CubebUtils::GetDeviceCollection(collection, aSide == AUDIO_INPUT
? CubebUtils::Side::Input
: CubebUtils::Side::Output);
if (aSide == AUDIO_INPUT) {
enumerator->EnumerateAudioInputDevices(collection);
} else {
enumerator->EnumerateAudioOutputDevices(collection);
}
for (auto device : collection) {
devices->AppendElement(device);
}
@ -3412,7 +3416,10 @@ nsDOMWindowUtils::GetOMTAStyle(Element* aElement, const nsAString& aProperty,
cssValue = new nsROCSSPrimitiveValue;
cssValue->SetNumber(value.get_float());
}
} else if (aProperty.EqualsLiteral("transform")) {
} else if (aProperty.EqualsLiteral("transform") ||
aProperty.EqualsLiteral("translate") ||
aProperty.EqualsLiteral("rotate") ||
aProperty.EqualsLiteral("scale")) {
OMTAValue value = GetOMTAValue(frame, DisplayItemType::TYPE_TRANSFORM,
GetWebRenderBridge());
if (value.type() == OMTAValue::TMatrix4x4) {

View File

@ -50,3 +50,4 @@ DEPRECATED_OPERATION(MozRequestFullScreenDeprecatedPrefix)
DEPRECATED_OPERATION(MozfullscreenchangeDeprecatedPrefix)
DEPRECATED_OPERATION(MozfullscreenerrorDeprecatedPrefix)
DEPRECATED_OPERATION(External_AddSearchProvider)
DEPRECATED_OPERATION(MouseEvent_MozPressure)

View File

@ -651,6 +651,11 @@ void DataTransfer::GetExternalClipboardFormats(const int32_t& aWhichClipboard,
const bool& aPlainTextOnly,
nsTArray<nsCString>* aResult) {
MOZ_ASSERT(aResult);
// NOTE: When you change this method, you may need to change
// GetExternalTransferableFormats() too since those methods should
// work similarly.
nsCOMPtr<nsIClipboard> clipboard =
do_GetService("@mozilla.org/widget/clipboard;1");
if (!clipboard || aWhichClipboard < 0) {
@ -696,6 +701,10 @@ void DataTransfer::GetExternalTransferableFormats(
aResult->Clear();
// NOTE: When you change this method, you may need to change
// GetExternalClipboardFormats() too since those methods should
// work similarly.
AutoTArray<nsCString, 10> flavors;
aTransferable->FlavorsTransferableCanExport(flavors);

View File

@ -406,7 +406,8 @@ void HTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent) {
void HTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
aVisitor.mWantsWillHandleEvent = true;
if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
if (aVisitor.mEvent->IsTrusted() &&
aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
uint32_t msg = aVisitor.mEvent->mMessage;
if (msg == eFormSubmit) {
if (mGeneratingSubmit) {
@ -443,7 +444,8 @@ void HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor) {
}
nsresult HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
if (aVisitor.mEvent->IsTrusted() &&
aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
EventMessage msg = aVisitor.mEvent->mMessage;
if (msg == eFormSubmit) {
// let the form know not to defer subsequent submissions

View File

@ -17,7 +17,7 @@ skip-if = toolkit == 'cocoa' # cocoa: disabled due to hangs, see changeset 6852e
[test_CrashService_crash.html]
skip-if = !(crashreporter && !e10s && (toolkit == 'gtk3' || toolkit == 'cocoa' || toolkit == 'windows'))
[test_temporaryfile_stream.html]
skip-if = !e10s || toolkit == 'android' # Bug 1525959
skip-if = !e10s || toolkit == 'android' || (os == "win" && processor == "aarch64") # Bug 1525959, aarch64 due to 1531150
support-files =
blob_verify.sjs
!/dom/canvas/test/captureStream_common.js

View File

@ -372,3 +372,5 @@ MozfullscreenchangeDeprecatedPrefixWarning=onmozfullscreenchange is deprecated.
MozfullscreenerrorDeprecatedPrefixWarning=onmozfullscreenerror is deprecated.
# LOCALIZATION NOTE(External_AddSearchProviderWarning): Do not translate AddSearchProvider.
External_AddSearchProviderWarning=AddSearchProvider is deprecated.
# LOCALIZATION NOTE: Do not translate "MouseEvent.mozPressure" and "PointerEvent.pressure".
MouseEvent_MozPressureWarning=MouseEvent.mozPressure is deprecated. Use PointerEvent.pressure instead.

View File

@ -638,89 +638,6 @@ char* GetForcedOutputDevice() {
return sCubebOutputDeviceName;
}
uint16_t ConvertCubebType(cubeb_device_type aType) {
uint16_t map[] = {
nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
nsIAudioDeviceInfo::TYPE_INPUT, // CUBEB_DEVICE_TYPE_INPUT,
nsIAudioDeviceInfo::TYPE_OUTPUT // CUBEB_DEVICE_TYPE_OUTPUT
};
return map[aType];
}
uint16_t ConvertCubebState(cubeb_device_state aState) {
uint16_t map[] = {
nsIAudioDeviceInfo::STATE_DISABLED, // CUBEB_DEVICE_STATE_DISABLED
nsIAudioDeviceInfo::STATE_UNPLUGGED, // CUBEB_DEVICE_STATE_UNPLUGGED
nsIAudioDeviceInfo::STATE_ENABLED // CUBEB_DEVICE_STATE_ENABLED
};
return map[aState];
}
uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) {
if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
return nsIAudioDeviceInfo::PREF_NONE;
} else if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
return nsIAudioDeviceInfo::PREF_ALL;
}
uint16_t preferred = 0;
if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
}
if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
preferred |= nsIAudioDeviceInfo::PREF_VOICE;
}
if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
}
return preferred;
}
uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat) {
uint16_t format = 0;
if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
format |= nsIAudioDeviceInfo::FMT_S16LE;
}
if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
format |= nsIAudioDeviceInfo::FMT_S16BE;
}
if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
format |= nsIAudioDeviceInfo::FMT_F32LE;
}
if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
format |= nsIAudioDeviceInfo::FMT_F32BE;
}
return format;
}
void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
Side aSide) {
cubeb* context = GetCubebContext();
if (context) {
cubeb_device_collection collection = {nullptr, 0};
if (cubeb_enumerate_devices(
context,
aSide == Input ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT,
&collection) == CUBEB_OK) {
for (unsigned int i = 0; i < collection.count; ++i) {
auto device = collection.device[i];
RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
device.devid, NS_ConvertUTF8toUTF16(device.friendly_name),
NS_ConvertUTF8toUTF16(device.group_id),
NS_ConvertUTF8toUTF16(device.vendor_name),
ConvertCubebType(device.type), ConvertCubebState(device.state),
ConvertCubebPreferred(device.preferred),
ConvertCubebFormat(device.format),
ConvertCubebFormat(device.default_format), device.max_channels,
device.default_rate, device.max_rate, device.min_rate,
device.latency_hi, device.latency_lo);
aDeviceInfos.AppendElement(info);
}
}
cubeb_device_collection_destroy(context, &collection);
}
}
cubeb_stream_prefs GetDefaultStreamPrefs() {
#ifdef XP_WIN
// Investigation for bug 1427011 - if we're in E10S mode, rely on the

View File

@ -43,8 +43,6 @@ uint32_t GetCubebPlaybackLatencyInMilliseconds();
uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params* params);
bool CubebLatencyPrefSet();
void GetCurrentBackend(nsAString& aBackend);
void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
Side aSide);
cubeb_stream_prefs GetDefaultStreamPrefs();
char* GetForcedOutputDevice();

View File

@ -11,7 +11,7 @@
#include "mozilla/SharedThreadPool.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Unused.h"
#include "CubebUtils.h"
#include "CubebDeviceEnumerator.h"
#include "Tracing.h"
#ifdef MOZ_WEBRTC
@ -578,15 +578,11 @@ bool AudioCallbackDriver::Init() {
char* forcedOutputDeviceName = CubebUtils::GetForcedOutputDevice();
if (forcedOutputDeviceName) {
nsTArray<RefPtr<AudioDeviceInfo>> deviceInfos;
GetDeviceCollection(deviceInfos, CubebUtils::Output);
for (const auto& device : deviceInfos) {
const nsString& name = device->Name();
if (name.Equals(NS_ConvertUTF8toUTF16(forcedOutputDeviceName))) {
if (device->DeviceID()) {
forcedOutputDeviceId = device->DeviceID();
}
}
RefPtr<CubebDeviceEnumerator> enumerator = Enumerator::GetInstance();
RefPtr<AudioDeviceInfo> device = enumerator->DeviceInfoFromName(
NS_ConvertUTF8toUTF16(forcedOutputDeviceName), EnumeratorSide::OUTPUT);
if (device->DeviceID()) {
forcedOutputDeviceId = device->DeviceID();
}
}

View File

@ -5,9 +5,10 @@
#define ENABLE_SET_CUBEB_BACKEND 1
#include "CubebDeviceEnumerator.h"
#include "gtest/gtest-printers.h"
#include "gtest/gtest.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Attributes.h"
#include "mozilla/UniquePtr.h"
#include "nsTArray.h"
using namespace mozilla;
@ -379,12 +380,13 @@ void PrintDevice(AudioDeviceInfo* aInfo) {
minRate, minLatency, maxLatency);
}
cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType) {
cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType,
const char* name) {
// A fake input device
cubeb_device_info device;
device.devid = aId;
device.device_id = "nice name";
device.friendly_name = "an even nicer name";
device.friendly_name = name;
device.group_id = "the physical device";
device.vendor_name = "mozilla";
device.type = aType;
@ -402,6 +404,10 @@ cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType) {
return device;
}
cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType) {
return DeviceTemplate(aId, aType, "nice name");
}
enum DeviceOperation { ADD, REMOVE };
void TestEnumeration(MockCubeb* aMock, uint32_t aExpectedDeviceCount,
@ -597,3 +603,56 @@ TEST(CubebDeviceEnumerator, DeviceInfoFromId) {
// Shutdown for `supports` to take effect
CubebDeviceEnumerator::Shutdown();
}
TEST(CubebDeviceEnumerator, DeviceInfoFromName) {
MockCubeb* mock = new MockCubeb();
mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
cubeb_device_type deviceTypes[2] = {CUBEB_DEVICE_TYPE_INPUT,
CUBEB_DEVICE_TYPE_OUTPUT};
bool supportsDeviceChangeCallback[2] = {true, false};
for (bool supports : supportsDeviceChangeCallback) {
// Shutdown for `supports` to take effect
CubebDeviceEnumerator::Shutdown();
mock->SetSupportDeviceChangeCallback(supports);
for (cubeb_device_type& deviceType : deviceTypes) {
cubeb_devid id_1 = reinterpret_cast<cubeb_devid>(1);
mock->AddDevice(DeviceTemplate(id_1, deviceType, "device name 1"));
cubeb_devid id_2 = reinterpret_cast<cubeb_devid>(2);
nsCString device_name = NS_LITERAL_CSTRING("device name 2");
mock->AddDevice(DeviceTemplate(id_2, deviceType, device_name.get()));
cubeb_devid id_3 = reinterpret_cast<cubeb_devid>(3);
mock->AddDevice(DeviceTemplate(id_3, deviceType, "device name 3"));
RefPtr<CubebDeviceEnumerator> enumerator =
CubebDeviceEnumerator::GetInstance();
RefPtr<AudioDeviceInfo> devInfo =
enumerator->DeviceInfoFromName(NS_ConvertUTF8toUTF16(device_name));
EXPECT_TRUE(devInfo) << "the device exist";
EXPECT_EQ(devInfo->Name(), NS_ConvertUTF8toUTF16(device_name))
<< "verify the device";
EnumeratorSide side = (deviceType == CUBEB_DEVICE_TYPE_INPUT)
? EnumeratorSide::INPUT
: EnumeratorSide::OUTPUT;
devInfo = enumerator->DeviceInfoFromName(
NS_ConvertUTF8toUTF16(device_name), side);
EXPECT_TRUE(devInfo) << "the device exist";
EXPECT_EQ(devInfo->Name(), NS_ConvertUTF8toUTF16(device_name))
<< "verify the device";
mock->RemoveDevice(id_2);
devInfo =
enumerator->DeviceInfoFromName(NS_ConvertUTF8toUTF16(device_name));
EXPECT_FALSE(devInfo) << "the device does not exist any more";
devInfo = enumerator->DeviceInfoFromName(
NS_ConvertUTF8toUTF16(device_name), side);
EXPECT_FALSE(devInfo) << "the device does not exist any more";
}
}
// Shutdown for `supports` to take effect
CubebDeviceEnumerator::Shutdown();
}

View File

@ -14,6 +14,7 @@ already_AddRefed<CubebDeviceEnumerator> CubebDeviceEnumerator::GetInstance() {
sInstance = new CubebDeviceEnumerator();
}
RefPtr<CubebDeviceEnumerator> instance = sInstance.get();
MOZ_ASSERT(instance);
return instance.forget();
}
@ -81,6 +82,92 @@ void CubebDeviceEnumerator::EnumerateAudioOutputDevices(
aOutDevices.AppendElements(mOutputDevices);
}
#ifndef ANDROID
static uint16_t ConvertCubebType(cubeb_device_type aType) {
uint16_t map[] = {
nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
nsIAudioDeviceInfo::TYPE_INPUT, // CUBEB_DEVICE_TYPE_INPUT,
nsIAudioDeviceInfo::TYPE_OUTPUT // CUBEB_DEVICE_TYPE_OUTPUT
};
return map[aType];
}
static uint16_t ConvertCubebState(cubeb_device_state aState) {
uint16_t map[] = {
nsIAudioDeviceInfo::STATE_DISABLED, // CUBEB_DEVICE_STATE_DISABLED
nsIAudioDeviceInfo::STATE_UNPLUGGED, // CUBEB_DEVICE_STATE_UNPLUGGED
nsIAudioDeviceInfo::STATE_ENABLED // CUBEB_DEVICE_STATE_ENABLED
};
return map[aState];
}
static uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) {
if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
return nsIAudioDeviceInfo::PREF_NONE;
}
if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
return nsIAudioDeviceInfo::PREF_ALL;
}
uint16_t preferred = 0;
if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
}
if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
preferred |= nsIAudioDeviceInfo::PREF_VOICE;
}
if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
}
return preferred;
}
static uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat) {
uint16_t format = 0;
if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
format |= nsIAudioDeviceInfo::FMT_S16LE;
}
if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
format |= nsIAudioDeviceInfo::FMT_S16BE;
}
if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
format |= nsIAudioDeviceInfo::FMT_F32LE;
}
if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
format |= nsIAudioDeviceInfo::FMT_F32BE;
}
return format;
}
static void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
Side aSide) {
cubeb* context = GetCubebContext();
if (context) {
cubeb_device_collection collection = {nullptr, 0};
if (cubeb_enumerate_devices(
context,
aSide == Input ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT,
&collection) == CUBEB_OK) {
for (unsigned int i = 0; i < collection.count; ++i) {
auto device = collection.device[i];
RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
device.devid, NS_ConvertUTF8toUTF16(device.friendly_name),
NS_ConvertUTF8toUTF16(device.group_id),
NS_ConvertUTF8toUTF16(device.vendor_name),
ConvertCubebType(device.type), ConvertCubebState(device.state),
ConvertCubebPreferred(device.preferred),
ConvertCubebFormat(device.format),
ConvertCubebFormat(device.default_format), device.max_channels,
device.default_rate, device.max_rate, device.min_rate,
device.latency_hi, device.latency_lo);
aDeviceInfos.AppendElement(info);
}
}
cubeb_device_collection_destroy(context, &collection);
}
}
#endif // non ANDROID
void CubebDeviceEnumerator::EnumerateAudioDevices(
CubebDeviceEnumerator::Side aSide) {
mMutex.AssertCurrentThreadOwns();
@ -130,9 +217,8 @@ void CubebDeviceEnumerator::EnumerateAudioDevices(
#else
if (devices.IsEmpty() || manualInvalidation) {
devices.Clear();
CubebUtils::GetDeviceCollection(devices, (aSide == Side::INPUT)
? CubebUtils::Input
: CubebUtils::Output);
GetDeviceCollection(devices, (aSide == Side::INPUT) ? CubebUtils::Input
: CubebUtils::Output);
}
#endif
@ -169,6 +255,37 @@ already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromID(
return nullptr;
}
already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
const nsString& aName) {
RefPtr<AudioDeviceInfo> other = DeviceInfoFromName(aName, Side::INPUT);
if (other) {
return other.forget();
}
return DeviceInfoFromName(aName, Side::OUTPUT);
}
already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
const nsString& aName, Side aSide) {
MutexAutoLock lock(mMutex);
nsTArray<RefPtr<AudioDeviceInfo>>& devices =
(aSide == Side::INPUT) ? mInputDevices : mOutputDevices;
bool manualInvalidation = (aSide == Side::INPUT) ? mManualInputInvalidation
: mManualOutputInvalidation;
if (devices.IsEmpty() || manualInvalidation) {
EnumerateAudioDevices(aSide);
}
for (uint32_t i = 0; i < devices.Length(); i++) {
if (devices[i]->Name().Equals(aName)) {
RefPtr<AudioDeviceInfo> other = devices[i];
return other.forget();
}
}
return nullptr;
}
void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb* aContext,
void* aUser) {
CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);

View File

@ -6,8 +6,8 @@
#define CUBEBDEVICEENUMERATOR_H_
#include "AudioDeviceInfo.h"
#include "cubeb/cubeb.h"
#include "CubebUtils.h"
#include "cubeb/cubeb.h"
#include "mozilla/Mutex.h"
#include "nsTArray.h"
@ -33,6 +33,16 @@ class CubebDeviceEnumerator final {
// This method is safe to call from any thread.
already_AddRefed<AudioDeviceInfo> DeviceInfoFromID(
CubebUtils::AudioDeviceID aID);
// From a device name, return the info for this device, if it's a valid name,
// or nullptr otherwise.
// This method is safe to call from any thread.
already_AddRefed<AudioDeviceInfo> DeviceInfoFromName(const nsString& aName);
enum class Side {
INPUT,
OUTPUT,
};
already_AddRefed<AudioDeviceInfo> DeviceInfoFromName(const nsString& aName,
Side aSide);
private:
CubebDeviceEnumerator();
@ -42,10 +52,6 @@ class CubebDeviceEnumerator final {
// simply calls `AudioDeviceListChanged` below.
static void InputAudioDeviceListChanged_s(cubeb* aContext, void* aUser);
static void OutputAudioDeviceListChanged_s(cubeb* aContext, void* aUser);
enum class Side {
INPUT,
OUTPUT,
};
// Invalidates the cached audio input device list, can be called on any
// thread.
void AudioDeviceListChanged(Side aSide);
@ -63,6 +69,8 @@ class CubebDeviceEnumerator final {
static StaticRefPtr<CubebDeviceEnumerator> sInstance;
};
typedef CubebDeviceEnumerator Enumerator;
typedef CubebDeviceEnumerator::Side EnumeratorSide;
} // namespace mozilla
#endif // CUBEBDEVICEENUMERATOR_H_

View File

@ -212,6 +212,7 @@ void MediaEngineWebRTC::EnumerateSpeakerDevices(
nsTArray<RefPtr<AudioDeviceInfo>> devices;
mEnumerator->EnumerateAudioOutputDevices(devices);
DebugOnly<bool> preferredDeviceFound = false;
for (auto& device : devices) {
if (device->State() == CUBEB_DEVICE_STATE_ENABLED) {
MOZ_ASSERT(device->Type() == CUBEB_DEVICE_TYPE_OUTPUT);
@ -221,7 +222,16 @@ void MediaEngineWebRTC::EnumerateSpeakerDevices(
// deviceIDs (in JS).
uuid.Append(NS_LITERAL_STRING("_Speaker"));
nsString groupId(device->GroupID());
aDevices->AppendElement(MakeRefPtr<MediaDevice>(device, uuid, groupId));
if (device->Preferred()) {
#ifdef DEBUG
MOZ_ASSERT(!preferredDeviceFound);
preferredDeviceFound = true;
#endif
aDevices->InsertElementAt(
0, MakeRefPtr<MediaDevice>(device, uuid, groupId));
} else {
aDevices->AppendElement(MakeRefPtr<MediaDevice>(device, uuid, groupId));
}
}
}
}

View File

@ -15,6 +15,7 @@ XPIDL_MODULE = 'content_webrtc'
EXPORTS += [
'AllocationHandle.h',
'CubebDeviceEnumerator.h',
'MediaEngine.h',
'MediaEngineDefault.h',
'MediaEnginePrefs.h',
@ -23,9 +24,12 @@ EXPORTS += [
'SineWaveGenerator.h',
]
SOURCES += [
'CubebDeviceEnumerator.cpp',
]
if CONFIG['MOZ_WEBRTC']:
EXPORTS += [
'CubebDeviceEnumerator.h',
'MediaEngineRemoteVideoSource.h',
'MediaEngineWebRTC.h'
]
@ -39,7 +43,6 @@ if CONFIG['MOZ_WEBRTC']:
]
# MediaEngineWebRTC.cpp needs to be built separately.
SOURCES += [
'CubebDeviceEnumerator.cpp',
'MediaEngineWebRTC.cpp',
]
LOCAL_INCLUDES += [

File diff suppressed because it is too large Load Diff

View File

@ -79,6 +79,7 @@ partial interface MouseEvent
{
// Finger or touch pressure event value
// ranges between 0.0 and 1.0
[Deprecated="MouseEvent_MozPressure"]
readonly attribute float mozPressure;
const unsigned short MOZ_SOURCE_UNKNOWN = 0;

View File

@ -56,12 +56,13 @@ support-files =
[test_WorkerDebugger.initialize.xul]
[test_WorkerDebugger.postMessage.xul]
[test_WorkerDebugger.xul]
skip-if = (verify && !debug && (os == 'linux'))
skip-if = webrender || (verify && !debug && os == 'linux') # Frequent intermittent on QR platforms Bug 1454935
[test_WorkerDebuggerGlobalScope.createSandbox.xul]
[test_WorkerDebuggerGlobalScope.enterEventLoop.xul]
[test_WorkerDebuggerGlobalScope.reportError.xul]
[test_WorkerDebuggerGlobalScope.setImmediate.xul]
[test_WorkerDebuggerManager.xul]
skip-if = webrender # Frequent intermittent on QR platforms Bug 1454935
[test_WorkerDebugger_console.xul]
[test_WorkerDebugger_frozen.xul]
[test_WorkerDebugger_promise.xul]

View File

@ -6,9 +6,8 @@
#if defined(MOZ_WIDGET_GTK)
# define GET_NATIVE_WINDOW_FROM_REAL_WIDGET(aWidget) \
((EGLNativeWindowType)aWidget->GetNativeData(NS_NATIVE_EGL_WINDOW))
# define GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget) \
((EGLNativeWindowType)aWidget->RealWidget()->GetNativeData( \
NS_NATIVE_EGL_WINDOW))
# define GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget) \
(aWidget->AsX11()->GetEGLNativeWindow())
#elif defined(MOZ_WIDGET_ANDROID)
# define GET_NATIVE_WINDOW_FROM_REAL_WIDGET(aWidget) \
((EGLNativeWindowType)aWidget->GetNativeData(NS_JAVA_SURFACE))
@ -73,6 +72,10 @@
#include "ScopedGLHelpers.h"
#include "TextureImageEGL.h"
#if defined(MOZ_WIDGET_GTK)
# include "mozilla/widget/GtkCompositorWidget.h"
#endif
#if defined(MOZ_WAYLAND)
# include "nsAutoPtr.h"
# include "nsDataHashtable.h"
@ -282,7 +285,9 @@ already_AddRefed<GLContext> GLContextEGLFactory::Create(
RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext(
flags, caps, false, config, surface, &discardFailureId);
if (!gl) {
gfxCriticalNote << "Failed to create EGLContext!";
const auto err = egl->fGetError();
gfxCriticalNote << "Failed to create EGLContext!: "
<< gfx::hexa(err);
mozilla::gl::DestroySurface(surface);
return nullptr;
}

View File

@ -12,6 +12,7 @@
#include "mozilla/dom/Nullable.h" // for dom::Nullable
#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
#include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction
#include "mozilla/LayerAnimationInfo.h" // for GetCSSPropertiesFor()
#include "mozilla/ServoBindings.h" // for Servo_ComposeAnimationSegment, etc
#include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
#include "nsDeviceContext.h" // for AppUnitsPerCSSPixel
@ -126,7 +127,7 @@ void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
}
}
AnimationArray* CompositorAnimationStorage::GetAnimations(
nsTArray<PropertyAnimationGroup>* CompositorAnimationStorage::GetAnimations(
const uint64_t& aId) const {
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
return mAnimations.Get(aId);
@ -135,17 +136,20 @@ AnimationArray* CompositorAnimationStorage::GetAnimations(
void CompositorAnimationStorage::SetAnimations(uint64_t aId,
const AnimationArray& aValue) {
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
AnimationArray* value = new AnimationArray(aValue);
mAnimations.Put(aId, value);
mAnimations.Put(aId, new nsTArray<PropertyAnimationGroup>(
AnimationHelper::ExtractAnimations(aValue)));
}
AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
enum class CanSkipCompose {
IfPossible,
No,
};
static AnimationHelper::SampleResult SampleAnimationForProperty(
TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime,
AnimationArray& aAnimations, InfallibleTArray<AnimData>& aAnimationData,
RefPtr<RawServoAnimationValue>& aAnimationValue,
const AnimatedValue* aPreviousValue) {
MOZ_ASSERT(!aAnimations.IsEmpty(), "Should be called with animations");
const AnimatedValue* aPreviousValue, CanSkipCompose aCanSkipCompose,
nsTArray<PropertyAnimation>& aPropertyAnimations,
RefPtr<RawServoAnimationValue>& aAnimationValue) {
MOZ_ASSERT(!aPropertyAnimations.IsEmpty(), "Should have animations");
bool hasInEffectAnimations = false;
#ifdef DEBUG
// In cases where this function returns a SampleResult::Skipped, we actually
@ -155,22 +159,18 @@ AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
// aAnimationValue in this scenario.
bool shouldBeSkipped = false;
#endif
// Process in order, since later aAnimations override earlier ones.
for (size_t i = 0, iEnd = aAnimations.Length(); i < iEnd; ++i) {
Animation& animation = aAnimations[i];
AnimData& animData = aAnimationData[i];
// Process in order, since later animations override earlier ones.
for (PropertyAnimation& animation : aPropertyAnimations) {
MOZ_ASSERT(
(!animation.originTime().IsNull() &&
animation.startTime().type() == MaybeTimeDuration::TTimeDuration) ||
animation.isNotPlaying(),
"If we are playing, we should have an origin time and a start"
" time");
(!animation.mOriginTime.IsNull() &&
animation.mStartTime.type() == MaybeTimeDuration::TTimeDuration) ||
animation.mIsNotPlaying,
"If we are playing, we should have an origin time and a start time");
// Determine if the animation was play-pending and used a ready time later
// than the previous frame time.
//
// To determine this, _all_ of the following consitions need to hold:
// To determine this, _all_ of the following conditions need to hold:
//
// * There was no previous animation value (i.e. this is the first frame for
// the animation since it was sent to the compositor), and
@ -179,7 +179,7 @@ AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
// * The ready time of the animation is ahead of the previous frame time.
//
bool hasFutureReadyTime = false;
if (!aPreviousValue && !animation.isNotPlaying() &&
if (!aPreviousValue && !animation.mIsNotPlaying &&
!aPreviousFrameTime.IsNull()) {
// This is the inverse of the calculation performed in
// AnimationInfo::StartPendingAnimations to calculate the start time of
@ -187,9 +187,9 @@ AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
// Note that we have to calculate (TimeStamp + TimeDuration) last to avoid
// underflow in the middle of the calulation.
const TimeStamp readyTime =
animation.originTime() +
(animation.startTime().get_TimeDuration() +
animation.holdTime().MultDouble(1.0 / animation.playbackRate()));
animation.mOriginTime +
(animation.mStartTime.get_TimeDuration() +
animation.mHoldTime.MultDouble(1.0 / animation.mPlaybackRate));
hasFutureReadyTime =
!readyTime.IsNull() && readyTime > aPreviousFrameTime;
}
@ -212,114 +212,158 @@ AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
// If the animation is not currently playing, e.g. paused or
// finished, then use the hold time to stay at the same position.
TimeDuration elapsedDuration =
animation.isNotPlaying() ||
animation.startTime().type() != MaybeTimeDuration::TTimeDuration
? animation.holdTime()
: (timeStamp - animation.originTime() -
animation.startTime().get_TimeDuration())
.MultDouble(animation.playbackRate());
animation.mIsNotPlaying ||
animation.mStartTime.type() != MaybeTimeDuration::TTimeDuration
? animation.mHoldTime
: (timeStamp - animation.mOriginTime -
animation.mStartTime.get_TimeDuration())
.MultDouble(animation.mPlaybackRate);
ComputedTiming computedTiming = dom::AnimationEffect::GetComputedTimingAt(
dom::Nullable<TimeDuration>(elapsedDuration), animData.mTiming,
animation.playbackRate());
dom::Nullable<TimeDuration>(elapsedDuration), animation.mTiming,
animation.mPlaybackRate);
if (computedTiming.mProgress.IsNull()) {
continue;
}
dom::IterationCompositeOperation iterCompositeOperation =
static_cast<dom::IterationCompositeOperation>(
animation.iterationComposite());
animation.mIterationComposite;
// Skip caluculation if the progress hasn't changed since the last
// Skip calculation if the progress hasn't changed since the last
// calculation.
// Note that we don't skip calculate this animation if there is another
// animation since the other animation might be 'accumulate' or 'add', or
// might have a missing keyframe (i.e. this animation value will be used in
// the missing keyframe).
// FIXME Bug 1455476: We should do this optimizations for the case where
// the layer has multiple animations.
if (iEnd == 1 && !dom::KeyframeEffect::HasComputedTimingChanged(
computedTiming, iterCompositeOperation,
animData.mProgressOnLastCompose,
animData.mCurrentIterationOnLastCompose)) {
// the layer has multiple animations and multiple properties.
if (aCanSkipCompose == CanSkipCompose::IfPossible &&
!dom::KeyframeEffect::HasComputedTimingChanged(
computedTiming, iterCompositeOperation,
animation.mProgressOnLastCompose,
animation.mCurrentIterationOnLastCompose)) {
#ifdef DEBUG
shouldBeSkipped = true;
#else
return SampleResult::Skipped;
return AnimationHelper::SampleResult::Skipped;
#endif
}
uint32_t segmentIndex = 0;
size_t segmentSize = animation.segments().Length();
AnimationSegment* segment = animation.segments().Elements();
while (segment->endPortion() < computedTiming.mProgress.Value() &&
size_t segmentSize = animation.mSegments.Length();
PropertyAnimation::SegmentData* segment = animation.mSegments.Elements();
while (segment->mEndPortion < computedTiming.mProgress.Value() &&
segmentIndex < segmentSize - 1) {
++segment;
++segmentIndex;
}
double positionInSegment =
(computedTiming.mProgress.Value() - segment->startPortion()) /
(segment->endPortion() - segment->startPortion());
(computedTiming.mProgress.Value() - segment->mStartPortion) /
(segment->mEndPortion - segment->mStartPortion);
double portion = ComputedTimingFunction::GetPortion(
animData.mFunctions[segmentIndex], positionInSegment,
computedTiming.mBeforeFlag);
segment->mFunction, positionInSegment, computedTiming.mBeforeFlag);
// Like above optimization, skip caluculation if the target segment isn't
// Like above optimization, skip calculation if the target segment isn't
// changed and if the portion in the segment isn't changed.
// This optimization is needed for CSS animations/transitions with step
// timing functions (e.g. the throbber animation on tab or frame based
// timing functions (e.g. the throbber animation on tabs or frame based
// animations).
// FIXME Bug 1455476: Like the above optimization, we should apply this
// optimizations for multiple animation cases as well.
if (iEnd == 1 && animData.mSegmentIndexOnLastCompose == segmentIndex &&
!animData.mPortionInSegmentOnLastCompose.IsNull() &&
animData.mPortionInSegmentOnLastCompose.Value() == portion) {
// optimizations for multiple animation cases and multiple properties as
// well.
if (aCanSkipCompose == CanSkipCompose::IfPossible &&
animation.mSegmentIndexOnLastCompose == segmentIndex &&
!animation.mPortionInSegmentOnLastCompose.IsNull() &&
animation.mPortionInSegmentOnLastCompose.Value() == portion) {
#ifdef DEBUG
shouldBeSkipped = true;
#else
return SampleResult::Skipped;
return AnimationHelper::SampleResult::Skipped;
#endif
}
AnimationPropertySegment animSegment;
animSegment.mFromKey = 0.0;
animSegment.mToKey = 1.0;
animSegment.mFromValue =
AnimationValue(animData.mStartValues[segmentIndex]);
animSegment.mToValue = AnimationValue(animData.mEndValues[segmentIndex]);
animSegment.mFromComposite =
static_cast<dom::CompositeOperation>(segment->startComposite());
animSegment.mToComposite =
static_cast<dom::CompositeOperation>(segment->endComposite());
animSegment.mFromValue = AnimationValue(segment->mStartValue);
animSegment.mToValue = AnimationValue(segment->mEndValue);
animSegment.mFromComposite = segment->mStartComposite;
animSegment.mToComposite = segment->mEndComposite;
// interpolate the property
aAnimationValue =
Servo_ComposeAnimationSegment(
&animSegment, aAnimationValue, animData.mEndValues.LastElement(),
iterCompositeOperation, portion, computedTiming.mCurrentIteration)
&animSegment, aAnimationValue,
animation.mSegments.LastElement().mEndValue, iterCompositeOperation,
portion, computedTiming.mCurrentIteration)
.Consume();
#ifdef DEBUG
if (shouldBeSkipped) {
return SampleResult::Skipped;
return AnimationHelper::SampleResult::Skipped;
}
#endif
hasInEffectAnimations = true;
animData.mProgressOnLastCompose = computedTiming.mProgress;
animData.mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
animData.mSegmentIndexOnLastCompose = segmentIndex;
animData.mPortionInSegmentOnLastCompose.SetValue(portion);
animation.mProgressOnLastCompose = computedTiming.mProgress;
animation.mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
animation.mSegmentIndexOnLastCompose = segmentIndex;
animation.mPortionInSegmentOnLastCompose.SetValue(portion);
}
return hasInEffectAnimations ? AnimationHelper::SampleResult::Sampled
: AnimationHelper::SampleResult::None;
}
AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime,
const AnimatedValue* aPreviousValue,
nsTArray<PropertyAnimationGroup>& aPropertyAnimationGroups,
nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues /* out */) {
MOZ_ASSERT(!aPropertyAnimationGroups.IsEmpty(),
"Should be called with animation data");
MOZ_ASSERT(aAnimationValues.IsEmpty(),
"Should be called with empty aAnimationValues");
for (PropertyAnimationGroup& group : aPropertyAnimationGroups) {
// Initialize animation value with base style.
RefPtr<RawServoAnimationValue> currValue = group.mBaseStyle;
CanSkipCompose canSkipCompose = aPropertyAnimationGroups.Length() == 1 &&
group.mAnimations.Length() == 1
? CanSkipCompose::IfPossible
: CanSkipCompose::No;
SampleResult result = SampleAnimationForProperty(
aPreviousFrameTime, aCurrentFrameTime, aPreviousValue, canSkipCompose,
group.mAnimations, currValue);
// FIXME: Bug 1455476: Do optimization for multiple properties. For now,
// the result is skipped only if the property count == 1.
if (result == SampleResult::Skipped) {
#ifdef DEBUG
aAnimationValues.AppendElement(std::move(currValue));
#endif
return SampleResult::Skipped;
}
if (result != SampleResult::Sampled) {
continue;
}
// Insert the interpolation result into the output array.
MOZ_ASSERT(currValue);
aAnimationValues.AppendElement(std::move(currValue));
}
#ifdef DEBUG
// Sanity check that all of animation data are the same.
const AnimationData& lastData = aAnimations.LastElement().data();
for (const Animation& animation : aAnimations) {
const AnimationData& data = animation.data();
const AnimationData& lastData =
aPropertyAnimationGroups.LastElement().mAnimationData;
for (const PropertyAnimationGroup& group : aPropertyAnimationGroups) {
const AnimationData& data = group.mAnimationData;
MOZ_ASSERT(data.type() == lastData.type(),
"The type of AnimationData should be the same");
if (data.type() == AnimationData::Tnull_t) {
@ -339,253 +383,136 @@ AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
}
#endif
return hasInEffectAnimations ? SampleResult::Sampled : SampleResult::None;
return aAnimationValues.IsEmpty() ? SampleResult::None
: SampleResult::Sampled;
}
struct BogusAnimation {};
static inline Result<Ok, BogusAnimation> SetCSSAngle(const CSSAngle& aAngle,
nsCSSValue& aValue) {
aValue.SetFloatValue(aAngle.value(), nsCSSUnit(aAngle.unit()));
if (!aValue.IsAngularUnit()) {
NS_ERROR("Bogus animation from IPC");
return Err(BogusAnimation{});
}
return Ok();
}
static Result<nsCSSValueSharedList*, BogusAnimation> CreateCSSValueList(
const InfallibleTArray<TransformFunction>& aFunctions) {
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList** resultTail = getter_Transfers(result);
for (uint32_t i = 0; i < aFunctions.Length(); i++) {
RefPtr<nsCSSValue::Array> arr;
switch (aFunctions[i].type()) {
case TransformFunction::TRotationX: {
const CSSAngle& angle = aFunctions[i].get_RotationX().angle();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotatex,
resultTail);
MOZ_TRY(SetCSSAngle(angle, arr->Item(1)));
break;
}
case TransformFunction::TRotationY: {
const CSSAngle& angle = aFunctions[i].get_RotationY().angle();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotatey,
resultTail);
MOZ_TRY(SetCSSAngle(angle, arr->Item(1)));
break;
}
case TransformFunction::TRotationZ: {
const CSSAngle& angle = aFunctions[i].get_RotationZ().angle();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotatez,
resultTail);
MOZ_TRY(SetCSSAngle(angle, arr->Item(1)));
break;
}
case TransformFunction::TRotation: {
const CSSAngle& angle = aFunctions[i].get_Rotation().angle();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotate,
resultTail);
MOZ_TRY(SetCSSAngle(angle, arr->Item(1)));
break;
}
case TransformFunction::TRotation3D: {
float x = aFunctions[i].get_Rotation3D().x();
float y = aFunctions[i].get_Rotation3D().y();
float z = aFunctions[i].get_Rotation3D().z();
const CSSAngle& angle = aFunctions[i].get_Rotation3D().angle();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotate3d,
resultTail);
arr->Item(1).SetFloatValue(x, eCSSUnit_Number);
arr->Item(2).SetFloatValue(y, eCSSUnit_Number);
arr->Item(3).SetFloatValue(z, eCSSUnit_Number);
MOZ_TRY(SetCSSAngle(angle, arr->Item(4)));
break;
}
case TransformFunction::TScale: {
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_scale3d,
resultTail);
arr->Item(1).SetFloatValue(aFunctions[i].get_Scale().x(),
eCSSUnit_Number);
arr->Item(2).SetFloatValue(aFunctions[i].get_Scale().y(),
eCSSUnit_Number);
arr->Item(3).SetFloatValue(aFunctions[i].get_Scale().z(),
eCSSUnit_Number);
break;
}
case TransformFunction::TTranslation: {
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_translate3d,
resultTail);
arr->Item(1).SetFloatValue(aFunctions[i].get_Translation().x(),
eCSSUnit_Pixel);
arr->Item(2).SetFloatValue(aFunctions[i].get_Translation().y(),
eCSSUnit_Pixel);
arr->Item(3).SetFloatValue(aFunctions[i].get_Translation().z(),
eCSSUnit_Pixel);
break;
}
case TransformFunction::TSkewX: {
const CSSAngle& x = aFunctions[i].get_SkewX().x();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_skewx,
resultTail);
MOZ_TRY(SetCSSAngle(x, arr->Item(1)));
break;
}
case TransformFunction::TSkewY: {
const CSSAngle& y = aFunctions[i].get_SkewY().y();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_skewy,
resultTail);
MOZ_TRY(SetCSSAngle(y, arr->Item(1)));
break;
}
case TransformFunction::TSkew: {
const CSSAngle& x = aFunctions[i].get_Skew().x();
const CSSAngle& y = aFunctions[i].get_Skew().y();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_skew,
resultTail);
MOZ_TRY(SetCSSAngle(x, arr->Item(1)));
MOZ_TRY(SetCSSAngle(y, arr->Item(2)));
break;
}
case TransformFunction::TTransformMatrix: {
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_matrix3d,
resultTail);
const gfx::Matrix4x4& matrix =
aFunctions[i].get_TransformMatrix().value();
arr->Item(1).SetFloatValue(matrix._11, eCSSUnit_Number);
arr->Item(2).SetFloatValue(matrix._12, eCSSUnit_Number);
arr->Item(3).SetFloatValue(matrix._13, eCSSUnit_Number);
arr->Item(4).SetFloatValue(matrix._14, eCSSUnit_Number);
arr->Item(5).SetFloatValue(matrix._21, eCSSUnit_Number);
arr->Item(6).SetFloatValue(matrix._22, eCSSUnit_Number);
arr->Item(7).SetFloatValue(matrix._23, eCSSUnit_Number);
arr->Item(8).SetFloatValue(matrix._24, eCSSUnit_Number);
arr->Item(9).SetFloatValue(matrix._31, eCSSUnit_Number);
arr->Item(10).SetFloatValue(matrix._32, eCSSUnit_Number);
arr->Item(11).SetFloatValue(matrix._33, eCSSUnit_Number);
arr->Item(12).SetFloatValue(matrix._34, eCSSUnit_Number);
arr->Item(13).SetFloatValue(matrix._41, eCSSUnit_Number);
arr->Item(14).SetFloatValue(matrix._42, eCSSUnit_Number);
arr->Item(15).SetFloatValue(matrix._43, eCSSUnit_Number);
arr->Item(16).SetFloatValue(matrix._44, eCSSUnit_Number);
break;
}
case TransformFunction::TPerspective: {
float perspective = aFunctions[i].get_Perspective().value();
arr = AnimationValue::AppendTransformFunction(eCSSKeyword_perspective,
resultTail);
arr->Item(1).SetFloatValue(perspective, eCSSUnit_Pixel);
break;
}
default:
NS_ASSERTION(false, "All functions should be implemented?");
}
}
if (aFunctions.Length() == 0) {
result = new nsCSSValueList();
result->mValue.SetNoneValue();
}
return new nsCSSValueSharedList(result.forget());
}
static already_AddRefed<RawServoAnimationValue> ToAnimationValue(
nsCSSPropertyID aProperty, const Animatable& aAnimatable) {
RefPtr<RawServoAnimationValue> result;
switch (aAnimatable.type()) {
case Animatable::Tnull_t:
break;
case Animatable::TArrayOfTransformFunction: {
const InfallibleTArray<TransformFunction>& transforms =
aAnimatable.get_ArrayOfTransformFunction();
auto listOrError = CreateCSSValueList(transforms);
if (listOrError.isOk()) {
RefPtr<nsCSSValueSharedList> list = listOrError.unwrap();
MOZ_ASSERT(list, "Transform list should be non null");
result = Servo_AnimationValue_Transform(*list).Consume();
static dom::FillMode GetAdjustedFillMode(const Animation& aAnimation) {
// Adjust fill mode so that if the main thread is delayed in clearing
// this animation we don't introduce flicker by jumping back to the old
// underlying value.
auto fillMode = static_cast<dom::FillMode>(aAnimation.fillMode());
float playbackRate = aAnimation.playbackRate();
switch (fillMode) {
case dom::FillMode::None:
if (playbackRate > 0) {
fillMode = dom::FillMode::Forwards;
} else if (playbackRate < 0) {
fillMode = dom::FillMode::Backwards;
}
break;
}
case Animatable::Tfloat:
result = Servo_AnimationValue_Opacity(aAnimatable.get_float()).Consume();
case dom::FillMode::Backwards:
if (playbackRate > 0) {
fillMode = dom::FillMode::Both;
}
break;
case Animatable::Tnscolor:
result = Servo_AnimationValue_Color(aProperty, aAnimatable.get_nscolor())
.Consume();
case dom::FillMode::Forwards:
if (playbackRate < 0) {
fillMode = dom::FillMode::Both;
}
break;
default:
MOZ_ASSERT_UNREACHABLE("Unsupported type");
break;
}
return result.forget();
return fillMode;
}
void AnimationHelper::SetAnimations(
AnimationArray& aAnimations, InfallibleTArray<AnimData>& aAnimData,
RefPtr<RawServoAnimationValue>& aBaseAnimationStyle) {
for (uint32_t i = 0; i < aAnimations.Length(); i++) {
Animation& animation = aAnimations[i];
// Adjust fill mode so that if the main thread is delayed in clearing
// this animation we don't introduce flicker by jumping back to the old
// underlying value.
switch (static_cast<dom::FillMode>(animation.fillMode())) {
case dom::FillMode::None:
if (animation.playbackRate() > 0) {
animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Forwards);
} else if (animation.playbackRate() < 0) {
animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Backwards);
}
break;
case dom::FillMode::Backwards:
if (animation.playbackRate() > 0) {
animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Both);
}
break;
case dom::FillMode::Forwards:
if (animation.playbackRate() < 0) {
animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Both);
}
break;
default:
break;
nsTArray<PropertyAnimationGroup> AnimationHelper::ExtractAnimations(
const AnimationArray& aAnimations) {
nsTArray<PropertyAnimationGroup> propertyAnimationGroupArray;
nsCSSPropertyID prevID = eCSSProperty_UNKNOWN;
PropertyAnimationGroup* currData = nullptr;
DebugOnly<const layers::Animatable*> currBaseStyle = nullptr;
for (const Animation& animation : aAnimations) {
// Animations with same property are grouped together, so we can just
// check if the current property is the same as the previous one for
// knowing this is a new group.
if (prevID != animation.property()) {
// Got a different group, we should create a different array.
currData = propertyAnimationGroupArray.AppendElement();
currData->mProperty = animation.property();
currData->mAnimationData = animation.data();
prevID = animation.property();
// Reset the debug pointer.
currBaseStyle = nullptr;
}
MOZ_ASSERT(currData);
if (animation.baseStyle().type() != Animatable::Tnull_t) {
aBaseAnimationStyle =
ToAnimationValue(animation.property(), animation.baseStyle());
MOZ_ASSERT(!currBaseStyle || *currBaseStyle == animation.baseStyle(),
"Should be the same base style");
currData->mBaseStyle = AnimationValue::FromAnimatable(
animation.property(), animation.baseStyle());
currBaseStyle = &animation.baseStyle();
}
AnimData* data = aAnimData.AppendElement();
PropertyAnimation* propertyAnimation =
currData->mAnimations.AppendElement();
data->mTiming =
propertyAnimation->mOriginTime = animation.originTime();
propertyAnimation->mStartTime = animation.startTime();
propertyAnimation->mHoldTime = animation.holdTime();
propertyAnimation->mPlaybackRate = animation.playbackRate();
propertyAnimation->mIterationComposite =
static_cast<dom::IterationCompositeOperation>(
animation.iterationComposite());
propertyAnimation->mIsNotPlaying = animation.isNotPlaying();
propertyAnimation->mTiming =
TimingParams{animation.duration(),
animation.delay(),
animation.endDelay(),
animation.iterations(),
animation.iterationStart(),
static_cast<dom::PlaybackDirection>(animation.direction()),
static_cast<dom::FillMode>(animation.fillMode()),
GetAdjustedFillMode(animation),
AnimationUtils::TimingFunctionToComputedTimingFunction(
animation.easingFunction())};
InfallibleTArray<Maybe<ComputedTimingFunction>>& functions =
data->mFunctions;
InfallibleTArray<RefPtr<RawServoAnimationValue>>& startValues =
data->mStartValues;
InfallibleTArray<RefPtr<RawServoAnimationValue>>& endValues =
data->mEndValues;
const InfallibleTArray<AnimationSegment>& segments = animation.segments();
for (const AnimationSegment& segment : segments) {
startValues.AppendElement(
ToAnimationValue(animation.property(), segment.startState()));
endValues.AppendElement(
ToAnimationValue(animation.property(), segment.endState()));
TimingFunction tf = segment.sampleFn();
Maybe<ComputedTimingFunction> ctf =
AnimationUtils::TimingFunctionToComputedTimingFunction(tf);
functions.AppendElement(ctf);
nsTArray<PropertyAnimation::SegmentData>& segmentData =
propertyAnimation->mSegments;
for (const AnimationSegment& segment : animation.segments()) {
segmentData.AppendElement(PropertyAnimation::SegmentData{
AnimationValue::FromAnimatable(animation.property(),
segment.startState()),
AnimationValue::FromAnimatable(animation.property(),
segment.endState()),
AnimationUtils::TimingFunctionToComputedTimingFunction(
segment.sampleFn()),
segment.startPortion(), segment.endPortion(),
static_cast<dom::CompositeOperation>(segment.startComposite()),
static_cast<dom::CompositeOperation>(segment.endComposite())});
}
}
#ifdef DEBUG
// Sanity check that the grouped animation data is correct by looking at the
// property set.
if (!propertyAnimationGroupArray.IsEmpty()) {
nsCSSPropertyIDSet seenProperties;
for (const auto& group : propertyAnimationGroupArray) {
nsCSSPropertyID id = group.mProperty;
MOZ_ASSERT(!seenProperties.HasProperty(id), "Should be a new property");
seenProperties.AddProperty(id);
}
MOZ_ASSERT(
seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
DisplayItemType::TYPE_TRANSFORM)) ||
seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
DisplayItemType::TYPE_OPACITY)) ||
seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
DisplayItemType::TYPE_BACKGROUND_COLOR)),
"The property set of output should be the subset of transform-like "
"properties, opacity, or background_color.");
}
#endif
return propertyAnimationGroupArray;
}
uint64_t AnimationHelper::GetNextCompositorAnimationsId() {
@ -612,55 +539,51 @@ bool AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage,
// Sample the animations in CompositorAnimationStorage
for (auto iter = aStorage->ConstAnimationsTableIter(); !iter.Done();
iter.Next()) {
AnimationArray* animations = iter.UserData();
if (animations->IsEmpty()) {
auto& propertyAnimationGroups = *iter.UserData();
if (propertyAnimationGroups.IsEmpty()) {
continue;
}
isAnimating = true;
RefPtr<RawServoAnimationValue> animationValue;
InfallibleTArray<AnimData> animationData;
AnimationHelper::SetAnimations(*animations, animationData, animationValue);
nsTArray<RefPtr<RawServoAnimationValue>> animationValues;
AnimatedValue* previousValue = aStorage->GetAnimatedValue(iter.Key());
AnimationHelper::SampleResult sampleResult =
AnimationHelper::SampleAnimationForEachNode(
aPreviousFrameTime, aCurrentFrameTime, *animations, animationData,
animationValue, previousValue);
aPreviousFrameTime, aCurrentFrameTime, previousValue,
propertyAnimationGroups, animationValues);
if (sampleResult != AnimationHelper::SampleResult::Sampled) {
continue;
}
const PropertyAnimationGroup& lastPropertyAnimationGroup =
propertyAnimationGroups.LastElement();
// Store the AnimatedValue
Animation& animation = animations->LastElement();
switch (animation.property()) {
switch (lastPropertyAnimationGroup.mProperty) {
case eCSSProperty_opacity: {
MOZ_ASSERT(animationValues.Length() == 1);
aStorage->SetAnimatedValue(
iter.Key(), Servo_AnimationValue_GetOpacity(animationValue));
iter.Key(), Servo_AnimationValue_GetOpacity(animationValues[0]));
break;
}
case eCSSProperty_rotate:
case eCSSProperty_scale:
case eCSSProperty_translate:
case eCSSProperty_transform: {
RefPtr<nsCSSValueSharedList> list;
Servo_AnimationValue_GetTransform(animationValue, &list);
const TransformData& transformData =
animation.data().get_TransformData();
nsPoint origin = transformData.origin();
// we expect all our transform data to arrive in device pixels
gfx::Point3D transformOrigin = transformData.transformOrigin();
nsDisplayTransform::FrameTransformProperties props(std::move(list),
transformOrigin);
lastPropertyAnimationGroup.mAnimationData.get_TransformData();
gfx::Matrix4x4 transform =
nsDisplayTransform::GetResultingTransformMatrix(
props, origin, transformData.appUnitsPerDevPixel(), 0,
&transformData.bounds());
ServoAnimationValueToMatrix4x4(animationValues, transformData);
gfx::Matrix4x4 frameTransform = transform;
// If the parent has perspective transform, then the offset into
// reference frame coordinates is already on this transform. If not,
// then we need to ask for it to be added here.
if (!transformData.hasPerspectiveParent()) {
nsLayoutUtils::PostTranslate(
transform, origin, transformData.appUnitsPerDevPixel(), true);
nsLayoutUtils::PostTranslate(transform, transformData.origin(),
transformData.appUnitsPerDevPixel(),
true);
}
transform.PostScale(transformData.inheritedXScale(),
@ -678,5 +601,51 @@ bool AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage,
return isAnimating;
}
gfx::Matrix4x4 AnimationHelper::ServoAnimationValueToMatrix4x4(
const nsTArray<RefPtr<RawServoAnimationValue>>& aValues,
const TransformData& aTransformData) {
// FIXME: Bug 1457033: We should convert servo's animation value to matrix
// directly without nsCSSValueSharedList.
// TODO: Bug 1429305: Support compositor animations for motion-path.
RefPtr<nsCSSValueSharedList> transform, translate, rotate, scale;
for (const auto& value : aValues) {
MOZ_ASSERT(value);
RefPtr<nsCSSValueSharedList> list;
nsCSSPropertyID id = Servo_AnimationValue_GetTransform(value, &list);
switch (id) {
case eCSSProperty_transform:
MOZ_ASSERT(!transform);
transform = list.forget();
break;
case eCSSProperty_translate:
MOZ_ASSERT(!translate);
translate = list.forget();
break;
case eCSSProperty_rotate:
MOZ_ASSERT(!rotate);
rotate = list.forget();
break;
case eCSSProperty_scale:
MOZ_ASSERT(!scale);
scale = list.forget();
break;
default:
MOZ_ASSERT_UNREACHABLE("Unsupported transform-like property");
}
}
RefPtr<nsCSSValueSharedList> individualList =
nsStyleDisplay::GenerateCombinedIndividualTransform(translate, rotate,
scale);
// We expect all our transform data to arrive in device pixels
gfx::Point3D transformOrigin = aTransformData.transformOrigin();
nsDisplayTransform::FrameTransformProperties props(
std::move(individualList), std::move(transform), transformOrigin);
return nsDisplayTransform::GetResultingTransformMatrix(
props, aTransformData.origin(), aTransformData.appUnitsPerDevPixel(), 0,
&aTransformData.bounds());
}
} // namespace layers
} // namespace mozilla

View File

@ -16,16 +16,30 @@
namespace mozilla {
struct AnimationValue;
namespace dom {
enum class CompositeOperation : uint8_t;
enum class IterationCompositeOperation : uint8_t;
}; // namespace dom
namespace layers {
class Animation;
typedef InfallibleTArray<layers::Animation> AnimationArray;
typedef nsTArray<layers::Animation> AnimationArray;
struct AnimData {
InfallibleTArray<RefPtr<RawServoAnimationValue>> mStartValues;
InfallibleTArray<RefPtr<RawServoAnimationValue>> mEndValues;
InfallibleTArray<Maybe<mozilla::ComputedTimingFunction>> mFunctions;
struct PropertyAnimation {
struct SegmentData {
RefPtr<RawServoAnimationValue> mStartValue;
RefPtr<RawServoAnimationValue> mEndValue;
Maybe<mozilla::ComputedTimingFunction> mFunction;
float mStartPortion;
float mEndPortion;
dom::CompositeOperation mStartComposite;
dom::CompositeOperation mEndComposite;
};
nsTArray<SegmentData> mSegments;
TimingParams mTiming;
// These two variables correspond to the variables of the same name in
// KeyframeEffectReadOnly and are used for the same purpose: to skip composing
// animations whose progress has not changed.
@ -35,6 +49,27 @@ struct AnimData {
// applied to the timing function in each keyframe.
uint32_t mSegmentIndexOnLastCompose = 0;
dom::Nullable<double> mPortionInSegmentOnLastCompose;
TimeStamp mOriginTime;
MaybeTimeDuration mStartTime;
TimeDuration mHoldTime;
float mPlaybackRate;
dom::IterationCompositeOperation mIterationComposite;
bool mIsNotPlaying;
};
struct PropertyAnimationGroup {
nsCSSPropertyID mProperty;
AnimationData mAnimationData;
nsTArray<PropertyAnimation> mAnimations;
RefPtr<RawServoAnimationValue> mBaseStyle;
bool IsEmpty() const { return mAnimations.IsEmpty(); }
void Clear() {
mAnimations.Clear();
mBaseStyle = nullptr;
}
};
struct AnimationTransform {
@ -96,7 +131,8 @@ struct AnimatedValue {
// mechanism).
class CompositorAnimationStorage final {
typedef nsClassHashtable<nsUint64HashKey, AnimatedValue> AnimatedValueTable;
typedef nsClassHashtable<nsUint64HashKey, AnimationArray> AnimationsTable;
typedef nsClassHashtable<nsUint64HashKey, nsTArray<PropertyAnimationGroup>>
AnimationsTable;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorAnimationStorage)
public:
@ -147,7 +183,7 @@ class CompositorAnimationStorage final {
/**
* Return the animations if a given id can map to its animations
*/
AnimationArray* GetAnimations(const uint64_t& aId) const;
nsTArray<PropertyAnimationGroup>* GetAnimations(const uint64_t& aId) const;
/**
* Return the iterator of animations table
@ -184,30 +220,84 @@ class AnimationHelper {
/**
* Sample animations based on a given time stamp for a element(layer) with
* its animation data.
* Generally |aPreviousFrameTimeStamp| is used for the sampling if it's
* Generally |aPreviousFrameTime| is used for the sampling if it's
* supplied to make the animation more in sync with other animations on the
* main-thread. But in the case where the animation just started at the time
* when the animation was sent to the compositor, |aCurrentTime| is used for
* the sampling instead to avoid flickering the animation.
* when the animation was sent to the compositor, |aCurrentFrameTime| is used
* for sampling instead to avoid flicker.
*
* Returns SampleResult::None if none of the animations are producing a result
* (e.g. they are in the delay phase with no backwards fill),
* SampleResult::Skipped if the animation output did not change since the last
* call of this function,
* SampleResult::Sampled if the animation output was updated.
*
* Using the same example from ExtractAnimations (below):
*
* Input |aPropertyAnimationGroups| (ignoring the base animation style):
*
* [
* Group A: [ { rotate, Animation A }, { rotate, Animation B } ],
* Group B: [ { scale, Animation B } ],
* Group C: [ { transform, Animation A }, { transform, Animation B } ],
* ]
*
* For each property group, this function interpolates each animation in turn,
* using the result of interpolating one animation as input for the next such
* that it reduces each property group to a single output value:
*
* [
* { rotate, RawServoAnimationValue },
* { scale, RawServoAnimationValue },
* { transform, RawServoAnimationValue },
* ]
*
* For transform animations, the caller (SampleAnimations) will combine the
* result of the various transform properties into a final matrix.
*/
static SampleResult SampleAnimationForEachNode(
TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime,
AnimationArray& aAnimations, InfallibleTArray<AnimData>& aAnimationData,
RefPtr<RawServoAnimationValue>& aAnimationValue,
const AnimatedValue* aPreviousValue);
const AnimatedValue* aPreviousValue,
nsTArray<PropertyAnimationGroup>& aPropertyAnimationGroups,
nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues);
/**
* Populates AnimData stuctures into |aAnimData| and |aBaseAnimationStyle|
* based on |aAnimations|.
* Extract organized animation data by property into an array of
* PropertyAnimationGroup objects.
*
* For example, suppose we have the following animations:
*
* Animation A: [ transform, rotate ]
* Animation B: [ rotate, scale ]
* Animation C: [ transform ]
* Animation D: [ opacity ]
*
* When we go to send transform-like properties to the compositor, we
* sort them as follows:
*
* [
* { rotate: Animation A (rotate segments only) },
* { rotate: Animation B ( " " ) },
* { scale: Animation B (scale segments only) },
* { transform: Animation A (transform segments only) },
* { transform: Animation C ( " " ) },
* ]
*
* In this function, we group these animations together by property producing
* output such as the following:
*
* [
* [ { rotate, Animation A }, { rotate, Animation B } ],
* [ { scale, Animation B } ],
* [ { transform, Animation A }, { transform, Animation B } ],
* ]
*
* In the process of grouping these animations, we also convert their values
* from the rather compact representation we use for transferring across the
* IPC boundary into something we can readily use for sampling.
*/
static void SetAnimations(
AnimationArray& aAnimations, InfallibleTArray<AnimData>& aAnimData,
RefPtr<RawServoAnimationValue>& aBaseAnimationStyle);
static nsTArray<PropertyAnimationGroup> ExtractAnimations(
const AnimationArray& aAnimations);
/**
* Get a unique id to represent the compositor animation between child
@ -227,10 +317,21 @@ class AnimationHelper {
* Note that even if there are only in-delay phase animations (i.e. not
* visually effective), this function returns true to ensure we composite
* again on the next tick.
*
* Note: This is called only by WebRender.
*/
static bool SampleAnimations(CompositorAnimationStorage* aStorage,
TimeStamp aPreviousFrameTime,
TimeStamp aCurrentFrameTime);
/**
* Convert an array of animation values into a matrix given the corresponding
* transform parameters. |aValue| must be a transform-like value
* (e.g. transform, translate etc.).
*/
static gfx::Matrix4x4 ServoAnimationValueToMatrix4x4(
const nsTArray<RefPtr<RawServoAnimationValue>>& aValue,
const TransformData& aTransformData);
};
} // namespace layers

View File

@ -8,6 +8,7 @@
#include "mozilla/LayerAnimationInfo.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/AnimationHelper.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/dom/Animation.h"
#include "nsIContent.h"
#include "PuppetWidget.h"
@ -26,6 +27,7 @@ void AnimationInfo::EnsureAnimationsId() {
}
Animation* AnimationInfo::AddAnimation() {
MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
// Here generates a new id when the first animation is added and
// this id is used to represent the animations in this layer.
EnsureAnimationsId();
@ -40,6 +42,7 @@ Animation* AnimationInfo::AddAnimation() {
}
Animation* AnimationInfo::AddAnimationForNextTransaction() {
MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread());
MOZ_ASSERT(mPendingAnimations,
"should have called ClearAnimationsForNextTransaction first");
@ -51,12 +54,12 @@ Animation* AnimationInfo::AddAnimationForNextTransaction() {
void AnimationInfo::ClearAnimations() {
mPendingAnimations = nullptr;
if (mAnimations.IsEmpty() && mAnimationData.IsEmpty()) {
if (mAnimations.IsEmpty() && mPropertyAnimationGroups.IsEmpty()) {
return;
}
mAnimations.Clear();
mAnimationData.Clear();
mPropertyAnimationGroups.Clear();
mMutated = true;
}
@ -72,11 +75,9 @@ void AnimationInfo::ClearAnimationsForNextTransaction() {
void AnimationInfo::SetCompositorAnimations(
const CompositorAnimations& aCompositorAnimations) {
mAnimations = aCompositorAnimations.animations();
mCompositorAnimationsId = aCompositorAnimations.id();
mAnimationData.Clear();
AnimationHelper::SetAnimations(mAnimations, mAnimationData,
mBaseAnimationStyle);
mPropertyAnimationGroups =
AnimationHelper::ExtractAnimations(aCompositorAnimations.animations());
}
bool AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime) {
@ -128,8 +129,10 @@ bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() {
}
bool AnimationInfo::HasTransformAnimation() const {
const nsCSSPropertyIDSet& transformSet =
LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM);
for (uint32_t i = 0; i < mAnimations.Length(); i++) {
if (mAnimations[i].property() == eCSSProperty_transform) {
if (transformSet.HasProperty(mAnimations[i].property())) {
return true;
}
}

View File

@ -23,10 +23,10 @@ class Animation;
class CompositorAnimations;
class Layer;
class LayerManager;
struct AnimData;
struct PropertyAnimationGroup;
class AnimationInfo final {
typedef InfallibleTArray<Animation> AnimationArray;
typedef nsTArray<Animation> AnimationArray;
public:
AnimationInfo();
@ -63,11 +63,12 @@ class AnimationInfo final {
void TransferMutatedFlagToLayer(Layer* aLayer);
uint64_t GetCompositorAnimationsId() { return mCompositorAnimationsId; }
RawServoAnimationValue* GetBaseAnimationStyle() const {
return mBaseAnimationStyle;
}
InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
// Note: We don't set mAnimations on the compositor thread, so this will
// always return an empty array on the compositor thread.
AnimationArray& GetAnimations() { return mAnimations; }
nsTArray<PropertyAnimationGroup>& GetPropertyAnimationGroups() {
return mPropertyAnimationGroups;
}
bool ApplyPendingUpdatesForThisTransaction();
bool HasTransformAnimation() const;
@ -77,7 +78,8 @@ class AnimationInfo final {
nsIFrame* aFrame, DisplayItemType aDisplayItemKey);
using CompositorAnimatableDisplayItemTypes =
Array<DisplayItemType, nsCSSPropertyIDSet::CompositorAnimatableCount()>;
Array<DisplayItemType,
nsCSSPropertyIDSet::CompositorAnimatableDisplayItemCount()>;
using AnimationGenerationCallback = std::function<bool(
const Maybe<uint64_t>& aGeneration, DisplayItemType aDisplayItemType)>;
// Enumerates animation generations on |aFrame| for the given display item
@ -90,14 +92,26 @@ class AnimationInfo final {
const AnimationGenerationCallback& aCallback);
protected:
// mAnimations (and mPendingAnimations) are only set on the main thread.
//
// Once the animations are received on the compositor thread/process we
// use AnimationHelper::ExtractAnimations to transform the rather compact
// representation of animation data we transfer into something we can more
// readily use for sampling and then store it in mPropertyAnimationGroups
// (below) or CompositorAnimationStorage.mAnimations for WebRender.
AnimationArray mAnimations;
uint64_t mCompositorAnimationsId;
nsAutoPtr<AnimationArray> mPendingAnimations;
InfallibleTArray<AnimData> mAnimationData;
uint64_t mCompositorAnimationsId;
// The extracted data produced by AnimationHelper::ExtractAnimations().
//
// Each entry in the array represents an animation list for one property. For
// transform-like properties (e.g. transform, rotate etc.), there may be
// multiple entries depending on how many transform-like properties we have.
nsTArray<PropertyAnimationGroup> mPropertyAnimationGroups;
// If this layer is used for OMTA, then this counter is used to ensure we
// stay in sync with the animation manager
Maybe<uint64_t> mAnimationGeneration;
RefPtr<RawServoAnimationValue> mBaseAnimationStyle;
bool mMutated;
};

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