Merge mozilla-central to inbound. a=merge CLOSED TREE
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
fastclick1.html
|
||||
fastclick2.html
|
||||
head.js
|
||||
test.html
|
||||
webcompat.html
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
"use strict";
|
||||
function FastClick() {}
|
||||
</script>
|
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
"use strict";
|
||||
function ObscuredFastClick() {
|
||||
}
|
||||
ObscuredFastClick.prototype = {
|
||||
needsClick: () => {},
|
||||
};
|
||||
window.someRandomVar = new ObscuredFastClick();
|
||||
</script>
|
@ -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";
|
||||
|
@ -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)
|
||||
|
BIN
browser/themes/linux/monitor-base.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
browser/themes/linux/monitor-border.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.5 KiB |
@ -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");
|
||||
}
|
@ -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)
|
||||
|
BIN
browser/themes/osx/monitor-base.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
browser/themes/osx/monitor-border.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 7.5 KiB |
@ -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)
|
||||
|
@ -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");
|
||||
}
|
@ -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)
|
||||
|
BIN
browser/themes/windows/monitor-base.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
browser/themes/windows/monitor-border.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.5 KiB |
@ -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");
|
||||
}
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
),
|
||||
|
@ -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"),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -25,3 +25,11 @@
|
||||
.debug-target-pane__title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.debug-target-pane__collapsable {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.debug-target-pane__collapsable--collapsed {
|
||||
max-height: 0;
|
||||
}
|
||||
|
@ -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,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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,
|
||||
),
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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,
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
@ -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));
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
@ -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() }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
@ -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));
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
)
|
||||
|
@ -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(
|
||||
{
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -300,6 +300,7 @@ function getUrlDetails(url) {
|
||||
scheme,
|
||||
unicodeUrl,
|
||||
isLocal,
|
||||
url,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
}));
|
||||
|
@ -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 */
|
||||
|
@ -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 + " " +
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;' });
|
||||
|
@ -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) {
|
||||
|
@ -50,3 +50,4 @@ DEPRECATED_OPERATION(MozRequestFullScreenDeprecatedPrefix)
|
||||
DEPRECATED_OPERATION(MozfullscreenchangeDeprecatedPrefix)
|
||||
DEPRECATED_OPERATION(MozfullscreenerrorDeprecatedPrefix)
|
||||
DEPRECATED_OPERATION(External_AddSearchProvider)
|
||||
DEPRECATED_OPERATION(MouseEvent_MozPressure)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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_
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 += [
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|