зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to inbound. a=merge CLOSED TREE
This commit is contained in:
Коммит
74f7da5f13
|
@ -53,6 +53,13 @@ GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, firefox.js)
|
|||
|
||||
endif
|
||||
|
||||
# channel-prefs.js is handled separate from other prefs due to bug 756325
|
||||
# DO NOT change the content of channel-prefs.js without taking the appropriate
|
||||
# steps. See bug 1431342.
|
||||
libs:: $(srcdir)/profile/channel-prefs.js
|
||||
$(NSINSTALL) -D $(DIST)/bin/defaults/pref
|
||||
$(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
|
||||
|
||||
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
|
||||
|
||||
MAC_APP_NAME = $(MOZ_APP_DISPLAYNAME)
|
||||
|
|
|
@ -43,9 +43,6 @@ SOURCES += [
|
|||
# Neither channel-prefs.js nor firefox.exe want to end up in dist/bin/browser.
|
||||
DIST_SUBDIR = ""
|
||||
|
||||
# channel-prefs.js is handled separate from other prefs due to bug 756325
|
||||
JS_PREFERENCE_PP_FILES += ['profile/channel-prefs.js']
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'!/build',
|
||||
'/toolkit/xre',
|
||||
|
|
|
@ -2,5 +2,4 @@
|
|||
* 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/. */
|
||||
|
||||
#filter substitution
|
||||
pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
|
||||
|
|
|
@ -165,7 +165,7 @@
|
|||
<broadcaster id="sync-status"/>
|
||||
<!-- broadcasters of the "hidden" attribute to reflect setup state for
|
||||
menus -->
|
||||
<broadcaster id="sync-setup-state"/>
|
||||
<broadcaster id="sync-setup-state" hidden="true"/>
|
||||
<broadcaster id="sync-unverified-state" hidden="true"/>
|
||||
<broadcaster id="sync-syncnow-state" hidden="true"/>
|
||||
<broadcaster id="sync-reauth-state" hidden="true"/>
|
||||
|
|
|
@ -122,8 +122,12 @@ var gSync = {
|
|||
this._definePrefGetters();
|
||||
|
||||
// initial label for the sync buttons.
|
||||
let broadcaster = document.getElementById("sync-status");
|
||||
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
|
||||
let statusBroadcaster = document.getElementById("sync-status");
|
||||
statusBroadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
|
||||
// We start with every broadcasters hidden, so that we don't need to init
|
||||
// the sync UI on windows like pageInfo.xul (see bug 1384856).
|
||||
let setupBroadcaster = document.getElementById("sync-setup-state");
|
||||
setupBroadcaster.hidden = false;
|
||||
|
||||
this._maybeUpdateUIState();
|
||||
|
||||
|
@ -176,6 +180,11 @@ var gSync = {
|
|||
},
|
||||
|
||||
updatePanelPopup(state) {
|
||||
// Some windows (e.g. places.xul) won't contain the panel UI, so we can
|
||||
// abort immediately for those (bug 1384856).
|
||||
if (!this.appMenuContainer) {
|
||||
return;
|
||||
}
|
||||
let defaultLabel = this.appMenuStatus.getAttribute("defaultlabel");
|
||||
// The localization string is for the signed in text, but it's the default text as well
|
||||
let defaultTooltiptext = this.appMenuStatus.getAttribute("signedinTooltiptext");
|
||||
|
|
|
@ -1574,6 +1574,17 @@
|
|||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="getTabSharingState">
|
||||
<parameter name="aTab"/>
|
||||
<body><![CDATA[
|
||||
// Normalize the state object for consumers (ie.extensions).
|
||||
let state = Object.assign({}, aTab._sharingState);
|
||||
// ensure bool if undefined
|
||||
state.camera = !!state.camera;
|
||||
state.microphone = !!state.microphone;
|
||||
return state;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- TODO: remove after 57, once we know add-ons can no longer use it. -->
|
||||
<method name="setTabTitleLoading">
|
||||
|
@ -3883,7 +3894,7 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
|
||||
!aTab.closing) {
|
||||
!aTab.closing && !aTab._sharingState) {
|
||||
aTab.setAttribute("hidden", "true");
|
||||
this._visibleTabs = null; // invalidate cache
|
||||
|
||||
|
|
|
@ -616,6 +616,14 @@ class Tab extends TabBase {
|
|||
return super.frameLoader || {lazyWidth: 0, lazyHeight: 0};
|
||||
}
|
||||
|
||||
get hidden() {
|
||||
return this.nativeTab.hidden;
|
||||
}
|
||||
|
||||
get sharingState() {
|
||||
return this.window.gBrowser.getTabSharingState(this.nativeTab);
|
||||
}
|
||||
|
||||
get cookieStoreId() {
|
||||
return getCookieStoreIdForTab(this, this.nativeTab);
|
||||
}
|
||||
|
@ -717,6 +725,7 @@ class Tab extends TabBase {
|
|||
highlighted: false,
|
||||
active: false,
|
||||
pinned: false,
|
||||
hidden: tabData.state ? tabData.state.hidden : tabData.hidden,
|
||||
incognito: Boolean(tabData.state && tabData.state.isPrivate),
|
||||
lastAccessed: tabData.state ? tabData.state.lastAccessed : tabData.lastAccessed,
|
||||
};
|
||||
|
|
|
@ -20,6 +20,11 @@ var {
|
|||
ExtensionError,
|
||||
} = ExtensionUtils;
|
||||
|
||||
const TABHIDE_PREFNAME = "extensions.webextensions.tabhide.enabled";
|
||||
|
||||
// WeakMap[Tab -> ExtensionID]
|
||||
let hiddenTabs = new WeakMap();
|
||||
|
||||
let tabListener = {
|
||||
tabReadyInitialized: false,
|
||||
tabReadyPromises: new WeakMap(),
|
||||
|
@ -77,6 +82,27 @@ let tabListener = {
|
|||
};
|
||||
|
||||
this.tabs = class extends ExtensionAPI {
|
||||
onShutdown(reason) {
|
||||
if (!this.extension.hasPermission("tabHide")) {
|
||||
return;
|
||||
}
|
||||
if (reason == "ADDON_DISABLE" ||
|
||||
reason == "ADDON_UNINSTALL") {
|
||||
// Show all hidden tabs if a tab managing extension is uninstalled or
|
||||
// disabled. If a user has more than one, the extensions will need to
|
||||
// self-manage re-hiding tabs.
|
||||
for (let tab of this.extension.tabManager.query()) {
|
||||
let nativeTab = tabTracker.getTab(tab.id);
|
||||
if (hiddenTabs.get(nativeTab) === this.extension.id) {
|
||||
hiddenTabs.delete(nativeTab);
|
||||
if (nativeTab.ownerGlobal) {
|
||||
nativeTab.ownerGlobal.gBrowser.showTab(nativeTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
let {extension} = context;
|
||||
|
||||
|
@ -261,6 +287,9 @@ this.tabs = class extends ExtensionAPI {
|
|||
if (changed.includes("label")) {
|
||||
needed.push("title");
|
||||
}
|
||||
if (changed.includes("sharing")) {
|
||||
needed.push("sharingState");
|
||||
}
|
||||
} else if (event.type == "TabPinned") {
|
||||
needed.push("pinned");
|
||||
} else if (event.type == "TabUnpinned") {
|
||||
|
@ -270,6 +299,12 @@ this.tabs = class extends ExtensionAPI {
|
|||
needed.push("discarded");
|
||||
} else if (event.type == "TabBrowserDiscarded") {
|
||||
needed.push("discarded");
|
||||
} else if (event.type == "TabShow") {
|
||||
needed.push("hidden");
|
||||
// Always remove the tab from the hiddenTabs map.
|
||||
hiddenTabs.delete(event.originalTarget);
|
||||
} else if (event.type == "TabHide") {
|
||||
needed.push("hidden");
|
||||
}
|
||||
|
||||
let tab = tabManager.getWrapper(event.originalTarget);
|
||||
|
@ -310,6 +345,8 @@ this.tabs = class extends ExtensionAPI {
|
|||
windowTracker.addListener("TabUnpinned", listener);
|
||||
windowTracker.addListener("TabBrowserInserted", listener);
|
||||
windowTracker.addListener("TabBrowserDiscarded", listener);
|
||||
windowTracker.addListener("TabShow", listener);
|
||||
windowTracker.addListener("TabHide", listener);
|
||||
|
||||
tabTracker.on("tab-isarticle", isArticleChangeListener);
|
||||
|
||||
|
@ -320,6 +357,8 @@ this.tabs = class extends ExtensionAPI {
|
|||
windowTracker.removeListener("TabUnpinned", listener);
|
||||
windowTracker.removeListener("TabBrowserInserted", listener);
|
||||
windowTracker.removeListener("TabBrowserDiscarded", listener);
|
||||
windowTracker.removeListener("TabShow", listener);
|
||||
windowTracker.removeListener("TabHide", listener);
|
||||
tabTracker.off("tab-isarticle", isArticleChangeListener);
|
||||
};
|
||||
}).api(),
|
||||
|
@ -978,6 +1017,47 @@ this.tabs = class extends ExtensionAPI {
|
|||
|
||||
tab.linkedBrowser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
|
||||
},
|
||||
|
||||
show(tabIds) {
|
||||
if (!Services.prefs.getBoolPref(TABHIDE_PREFNAME, false)) {
|
||||
throw new ExtensionError(`tabs.show is currently experimental and must be enabled with the ${TABHIDE_PREFNAME} preference.`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(tabIds)) {
|
||||
tabIds = [tabIds];
|
||||
}
|
||||
|
||||
for (let tabId of tabIds) {
|
||||
let tab = tabTracker.getTab(tabId);
|
||||
if (tab.ownerGlobal) {
|
||||
hiddenTabs.delete(tab);
|
||||
tab.ownerGlobal.gBrowser.showTab(tab);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hide(tabIds) {
|
||||
if (!Services.prefs.getBoolPref(TABHIDE_PREFNAME, false)) {
|
||||
throw new ExtensionError(`tabs.hide is currently experimental and must be enabled with the ${TABHIDE_PREFNAME} preference.`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(tabIds)) {
|
||||
tabIds = [tabIds];
|
||||
}
|
||||
|
||||
let hidden = [];
|
||||
let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
|
||||
for (let tab of tabs) {
|
||||
if (tab.ownerGlobal && !tab.hidden) {
|
||||
tab.ownerGlobal.gBrowser.hideTab(tab);
|
||||
if (tab.hidden) {
|
||||
hiddenTabs.set(tab, extension.id);
|
||||
hidden.push(tabTracker.getId(tab));
|
||||
}
|
||||
}
|
||||
}
|
||||
return hidden;
|
||||
},
|
||||
},
|
||||
};
|
||||
return self;
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"activeTab",
|
||||
"tabs"
|
||||
"tabs",
|
||||
"tabHide"
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
@ -52,6 +53,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "SharingState",
|
||||
"type": "object",
|
||||
"description": "Tab sharing state for screen, microphone and camera.",
|
||||
"properties": {
|
||||
"screen": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"description": "If the tab is sharing the screen the value will be one of \"Screen\", \"Window\", or \"Application\", or undefined if not screen sharing."
|
||||
},
|
||||
"camera": {
|
||||
"type": "boolean",
|
||||
"description": "True if the tab is using the camera."
|
||||
},
|
||||
"microphone": {
|
||||
"type": "boolean",
|
||||
"description": "True if the tab is using the microphone."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Tab",
|
||||
"type": "object",
|
||||
|
@ -75,10 +96,12 @@
|
|||
"incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
|
||||
"width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
|
||||
"height": {"type": "integer", "optional": true, "description": "The height of the tab in pixels."},
|
||||
"hidden": {"type": "boolean", "optional": true, "description": "True if the tab is hidden."},
|
||||
"sessionId": {"type": "string", "optional": true, "description": "The session ID used to uniquely identify a Tab obtained from the $(ref:sessions) API."},
|
||||
"cookieStoreId": {"type": "string", "optional": true, "description": "The CookieStoreId used for the tab."},
|
||||
"isArticle": {"type": "boolean", "optional": true, "description": "Whether the document in the tab can be rendered in reader mode."},
|
||||
"isInReaderMode": {"type": "boolean", "optional": true, "description": "Whether the document in the tab is being rendered in reader mode."}
|
||||
"isInReaderMode": {"type": "boolean", "optional": true, "description": "Whether the document in the tab is being rendered in reader mode."},
|
||||
"sharingState": {"$ref": "SharingState", "optional": true, "description": "Current tab sharing state for screen, microphone and camera."}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -600,6 +623,11 @@
|
|||
"optional": true,
|
||||
"description": "True while the tabs are not loaded with content."
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "True while the tabs are hidden."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
|
@ -640,6 +668,24 @@
|
|||
"minimum": 0,
|
||||
"optional": true,
|
||||
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
|
||||
},
|
||||
"screen": {
|
||||
"choices": [
|
||||
{"type": "string", "enum": ["Screen", "Window", "Application"]},
|
||||
{"type": "boolean"}
|
||||
],
|
||||
"optional": true,
|
||||
"description": "True for any screen sharing, or a string to specify type of screen sharing."
|
||||
},
|
||||
"camera": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "True if the tab is using the camera."
|
||||
},
|
||||
"microphone": {
|
||||
"type": "boolean",
|
||||
"optional": true,
|
||||
"description": "True if the tab is using the microphone."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1232,6 +1278,40 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "show",
|
||||
"type": "function",
|
||||
"description": "Shows one or more tabs.",
|
||||
"permissions": ["tabHide"],
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tabIds",
|
||||
"description": "The TAB ID or list of TAB IDs to show.",
|
||||
"choices": [
|
||||
{"type": "integer", "minimum": 0},
|
||||
{"type": "array", "items": {"type": "integer", "minimum": 0}}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hide",
|
||||
"type": "function",
|
||||
"description": "Hides one or more tabs. The <code>\"tabHide\"</code> permission is required to hide tabs. Not all tabs are hidable. Returns an array of hidden tabs.",
|
||||
"permissions": ["tabHide"],
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tabIds",
|
||||
"description": "The TAB ID or list of TAB IDs to hide.",
|
||||
"choices": [
|
||||
{"type": "integer", "minimum": 0},
|
||||
{"type": "array", "items": {"type": "integer", "minimum": 0}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
|
|
@ -153,6 +153,7 @@ skip-if = !e10s
|
|||
[browser_ext_tabs_executeScript_no_create.js]
|
||||
[browser_ext_tabs_executeScript_runAt.js]
|
||||
[browser_ext_tabs_getCurrent.js]
|
||||
[browser_ext_tabs_hide.js]
|
||||
[browser_ext_tabs_insertCSS.js]
|
||||
[browser_ext_tabs_lastAccessed.js]
|
||||
[browser_ext_tabs_lazy.js]
|
||||
|
@ -170,6 +171,7 @@ skip-if = !e10s
|
|||
[browser_ext_tabs_reload.js]
|
||||
[browser_ext_tabs_reload_bypass_cache.js]
|
||||
[browser_ext_tabs_sendMessage.js]
|
||||
[browser_ext_tabs_sharingState.js]
|
||||
[browser_ext_tabs_cookieStoreId.js]
|
||||
[browser_ext_tabs_update.js]
|
||||
[browser_ext_tabs_zoom.js]
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
"use strict";
|
||||
|
||||
const {Utils} = Cu.import("resource://gre/modules/sessionstore/Utils.jsm", {});
|
||||
const triggeringPrincipal_base64 = Utils.SERIALIZED_SYSTEMPRINCIPAL;
|
||||
|
||||
// Ensure the pref prevents API use when the extension has the tabHide permission.
|
||||
add_task(async function test_pref_disabled() {
|
||||
async function background() {
|
||||
let tabs = await browser.tabs.query({hidden: false});
|
||||
let ids = tabs.map(tab => tab.id);
|
||||
|
||||
await browser.test.assertRejects(
|
||||
browser.tabs.hide(ids),
|
||||
/tabs.hide is currently experimental/,
|
||||
"Got the expected error when pref not enabled"
|
||||
).catch(err => {
|
||||
browser.test.notifyFail("pref-test");
|
||||
throw err;
|
||||
});
|
||||
|
||||
browser.test.notifyPass("pref-test");
|
||||
}
|
||||
|
||||
let extdata = {
|
||||
manifest: {permissions: ["tabs", "tabHide"]},
|
||||
background,
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extdata);
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("pref-test");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_tabs_showhide() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.webextensions.tabhide.enabled", true]],
|
||||
});
|
||||
|
||||
async function background() {
|
||||
browser.test.onMessage.addListener(async (msg, data) => {
|
||||
switch (msg) {
|
||||
case "hideall": {
|
||||
let tabs = await browser.tabs.query({hidden: false});
|
||||
browser.test.assertEq(tabs.length, 5, "got 5 tabs");
|
||||
let ids = tabs.map(tab => tab.id);
|
||||
browser.test.log(`working with ids ${JSON.stringify(ids)}`);
|
||||
|
||||
let hidden = await browser.tabs.hide(ids);
|
||||
browser.test.assertEq(hidden.length, 3, "hid 3 tabs");
|
||||
tabs = await browser.tabs.query({hidden: true});
|
||||
ids = tabs.map(tab => tab.id);
|
||||
browser.test.assertEq(JSON.stringify(hidden.sort()),
|
||||
JSON.stringify(ids.sort()), "hidden tabIds match");
|
||||
|
||||
browser.test.sendMessage("hidden", {hidden});
|
||||
break;
|
||||
}
|
||||
case "showall": {
|
||||
let tabs = await browser.tabs.query({hidden: true});
|
||||
for (let tab of tabs) {
|
||||
browser.test.assertTrue(tab.hidden, "tab is hidden");
|
||||
}
|
||||
let ids = tabs.map(tab => tab.id);
|
||||
browser.tabs.show(ids);
|
||||
browser.test.sendMessage("shown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let extdata = {
|
||||
manifest: {permissions: ["tabs", "tabHide"]},
|
||||
background,
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extdata);
|
||||
await extension.startup();
|
||||
|
||||
let sessData = {
|
||||
windows: [{
|
||||
tabs: [
|
||||
{entries: [{url: "about:blank", triggeringPrincipal_base64}]},
|
||||
{entries: [{url: "https://example.com/", triggeringPrincipal_base64}]},
|
||||
{entries: [{url: "https://mochi.test:8888/", triggeringPrincipal_base64}]},
|
||||
],
|
||||
}, {
|
||||
tabs: [
|
||||
{entries: [{url: "about:blank", triggeringPrincipal_base64}]},
|
||||
{entries: [{url: "http://test1.example.com/", triggeringPrincipal_base64}]},
|
||||
],
|
||||
}],
|
||||
};
|
||||
|
||||
// Set up a test session with 2 windows and 5 tabs.
|
||||
let oldState = SessionStore.getBrowserState();
|
||||
let restored = TestUtils.topicObserved("sessionstore-browser-state-restored");
|
||||
SessionStore.setBrowserState(JSON.stringify(sessData));
|
||||
await restored;
|
||||
|
||||
// Attempt to hide all the tabs, however the active tab in each window cannot
|
||||
// be hidden, so the result will be 3 hidden tabs.
|
||||
extension.sendMessage("hideall");
|
||||
await extension.awaitMessage("hidden");
|
||||
|
||||
// We have 2 windows in this session. Otherwin is the non-current window.
|
||||
// In each window, the first tab will be the selected tab and should not be
|
||||
// hidden. The rest of the tabs should be hidden at this point. Hidden
|
||||
// status was already validated inside the extension, this double checks
|
||||
// from chrome code.
|
||||
let otherwin;
|
||||
for (let win of BrowserWindowIterator()) {
|
||||
if (win != window) {
|
||||
otherwin = win;
|
||||
}
|
||||
let tabs = Array.from(win.gBrowser.tabs.values());
|
||||
ok(!tabs[0].hidden, "first tab not hidden");
|
||||
for (let i = 1; i < tabs.length; i++) {
|
||||
ok(tabs[i].hidden, "tab hidden value is correct");
|
||||
}
|
||||
}
|
||||
|
||||
// Test closing the last visible tab, the next tab which is hidden should become
|
||||
// the selectedTab and will be visible.
|
||||
ok(!otherwin.gBrowser.selectedTab.hidden, "selected tab is not hidden");
|
||||
await BrowserTestUtils.removeTab(otherwin.gBrowser.selectedTab);
|
||||
ok(!otherwin.gBrowser.selectedTab.hidden, "tab was unhidden");
|
||||
|
||||
// Showall will unhide any remaining hidden tabs.
|
||||
extension.sendMessage("showall");
|
||||
await extension.awaitMessage("shown");
|
||||
|
||||
// Check from chrome code that all tabs are visible again.
|
||||
for (let win of BrowserWindowIterator()) {
|
||||
let tabs = Array.from(win.gBrowser.tabs.values());
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
ok(!tabs[i].hidden, "tab hidden value is correct");
|
||||
}
|
||||
}
|
||||
|
||||
// Close second window.
|
||||
await BrowserTestUtils.closeWindow(otherwin);
|
||||
|
||||
await extension.unload();
|
||||
|
||||
// Restore pre-test state.
|
||||
restored = TestUtils.topicObserved("sessionstore-browser-state-restored");
|
||||
SessionStore.setBrowserState(oldState);
|
||||
await restored;
|
||||
});
|
||||
|
||||
// Test our shutdown handling. Currently this means any hidden tabs will be
|
||||
// shown when a tabHide extension is shutdown. We additionally test the
|
||||
// tabs.onUpdated listener gets called with hidden state changes.
|
||||
add_task(async function test_tabs_shutdown() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.webextensions.tabhide.enabled", true]],
|
||||
});
|
||||
|
||||
let tabs = [
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/", true, true),
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true, true),
|
||||
];
|
||||
|
||||
async function background() {
|
||||
let tabs = await browser.tabs.query({url: "http://example.com/"});
|
||||
let testTab = tabs[0];
|
||||
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if ("hidden" in changeInfo) {
|
||||
browser.test.assertEq(tabId, testTab.id, "correct tab was hidden");
|
||||
browser.test.assertTrue(changeInfo.hidden, "tab is hidden");
|
||||
browser.test.sendMessage("changeInfo");
|
||||
}
|
||||
});
|
||||
|
||||
let hidden = await browser.tabs.hide(testTab.id);
|
||||
browser.test.assertEq(hidden[0], testTab.id, "tab was hidden");
|
||||
tabs = await browser.tabs.query({hidden: true});
|
||||
browser.test.assertEq(tabs[0].id, testTab.id, "tab was hidden");
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extdata = {
|
||||
manifest: {permissions: ["tabs", "tabHide"]},
|
||||
useAddonManager: "temporary", // For testing onShutdown.
|
||||
background,
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extdata);
|
||||
await extension.startup();
|
||||
|
||||
// test onUpdated
|
||||
await Promise.all([
|
||||
extension.awaitMessage("ready"),
|
||||
extension.awaitMessage("changeInfo"),
|
||||
]);
|
||||
Assert.ok(tabs[0].hidden, "Tab is hidden by extension");
|
||||
|
||||
await extension.unload();
|
||||
|
||||
Assert.ok(!tabs[0].hidden, "Tab is not hidden after unloading extension");
|
||||
await BrowserTestUtils.removeTab(tabs[0]);
|
||||
await BrowserTestUtils.removeTab(tabs[1]);
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
"use strict";
|
||||
|
||||
add_task(async function test_tabs_mediaIndicators() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.webextensions.tabhide.enabled", true]],
|
||||
});
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
|
||||
// setBrowserSharing is called when a request for media icons occurs. We're
|
||||
// just testing that extension tabs get the info and are updated when it is
|
||||
// called.
|
||||
gBrowser.setBrowserSharing(tab.linkedBrowser, {screen: "Window", microphone: true, camera: true});
|
||||
|
||||
async function background() {
|
||||
let tabs = await browser.tabs.query({microphone: true});
|
||||
let testTab = tabs[0];
|
||||
|
||||
let state = testTab.sharingState;
|
||||
browser.test.assertTrue(state.camera, "sharing camera was turned on");
|
||||
browser.test.assertTrue(state.microphone, "sharing mic was turned on");
|
||||
browser.test.assertEq(state.screen, "Window", "sharing screen is window");
|
||||
|
||||
tabs = await browser.tabs.query({screen: true});
|
||||
browser.test.assertEq(tabs.length, 1, "screen sharing tab was found");
|
||||
|
||||
tabs = await browser.tabs.query({screen: "Window"});
|
||||
browser.test.assertEq(tabs.length, 1, "screen sharing (window) tab was found");
|
||||
|
||||
tabs = await browser.tabs.query({screen: "Screen"});
|
||||
browser.test.assertEq(tabs.length, 0, "screen sharing tab was not found");
|
||||
|
||||
// Verify we cannot hide a sharing tab.
|
||||
let hidden = await browser.tabs.hide(testTab.id);
|
||||
browser.test.assertEq(hidden.length, 0, "unable to hide sharing tab");
|
||||
tabs = await browser.tabs.query({hidden: true});
|
||||
browser.test.assertEq(tabs.length, 0, "unable to hide sharing tab");
|
||||
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (testTab.id !== tabId) {
|
||||
return;
|
||||
}
|
||||
let state = tab.sharingState;
|
||||
browser.test.assertFalse(state.camera, "sharing camera was turned off");
|
||||
browser.test.assertFalse(state.microphone, "sharing mic was turned off");
|
||||
browser.test.assertFalse(state.screen, "sharing screen was turned off");
|
||||
browser.test.notifyPass("done");
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extdata = {
|
||||
manifest: {permissions: ["tabs", "tabHide"]},
|
||||
useAddonManager: "temporary",
|
||||
background,
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extdata);
|
||||
await extension.startup();
|
||||
|
||||
// Test that onUpdated is called after the sharing state is changed from
|
||||
// chrome code.
|
||||
await extension.awaitMessage("ready");
|
||||
gBrowser.setBrowserSharing(tab.linkedBrowser, {});
|
||||
|
||||
await extension.awaitFinish("done");
|
||||
await extension.unload();
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
|
@ -20,7 +20,7 @@
|
|||
* promiseContentDimensions alterContent
|
||||
* promisePrefChangeObserved openContextMenuInFrame
|
||||
* promiseAnimationFrame getCustomizableUIPanelID
|
||||
* awaitEvent
|
||||
* awaitEvent BrowserWindowIterator
|
||||
*/
|
||||
|
||||
// There are shutdown issues for which multiple rejections are left uncaught.
|
||||
|
@ -484,3 +484,13 @@ function awaitEvent(eventName, id) {
|
|||
Management.on(eventName, listener);
|
||||
});
|
||||
}
|
||||
|
||||
function* BrowserWindowIterator() {
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let currentWindow = windowsEnum.getNext();
|
||||
if (!currentWindow.closed) {
|
||||
yield currentWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
/* import-globals-from editBookmarkOverlay.js */
|
||||
// Via downloadsViewOverlay.xul -> allDownloadsViewOverlay.xul
|
||||
/* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
|
||||
/* import-globals-from ../../../base/content/browser-sync.js */
|
||||
|
||||
Components.utils.import("resource://gre/modules/AppConstants.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -137,6 +138,7 @@ var PlacesOrganizer = {
|
|||
.removeChild(document.getElementById("placesContext_show:info"));
|
||||
|
||||
ContentArea.focus();
|
||||
gSync.init();
|
||||
},
|
||||
|
||||
QueryInterface: function PO_QueryInterface(aIID) {
|
||||
|
|
|
@ -45,9 +45,11 @@
|
|||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/places.js"/>
|
||||
#ifndef XP_MACOSX
|
||||
<!-- On Mac, this is included via macBrowserOverlay.xul -> browser.js -> defineLazyScriptGetter -->
|
||||
<!-- On Mac, these are included via macBrowserOverlay.xul -> browser.js -> defineLazyScriptGetter -->
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/places/editBookmarkOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/browser-sync.js"/>
|
||||
#endif
|
||||
|
||||
<stringbundleset id="placesStringSet">
|
||||
|
|
|
@ -109,7 +109,8 @@ this.ShieldPreferences = {
|
|||
checkbox.setAttribute("class", "tail-with-learn-more");
|
||||
checkbox.setAttribute("label", "Allow Firefox to install and run studies");
|
||||
checkbox.setAttribute("preference", OPT_OUT_STUDIES_ENABLED_PREF);
|
||||
checkbox.setAttribute("disabled", !Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF));
|
||||
checkbox.setAttribute("disabled", Services.prefs.prefIsLocked(FHR_UPLOAD_ENABLED_PREF) ||
|
||||
!AppConstants.MOZ_TELEMETRY_REPORTING);
|
||||
hContainer.appendChild(checkbox);
|
||||
|
||||
const viewStudies = doc.createElementNS(XUL_NS, "label");
|
||||
|
@ -132,7 +133,7 @@ this.ShieldPreferences = {
|
|||
doc.defaultView.addEventListener("unload", () => fhrPref.off("change", onChangeFHRPref), { once: true });
|
||||
|
||||
// Actually inject the elements we've created.
|
||||
const parent = doc.getElementById("submitHealthReportBox").closest("vbox");
|
||||
const parent = doc.getElementById("submitHealthReportBox").closest("description");
|
||||
parent.appendChild(container);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -116,6 +116,7 @@ webextPerms.description.privacy=Read and modify privacy settings
|
|||
webextPerms.description.proxy=Control browser proxy settings
|
||||
webextPerms.description.sessions=Access recently closed tabs
|
||||
webextPerms.description.tabs=Access browser tabs
|
||||
webextPerms.description.tabHide=Hide and show browser tabs
|
||||
webextPerms.description.topSites=Access browsing history
|
||||
webextPerms.description.unlimitedStorage=Store unlimited amount of client-side data
|
||||
webextPerms.description.webNavigation=Access browser activity during navigation
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
diff -Nru dpkg-1.17.20/debian/changelog dpkg-1.17.20.deb7moz1/debian/changelog
|
||||
--- dpkg-1.17.20/debian/changelog 2014-10-23 08:56:30.000000000 +0900
|
||||
+++ dpkg-1.17.20.deb7moz1/debian/changelog 2018-01-17 18:30:46.000000000 +0900
|
||||
@@ -1,3 +1,10 @@
|
||||
+dpkg (1.17.20.deb7moz1) wheezy; urgency=medium
|
||||
+
|
||||
+ * Mozilla backport for wheezy.
|
||||
+ * Apply patch from 1.17.23 fixing build against perl 5.14.
|
||||
+
|
||||
+ -- Mike Hommey <glandium@mozilla.com> Wed, 17 Jan 2018 18:30:46 +0900
|
||||
+
|
||||
dpkg (1.17.20) unstable; urgency=low
|
||||
|
||||
[ Guillem Jover ]
|
||||
diff -Nru dpkg-1.17.20/dselect/mkcurkeys.pl dpkg-1.17.20.deb7moz1/dselect/mkcurkeys.pl
|
||||
--- dpkg-1.17.20/dselect/mkcurkeys.pl 2014-10-21 09:45:43.000000000 +0900
|
||||
+++ dpkg-1.17.20.deb7moz1/dselect/mkcurkeys.pl 2018-01-17 18:30:46.000000000 +0900
|
||||
@@ -140,6 +140,6 @@
|
||||
sub p {
|
||||
my ($k, $v) = @_;
|
||||
|
||||
- $v =~ s/["\\]/\\${^MATCH}/pg;
|
||||
+ $v =~ s/(["\\])/\\$1/g;
|
||||
printf(" { %-15s \"%-20s },\n", $k . ',', $v . '"') or die $!;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
diff -Nru git-2.8.0~rc3/debian/changelog git-2.8.0~rc3/debian/changelog
|
||||
--- git-2.8.0~rc3/debian/changelog 2016-03-17 10:28:14.000000000 +0900
|
||||
+++ git-2.8.0~rc3/debian/changelog 2018-01-12 14:46:45.000000000 +0900
|
||||
@@ -1,3 +1,9 @@
|
||||
+git (1:2.8.0~rc3-1.deb7moz1) wheezy; urgency=medium
|
||||
+
|
||||
+ * Mozilla backport for wheezy.
|
||||
+
|
||||
+ -- Mike Hommey <glandium@mozilla.com> Fri, 12 Jan 2018 14:46:45 +0900
|
||||
+
|
||||
git (1:2.8.0~rc3-1) unstable; urgency=medium
|
||||
|
||||
* new upstream release candidate (see RelNotes/2.8.0.txt).
|
|
@ -1,13 +0,0 @@
|
|||
diff -Nru ninja-build-1.6.0/debian/changelog ninja-build-1.6.0/debian/changelog
|
||||
--- ninja-build-1.6.0/debian/changelog 2016-02-09 04:54:03.000000000 +0900
|
||||
+++ ninja-build-1.6.0/debian/changelog 2017-12-21 16:38:47.000000000 +0900
|
||||
@@ -1,3 +1,9 @@
|
||||
+ninja-build (1.6.0-1.deb7moz1) wheezy; urgency=medium
|
||||
+
|
||||
+ * Mozilla backport for wheezy.
|
||||
+
|
||||
+ -- Mike Hommey <glandium@mozilla.com> Thu, 21 Dec 2017 16:38:47 +0900
|
||||
+
|
||||
ninja-build (1.6.0-1) unstable; urgency=medium
|
||||
|
||||
* New maintainer. (Closes: #810025)
|
|
@ -8,17 +8,23 @@ const { AnimationsFront } = require("devtools/shared/fronts/animation");
|
|||
const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const App = createFactory(require("./components/App"));
|
||||
const { isAllTimingEffectEqual } = require("./utils/utils");
|
||||
|
||||
const { updateAnimations } = require("./actions/animations");
|
||||
const { updateElementPickerEnabled } = require("./actions/element-picker");
|
||||
const { updateSidebarSize } = require("./actions/sidebar");
|
||||
const { isAllAnimationEqual } = require("./utils/utils");
|
||||
|
||||
class AnimationInspector {
|
||||
constructor(inspector) {
|
||||
constructor(inspector, win) {
|
||||
this.inspector = inspector;
|
||||
this.win = win;
|
||||
|
||||
this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
|
||||
this.getNodeFromActor = this.getNodeFromActor.bind(this);
|
||||
this.simulateAnimation = this.simulateAnimation.bind(this);
|
||||
this.toggleElementPicker = this.toggleElementPicker.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
|
||||
|
@ -26,10 +32,30 @@ class AnimationInspector {
|
|||
this.onSidebarResized = this.onSidebarResized.bind(this);
|
||||
this.onSidebarSelect = this.onSidebarSelect.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
this.emit = this.emit.bind(this);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const {
|
||||
setSelectedNode,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
} = this.inspector.getCommonComponentProps();
|
||||
|
||||
const {
|
||||
onHideBoxModelHighlighter,
|
||||
} = this.inspector.getPanel("boxmodel").getComponentProps();
|
||||
|
||||
const {
|
||||
emit: emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
simulateAnimation,
|
||||
toggleElementPicker,
|
||||
} = this;
|
||||
|
||||
const target = this.inspector.target;
|
||||
this.animationsFront = new AnimationsFront(target.client, target.form);
|
||||
|
||||
|
@ -41,7 +67,14 @@ class AnimationInspector {
|
|||
},
|
||||
App(
|
||||
{
|
||||
toggleElementPicker: this.toggleElementPicker
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
toggleElementPicker,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -61,7 +94,113 @@ class AnimationInspector {
|
|||
this.inspector.toolbox.off("picker-started", this.onElementPickerStarted);
|
||||
this.inspector.toolbox.off("picker-stopped", this.onElementPickerStopped);
|
||||
|
||||
if (this.simulatedAnimation) {
|
||||
this.simulatedAnimation.cancel();
|
||||
this.simulatedAnimation = null;
|
||||
}
|
||||
|
||||
if (this.simulatedElement) {
|
||||
this.simulatedElement.remove();
|
||||
this.simulatedElement = null;
|
||||
}
|
||||
|
||||
this.inspector = null;
|
||||
this.win = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of animated property from given animation actor.
|
||||
*
|
||||
* @param {Object} animation
|
||||
* @return {Map} A map of animated property
|
||||
* key: {String} Animated property name
|
||||
* value: {Array} Array of keyframe object
|
||||
* Also, the keyframe object is consisted as following.
|
||||
* {
|
||||
* value: {String} style,
|
||||
* offset: {Number} offset of keyframe,
|
||||
* easing: {String} easing from this keyframe to next keyframe,
|
||||
* distance: {Number} use as y coordinate in graph,
|
||||
* }
|
||||
*/
|
||||
async getAnimatedPropertyMap(animation) {
|
||||
let properties = [];
|
||||
|
||||
try {
|
||||
properties = await animation.getProperties();
|
||||
} catch (e) {
|
||||
// Expected if we've already been destroyed in the meantime.
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const animatedPropertyMap = new Map();
|
||||
|
||||
for (const { name, values } of properties) {
|
||||
const keyframes = values.map(({ value, offset, easing, distance = 0 }) => {
|
||||
offset = parseFloat(offset.toFixed(3));
|
||||
return { value, offset, easing, distance };
|
||||
});
|
||||
|
||||
animatedPropertyMap.set(name, keyframes);
|
||||
}
|
||||
|
||||
return animatedPropertyMap;
|
||||
}
|
||||
|
||||
getNodeFromActor(actorID) {
|
||||
return this.inspector.walker.getNodeFromActor(actorID, ["node"]);
|
||||
}
|
||||
|
||||
isPanelVisible() {
|
||||
return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
|
||||
this.inspector.toolbox.currentToolId === "inspector" &&
|
||||
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns simulatable animation by given parameters.
|
||||
* The returned animation is implementing Animation interface of Web Animation API.
|
||||
* https://drafts.csswg.org/web-animations/#the-animation-interface
|
||||
*
|
||||
* @param {Array} keyframes
|
||||
* e.g. [{ opacity: 0 }, { opacity: 1 }]
|
||||
* @param {Object} effectTiming
|
||||
* e.g. { duration: 1000, fill: "both" }
|
||||
* @param {Boolean} isElementNeeded
|
||||
* true: create animation with an element.
|
||||
* If want to know computed value of the element, turn on.
|
||||
* false: create animation without an element,
|
||||
* If need to know only timing progress.
|
||||
* @return {Animation}
|
||||
* https://drafts.csswg.org/web-animations/#the-animation-interface
|
||||
*/
|
||||
simulateAnimation(keyframes, effectTiming, isElementNeeded) {
|
||||
let targetEl = null;
|
||||
|
||||
if (isElementNeeded) {
|
||||
if (!this.simulatedElement) {
|
||||
this.simulatedElement = this.win.document.createElement("div");
|
||||
this.win.document.documentElement.appendChild(this.simulatedElement);
|
||||
} else {
|
||||
// Reset styles.
|
||||
this.simulatedElement.style.cssText = "";
|
||||
}
|
||||
|
||||
targetEl = this.simulatedElement;
|
||||
}
|
||||
|
||||
if (!this.simulatedAnimation) {
|
||||
this.simulatedAnimation = new this.win.Animation();
|
||||
}
|
||||
|
||||
this.simulatedAnimation.effect =
|
||||
new this.win.KeyframeEffect(targetEl, keyframes, effectTiming);
|
||||
|
||||
return this.simulatedAnimation;
|
||||
}
|
||||
|
||||
toggleElementPicker() {
|
||||
this.inspector.toolbox.highlighterUtils.togglePicker();
|
||||
}
|
||||
|
||||
async update() {
|
||||
|
@ -78,7 +217,7 @@ class AnimationInspector {
|
|||
? await this.animationsFront.getAnimationPlayersForNode(selection.nodeFront)
|
||||
: [];
|
||||
|
||||
if (!this.animations || !isAllTimingEffectEqual(animations, this.animations)) {
|
||||
if (!this.animations || !isAllAnimationEqual(animations, this.animations)) {
|
||||
this.inspector.store.dispatch(updateAnimations(animations));
|
||||
this.animations = animations;
|
||||
}
|
||||
|
@ -86,16 +225,6 @@ class AnimationInspector {
|
|||
done();
|
||||
}
|
||||
|
||||
isPanelVisible() {
|
||||
return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
|
||||
this.inspector.toolbox.currentToolId === "inspector" &&
|
||||
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
|
||||
}
|
||||
|
||||
toggleElementPicker() {
|
||||
this.inspector.toolbox.highlighterUtils.togglePicker();
|
||||
}
|
||||
|
||||
onElementPickerStarted() {
|
||||
this.inspector.store.dispatch(updateElementPickerEnabled(true));
|
||||
}
|
||||
|
|
|
@ -4,22 +4,64 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
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 AnimationTarget = createFactory(require("./AnimationTarget"));
|
||||
const SummaryGraph = createFactory(require("./graph/SummaryGraph"));
|
||||
|
||||
class AnimationItem extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getAnimatedPropertyMap: PropTypes.func.isRequired,
|
||||
getNodeFromActor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
return dom.li(
|
||||
{
|
||||
className: "animation-item"
|
||||
}
|
||||
className: `animation-item ${ animation.state.type }`
|
||||
},
|
||||
AnimationTarget(
|
||||
{
|
||||
animation,
|
||||
emitEventForTest,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
}
|
||||
),
|
||||
SummaryGraph(
|
||||
{
|
||||
animation,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,15 +14,49 @@ class AnimationList extends PureComponent {
|
|||
static get propTypes() {
|
||||
return {
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getAnimatedPropertyMap: PropTypes.func.isRequired,
|
||||
getNodeFromActor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animations,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
return dom.ul(
|
||||
{
|
||||
className: "animation-list"
|
||||
},
|
||||
this.props.animations.map(animation => AnimationItem({ animation }))
|
||||
animations.map(animation =>
|
||||
AnimationItem(
|
||||
{
|
||||
animation,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,15 +12,34 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
|||
const AnimationList = createFactory(require("./AnimationList"));
|
||||
const AnimationListHeader = createFactory(require("./AnimationListHeader"));
|
||||
|
||||
const TimeScale = require("../utils/timescale");
|
||||
|
||||
class AnimationListContainer extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getAnimatedPropertyMap: PropTypes.func.isRequired,
|
||||
getNodeFromActor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { animations } = this.props;
|
||||
const {
|
||||
animations,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
} = this.props;
|
||||
const timeScale = new TimeScale(animations);
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
|
@ -28,12 +47,20 @@ class AnimationListContainer extends PureComponent {
|
|||
},
|
||||
AnimationListHeader(
|
||||
{
|
||||
animations
|
||||
timeScale,
|
||||
}
|
||||
),
|
||||
AnimationList(
|
||||
{
|
||||
animations
|
||||
animations,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -14,12 +14,12 @@ const AnimationTimelineTickList = createFactory(require("./AnimationTimelineTick
|
|||
class AnimationListHeader extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { animations } = this.props;
|
||||
const { timeScale } = this.props;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ class AnimationListHeader extends PureComponent {
|
|||
},
|
||||
AnimationTimelineTickList(
|
||||
{
|
||||
animations
|
||||
timeScale
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const { Rep } = REPS;
|
||||
const ElementNode = REPS.ElementNode;
|
||||
|
||||
class AnimationTarget extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getNodeFromActor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
nodeFront: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.updateNodeFront(this.props.animation);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.animation.actorID !== nextProps.animation.actorID) {
|
||||
this.updateNodeFront(nextProps.animation);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return this.state.nodeFront !== nextState.nodeFront;
|
||||
}
|
||||
|
||||
/**
|
||||
* While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
|
||||
* translate nodeFront to a grip-like object that can be used with an ElementNode rep.
|
||||
*
|
||||
* @params {NodeFront} nodeFront
|
||||
* The NodeFront for which we want to create a grip-like object.
|
||||
* @returns {Object} a grip-like object that can be used with Reps.
|
||||
*/
|
||||
translateNodeFrontToGrip(nodeFront) {
|
||||
let { attributes } = nodeFront;
|
||||
|
||||
// The main difference between NodeFront and grips is that attributes are treated as
|
||||
// a map in grips and as an array in NodeFronts.
|
||||
let attributesMap = {};
|
||||
for (let {name, value} of attributes) {
|
||||
attributesMap[name] = value;
|
||||
}
|
||||
|
||||
return {
|
||||
actor: nodeFront.actorID,
|
||||
preview: {
|
||||
attributes: attributesMap,
|
||||
attributesLength: attributes.length,
|
||||
isConnected: true,
|
||||
nodeName: nodeFront.nodeName.toLowerCase(),
|
||||
nodeType: nodeFront.nodeType,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async updateNodeFront(animation) {
|
||||
const { emitEventForTest, getNodeFromActor } = this.props;
|
||||
|
||||
// Try and get it from the playerFront directly.
|
||||
let nodeFront = animation.animationTargetNodeFront;
|
||||
|
||||
// Next, get it from the walkerActor if it wasn't found.
|
||||
if (!nodeFront) {
|
||||
try {
|
||||
nodeFront = await getNodeFromActor(animation.actorID);
|
||||
} catch (e) {
|
||||
// If an error occured while getting the nodeFront and if it can't be
|
||||
// attributed to the panel having been destroyed in the meantime, this
|
||||
// error needs to be logged and render needs to stop.
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ nodeFront });
|
||||
emitEventForTest("animation-target-rendered");
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
} = this.props;
|
||||
|
||||
const { nodeFront } = this.state;
|
||||
|
||||
if (!nodeFront) {
|
||||
return dom.div(
|
||||
{
|
||||
className: "animation-target"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "animation-target"
|
||||
},
|
||||
Rep(
|
||||
{
|
||||
defaultRep: ElementNode,
|
||||
mode: MODE.TINY,
|
||||
object: this.translateNodeFrontToGrip(nodeFront),
|
||||
onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
|
||||
onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront),
|
||||
onInspectIconClick: () => setSelectedNode(nodeFront, "animation-panel"),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimationTarget;
|
|
@ -13,7 +13,6 @@ const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
|||
|
||||
const AnimationTimelineTickItem = createFactory(require("./AnimationTimelineTickItem"));
|
||||
|
||||
const TimeScale = require("../utils/timescale");
|
||||
const { findOptimalTimeInterval } = require("../utils/utils");
|
||||
|
||||
// The minimum spacing between 2 time graduation headers in the timeline (px).
|
||||
|
@ -22,8 +21,8 @@ const TIME_GRADUATION_MIN_SPACING = 40;
|
|||
class AnimationTimelineTickList extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sidebarWidth: PropTypes.number.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -61,8 +60,7 @@ class AnimationTimelineTickList extends PureComponent {
|
|||
}
|
||||
|
||||
updateTickList() {
|
||||
const { animations } = this.props;
|
||||
const timeScale = new TimeScale(animations);
|
||||
const { timeScale } = this.props;
|
||||
const tickListEl = ReactDOM.findDOMNode(this);
|
||||
const width = tickListEl.offsetWidth;
|
||||
const animationDuration = timeScale.getDuration();
|
||||
|
|
|
@ -16,6 +16,13 @@ class App extends PureComponent {
|
|||
static get propTypes() {
|
||||
return {
|
||||
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getAnimatedPropertyMap: PropTypes.func.isRequired,
|
||||
getNodeFromActor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
toggleElementPicker: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -25,7 +32,17 @@ class App extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { animations, toggleElementPicker } = this.props;
|
||||
const {
|
||||
animations,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
toggleElementPicker,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
|
@ -34,7 +51,14 @@ class App extends PureComponent {
|
|||
animations.length ?
|
||||
AnimationListContainer(
|
||||
{
|
||||
animations
|
||||
animations,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
getNodeFromActor,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
simulateAnimation,
|
||||
}
|
||||
)
|
||||
:
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/* 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 { 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");
|
||||
|
||||
class AnimationName extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
} = this.props;
|
||||
|
||||
return dom.svg(
|
||||
{
|
||||
className: "animation-name",
|
||||
},
|
||||
dom.text(
|
||||
{
|
||||
y: "50%",
|
||||
x: "100%"
|
||||
},
|
||||
animation.state.name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimationName;
|
|
@ -0,0 +1,94 @@
|
|||
/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const { SummaryGraphHelper, toPathString } = require("../../utils/graph-helper");
|
||||
const TimingPath = require("./TimingPath");
|
||||
|
||||
class ComputedTimingPath extends TimingPath {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
durationPerPixel: PropTypes.number.isRequired,
|
||||
keyframes: PropTypes.object.isRequired,
|
||||
opacity: PropTypes.number.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
totalDuration: PropTypes.number.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
durationPerPixel,
|
||||
keyframes,
|
||||
opacity,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
} = this.props;
|
||||
|
||||
const { state } = animation;
|
||||
const effectTiming = Object.assign({}, state, {
|
||||
iterations: state.iterationCount ? state.iterationCount : Infinity
|
||||
});
|
||||
|
||||
// Create new keyframes for opacity as computed style.
|
||||
// The reason why we use computed value instead of computed timing progress is to
|
||||
// include the easing in keyframes as well. Although the computed timing progress
|
||||
// is not affected by the easing in keyframes at all, computed value reflects that.
|
||||
const frames = keyframes.map(keyframe => {
|
||||
return {
|
||||
opacity: keyframe.offset,
|
||||
offset: keyframe.offset,
|
||||
easing: keyframe.easing
|
||||
};
|
||||
});
|
||||
const simulatedAnimation = simulateAnimation(frames, effectTiming, true);
|
||||
const simulatedElement = simulatedAnimation.effect.target;
|
||||
const win = simulatedElement.ownerGlobal;
|
||||
const endTime = simulatedAnimation.effect.getComputedTiming().endTime;
|
||||
|
||||
// Set the underlying opacity to zero so that if we sample the animation's output
|
||||
// during the delay phase and it is not filling backwards, we get zero.
|
||||
simulatedElement.style.opacity = 0;
|
||||
|
||||
const getValueFunc = time => {
|
||||
if (time < 0) {
|
||||
return { x: time, y: 0 };
|
||||
}
|
||||
|
||||
simulatedAnimation.currentTime = time < endTime ? time : endTime;
|
||||
return win.getComputedStyle(simulatedElement).opacity;
|
||||
};
|
||||
|
||||
const toPathStringFunc = segments => {
|
||||
const firstSegment = segments[0];
|
||||
let pathString = `M${ firstSegment.x },0 `;
|
||||
pathString += toPathString(segments);
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
pathString += `L${ lastSegment.x },0 Z`;
|
||||
return pathString;
|
||||
};
|
||||
|
||||
const helper = new SummaryGraphHelper(state, keyframes,
|
||||
totalDuration, durationPerPixel,
|
||||
getValueFunc, toPathStringFunc);
|
||||
const offset = state.previousStartTime ? state.previousStartTime : 0;
|
||||
|
||||
return dom.g(
|
||||
{
|
||||
className: "animation-computed-timing-path",
|
||||
style: { opacity },
|
||||
transform: `translate(${ offset })`
|
||||
},
|
||||
super.renderGraph(state, helper)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComputedTimingPath;
|
|
@ -0,0 +1,46 @@
|
|||
/* 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 { 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");
|
||||
|
||||
class DelaySign extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
const { state } = animation;
|
||||
const startTime = (state.previousStartTime || 0) - timeScale.minStartTime
|
||||
+ (state.delay < 0 ? state.delay : 0);
|
||||
const offset = startTime / timeScale.getDuration() * 100;
|
||||
const width = Math.abs(state.delay) / timeScale.getDuration() * 100;
|
||||
|
||||
const delayClass = state.delay < 0 ? "negative" : "";
|
||||
const fillClass = state.fill === "both" || state.fill === "backwards" ? "fill" : "";
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: `animation-delay-sign ${ delayClass } ${ fillClass }`,
|
||||
style: {
|
||||
width: `${ width }%`,
|
||||
left: `${ offset }%`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DelaySign;
|
|
@ -0,0 +1,72 @@
|
|||
/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const { SummaryGraphHelper, toPathString } = require("../../utils/graph-helper");
|
||||
const TimingPath = require("./TimingPath");
|
||||
|
||||
class EffectTimingPath extends TimingPath {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
durationPerPixel: PropTypes.number.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
totalDuration: PropTypes.number.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
durationPerPixel,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
} = this.props;
|
||||
|
||||
const { state } = animation;
|
||||
const effectTiming = Object.assign({}, state, {
|
||||
iterations: state.iterationCount ? state.iterationCount : Infinity
|
||||
});
|
||||
|
||||
const simulatedAnimation = simulateAnimation(null, effectTiming, false);
|
||||
const endTime = simulatedAnimation.effect.getComputedTiming().endTime;
|
||||
|
||||
const getValueFunc = time => {
|
||||
if (time < 0) {
|
||||
return { x: time, y: 0 };
|
||||
}
|
||||
|
||||
simulatedAnimation.currentTime = time < endTime ? time : endTime;
|
||||
return Math.max(simulatedAnimation.effect.getComputedTiming().progress, 0);
|
||||
};
|
||||
|
||||
const toPathStringFunc = segments => {
|
||||
const firstSegment = segments[0];
|
||||
let pathString = `M${ firstSegment.x },0 `;
|
||||
pathString += toPathString(segments);
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
pathString += `L${ lastSegment.x },0`;
|
||||
return pathString;
|
||||
};
|
||||
|
||||
const helper = new SummaryGraphHelper(state, null,
|
||||
totalDuration, durationPerPixel,
|
||||
getValueFunc, toPathStringFunc);
|
||||
const offset = state.previousStartTime ? state.previousStartTime : 0;
|
||||
|
||||
return dom.g(
|
||||
{
|
||||
className: "animation-effect-timing-path",
|
||||
transform: `translate(${ offset })`
|
||||
},
|
||||
super.renderGraph(state, helper)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EffectTimingPath;
|
|
@ -0,0 +1,47 @@
|
|||
/* 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 { 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");
|
||||
|
||||
class EndDelaySign extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
const { state } = animation;
|
||||
const startTime = (state.previousStartTime || 0) - timeScale.minStartTime;
|
||||
const endTime = state.duration * state.iterationCount + state.delay;
|
||||
const endDelay = state.endDelay < 0 ? state.endDelay : 0;
|
||||
const offset = (startTime + endTime + endDelay) / timeScale.getDuration() * 100;
|
||||
const width = Math.abs(state.endDelay) / timeScale.getDuration() * 100;
|
||||
|
||||
const endDelayClass = state.endDelay < 0 ? "negative" : "";
|
||||
const fillClass = state.fill === "both" || state.fill === "forwards" ? "fill" : "";
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: `animation-end-delay-sign ${ endDelayClass } ${ fillClass }`,
|
||||
style: {
|
||||
width: `${ width }%`,
|
||||
left: `${ offset }%`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EndDelaySign;
|
|
@ -0,0 +1,41 @@
|
|||
/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const NegativePath = require("./NegativePath");
|
||||
|
||||
class NegativeDelayPath extends NegativePath {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
durationPerPixel: PropTypes.number.isRequired,
|
||||
keyframes: PropTypes.object.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
totalDuration: PropTypes.number.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
props.className = "animation-negative-delay-path";
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderGraph(state, helper) {
|
||||
const startTime = state.delay;
|
||||
const endTime = 0;
|
||||
const segments = helper.createPathSegments(startTime, endTime);
|
||||
|
||||
return dom.path(
|
||||
{
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NegativeDelayPath;
|
|
@ -0,0 +1,41 @@
|
|||
/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const NegativePath = require("./NegativePath");
|
||||
|
||||
class NegativeEndDelayPath extends NegativePath {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
durationPerPixel: PropTypes.number.isRequired,
|
||||
keyframes: PropTypes.object.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
totalDuration: PropTypes.number.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
props.className = "animation-negative-end-delay-path";
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderGraph(state, helper) {
|
||||
const endTime = state.delay + state.iterationCount * state.duration;
|
||||
const startTime = endTime + state.endDelay;
|
||||
const segments = helper.createPathSegments(startTime, endTime);
|
||||
|
||||
return dom.path(
|
||||
{
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NegativeEndDelayPath;
|
|
@ -0,0 +1,90 @@
|
|||
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const { SummaryGraphHelper, toPathString } = require("../../utils/graph-helper");
|
||||
|
||||
class NegativePath extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
className: PropTypes.string.isRequired,
|
||||
durationPerPixel: PropTypes.number.isRequired,
|
||||
keyframes: PropTypes.object.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
totalDuration: PropTypes.number.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
className,
|
||||
durationPerPixel,
|
||||
keyframes,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
} = this.props;
|
||||
|
||||
const { state } = animation;
|
||||
const effectTiming = Object.assign({}, state, {
|
||||
fill: "both",
|
||||
iterations: state.iterationCount ? state.iterationCount : Infinity
|
||||
});
|
||||
|
||||
// Create new keyframes for opacity as computed style.
|
||||
// The reason why we use computed value instead of computed timing progress is to
|
||||
// include the easing in keyframes as well. Although the computed timing progress
|
||||
// is not affected by the easing in keyframes at all, computed value reflects that.
|
||||
const frames = keyframes.map(keyframe => {
|
||||
return {
|
||||
opacity: keyframe.offset,
|
||||
offset: keyframe.offset,
|
||||
easing: keyframe.easing
|
||||
};
|
||||
});
|
||||
|
||||
const simulatedAnimation = simulateAnimation(frames, effectTiming, true);
|
||||
const simulatedElement = simulatedAnimation.effect.target;
|
||||
const win = simulatedElement.ownerGlobal;
|
||||
|
||||
// Set the underlying opacity to zero so that if we sample the animation's output
|
||||
// during the delay phase and it is not filling backwards, we get zero.
|
||||
simulatedElement.style.opacity = 0;
|
||||
|
||||
const getValueFunc = time => {
|
||||
simulatedAnimation.currentTime = time;
|
||||
return win.getComputedStyle(simulatedElement).opacity;
|
||||
};
|
||||
|
||||
const toPathStringFunc = segments => {
|
||||
const firstSegment = segments[0];
|
||||
let pathString = `M${ firstSegment.x },0 `;
|
||||
pathString += toPathString(segments);
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
pathString += `L${ lastSegment.x },0 Z`;
|
||||
return pathString;
|
||||
};
|
||||
|
||||
const helper = new SummaryGraphHelper(state, keyframes,
|
||||
totalDuration, durationPerPixel,
|
||||
getValueFunc, toPathStringFunc);
|
||||
const offset = state.previousStartTime ? state.previousStartTime : 0;
|
||||
|
||||
return dom.g(
|
||||
{
|
||||
className,
|
||||
transform: `translate(${ offset })`
|
||||
},
|
||||
this.renderGraph(state, helper)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NegativePath;
|
|
@ -0,0 +1,204 @@
|
|||
/* 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 AnimationName = createFactory(require("./AnimationName"));
|
||||
const DelaySign = createFactory(require("./DelaySign"));
|
||||
const EndDelaySign = createFactory(require("./EndDelaySign"));
|
||||
const SummaryGraphPath = createFactory(require("./SummaryGraphPath"));
|
||||
|
||||
const { getFormatStr, getStr, numberWithDecimals } = require("../../utils/l10n");
|
||||
|
||||
class SummaryGraph extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getAnimatedPropertyMap: PropTypes.func.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
getTitleText(state) {
|
||||
const getTime =
|
||||
time => getFormatStr("player.timeLabel", numberWithDecimals(time / 1000, 2));
|
||||
|
||||
let text = "";
|
||||
|
||||
// Adding the name.
|
||||
text += getFormattedTitle(state);
|
||||
text += "\n";
|
||||
|
||||
// Adding the delay.
|
||||
if (state.delay) {
|
||||
text += getStr("player.animationDelayLabel") + " ";
|
||||
text += getTime(state.delay);
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the duration.
|
||||
text += getStr("player.animationDurationLabel") + " ";
|
||||
text += getTime(state.duration);
|
||||
text += "\n";
|
||||
|
||||
// Adding the endDelay.
|
||||
if (state.endDelay) {
|
||||
text += getStr("player.animationEndDelayLabel") + " ";
|
||||
text += getTime(state.endDelay);
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the iteration count (the infinite symbol, or an integer).
|
||||
if (state.iterationCount !== 1) {
|
||||
text += getStr("player.animationIterationCountLabel") + " ";
|
||||
text += state.iterationCount || getStr("player.infiniteIterationCountText");
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the iteration start.
|
||||
if (state.iterationStart !== 0) {
|
||||
const iterationStartTime = state.iterationStart * state.duration / 1000;
|
||||
text += getFormatStr("player.animationIterationStartLabel",
|
||||
state.iterationStart,
|
||||
numberWithDecimals(iterationStartTime, 2));
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the easing if it is not "linear".
|
||||
if (state.easing && state.easing !== "linear") {
|
||||
text += getStr("player.animationOverallEasingLabel") + " ";
|
||||
text += state.easing;
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the fill mode.
|
||||
if (state.fill && state.fill !== "none") {
|
||||
text += getStr("player.animationFillLabel") + " ";
|
||||
text += state.fill;
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the direction mode if it is not "normal".
|
||||
if (state.direction && state.direction !== "normal") {
|
||||
text += getStr("player.animationDirectionLabel") + " ";
|
||||
text += state.direction;
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the playback rate if it's different than 1.
|
||||
if (state.playbackRate !== 1) {
|
||||
text += getStr("player.animationRateLabel") + " ";
|
||||
text += state.playbackRate;
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding the animation-timing-function
|
||||
// if it is not "ease" which is default value for CSS Animations.
|
||||
if (state.animationTimingFunction && state.animationTimingFunction !== "ease") {
|
||||
text += getStr("player.animationTimingFunctionLabel") + " ";
|
||||
text += state.animationTimingFunction;
|
||||
text += "\n";
|
||||
}
|
||||
|
||||
// Adding a note that the animation is running on the compositor thread if
|
||||
// needed.
|
||||
if (state.propertyState) {
|
||||
if (state.propertyState.every(propState => propState.runningOnCompositor)) {
|
||||
text += getStr("player.allPropertiesOnCompositorTooltip");
|
||||
} else if (state.propertyState.some(propState => propState.runningOnCompositor)) {
|
||||
text += getStr("player.somePropertiesOnCompositorTooltip");
|
||||
}
|
||||
} else if (state.isRunningOnCompositor) {
|
||||
text += getStr("player.runningOnCompositorTooltip");
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
animation,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "animation-summary-graph" +
|
||||
(animation.state.isRunningOnCompositor ? " compositor" : ""),
|
||||
title: this.getTitleText(animation.state),
|
||||
},
|
||||
SummaryGraphPath(
|
||||
{
|
||||
animation,
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
}
|
||||
),
|
||||
animation.state.delay ?
|
||||
DelaySign(
|
||||
{
|
||||
animation,
|
||||
timeScale,
|
||||
}
|
||||
)
|
||||
:
|
||||
null,
|
||||
animation.state.iterationCount && animation.state.endDelay ?
|
||||
EndDelaySign(
|
||||
{
|
||||
animation,
|
||||
timeScale,
|
||||
}
|
||||
)
|
||||
:
|
||||
null,
|
||||
animation.state.name ?
|
||||
AnimationName(
|
||||
{
|
||||
animation
|
||||
}
|
||||
)
|
||||
:
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted title for this animation. This will be either:
|
||||
* "%S", "%S : CSS Transition", "%S : CSS Animation",
|
||||
* "%S : Script Animation", or "Script Animation", depending
|
||||
* if the server provides the type, what type it is and if the animation
|
||||
* has a name.
|
||||
*
|
||||
* @param {Object} state
|
||||
*/
|
||||
function getFormattedTitle(state) {
|
||||
// Older servers don't send a type, and only know about
|
||||
// CSSAnimations and CSSTransitions, so it's safe to use
|
||||
// just the name.
|
||||
if (!state.type) {
|
||||
return state.name;
|
||||
}
|
||||
|
||||
// Script-generated animations may not have a name.
|
||||
if (state.type === "scriptanimation" && !state.name) {
|
||||
return getStr("timeline.scriptanimation.unnamedLabel");
|
||||
}
|
||||
|
||||
return getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
|
||||
}
|
||||
|
||||
module.exports = SummaryGraph;
|
|
@ -0,0 +1,242 @@
|
|||
/* 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 PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
const ComputedTimingPath = createFactory(require("./ComputedTimingPath"));
|
||||
const EffectTimingPath = createFactory(require("./EffectTimingPath"));
|
||||
const NegativeDelayPath = createFactory(require("./NegativeDelayPath"));
|
||||
const NegativeEndDelayPath = createFactory(require("./NegativeEndDelayPath"));
|
||||
const { DEFAULT_GRAPH_HEIGHT } = require("../../utils/graph-helper");
|
||||
|
||||
// Minimum opacity for semitransparent fill color for keyframes's easing graph.
|
||||
const MIN_KEYFRAMES_EASING_OPACITY = 0.5;
|
||||
|
||||
class SummaryGraphPath extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
animation: PropTypes.object.isRequired,
|
||||
emitEventForTest: PropTypes.func.isRequired,
|
||||
getAnimatedPropertyMap: PropTypes.object.isRequired,
|
||||
simulateAnimation: PropTypes.func.isRequired,
|
||||
timeScale: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// Duration which can display in one pixel.
|
||||
durationPerPixel: 0,
|
||||
// List of keyframe which consists by only offset and easing.
|
||||
keyframesList: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateState(this.props.animation);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.updateState(nextProps.animation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return animatable keyframes list which has only offset and easing.
|
||||
* Also, this method remove duplicate keyframes.
|
||||
* For example, if the given animatedPropertyMap is,
|
||||
* [
|
||||
* {
|
||||
* key: "color",
|
||||
* values: [
|
||||
* {
|
||||
* offset: 0,
|
||||
* easing: "ease",
|
||||
* value: "rgb(255, 0, 0)",
|
||||
* },
|
||||
* {
|
||||
* offset: 1,
|
||||
* value: "rgb(0, 255, 0)",
|
||||
* },
|
||||
* ],
|
||||
* },
|
||||
* {
|
||||
* key: "opacity",
|
||||
* values: [
|
||||
* {
|
||||
* offset: 0,
|
||||
* easing: "ease",
|
||||
* value: 0,
|
||||
* },
|
||||
* {
|
||||
* offset: 1,
|
||||
* value: 1,
|
||||
* },
|
||||
* ],
|
||||
* },
|
||||
* ]
|
||||
*
|
||||
* then this method returns,
|
||||
* [
|
||||
* [
|
||||
* {
|
||||
* offset: 0,
|
||||
* easing: "ease",
|
||||
* },
|
||||
* {
|
||||
* offset: 1,
|
||||
* },
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
* @param {Map} animated property map
|
||||
* which can get form getAnimatedPropertyMap in animation.js
|
||||
* @return {Array} list of keyframes which has only easing and offset.
|
||||
*/
|
||||
getOffsetAndEasingOnlyKeyframes(animatedPropertyMap) {
|
||||
return [...animatedPropertyMap.values()].filter((keyframes1, i, self) => {
|
||||
return i !== self.findIndex((keyframes2, j) => {
|
||||
return this.isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2) ? j : -1;
|
||||
});
|
||||
}).map(keyframes => {
|
||||
return keyframes.map(keyframe => {
|
||||
return { easing: keyframe.easing, offset: keyframe.offset };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getTotalDuration(animation, timeScale) {
|
||||
return animation.state.playbackRate * timeScale.getDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if given keyframes have same length, offset and easing.
|
||||
*
|
||||
* @param {Array} keyframes1
|
||||
* @param {Array} keyframes2
|
||||
* @return {Boolean} true: equals
|
||||
*/
|
||||
isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2) {
|
||||
if (keyframes1.length !== keyframes2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < keyframes1.length; i++) {
|
||||
const keyframe1 = keyframes1[i];
|
||||
const keyframe2 = keyframes2[i];
|
||||
|
||||
if (keyframe1.offset !== keyframe2.offset ||
|
||||
keyframe1.easing !== keyframe2.easing) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async updateState(animation) {
|
||||
const {
|
||||
emitEventForTest,
|
||||
getAnimatedPropertyMap,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
const animatedPropertyMap = await getAnimatedPropertyMap(animation);
|
||||
const keyframesList = this.getOffsetAndEasingOnlyKeyframes(animatedPropertyMap);
|
||||
|
||||
const thisEl = ReactDOM.findDOMNode(this);
|
||||
const totalDuration = this.getTotalDuration(animation, timeScale);
|
||||
const durationPerPixel = totalDuration / thisEl.parentNode.clientWidth;
|
||||
|
||||
this.setState({ durationPerPixel, keyframesList });
|
||||
|
||||
emitEventForTest("animation-summary-graph-rendered");
|
||||
}
|
||||
|
||||
render() {
|
||||
const { durationPerPixel, keyframesList } = this.state;
|
||||
|
||||
if (!durationPerPixel) {
|
||||
return dom.svg();
|
||||
}
|
||||
|
||||
const {
|
||||
animation,
|
||||
simulateAnimation,
|
||||
timeScale,
|
||||
} = this.props;
|
||||
|
||||
const totalDuration = this.getTotalDuration(animation, timeScale);
|
||||
const startTime = timeScale.minStartTime;
|
||||
const opacity = Math.max(1 / keyframesList.length, MIN_KEYFRAMES_EASING_OPACITY);
|
||||
|
||||
return dom.svg(
|
||||
{
|
||||
className: "animation-summary-graph-path",
|
||||
preserveAspectRatio: "none",
|
||||
viewBox: `${ startTime } -${ DEFAULT_GRAPH_HEIGHT } `
|
||||
+ `${ totalDuration } ${ DEFAULT_GRAPH_HEIGHT }`,
|
||||
},
|
||||
keyframesList.map(keyframes =>
|
||||
ComputedTimingPath(
|
||||
{
|
||||
animation,
|
||||
durationPerPixel,
|
||||
keyframes,
|
||||
opacity,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
}
|
||||
)
|
||||
),
|
||||
animation.state.easing !== "linear" ?
|
||||
EffectTimingPath(
|
||||
{
|
||||
animation,
|
||||
durationPerPixel,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
}
|
||||
)
|
||||
:
|
||||
null,
|
||||
animation.state.delay < 0 ?
|
||||
keyframesList.map(keyframes => {
|
||||
return NegativeDelayPath(
|
||||
{
|
||||
animation,
|
||||
durationPerPixel,
|
||||
keyframes,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
}
|
||||
);
|
||||
})
|
||||
:
|
||||
null,
|
||||
animation.state.iterationCount && animation.state.endDelay < 0 ?
|
||||
keyframesList.map(keyframes => {
|
||||
return NegativeEndDelayPath(
|
||||
{
|
||||
animation,
|
||||
durationPerPixel,
|
||||
keyframes,
|
||||
simulateAnimation,
|
||||
totalDuration,
|
||||
}
|
||||
);
|
||||
})
|
||||
:
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SummaryGraphPath;
|
|
@ -0,0 +1,349 @@
|
|||
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
// Show max 10 iterations for infinite animations
|
||||
// to give users a clue that the animation does repeat.
|
||||
const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
|
||||
|
||||
class TimingPath extends PureComponent {
|
||||
/**
|
||||
* Render a graph of given parameters and return as <path> element list.
|
||||
*
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
* @return {Array}
|
||||
* list of <path> element.
|
||||
*/
|
||||
renderGraph(state, helper) {
|
||||
// Starting time of main iteration.
|
||||
let mainIterationStartTime = 0;
|
||||
let iterationStart = state.iterationStart;
|
||||
let iterationCount = state.iterationCount ? state.iterationCount : Infinity;
|
||||
|
||||
const pathList = [];
|
||||
|
||||
// Append delay.
|
||||
if (state.delay > 0) {
|
||||
this.renderDelay(pathList, state, helper);
|
||||
mainIterationStartTime = state.delay;
|
||||
} else {
|
||||
const negativeDelayCount = -state.delay / state.duration;
|
||||
// Move to forward the starting point for negative delay.
|
||||
iterationStart += negativeDelayCount;
|
||||
// Consume iteration count by negative delay.
|
||||
if (iterationCount !== Infinity) {
|
||||
iterationCount -= negativeDelayCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Append 1st section of iterations,
|
||||
// This section is only useful in cases where iterationStart has decimals.
|
||||
// e.g.
|
||||
// if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
|
||||
const firstSectionCount = iterationStart % 1 === 0
|
||||
? 0
|
||||
: Math.min(iterationCount, 1) - iterationStart % 1;
|
||||
if (firstSectionCount) {
|
||||
this.renderFirstIteration(pathList, state,
|
||||
mainIterationStartTime, firstSectionCount, helper);
|
||||
}
|
||||
|
||||
if (iterationCount === Infinity) {
|
||||
// If the animation repeats infinitely,
|
||||
// we fill the remaining area with iteration paths.
|
||||
this.renderInfinity(pathList, state,
|
||||
mainIterationStartTime, firstSectionCount, helper);
|
||||
} else {
|
||||
// Otherwise, we show remaining iterations, endDelay and fill.
|
||||
|
||||
// Append forwards fill-mode.
|
||||
if (state.fill === "both" || state.fill === "forwards") {
|
||||
this.renderForwardsFill(pathList, state,
|
||||
mainIterationStartTime, iterationCount, helper);
|
||||
}
|
||||
|
||||
// Append middle section of iterations.
|
||||
// e.g.
|
||||
// if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
|
||||
const middleSectionCount = Math.floor(iterationCount - firstSectionCount);
|
||||
this.renderMiddleIterations(pathList, state, mainIterationStartTime,
|
||||
firstSectionCount, middleSectionCount, helper);
|
||||
|
||||
// Append last section of iterations, if there is remaining iteration.
|
||||
// e.g.
|
||||
// if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
|
||||
const lastSectionCount = iterationCount - middleSectionCount - firstSectionCount;
|
||||
if (lastSectionCount) {
|
||||
this.renderLastIteration(pathList, state, mainIterationStartTime,
|
||||
firstSectionCount, middleSectionCount,
|
||||
lastSectionCount, helper);
|
||||
}
|
||||
|
||||
// Append endDelay.
|
||||
if (state.endDelay > 0) {
|
||||
this.renderEndDelay(pathList, state,
|
||||
mainIterationStartTime, iterationCount, helper);
|
||||
}
|
||||
}
|
||||
return pathList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 'delay' part in animation and add a <path> element to given pathList.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> element to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderDelay(pathList, state, helper) {
|
||||
const startSegment = helper.getSegment(0);
|
||||
const endSegment = { x: state.delay, y: startSegment.y };
|
||||
const segments = [startSegment, endSegment];
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-delay-path",
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 1st section of iterations and add a <path> element to given pathList.
|
||||
* This section is only useful in cases where iterationStart has decimals.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> element to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Number} mainIterationStartTime
|
||||
* Start time of main iteration.
|
||||
* @param {Number} firstSectionCount
|
||||
* Iteration count of first section.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderFirstIteration(pathList, state, mainIterationStartTime,
|
||||
firstSectionCount, helper) {
|
||||
const startTime = mainIterationStartTime;
|
||||
const endTime = startTime + firstSectionCount * state.duration;
|
||||
const segments = helper.createPathSegments(startTime, endTime);
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-iteration-path",
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render middle iterations and add <path> elements to given pathList.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> elements to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Number} mainIterationStartTime
|
||||
* Starting time of main iteration.
|
||||
* @param {Number} firstSectionCount
|
||||
* Iteration count of first section.
|
||||
* @param {Number} middleSectionCount
|
||||
* Iteration count of middle section.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderMiddleIterations(pathList, state, mainIterationStartTime,
|
||||
firstSectionCount, middleSectionCount, helper) {
|
||||
const offset = mainIterationStartTime + firstSectionCount * state.duration;
|
||||
for (let i = 0; i < middleSectionCount; i++) {
|
||||
// Get the path segments of each iteration.
|
||||
const startTime = offset + i * state.duration;
|
||||
const endTime = startTime + state.duration;
|
||||
const segments = helper.createPathSegments(startTime, endTime);
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-iteration-path",
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render last section of iterations and add a <path> element to given pathList.
|
||||
* This section is only useful in cases where iterationStart has decimals.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> elements to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Number} mainIterationStartTime
|
||||
* Starting time of main iteration.
|
||||
* @param {Number} firstSectionCount
|
||||
* Iteration count of first section.
|
||||
* @param {Number} middleSectionCount
|
||||
* Iteration count of middle section.
|
||||
* @param {Number} lastSectionCount
|
||||
* Iteration count of last section.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderLastIteration(pathList, state, mainIterationStartTime, firstSectionCount,
|
||||
middleSectionCount, lastSectionCount, helper) {
|
||||
const startTime = mainIterationStartTime
|
||||
+ (firstSectionCount + middleSectionCount) * state.duration;
|
||||
const endTime = startTime + lastSectionCount * state.duration;
|
||||
const segments = helper.createPathSegments(startTime, endTime);
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-iteration-path",
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render infinity iterations and add <path> elements to given pathList.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> elements to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Number} mainIterationStartTime
|
||||
* Starting time of main iteration.
|
||||
* @param {Number} firstSectionCount
|
||||
* Iteration count of first section.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderInfinity(pathList, state, mainIterationStartTime, firstSectionCount, helper) {
|
||||
// Calculate the number of iterations to display,
|
||||
// with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS
|
||||
let uncappedInfinityIterationCount =
|
||||
(helper.totalDuration - firstSectionCount * state.duration) / state.duration;
|
||||
// If there is a small floating point error resulting in, e.g. 1.0000001
|
||||
// ceil will give us 2 so round first.
|
||||
uncappedInfinityIterationCount =
|
||||
parseFloat(uncappedInfinityIterationCount.toPrecision(6));
|
||||
const infinityIterationCount = Math.min(MAX_INFINITE_ANIMATIONS_ITERATIONS,
|
||||
Math.ceil(uncappedInfinityIterationCount));
|
||||
|
||||
// Append first full iteration path.
|
||||
const firstStartTime =
|
||||
mainIterationStartTime + firstSectionCount * state.duration;
|
||||
const firstEndTime = firstStartTime + state.duration;
|
||||
const firstSegments = helper.createPathSegments(firstStartTime, firstEndTime);
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-iteration-path",
|
||||
d: helper.toPathString(firstSegments),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Append other iterations. We can copy first segments.
|
||||
const isAlternate = state.direction.match(/alternate/);
|
||||
for (let i = 1; i < infinityIterationCount; i++) {
|
||||
const startTime = firstStartTime + i * state.duration;
|
||||
let segments;
|
||||
if (isAlternate && i % 2) {
|
||||
// Copy as reverse.
|
||||
segments = firstSegments.map(segment => {
|
||||
return { x: firstEndTime - segment.x + startTime, y: segment.y };
|
||||
});
|
||||
} else {
|
||||
// Copy as is.
|
||||
segments = firstSegments.map(segment => {
|
||||
return { x: segment.x - firstStartTime + startTime, y: segment.y };
|
||||
});
|
||||
}
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-iteration-path infinity",
|
||||
d: helper.toPathString(segments),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 'endDelay' part in animation and add a <path> element to given pathList.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> element to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Number} mainIterationStartTime
|
||||
* Starting time of main iteration.
|
||||
* @param {Number} iterationCount
|
||||
* Iteration count of whole animation.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderEndDelay(pathList, state, mainIterationStartTime, iterationCount, helper) {
|
||||
const startTime = mainIterationStartTime + iterationCount * state.duration;
|
||||
const startSegment = helper.getSegment(startTime);
|
||||
const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-enddelay-path",
|
||||
d: helper.toPathString([startSegment, endSegment]),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 'fill' for forwards part in animation and
|
||||
* add a <path> element to given pathList.
|
||||
*
|
||||
* @param {Array} pathList
|
||||
* Add rendered <path> element to this array.
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Number} mainIterationStartTime
|
||||
* Starting time of main iteration.
|
||||
* @param {Number} iterationCount
|
||||
* Iteration count of whole animation.
|
||||
* @param {SummaryGraphHelper} helper
|
||||
* Instance of SummaryGraphHelper.
|
||||
*/
|
||||
renderForwardsFill(pathList, state, mainIterationStartTime, iterationCount, helper) {
|
||||
const startTime = mainIterationStartTime + iterationCount * state.duration
|
||||
+ (state.endDelay > 0 ? state.endDelay : 0);
|
||||
const startSegment = helper.getSegment(startTime);
|
||||
const endSegment = { x: helper.totalDuration, y: startSegment.y };
|
||||
pathList.push(
|
||||
dom.path(
|
||||
{
|
||||
className: "animation-fill-forwards-path",
|
||||
d: helper.toPathString([startSegment, endSegment]),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimingPath;
|
|
@ -0,0 +1,17 @@
|
|||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'AnimationName.js',
|
||||
'ComputedTimingPath.js',
|
||||
'DelaySign.js',
|
||||
'EffectTimingPath.js',
|
||||
'EndDelaySign.js',
|
||||
'NegativeDelayPath.js',
|
||||
'NegativeEndDelayPath.js',
|
||||
'NegativePath.js',
|
||||
'SummaryGraph.js',
|
||||
'SummaryGraphPath.js',
|
||||
'TimingPath.js'
|
||||
)
|
|
@ -2,13 +2,18 @@
|
|||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'graph'
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'AnimationItem.js',
|
||||
'AnimationList.js',
|
||||
'AnimationListContainer.js',
|
||||
'AnimationListHeader.js',
|
||||
'AnimationTarget.js',
|
||||
'AnimationTimelineTickItem.js',
|
||||
'AnimationTimelineTickList.js',
|
||||
'App.js',
|
||||
'NoAnimationPanel.js'
|
||||
'NoAnimationPanel.js',
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_multi_timings.html
|
||||
doc_simple_animation.html
|
||||
head.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
@ -10,7 +11,17 @@ support-files =
|
|||
!/devtools/client/shared/test/test-actor-registry.js
|
||||
!/devtools/client/shared/test/test-actor.js
|
||||
|
||||
[browser_animation_animation_list_exists.js]
|
||||
[browser_animation_animation_list_time_tick.js]
|
||||
[browser_animation_animation-list.js]
|
||||
[browser_animation_animation-target.js]
|
||||
[browser_animation_animation-timeline-tick.js]
|
||||
[browser_animation_empty_on_invalid_nodes.js]
|
||||
[browser_animation_inspector_exists.js]
|
||||
[browser_animation_summary-graph_animation-name.js]
|
||||
[browser_animation_summary-graph_compositor.js]
|
||||
[browser_animation_summary-graph_computed-timing-path.js]
|
||||
[browser_animation_summary-graph_delay-sign.js]
|
||||
[browser_animation_summary-graph_end-delay-sign.js]
|
||||
[browser_animation_summary-graph_effect-timing-path.js]
|
||||
[browser_animation_summary-graph_negative-delay-path.js]
|
||||
[browser_animation_summary-graph_negative-end-delay-path.js]
|
||||
[browser_animation_summary-graph_tooltip.js]
|
||||
|
|
|
@ -31,10 +31,4 @@ add_task(async function () {
|
|||
await selectNodeAndWaitForAnimations(animatedNode, inspector);
|
||||
is(panel.querySelectorAll(".animation-list .animation-item").length, 1,
|
||||
"The number of animations displayed should be 1 for .animated element");
|
||||
|
||||
// TODO: We need to add following tests after implement since this test has same role
|
||||
// of animationinspector/test/browser_animation_timeline_ui.js
|
||||
// * name label in animation element existance.
|
||||
// * target node in animation element existance.
|
||||
// * summary graph in animation element existance.
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following AnimationTarget component works.
|
||||
// * element existance
|
||||
// * number of elements
|
||||
// * content of element
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_simple_animation.html");
|
||||
const { animationInspector, inspector, panel } = await openAnimationInspector();
|
||||
|
||||
info("Checking the animation target elements existance");
|
||||
const animationItemEls = panel.querySelectorAll(".animation-list .animation-item");
|
||||
is(animationItemEls.length, animationInspector.animations.length,
|
||||
"Number of animation target element should be same to number of animations "
|
||||
+ "that displays");
|
||||
|
||||
for (const animationItemEl of animationItemEls) {
|
||||
const animationTargetEl = animationItemEl.querySelector(".animation-target");
|
||||
ok(animationTargetEl,
|
||||
"The animation target element should be in each animation item element");
|
||||
}
|
||||
|
||||
info("Checking the content of animation target");
|
||||
await selectNodeAndWaitForAnimations(".animated", inspector);
|
||||
const animationTargetEl =
|
||||
panel.querySelector(".animation-list .animation-item .animation-target");
|
||||
is(animationTargetEl.textContent, "div.ball.animated",
|
||||
"The target element's content is correct");
|
||||
ok(animationTargetEl.querySelector(".objectBox"),
|
||||
"objectBox is in the page exists");
|
||||
});
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// Test for following time tick items.
|
||||
// Test for following timeline tick items.
|
||||
// * animation list header elements existence
|
||||
// * time tick item elements existence
|
||||
// * count and label of time tick elements changing by the sidebar width
|
||||
// * timeline tick item elements existence
|
||||
// * count and label of timeline tick elements changing by the sidebar width
|
||||
|
||||
const TimeScale = require("devtools/client/inspector/animation/utils/timescale");
|
||||
const { findOptimalTimeInterval } =
|
|
@ -0,0 +1,59 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following AnimationName component works.
|
||||
// * element existance
|
||||
// * name text
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "cssanimation-normal",
|
||||
expectedLabel: "cssanimation",
|
||||
},
|
||||
{
|
||||
targetClassName: "cssanimation-linear",
|
||||
expectedLabel: "cssanimation",
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-positive",
|
||||
expectedLabel: "test-delay-animation",
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-negative",
|
||||
expectedLabel: "test-negative-delay-animation",
|
||||
},
|
||||
{
|
||||
targetClassName: "easing-step",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedLabel,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking animation name element existance for ${ targetClassName }`);
|
||||
const animationNameEl = animationItemEl.querySelector(".animation-name");
|
||||
|
||||
if (expectedLabel) {
|
||||
ok(animationNameEl,
|
||||
"The animation name element should be in animation item element");
|
||||
is(animationNameEl.textContent, expectedLabel,
|
||||
`The animation name should be ${ expectedLabel }`);
|
||||
} else {
|
||||
ok(!animationNameEl,
|
||||
"The animation name element should not be in animation item element");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that when animations displayed in the timeline are running on the
|
||||
// compositor, they get a special icon and information in the tooltip.
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_simple_animation.html");
|
||||
|
||||
const { inspector, panel } = await openAnimationInspector();
|
||||
|
||||
info("Select a test node we know has an animation running on the compositor");
|
||||
await selectNodeAndWaitForAnimations(".compositor-all", inspector);
|
||||
|
||||
const summaryGraphEl = panel.querySelector(".animation-summary-graph");
|
||||
ok(summaryGraphEl.classList.contains("compositor"),
|
||||
"The element has the compositor css class");
|
||||
ok(hasTooltip(summaryGraphEl,
|
||||
ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
|
||||
"The element has the right tooltip content");
|
||||
|
||||
info("Select a node we know doesn't have an animation on the compositor");
|
||||
await selectNodeAndWaitForAnimations(".no-compositor", inspector);
|
||||
|
||||
ok(!summaryGraphEl.classList.contains("compositor"),
|
||||
"The element does not have the compositor css class");
|
||||
ok(!hasTooltip(summaryGraphEl,
|
||||
ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
|
||||
"The element does not have oncompositor tooltip content");
|
||||
ok(!hasTooltip(summaryGraphEl,
|
||||
ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
|
||||
"The element does not have oncompositor tooltip content");
|
||||
|
||||
info("Select a node we know has animation on the compositor and not on the compositor");
|
||||
await selectNodeAndWaitForAnimations(".compositor-notall", inspector);
|
||||
|
||||
ok(summaryGraphEl.classList.contains("compositor"),
|
||||
"The element has the compositor css class");
|
||||
ok(hasTooltip(summaryGraphEl,
|
||||
ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
|
||||
"The element has the right tooltip content");
|
||||
});
|
||||
|
||||
function hasTooltip(summaryGraphEl, expected) {
|
||||
const tooltip = summaryGraphEl.getAttribute("title");
|
||||
return tooltip.includes(expected);
|
||||
}
|
|
@ -0,0 +1,466 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following ComputedTimingPath component works.
|
||||
// * element existance
|
||||
// * iterations: path, count
|
||||
// * delay: path
|
||||
// * fill: path
|
||||
// * endDelay: path
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "cssanimation-normal",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 40.851 },
|
||||
{ x: 50000, y: 80.24},
|
||||
{ x: 75000, y: 96.05 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "cssanimation-linear",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-positive",
|
||||
expectedDelayPath: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 50000, y: 0 },
|
||||
],
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 50000, y: 0 },
|
||||
{ x: 75000, y: 25 },
|
||||
{ x: 100000, y: 50 },
|
||||
{ x: 125000, y: 75 },
|
||||
{ x: 150000, y: 100 },
|
||||
{ x: 150000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-negative",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 25000, y: 75 },
|
||||
{ x: 50000, y: 100 },
|
||||
{ x: 50000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "easing-step",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 49999, y: 0 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 99999, y: 50 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-positive",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
expectedEndDelayPath: [
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 150000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-negative",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 50000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-with-fill-forwards",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
expectedEndDelayPath: [
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 150000, y: 100 },
|
||||
{ x: 150000, y: 0 },
|
||||
],
|
||||
expectedForwardsPath: [
|
||||
{ x: 150000, y: 0 },
|
||||
{ x: 150000, y: 100 },
|
||||
{ x: 200000, y: 100 },
|
||||
{ x: 200000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-with-iterations-infinity",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 125000, y: 25 },
|
||||
{ x: 150000, y: 50 },
|
||||
{ x: 175000, y: 75 },
|
||||
{ x: 200000, y: 100 },
|
||||
{ x: 200000, y: 0 },
|
||||
]
|
||||
],
|
||||
isInfinity: true,
|
||||
},
|
||||
{
|
||||
targetClassName: "direction-alternate-with-iterations-infinity",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 125000, y: 75 },
|
||||
{ x: 150000, y: 50 },
|
||||
{ x: 175000, y: 25 },
|
||||
{ x: 200000, y: 0 },
|
||||
]
|
||||
],
|
||||
isInfinity: true,
|
||||
},
|
||||
{
|
||||
targetClassName: "direction-alternate-reverse-with-iterations-infinity",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 25000, y: 75 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 25 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 125000, y: 25 },
|
||||
{ x: 150000, y: 50 },
|
||||
{ x: 175000, y: 75 },
|
||||
{ x: 200000, y: 100 },
|
||||
{ x: 200000, y: 0 },
|
||||
]
|
||||
],
|
||||
isInfinity: true,
|
||||
},
|
||||
{
|
||||
targetClassName: "direction-reverse-with-iterations-infinity",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 25000, y: 75 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 25 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 125000, y: 75 },
|
||||
{ x: 150000, y: 50 },
|
||||
{ x: 175000, y: 25 },
|
||||
{ x: 200000, y: 0 },
|
||||
]
|
||||
],
|
||||
isInfinity: true,
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-backwards",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-backwards-with-delay-iterationstart",
|
||||
expectedDelayPath: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 50000, y: 0 },
|
||||
],
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 125000, y: 25 },
|
||||
{ x: 150000, y: 50 },
|
||||
{ x: 150000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-both",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
expectedForwardsPath: [
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 200000, y: 100 },
|
||||
{ x: 200000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-both-width-delay-iterationstart",
|
||||
expectedDelayPath: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 50000, y: 0 },
|
||||
],
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 125000, y: 25 },
|
||||
{ x: 150000, y: 50 },
|
||||
{ x: 150000, y: 0 },
|
||||
]
|
||||
],
|
||||
expectedForwardsPath: [
|
||||
{ x: 150000, y: 0 },
|
||||
{ x: 150000, y: 50 },
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-forwards",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
expectedForwardsPath: [
|
||||
{ x: 100000, y: 0 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 200000, y: 100 },
|
||||
{ x: 200000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "iterationstart",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 25000, y: 75 },
|
||||
{ x: 50000, y: 100 },
|
||||
{ x: 50000, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 50000, y: 0 },
|
||||
{ x: 75000, y: 25 },
|
||||
{ x: 100000, y: 50 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "no-compositor",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "keyframes-easing-step",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 49999, y: 0 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 99999, y: 50 },
|
||||
{ x: 100000, y: 0 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "narrow-keyframes",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 10000, y: 10 },
|
||||
{ x: 11000, y: 10 },
|
||||
{ x: 11500, y: 10 },
|
||||
{ x: 12999, y: 10 },
|
||||
{ x: 13000, y: 13 },
|
||||
{ x: 13500, y: 13.5 },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "duplicate-offsets",
|
||||
expectedIterationPathList: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 25000, y: 25 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 99999, y: 50 },
|
||||
]
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedDelayPath,
|
||||
expectedEndDelayPath,
|
||||
expectedForwardsPath,
|
||||
expectedIterationPathList,
|
||||
isInfinity,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking computed timing path existance for ${ targetClassName }`);
|
||||
const computedTimingPathEl =
|
||||
animationItemEl.querySelector(".animation-computed-timing-path");
|
||||
ok(computedTimingPathEl,
|
||||
"The computed timing path element should be in each animation item element");
|
||||
|
||||
info(`Checking delay path for ${ targetClassName }`);
|
||||
const delayPathEl = computedTimingPathEl.querySelector(".animation-delay-path");
|
||||
|
||||
if (expectedDelayPath) {
|
||||
ok(delayPathEl, "delay path should be existance");
|
||||
assertPathSegments(delayPathEl, true, expectedDelayPath);
|
||||
} else {
|
||||
ok(!delayPathEl, "delay path should not be existance");
|
||||
}
|
||||
|
||||
info(`Checking iteration path list for ${ targetClassName }`);
|
||||
const iterationPathEls =
|
||||
computedTimingPathEl.querySelectorAll(".animation-iteration-path");
|
||||
is(iterationPathEls.length, expectedIterationPathList.length,
|
||||
`Number of iteration path should be ${ expectedIterationPathList.length }`);
|
||||
|
||||
for (const [j, iterationPathEl] of iterationPathEls.entries()) {
|
||||
assertPathSegments(iterationPathEl, true, expectedIterationPathList[j]);
|
||||
|
||||
info(`Checking infinity ${ targetClassName }`);
|
||||
if (isInfinity && j >= 1) {
|
||||
ok(iterationPathEl.classList.contains("infinity"),
|
||||
"iteration path should have 'infinity' class");
|
||||
} else {
|
||||
ok(!iterationPathEl.classList.contains("infinity"),
|
||||
"iteration path should not have 'infinity' class");
|
||||
}
|
||||
}
|
||||
|
||||
info(`Checking endDelay path for ${ targetClassName }`);
|
||||
const endDelayPathEl = computedTimingPathEl.querySelector(".animation-enddelay-path");
|
||||
|
||||
if (expectedEndDelayPath) {
|
||||
ok(endDelayPathEl, "endDelay path should be existance");
|
||||
assertPathSegments(endDelayPathEl, true, expectedEndDelayPath);
|
||||
} else {
|
||||
ok(!endDelayPathEl, "endDelay path should not be existance");
|
||||
}
|
||||
|
||||
info(`Checking forwards fill path for ${ targetClassName }`);
|
||||
const forwardsPathEl =
|
||||
computedTimingPathEl.querySelector(".animation-fill-forwards-path");
|
||||
|
||||
if (expectedForwardsPath) {
|
||||
ok(forwardsPathEl, "forwards path should be existance");
|
||||
assertPathSegments(forwardsPathEl, true, expectedForwardsPath);
|
||||
} else {
|
||||
ok(!forwardsPathEl, "forwards path should not be existance");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following DelaySign component works.
|
||||
// * element existance
|
||||
// * left position
|
||||
// * width
|
||||
// * additinal class
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "delay-positive",
|
||||
expectedResult: {
|
||||
left: "25%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-negative",
|
||||
expectedResult: {
|
||||
additionalClass: "negative",
|
||||
left: "0%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-backwards-with-delay-iterationstart",
|
||||
expectedResult: {
|
||||
additionalClass: "fill",
|
||||
left: "25%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-both",
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-both-width-delay-iterationstart",
|
||||
expectedResult: {
|
||||
additionalClass: "fill",
|
||||
left: "25%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "keyframes-easing-step",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedResult,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking delay sign existance for ${ targetClassName }`);
|
||||
const delaySignEl = animationItemEl.querySelector(".animation-delay-sign");
|
||||
|
||||
if (expectedResult) {
|
||||
ok(delaySignEl, "The delay sign element should be in animation item element");
|
||||
|
||||
is(delaySignEl.style.left, expectedResult.left,
|
||||
`Left position should be ${ expectedResult.left }`);
|
||||
is(delaySignEl.style.width, expectedResult.width,
|
||||
`Width should be ${ expectedResult.width }`);
|
||||
|
||||
if (expectedResult.additionalClass) {
|
||||
ok(delaySignEl.classList.contains(expectedResult.additionalClass),
|
||||
`delay sign element should have ${ expectedResult.additionalClass } class`);
|
||||
} else {
|
||||
ok(!delaySignEl.classList.contains(expectedResult.additionalClass),
|
||||
"delay sign element should not have " +
|
||||
`${ expectedResult.additionalClass } class`);
|
||||
}
|
||||
} else {
|
||||
ok(!delaySignEl, "The delay sign element should not be in animation item element");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following EffectTimingPath component works.
|
||||
// * element existance
|
||||
// * path
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "cssanimation-linear",
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-negative",
|
||||
},
|
||||
{
|
||||
targetClassName: "easing-step",
|
||||
expectedPath: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 49900, y: 0 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 99999, y: 50 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
targetClassName: "keyframes-easing-step",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedPath,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking effect timing path existance for ${ targetClassName }`);
|
||||
const effectTimingPathEl =
|
||||
animationItemEl.querySelector(".animation-effect-timing-path");
|
||||
|
||||
if (expectedPath) {
|
||||
ok(effectTimingPathEl,
|
||||
"The effect timing path element should be in animation item element");
|
||||
const pathEl = effectTimingPathEl.querySelector(".animation-iteration-path");
|
||||
assertPathSegments(pathEl, false, expectedPath);
|
||||
} else {
|
||||
ok(!effectTimingPathEl,
|
||||
"The effect timing path element should not be in animation item element");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following EndDelaySign component works.
|
||||
// * element existance
|
||||
// * left position
|
||||
// * width
|
||||
// * additinal class
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "enddelay-positive",
|
||||
expectedResult: {
|
||||
left: "75%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-negative",
|
||||
expectedResult: {
|
||||
additionalClass: "negative",
|
||||
left: "50%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-with-fill-forwards",
|
||||
expectedResult: {
|
||||
additionalClass: "fill",
|
||||
left: "75%",
|
||||
width: "25%",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-with-iterations-infinity",
|
||||
},
|
||||
{
|
||||
targetClassName: "keyframes-easing-step",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedResult,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking endDelay sign existance for ${ targetClassName }`);
|
||||
const endDelaySignEl = animationItemEl.querySelector(".animation-end-delay-sign");
|
||||
|
||||
if (expectedResult) {
|
||||
ok(endDelaySignEl, "The endDelay sign element should be in animation item element");
|
||||
|
||||
is(endDelaySignEl.style.left, expectedResult.left,
|
||||
`Left position should be ${ expectedResult.left }`);
|
||||
is(endDelaySignEl.style.width, expectedResult.width,
|
||||
`Width should be ${ expectedResult.width }`);
|
||||
|
||||
if (expectedResult.additionalClass) {
|
||||
ok(endDelaySignEl.classList.contains(expectedResult.additionalClass),
|
||||
`endDelay sign element should have ${ expectedResult.additionalClass } class`);
|
||||
} else {
|
||||
ok(!endDelaySignEl.classList.contains(expectedResult.additionalClass),
|
||||
"endDelay sign element should not have " +
|
||||
`${ expectedResult.additionalClass } class`);
|
||||
}
|
||||
} else {
|
||||
ok(!endDelaySignEl,
|
||||
"The endDelay sign element should not be in animation item element");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following NegativeDelayPath component works.
|
||||
// * element existance
|
||||
// * path
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "delay-positive",
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-negative",
|
||||
expectedPath: [
|
||||
{ x: -50000, y: 0 },
|
||||
{ x: -25000, y: 25 },
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 0, y: 0 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedPath,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking negative delay path existence for ${ targetClassName }`);
|
||||
const negativeDelayPathEl =
|
||||
animationItemEl.querySelector(".animation-negative-delay-path");
|
||||
|
||||
if (expectedPath) {
|
||||
ok(negativeDelayPathEl,
|
||||
"The negative delay path element should be in animation item element");
|
||||
const pathEl = negativeDelayPathEl.querySelector("path");
|
||||
assertPathSegments(pathEl, true, expectedPath);
|
||||
} else {
|
||||
ok(!negativeDelayPathEl,
|
||||
"The negative delay path element should not be in animation item element");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for following NegativeEndDelayPath component works.
|
||||
// * element existance
|
||||
// * path
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "enddelay-positive",
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-negative",
|
||||
expectedPath: [
|
||||
{ x: 50000, y: 0 },
|
||||
{ x: 50000, y: 50 },
|
||||
{ x: 75000, y: 75 },
|
||||
{ x: 100000, y: 100 },
|
||||
{ x: 100000, y: 0 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedPath,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
|
||||
info(`Checking negative endDelay path existance for ${ targetClassName }`);
|
||||
const negativeEndDelayPathEl =
|
||||
animationItemEl.querySelector(".animation-negative-end-delay-path");
|
||||
|
||||
if (expectedPath) {
|
||||
ok(negativeEndDelayPathEl,
|
||||
"The negative endDelay path element should be in animation item element");
|
||||
const pathEl = negativeEndDelayPathEl.querySelector("path");
|
||||
assertPathSegments(pathEl, true, expectedPath);
|
||||
} else {
|
||||
ok(!negativeEndDelayPathEl,
|
||||
"The negative endDelay path element should not be in animation item element");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,277 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for existance and content of tooltip on summary graph element.
|
||||
|
||||
const TEST_CASES = [
|
||||
{
|
||||
targetClassName: "cssanimation-normal",
|
||||
expectedResult: {
|
||||
nameAndType: "cssanimation - CSS Animation",
|
||||
duration: "100s",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "cssanimation-linear",
|
||||
expectedResult: {
|
||||
nameAndType: "cssanimation - CSS Animation",
|
||||
duration: "100s",
|
||||
animationTimingFunction: "linear",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-positive",
|
||||
expectedResult: {
|
||||
nameAndType: "test-delay-animation - Script Animation",
|
||||
delay: "50s",
|
||||
duration: "100s",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "delay-negative",
|
||||
expectedResult: {
|
||||
nameAndType: "test-negative-delay-animation - Script Animation",
|
||||
delay: "-50s",
|
||||
duration: "100s",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "easing-step",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
easing: "steps(2)",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-positive",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
endDelay: "50s",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-negative",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
endDelay: "-50s",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-with-fill-forwards",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
endDelay: "50s",
|
||||
fill: "forwards",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "enddelay-with-iterations-infinity",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
endDelay: "50s",
|
||||
iterations: "\u221E",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "direction-alternate-with-iterations-infinity",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
direction: "alternate",
|
||||
iterations: "\u221E",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "direction-alternate-reverse-with-iterations-infinity",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
direction: "alternate-reverse",
|
||||
iterations: "\u221E",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "direction-reverse-with-iterations-infinity",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
direction: "reverse",
|
||||
iterations: "\u221E",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-backwards",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
fill: "backwards",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-backwards-with-delay-iterationstart",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
delay: "50s",
|
||||
duration: "100s",
|
||||
fill: "backwards",
|
||||
iterationStart: "0.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-both",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
fill: "both",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-both-width-delay-iterationstart",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
delay: "50s",
|
||||
duration: "100s",
|
||||
fill: "both",
|
||||
iterationStart: "0.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "fill-forwards",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
fill: "forwards",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "iterationstart",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
iterationStart: "0.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "no-compositor",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
},
|
||||
},
|
||||
{
|
||||
targetClassName: "keyframes-easing-step",
|
||||
expectedResult: {
|
||||
nameAndType: "Script Animation",
|
||||
duration: "100s",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(URL_ROOT + "doc_multi_timings.html");
|
||||
|
||||
const { panel } = await openAnimationInspector();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
const {
|
||||
expectedResult,
|
||||
targetClassName,
|
||||
} = testCase;
|
||||
|
||||
const animationItemEl =
|
||||
findAnimationItemElementsByTargetClassName(panel, targetClassName);
|
||||
const summaryGraphEl = animationItemEl.querySelector(".animation-summary-graph");
|
||||
|
||||
info(`Checking tooltip for ${ targetClassName }`);
|
||||
ok(summaryGraphEl.hasAttribute("title"),
|
||||
"Summary graph should have 'title' attribute");
|
||||
|
||||
const tooltip = summaryGraphEl.getAttribute("title");
|
||||
const {
|
||||
animationTimingFunction,
|
||||
delay,
|
||||
easing,
|
||||
endDelay,
|
||||
direction,
|
||||
duration,
|
||||
fill,
|
||||
iterations,
|
||||
iterationStart,
|
||||
nameAndType,
|
||||
} = expectedResult;
|
||||
|
||||
ok(tooltip.startsWith(nameAndType), "Tooltip should start with name and type");
|
||||
|
||||
if (animationTimingFunction) {
|
||||
const expected = `Animation timing function: ${ animationTimingFunction }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Animation timing function:"),
|
||||
"Tooltip should not include animation timing function");
|
||||
}
|
||||
|
||||
if (delay) {
|
||||
const expected = `Delay: ${ delay }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Delay:"), "Tooltip should not include delay");
|
||||
}
|
||||
|
||||
if (direction) {
|
||||
const expected = `Direction: ${ direction }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Direction:"), "Tooltip should not include delay");
|
||||
}
|
||||
|
||||
if (duration) {
|
||||
const expected = `Duration: ${ duration }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Duration:"), "Tooltip should not include delay");
|
||||
}
|
||||
|
||||
if (easing) {
|
||||
const expected = `Overall easing: ${ easing }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Overall easing:"), "Tooltip should not include easing");
|
||||
}
|
||||
|
||||
if (endDelay) {
|
||||
const expected = `End delay: ${ endDelay }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("End delay:"), "Tooltip should not include endDelay");
|
||||
}
|
||||
|
||||
if (fill) {
|
||||
const expected = `Fill: ${ fill }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Fill:"), "Tooltip should not include fill");
|
||||
}
|
||||
|
||||
if (iterations) {
|
||||
const expected = `Repeats: ${ iterations }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Repeats:"), "Tooltip should not include iterations");
|
||||
}
|
||||
|
||||
if (iterationStart) {
|
||||
const expected = `Iteration start: ${ iterationStart }`;
|
||||
ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
|
||||
} else {
|
||||
ok(!tooltip.includes("Iteration start:"),
|
||||
"Tooltip should not include iterationStart");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,161 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
div {
|
||||
background-color: lime;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.cssanimation-normal {
|
||||
animation: cssanimation 100s;
|
||||
}
|
||||
|
||||
.cssanimation-linear {
|
||||
animation: cssanimation 100s linear;
|
||||
}
|
||||
|
||||
@keyframes cssanimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cssanimation-normal"></div>
|
||||
<div class="cssanimation-linear"></div>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
const duration = 100000;
|
||||
|
||||
function createAnimation(keyframes, effect, className) {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add(className);
|
||||
document.body.appendChild(div);
|
||||
effect.duration = duration;
|
||||
div.animate(keyframes, effect);
|
||||
}
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ delay: 50000, id: "test-delay-animation" },
|
||||
"delay-positive");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ delay: -50000, id: "test-negative-delay-animation" },
|
||||
"delay-negative");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ easing: "steps(2)" },
|
||||
"easing-step");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ endDelay: 50000 },
|
||||
"enddelay-positive");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ endDelay: -50000 },
|
||||
"enddelay-negative");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ endDelay: 50000, fill: "forwards" },
|
||||
"enddelay-with-fill-forwards");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ endDelay: 50000, iterations: Infinity },
|
||||
"enddelay-with-iterations-infinity");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ direction: "alternate", iterations: Infinity },
|
||||
"direction-alternate-with-iterations-infinity");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ direction: "alternate-reverse", iterations: Infinity },
|
||||
"direction-alternate-reverse-with-iterations-infinity");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ direction: "reverse", iterations: Infinity },
|
||||
"direction-reverse-with-iterations-infinity");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ fill: "backwards" },
|
||||
"fill-backwards");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ fill: "backwards", delay: 50000, iterationStart: 0.5 },
|
||||
"fill-backwards-with-delay-iterationstart");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ fill: "both" },
|
||||
"fill-both");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ fill: "both", delay: 50000, iterationStart: 0.5 },
|
||||
"fill-both-width-delay-iterationstart");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ fill: "forwards" },
|
||||
"fill-forwards");
|
||||
|
||||
createAnimation({ opacity: [0, 1] },
|
||||
{ iterationStart: 0.5 },
|
||||
"iterationstart");
|
||||
|
||||
createAnimation({ width: ["100px", "150px"] },
|
||||
{},
|
||||
"no-compositor");
|
||||
|
||||
createAnimation([{ opacity: 0, easing: "steps(2)" }, { opacity: 1 }],
|
||||
{},
|
||||
"keyframes-easing-step");
|
||||
|
||||
createAnimation(
|
||||
[
|
||||
{
|
||||
opacity: 0,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
offset: 0.1,
|
||||
easing: "steps(1)"
|
||||
},
|
||||
{
|
||||
opacity: 0,
|
||||
offset: 0.13,
|
||||
}
|
||||
],
|
||||
{},
|
||||
"narrow-keyframes");
|
||||
|
||||
createAnimation(
|
||||
[
|
||||
{
|
||||
offset: 0,
|
||||
opacity: 1,
|
||||
},
|
||||
{
|
||||
offset: 0.5,
|
||||
opacity: 1,
|
||||
},
|
||||
{
|
||||
offset: 0.5,
|
||||
easing: "steps(1)",
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
opacity: 1,
|
||||
}
|
||||
],
|
||||
{},
|
||||
"duplicate-offsets");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -37,6 +37,7 @@ const openAnimationInspector = async function () {
|
|||
const { inspector, toolbox } = await openInspectorSidebarTab(TAB_NAME);
|
||||
await inspector.once("inspector-updated");
|
||||
const { animationinspector: animationInspector } = inspector;
|
||||
await waitForRendering(animationInspector);
|
||||
const panel = inspector.panelWin.document.getElementById("animation-container");
|
||||
return { animationInspector, toolbox, inspector, panel };
|
||||
};
|
||||
|
@ -104,6 +105,7 @@ const selectNodeAndWaitForAnimations = async function (data, inspector, reason =
|
|||
const onUpdated = inspector.once("inspector-updated");
|
||||
await selectNode(data, inspector, reason);
|
||||
await onUpdated;
|
||||
await waitForRendering(inspector.animationinspector);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -120,3 +122,125 @@ const setSidebarWidth = async function (width, inspector) {
|
|||
inspector.splitBox.setState({ width });
|
||||
await onUpdated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for rendering.
|
||||
*
|
||||
* @param {AnimationInspector} animationInspector
|
||||
*/
|
||||
const waitForRendering = async function (animationInspector) {
|
||||
await Promise.all([
|
||||
waitForAllAnimationTargets(animationInspector),
|
||||
waitForAllSummaryGraph(animationInspector),
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for all AnimationTarget components to be fully loaded
|
||||
* (fetched their related actor and rendered).
|
||||
*
|
||||
* @param {AnimationInspector} animationInspector
|
||||
*/
|
||||
const waitForAllAnimationTargets = async function (animationInspector) {
|
||||
for (let i = 0; i < animationInspector.animations.length; i++) {
|
||||
await animationInspector.once("animation-target-rendered");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for all SummaryGraph components to be fully loaded
|
||||
*
|
||||
* @param {AnimationInspector} inspector
|
||||
*/
|
||||
const waitForAllSummaryGraph = async function (animationInspector) {
|
||||
for (let i = 0; i < animationInspector.animations.length; i++) {
|
||||
await animationInspector.once("animation-summary-graph-rendered");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* SummaryGraph is constructed by <path> element.
|
||||
* This function checks the vertex of path segments.
|
||||
*
|
||||
* @param {Element} pathEl
|
||||
* <path> element.
|
||||
* @param {boolean} hasClosePath
|
||||
* Set true if the path shoud be closing.
|
||||
* @param {Object} expectedValues
|
||||
* JSON object format. We can test the vertex and color.
|
||||
* e.g.
|
||||
* [
|
||||
* { x: 0, y: 0 },
|
||||
* { x: 0, y: 1 },
|
||||
* ]
|
||||
*/
|
||||
function assertPathSegments(pathEl, hasClosePath, expectedValues) {
|
||||
const pathSegList = pathEl.pathSegList;
|
||||
ok(pathSegList, "The tested element should have pathSegList");
|
||||
|
||||
expectedValues.forEach(expectedValue => {
|
||||
ok(isPassingThrough(pathSegList, expectedValue.x, expectedValue.y),
|
||||
`The path segment of x ${ expectedValue.x }, y ${ expectedValue.y } `
|
||||
+ `should be passing through`);
|
||||
});
|
||||
|
||||
if (hasClosePath) {
|
||||
const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
|
||||
is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
|
||||
"The last segment should be close path");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given vertex is passing throug on the path.
|
||||
*
|
||||
* @param {pathSegList} pathSegList - pathSegList of <path> element.
|
||||
* @param {float} x - x of vertex.
|
||||
* @param {float} y - y of vertex.
|
||||
* @return {boolean} true: passing through, false: no on the path.
|
||||
*/
|
||||
function isPassingThrough(pathSegList, x, y) {
|
||||
let previousPathSeg = pathSegList.getItem(0);
|
||||
for (let i = 0; i < pathSegList.numberOfItems; i++) {
|
||||
const pathSeg = pathSegList.getItem(i);
|
||||
if (pathSeg.x === undefined) {
|
||||
continue;
|
||||
}
|
||||
const currentX = parseFloat(pathSeg.x.toFixed(3));
|
||||
const currentY = parseFloat(pathSeg.y.toFixed(3));
|
||||
if (currentX === x && currentY === y) {
|
||||
return true;
|
||||
}
|
||||
const previousX = parseFloat(previousPathSeg.x.toFixed(3));
|
||||
const previousY = parseFloat(previousPathSeg.y.toFixed(3));
|
||||
if (previousX <= x && x <= currentX &&
|
||||
Math.min(previousY, currentY) <= y && y <= Math.max(previousY, currentY)) {
|
||||
return true;
|
||||
}
|
||||
previousPathSeg = pathSeg;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return animation item element by target node class.
|
||||
* This function compares betweem animation-target textContent and given className.
|
||||
* Also, this function premises one class name.
|
||||
*
|
||||
* @param {Element} panel - root element of animation inspector.
|
||||
* @param {String} targetClassName - class name of tested element.
|
||||
* @return {Element} animation item element.
|
||||
*/
|
||||
function findAnimationItemElementsByTargetClassName(panel, targetClassName) {
|
||||
const animationTargetEls = panel.querySelectorAll(".animation-target");
|
||||
|
||||
for (const animationTargetEl of animationTargetEls) {
|
||||
const className = animationTargetEl.textContent.split(".")[1];
|
||||
|
||||
if (className === targetClassName) {
|
||||
return animationTargetEl.closest(".animation-item");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
/* 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";
|
||||
|
||||
// BOUND_EXCLUDING_TIME should be less than 1ms and is used to exclude start
|
||||
// and end bounds when dividing duration in createPathSegments.
|
||||
const BOUND_EXCLUDING_TIME = 0.001;
|
||||
// We define default graph height since if the height of viewport in SVG is
|
||||
// too small (e.g. 1), vector-effect may not be able to calculate correctly.
|
||||
const DEFAULT_GRAPH_HEIGHT = 100;
|
||||
// DEFAULT_MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
|
||||
const DEFAULT_MIN_PROGRESS_THRESHOLD = 0.1;
|
||||
// In the createPathSegments function, an animation duration is divided by
|
||||
// DURATION_RESOLUTION in order to draw the way the animation progresses.
|
||||
// But depending on the timing-function, we may be not able to make the graph
|
||||
// smoothly progress if this resolution is not high enough.
|
||||
// So, if the difference of animation progress between 2 divisions is more than
|
||||
// DEFAULT_MIN_PROGRESS_THRESHOLD * DEFAULT_GRAPH_HEIGHT, then createPathSegments
|
||||
// re-divides by DURATION_RESOLUTION.
|
||||
// DURATION_RESOLUTION shoud be integer and more than 2.
|
||||
const DURATION_RESOLUTION = 4;
|
||||
|
||||
/**
|
||||
* The helper class for creating summary graph.
|
||||
*/
|
||||
class SummaryGraphHelper {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Array} keyframes
|
||||
* Array of keyframe.
|
||||
* @param {Number} totalDuration
|
||||
* Total displayable duration.
|
||||
* @param {Number} minSegmentDuration
|
||||
* Minimum segment duration.
|
||||
* @param {Function} getValueFunc
|
||||
* Which returns graph value of given time.
|
||||
* The function should return a number value between 0 - 1.
|
||||
* e.g. time => { return 1.0 };
|
||||
* @param {Function} toPathStringFunc
|
||||
* Which returns a path string for 'd' attribute for <path> from given segments.
|
||||
*/
|
||||
constructor(state, keyframes, totalDuration, minSegmentDuration,
|
||||
getValueFunc, toPathStringFunc) {
|
||||
this.totalDuration = totalDuration;
|
||||
this.minSegmentDuration = minSegmentDuration;
|
||||
this.minProgressThreshold =
|
||||
getPreferredProgressThreshold(state, keyframes) * DEFAULT_GRAPH_HEIGHT;
|
||||
this.durationResolution = getPreferredDurationResolution(keyframes);
|
||||
this.getValue = getValueFunc;
|
||||
this.toPathString = toPathStringFunc;
|
||||
|
||||
this.getSegment = this.getSegment.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the path segments from given parameters.
|
||||
*
|
||||
* @param {Number} startTime
|
||||
* Starting time of animation.
|
||||
* @param {Number} endTime
|
||||
* Ending time of animation.
|
||||
* @return {Array}
|
||||
* Array of path segment.
|
||||
* e.g.[{x: {Number} time, y: {Number} progress}, ...]
|
||||
*/
|
||||
createPathSegments(startTime, endTime) {
|
||||
return createPathSegments(startTime, endTime,
|
||||
this.minSegmentDuration, this.minProgressThreshold,
|
||||
this.durationResolution, this.getSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a coordinate as a graph segment at given time.
|
||||
*
|
||||
* @param {Number} time
|
||||
* @return {Object}
|
||||
* { x: Number, y: Number }
|
||||
*/
|
||||
getSegment(time) {
|
||||
const value = this.getValue(time);
|
||||
return { x: time, y: value * DEFAULT_GRAPH_HEIGHT };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the path segments from given parameters.
|
||||
*
|
||||
* @param {Number} startTime
|
||||
* Starting time of animation.
|
||||
* @param {Number} endTime
|
||||
* Ending time of animation.
|
||||
* @param {Number} minSegmentDuration
|
||||
* Minimum segment duration.
|
||||
* @param {Number} minProgressThreshold
|
||||
* Minimum progress threshold.
|
||||
* @param {Number} resolution
|
||||
* Duration resolution for first time.
|
||||
* @param {Function} getSegment
|
||||
* A function that calculate the graph segment.
|
||||
* @return {Array}
|
||||
* Array of path segment.
|
||||
* e.g.[{x: {Number} time, y: {Number} progress}, ...]
|
||||
*/
|
||||
function createPathSegments(startTime, endTime, minSegmentDuration,
|
||||
minProgressThreshold, resolution, getSegment) {
|
||||
// If the duration is too short, early return.
|
||||
if (endTime - startTime < minSegmentDuration) {
|
||||
return [getSegment(startTime), getSegment(endTime)];
|
||||
}
|
||||
|
||||
// Otherwise, start creating segments.
|
||||
let pathSegments = [];
|
||||
|
||||
// Append the segment for the startTime position.
|
||||
const startTimeSegment = getSegment(startTime);
|
||||
pathSegments.push(startTimeSegment);
|
||||
let previousSegment = startTimeSegment;
|
||||
|
||||
// Split the duration in equal intervals, and iterate over them.
|
||||
// See the definition of DURATION_RESOLUTION for more information about this.
|
||||
const interval = (endTime - startTime) / resolution;
|
||||
for (let index = 1; index <= resolution; index++) {
|
||||
// Create a segment for this interval.
|
||||
const currentSegment = getSegment(startTime + index * interval);
|
||||
|
||||
// If the distance between the Y coordinate (the animation's progress) of
|
||||
// the previous segment and the Y coordinate of the current segment is too
|
||||
// large, then recurse with a smaller duration to get more details
|
||||
// in the graph.
|
||||
if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
|
||||
// Divide the current interval (excluding start and end bounds
|
||||
// by adding/subtracting BOUND_EXCLUDING_TIME).
|
||||
const nextStartTime = previousSegment.x + BOUND_EXCLUDING_TIME;
|
||||
const nextEndTime = currentSegment.x - BOUND_EXCLUDING_TIME;
|
||||
const segments =
|
||||
createPathSegments(nextStartTime, nextEndTime, minSegmentDuration,
|
||||
minProgressThreshold, DURATION_RESOLUTION, getSegment);
|
||||
pathSegments = pathSegments.concat(segments);
|
||||
}
|
||||
|
||||
pathSegments.push(currentSegment);
|
||||
previousSegment = currentSegment;
|
||||
}
|
||||
|
||||
return pathSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return preferred duration resolution.
|
||||
* This corresponds to narrow interval keyframe offset.
|
||||
*
|
||||
* @param {Array} keyframes
|
||||
* Array of keyframe.
|
||||
* @return {Number}
|
||||
* Preferred duration resolution.
|
||||
*/
|
||||
function getPreferredDurationResolution(keyframes) {
|
||||
if (!keyframes) {
|
||||
return DURATION_RESOLUTION;
|
||||
}
|
||||
|
||||
let durationResolution = DURATION_RESOLUTION;
|
||||
let previousOffset = 0;
|
||||
for (let keyframe of keyframes) {
|
||||
if (previousOffset && previousOffset != keyframe.offset) {
|
||||
const interval = keyframe.offset - previousOffset;
|
||||
durationResolution = Math.max(durationResolution, Math.ceil(1 / interval));
|
||||
}
|
||||
previousOffset = keyframe.offset;
|
||||
}
|
||||
|
||||
return durationResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return preferred progress threshold to render summary graph.
|
||||
*
|
||||
* @param {Object} state
|
||||
* State of animation.
|
||||
* @param {Array} keyframes
|
||||
* Array of keyframe.
|
||||
* @return {float}
|
||||
* Preferred threshold.
|
||||
*/
|
||||
function getPreferredProgressThreshold(state, keyframes) {
|
||||
let threshold = DEFAULT_MIN_PROGRESS_THRESHOLD;
|
||||
let stepsOrFrames;
|
||||
|
||||
if ((stepsOrFrames = getStepsOrFramesCount(state.easing))) {
|
||||
threshold = Math.min(threshold, (1 / (stepsOrFrames + 1)));
|
||||
}
|
||||
|
||||
if (!keyframes) {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
for (let i = 0; i < keyframes.length - 1; i++) {
|
||||
const keyframe = keyframes[i];
|
||||
|
||||
if (!keyframe.easing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((stepsOrFrames = getStepsOrFramesCount(keyframe.easing))) {
|
||||
const nextKeyframe = keyframes[i + 1];
|
||||
threshold =
|
||||
Math.min(threshold,
|
||||
1 / (stepsOrFrames + 1) * (nextKeyframe.offset - keyframe.offset));
|
||||
}
|
||||
}
|
||||
|
||||
return threshold;
|
||||
}
|
||||
|
||||
function getStepsOrFramesCount(easing) {
|
||||
const stepsOrFramesFunction = easing.match(/(steps|frames)\((\d+)/);
|
||||
return stepsOrFramesFunction ? parseInt(stepsOrFramesFunction[2], 10) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return path string for 'd' attribute for <path> from given segments.
|
||||
*
|
||||
* @param {Array} segments
|
||||
* e.g. [{ x: 100, y: 0 }, { x: 200, y: 1 }]
|
||||
* @return {String}
|
||||
* Path string.
|
||||
* e.g. "L100,0 L200,1"
|
||||
*/
|
||||
function toPathString(segments) {
|
||||
let pathString = "";
|
||||
segments.forEach(segment => {
|
||||
pathString += `L${ segment.x },${ segment.y } `;
|
||||
});
|
||||
return pathString;
|
||||
}
|
||||
|
||||
module.exports.DEFAULT_GRAPH_HEIGHT = DEFAULT_GRAPH_HEIGHT;
|
||||
exports.SummaryGraphHelper = SummaryGraphHelper;
|
||||
exports.toPathString = toPathString;
|
|
@ -10,4 +10,6 @@ const L10N =
|
|||
|
||||
module.exports = {
|
||||
getFormatStr: (...args) => L10N.getFormatStr(...args),
|
||||
getStr: (...args) => L10N.getStr(...args),
|
||||
numberWithDecimals: (...args) => L10N.numberWithDecimals(...args),
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'graph-helper.js',
|
||||
'l10n.js',
|
||||
'timescale.js',
|
||||
'utils.js',
|
||||
|
|
|
@ -45,19 +45,23 @@ function findOptimalTimeInterval(minTimeInterval) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check the equality timing effects from given animations.
|
||||
* Check the equality of the given animations.
|
||||
*
|
||||
* @param {Array} animations.
|
||||
* @param {Array} same to avobe.
|
||||
* @return {Boolean} true: same timing effects
|
||||
* @param {Array} same to above.
|
||||
* @return {Boolean} true: same animations
|
||||
*/
|
||||
function isAllTimingEffectEqual(animationsA, animationsB) {
|
||||
function isAllAnimationEqual(animationsA, animationsB) {
|
||||
if (animationsA.length !== animationsB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < animationsA.length; i++) {
|
||||
if (!isTimingEffectEqual(animationsA[i].state, animationsB[i].state)) {
|
||||
const animationA = animationsA[i];
|
||||
const animationB = animationsB[i];
|
||||
|
||||
if (animationA.actorID !== animationB.actorID ||
|
||||
!isTimingEffectEqual(animationsA[i].state, animationsB[i].state)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -84,5 +88,5 @@ function isTimingEffectEqual(stateA, stateB) {
|
|||
}
|
||||
|
||||
exports.findOptimalTimeInterval = findOptimalTimeInterval;
|
||||
exports.isAllTimingEffectEqual = isAllTimingEffectEqual;
|
||||
exports.isAllAnimationEqual = isAllAnimationEqual;
|
||||
exports.isTimingEffectEqual = isTimingEffectEqual;
|
||||
|
|
|
@ -807,7 +807,7 @@ Inspector.prototype = {
|
|||
panel: () => {
|
||||
const AnimationInspector =
|
||||
this.browserRequire("devtools/client/inspector/animation/animation");
|
||||
this.animationinspector = new AnimationInspector(this);
|
||||
this.animationinspector = new AnimationInspector(this, this.panelWin);
|
||||
return this.animationinspector.provider;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
:root {
|
||||
--animation-even-background-color: rgba(0, 0, 0, 0.05);
|
||||
--command-pick-image: url(chrome://devtools/skin/images/command-pick.svg);
|
||||
--graph-right-offset: 10px;
|
||||
--sidebar-width: 200px;
|
||||
}
|
||||
|
||||
:root.theme-dark {
|
||||
|
@ -17,6 +19,18 @@
|
|||
--command-pick-image: url(chrome://devtools/skin/images/firebug/command-pick.svg);
|
||||
}
|
||||
|
||||
/* Root element of animation inspector */
|
||||
#animation-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Animation List Container */
|
||||
.animation-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Animation List Header */
|
||||
.animation-list-header {
|
||||
display: flex;
|
||||
|
@ -26,9 +40,9 @@
|
|||
|
||||
/* Animation Timeline Tick List */
|
||||
.animation-timeline-tick-list {
|
||||
margin-right: 10px;
|
||||
margin-right: var(--graph-right-offset);
|
||||
position: relative;
|
||||
width: calc(100% - 210px);
|
||||
width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
|
||||
}
|
||||
|
||||
.animation-timeline-tick-item {
|
||||
|
@ -39,13 +53,16 @@
|
|||
|
||||
/* Animation List */
|
||||
.animation-list {
|
||||
flex: 1;
|
||||
list-style-type: none;
|
||||
margin-top: 0;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Animation Item */
|
||||
.animation-item {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
|
@ -53,6 +70,151 @@
|
|||
background-color: var(--animation-even-background-color);
|
||||
}
|
||||
|
||||
.animation-item.cssanimation {
|
||||
--computed-timing-graph-color: var(--theme-contrast-background);
|
||||
--effect-timing-graph-color: var(--theme-highlight-lightorange);
|
||||
}
|
||||
|
||||
.animation-item.csstransition {
|
||||
--computed-timing-graph-color: var(--theme-highlight-blue);
|
||||
--effect-timing-graph-color: var(--theme-highlight-bluegrey);
|
||||
}
|
||||
|
||||
.animation-item.scriptanimation {
|
||||
--computed-timing-graph-color: var(--theme-graphs-green);
|
||||
--effect-timing-graph-color: var(--theme-highlight-green);
|
||||
}
|
||||
|
||||
/* Animation Target */
|
||||
.animation-target {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-left: 4px;
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.animation-target .tag-name {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Summary Graph */
|
||||
.animation-summary-graph {
|
||||
height: 100%;
|
||||
padding-top: 5px;
|
||||
position: relative;
|
||||
width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
|
||||
}
|
||||
|
||||
.animation-summary-graph.compositor::after {
|
||||
background-image: url("images/animation-fast-track.svg");
|
||||
background-repeat: no-repeat;
|
||||
content: "";
|
||||
display: block;
|
||||
fill: var(--theme-content-color3);
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 5px;
|
||||
width: 15px;
|
||||
z-index: 1;
|
||||
-moz-context-properties: fill;
|
||||
}
|
||||
|
||||
.animation-summary-graph-path {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.animation-computed-timing-path path {
|
||||
fill: var(--computed-timing-graph-color);
|
||||
vector-effect: non-scaling-stroke;
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
|
||||
.animation-computed-timing-path path.infinity:nth-child(n+2) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.animation-effect-timing-path path {
|
||||
fill: none;
|
||||
stroke: var(--effect-timing-graph-color);
|
||||
stroke-dasharray: 2px 2px;
|
||||
transform: scale(1, -1);
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.animation-effect-timing-path path.infinity:nth-child(n+2) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.animation-negative-delay-path path,
|
||||
.animation-negative-end-delay-path path {
|
||||
fill: none;
|
||||
stroke: var(--theme-graphs-grey);
|
||||
stroke-dasharray: 2px 2px;
|
||||
transform: scale(1, -1);
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.animation-delay-sign,
|
||||
.animation-end-delay-sign {
|
||||
background-color: var(--theme-graphs-grey);
|
||||
height: 3px;
|
||||
position: absolute;
|
||||
top: calc(100% - 1.5px);
|
||||
}
|
||||
|
||||
.animation-delay-sign::before,
|
||||
.animation-end-delay-sign::before {
|
||||
background-color: inherit;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
top: -1.5px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.animation-delay-sign.fill,
|
||||
.animation-end-delay-sign.fill {
|
||||
background-color: var(--effect-timing-graph-color);
|
||||
}
|
||||
|
||||
.animation-delay-sign.negative::before {
|
||||
left: unset;
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
.animation-end-delay-sign::before {
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
.animation-end-delay-sign.negative::before {
|
||||
left: -3px;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
.animation-name {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.animation-name text {
|
||||
dominant-baseline: middle;
|
||||
fill: var(--theme-focus-outline-color);
|
||||
paint-order: stroke;
|
||||
stroke: var(--theme-body-background);
|
||||
stroke-linejoin: round;
|
||||
stroke-opacity: .5;
|
||||
stroke-width: 4;
|
||||
text-anchor: end;
|
||||
}
|
||||
|
||||
/* No Animation Panel */
|
||||
.animation-error-message {
|
||||
overflow: auto;
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
#include "mozilla/StickyTimeDuration.h"
|
||||
#include "mozilla/ComputedTimingFunction.h"
|
||||
|
||||
// X11 has a #define for None
|
||||
#ifdef None
|
||||
#undef None
|
||||
#endif
|
||||
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // FillMode
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
@ -7,10 +7,6 @@
|
|||
#ifndef mozilla_KeyframeEffectParams_h
|
||||
#define mozilla_KeyframeEffectParams_h
|
||||
|
||||
// X11 has a #define for None
|
||||
#ifdef None
|
||||
#undef None
|
||||
#endif
|
||||
#include "mozilla/dom/KeyframeEffectBinding.h" // IterationCompositeOperation
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
@ -15,10 +15,6 @@
|
|||
#include "mozilla/StickyTimeDuration.h"
|
||||
#include "mozilla/TimeStamp.h" // for TimeDuration
|
||||
|
||||
// X11 has a #define for None
|
||||
#ifdef None
|
||||
#undef None
|
||||
#endif
|
||||
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for FillMode
|
||||
// and PlaybackDirection
|
||||
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
|
||||
#include "ipc/IPCMessageUtils.h"
|
||||
|
||||
// Fix X11 header brain damage that conflicts with FrameType::None
|
||||
#undef None
|
||||
|
||||
#include "mozilla/dom/ClientBinding.h"
|
||||
#include "mozilla/dom/ClientsBinding.h"
|
||||
#include "mozilla/dom/DocumentBinding.h"
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
|
||||
#include "ipc/IPCMessageUtils.h"
|
||||
|
||||
// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None
|
||||
#undef None
|
||||
|
||||
#include "mozilla/dom/HeadersBinding.h"
|
||||
#include "mozilla/dom/RequestBinding.h"
|
||||
#include "mozilla/dom/ResponseBinding.h"
|
||||
|
|
|
@ -35,8 +35,8 @@ public:
|
|||
// We should only have one decrypt request being processed at once.
|
||||
MOZ_RELEASE_ASSERT(!mThrottleScheduler.IsScheduled());
|
||||
|
||||
const TimeDuration WindowSize = TimeDuration::FromSeconds(1.0);
|
||||
const TimeDuration MaxThroughput = TimeDuration::FromSeconds(2.0);
|
||||
const TimeDuration WindowSize = TimeDuration::FromSeconds(0.1);
|
||||
const TimeDuration MaxThroughput = TimeDuration::FromSeconds(0.2);
|
||||
|
||||
// Forget decrypts that happened before the start of our window.
|
||||
const TimeStamp now = TimeStamp::Now();
|
||||
|
|
|
@ -57,7 +57,7 @@ skip-if = os == 'mac' || os == 'win' || toolkit == 'android' # Bug 1404995, no l
|
|||
[test_getUserMedia_basicVideo.html]
|
||||
[test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
|
||||
[test_getUserMedia_basicScreenshare.html]
|
||||
skip-if = toolkit == 'android' # no screenshare on android
|
||||
skip-if = toolkit == 'android' || (webrender && toolkit == 'windows') # no screenshare on android; bug 1405083 permafail on webrender
|
||||
[test_getUserMedia_basicTabshare.html]
|
||||
skip-if = toolkit == 'android' # no windowshare on android
|
||||
[test_getUserMedia_basicWindowshare.html]
|
||||
|
|
|
@ -269,4 +269,18 @@ NP_EXPORT(NPError) NP_GetValue(void *future, NPPVariable aVariable, void *aV
|
|||
#endif
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
// See bug 1431030
|
||||
#if defined(XP_WIN)
|
||||
#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (__stdcall * _name)
|
||||
#else
|
||||
#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (* _name)
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_GETENTRYPOINTS) (NPPluginFuncs* pCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGININIT) (const NPNetscapeFuncs* pCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINUNIXINIT) (const NPNetscapeFuncs* pCallbacks, NPPluginFuncs* fCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINSHUTDOWN) (void);
|
||||
|
||||
#endif /* npfunctions_h_ */
|
||||
|
|
|
@ -14,20 +14,6 @@
|
|||
#include "mozilla/PluginLibrary.h"
|
||||
#include "mozilla/RefCounted.h"
|
||||
|
||||
// clang-format off
|
||||
// See bug 1431030
|
||||
#if defined(XP_WIN)
|
||||
#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (__stdcall * _name)
|
||||
#else
|
||||
#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (* _name)
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_GETENTRYPOINTS) (NPPluginFuncs* pCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGININIT) (const NPNetscapeFuncs* pCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINUNIXINIT) (const NPNetscapeFuncs* pCallbacks, NPPluginFuncs* fCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINSHUTDOWN) ();
|
||||
|
||||
// nsNPAPIPlugin is held alive both by active nsPluginTag instances and
|
||||
// by active nsNPAPIPluginInstance.
|
||||
class nsNPAPIPlugin final
|
||||
|
|
|
@ -32,19 +32,6 @@
|
|||
#include "mozilla/plugins/PluginMessageUtils.h"
|
||||
#include "mozilla/plugins/PluginQuirks.h"
|
||||
|
||||
// NOTE: stolen from nsNPAPIPlugin.h
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (__stdcall * _name)
|
||||
#else
|
||||
#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (* _name)
|
||||
#endif
|
||||
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_GETENTRYPOINTS) (NPPluginFuncs* pCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGININIT) (const NPNetscapeFuncs* pCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINUNIXINIT) (const NPNetscapeFuncs* pCallbacks, NPPluginFuncs* fCallbacks);
|
||||
typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINSHUTDOWN) (void);
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ChildProfilerController;
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
#include "mozilla/layers/TextureClient.h" // for TextureClient, etc
|
||||
#include "mozilla/layers/PersistentBufferProvider.h"
|
||||
|
||||
// Fix X11 header brain damage that conflicts with MaybeOneOf::None
|
||||
#undef None
|
||||
#include "mozilla/MaybeOneOf.h"
|
||||
|
||||
#include "mozilla/mozalloc.h" // for operator delete
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
#include <chrono>
|
||||
#if defined(__linux__) || defined(XP_MACOSX)
|
||||
#ifdef JS_POSIX_NSPR
|
||||
# include <dlfcn.h>
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
|
@ -65,7 +65,7 @@
|
|||
# include "jswin.h"
|
||||
#endif
|
||||
#include "jswrapper.h"
|
||||
#if !defined(__linux__) && !defined(XP_MACOSX)
|
||||
#ifndef JS_POSIX_NSPR
|
||||
# include "prerror.h"
|
||||
# include "prlink.h"
|
||||
#endif
|
||||
|
@ -142,7 +142,7 @@ using mozilla::TimeDuration;
|
|||
using mozilla::TimeStamp;
|
||||
|
||||
// Avoid an unnecessary NSPR dependency on Linux and OS X just for the shell.
|
||||
#if defined(__linux__) || defined(XP_MACOSX)
|
||||
#ifdef JS_POSIX_NSPR
|
||||
typedef void PRLibrary;
|
||||
|
||||
static PRLibrary*
|
||||
|
@ -8786,7 +8786,7 @@ class AutoLibraryLoader {
|
|||
PRLibrary* load(const char* path) {
|
||||
PRLibrary* dll = PR_LoadLibrary(path);
|
||||
if (!dll) {
|
||||
#if defined(__linux__) || defined(XP_MACOSX)
|
||||
#ifdef JS_POSIX_NSPR
|
||||
fprintf(stderr, "LoadLibrary '%s' failed: %s\n", path, dlerror());
|
||||
#else
|
||||
fprintf(stderr, "LoadLibrary '%s' failed with code %d\n", path, PR_GetError());
|
||||
|
|
|
@ -2707,7 +2707,9 @@ nsLayoutUtils::GetTransformToAncestor(nsIFrame *aFrame,
|
|||
ctm = aFrame->GetTransformMatrix(aAncestor, &parent, aFlags);
|
||||
while (parent && parent != aAncestor &&
|
||||
(!(aFlags & nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) ||
|
||||
(!parent->IsStackingContext() && !FrameHasDisplayPort(parent)))) {
|
||||
(!parent->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
|
||||
!parent->IsStackingContext() &&
|
||||
!FrameHasDisplayPort(parent)))) {
|
||||
if (!parent->Extend3DContext()) {
|
||||
ctm.ProjectTo2D();
|
||||
}
|
||||
|
|
|
@ -1019,6 +1019,11 @@ nsIFrame::MarkNeedsDisplayItemRebuild()
|
|||
return;
|
||||
}
|
||||
|
||||
if (Type() == LayoutFrameType::Placeholder) {
|
||||
// Do not mark placeholder frames modified.
|
||||
return;
|
||||
}
|
||||
|
||||
nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
|
||||
MOZ_ASSERT(displayRoot);
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include "RetainedDisplayListBuilder.h"
|
||||
#include "nsPlaceholderFrame.h"
|
||||
#include "nsSubDocumentFrame.h"
|
||||
#include "nsViewManager.h"
|
||||
|
||||
|
@ -77,6 +78,24 @@ bool IsAnyAncestorModified(nsIFrame* aFrame)
|
|||
return false;
|
||||
}
|
||||
|
||||
static AnimatedGeometryRoot*
|
||||
SelectAGRForFrame(nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR)
|
||||
{
|
||||
if (!aFrame->IsStackingContext()) {
|
||||
return aParentAGR;
|
||||
}
|
||||
|
||||
if (!aFrame->HasOverrideDirtyRegion()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsDisplayListBuilder::DisplayListBuildingData* data =
|
||||
aFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
||||
|
||||
return data && data->mModifiedAGR ? data->mModifiedAGR.get()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
// Removes any display items that belonged to a frame that was deleted,
|
||||
// and mark frames that belong to a different AGR so that get their
|
||||
// items built again.
|
||||
|
@ -97,19 +116,7 @@ RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList,
|
|||
nsIFrame* f = i->Frame();
|
||||
|
||||
if (i->GetChildren()) {
|
||||
AnimatedGeometryRoot *childAGR = aAGR;
|
||||
if (f->IsStackingContext()) {
|
||||
if (f->HasOverrideDirtyRegion()) {
|
||||
nsDisplayListBuilder::DisplayListBuildingData* data =
|
||||
f->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
||||
if (data) {
|
||||
childAGR = data->mModifiedAGR;
|
||||
}
|
||||
} else {
|
||||
childAGR = nullptr;
|
||||
}
|
||||
}
|
||||
PreProcessDisplayList(i->GetChildren(), childAGR);
|
||||
PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR));
|
||||
}
|
||||
|
||||
// TODO: We should be able to check the clipped bounds relative
|
||||
|
@ -642,6 +649,165 @@ GetModifiedFrames(nsDisplayListBuilder* aBuilder)
|
|||
# define CRR_LOG(...)
|
||||
#endif
|
||||
|
||||
static nsIFrame*
|
||||
HandlePreserve3D(nsIFrame* aFrame, nsRect& aOverflow)
|
||||
{
|
||||
// Preserve-3d frames don't have valid overflow areas, and they might
|
||||
// have singular transforms (despite still being visible when combined
|
||||
// with their ancestors). If we're at one, jump up to the root of the
|
||||
// preserve-3d context and use the whole overflow area.
|
||||
nsIFrame* last = aFrame;
|
||||
while (aFrame->Extend3DContext() ||
|
||||
aFrame->Combines3DTransformWithAncestors()) {
|
||||
last = aFrame;
|
||||
aFrame = aFrame->GetParent();
|
||||
}
|
||||
if (last != aFrame) {
|
||||
aOverflow = last->GetVisualOverflowRectRelativeToParent();
|
||||
}
|
||||
|
||||
return aFrame;
|
||||
}
|
||||
|
||||
static void
|
||||
ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder& aBuilder,
|
||||
AnimatedGeometryRoot** aAGR, nsRect& aOverflow,
|
||||
nsIFrame* aStopAtFrame, nsTArray<nsIFrame*>& aOutFramesWithProps,
|
||||
const bool aStopAtStackingContext)
|
||||
{
|
||||
nsIFrame* currentFrame = aFrame;
|
||||
|
||||
while (currentFrame != aStopAtFrame) {
|
||||
currentFrame = HandlePreserve3D(currentFrame, aOverflow);
|
||||
|
||||
// Convert 'aOverflow' into the coordinate space of the nearest stacking context
|
||||
// or display port ancestor and update 'currentFrame' to point to that frame.
|
||||
nsIFrame* previousFrame = currentFrame;
|
||||
aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, aOverflow, aStopAtFrame,
|
||||
nullptr, nullptr,
|
||||
/* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
|
||||
¤tFrame);
|
||||
MOZ_ASSERT(currentFrame);
|
||||
|
||||
// If the current frame is an OOF frame, DisplayListBuildingData needs to be
|
||||
// set on all the ancestor stacking contexts of the placeholder frame, up
|
||||
// to the containing block of the OOF frame. This is done to ensure that the
|
||||
// content that might be behind the OOF frame is built for merging.
|
||||
nsIFrame* placeholder = previousFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
|
||||
? previousFrame->GetPlaceholderFrame()
|
||||
: nullptr;
|
||||
|
||||
if (placeholder) {
|
||||
nsRect placeholderOverflow =
|
||||
aOverflow + previousFrame->GetOffsetTo(placeholder);
|
||||
|
||||
CRR_LOG("Processing placeholder %p for OOF frame %p\n",
|
||||
placeholder, previousFrame);
|
||||
|
||||
CRR_LOG("OOF frame draw area: %d %d %d %d\n",
|
||||
placeholderOverflow.x, placeholderOverflow.y,
|
||||
placeholderOverflow.width, placeholderOverflow.height);
|
||||
|
||||
// Tracking AGRs for the placeholder processing is not necessary, as the
|
||||
// goal is to only modify the DisplayListBuildingData rect.
|
||||
AnimatedGeometryRoot* dummyAGR = nullptr;
|
||||
|
||||
// Find a common ancestor frame to handle frame continuations.
|
||||
// TODO: It might be possible to write a more specific and efficient
|
||||
// function for this.
|
||||
nsIFrame* ancestor =
|
||||
nsLayoutUtils::FindNearestCommonAncestorFrame(previousFrame->GetParent(),
|
||||
placeholder->GetParent());
|
||||
|
||||
ProcessFrame(placeholder, aBuilder, &dummyAGR, placeholderOverflow,
|
||||
ancestor, aOutFramesWithProps, false);
|
||||
}
|
||||
|
||||
if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
|
||||
CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
|
||||
nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
|
||||
MOZ_ASSERT(sf);
|
||||
nsRect displayPort;
|
||||
DebugOnly<bool> hasDisplayPort =
|
||||
nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort,
|
||||
RelativeTo::ScrollPort);
|
||||
MOZ_ASSERT(hasDisplayPort);
|
||||
// get it relative to the scrollport (from the scrollframe)
|
||||
nsRect r = aOverflow - sf->GetScrollPortRect().TopLeft();
|
||||
r.IntersectRect(r, displayPort);
|
||||
if (!r.IsEmpty()) {
|
||||
nsRect* rect =
|
||||
currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
|
||||
if (!rect) {
|
||||
rect = new nsRect();
|
||||
currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
|
||||
currentFrame->SetHasOverrideDirtyRegion(true);
|
||||
aOutFramesWithProps.AppendElement(currentFrame);
|
||||
}
|
||||
rect->UnionRect(*rect, r);
|
||||
CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n",
|
||||
r.x, r.y, r.width, r.height);
|
||||
|
||||
// TODO: Can we just use MarkFrameForDisplayIfVisible, plus MarkFramesForDifferentAGR to
|
||||
// ensure that this displayport, plus any items that move relative to it get rebuilt,
|
||||
// and then not contribute to the root dirty area?
|
||||
aOverflow = sf->GetScrollPortRect();
|
||||
} else {
|
||||
// Don't contribute to the root dirty area at all.
|
||||
aOverflow.SetEmpty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFrame->IsStackingContext()) {
|
||||
CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
|
||||
// If we found an intermediate stacking context with an existing display item
|
||||
// then we can store the dirty rect there and stop. If we couldn't find one then
|
||||
// we need to keep bubbling up to the next stacking context.
|
||||
if (currentFrame == aBuilder.RootReferenceFrame() ||
|
||||
!currentFrame->HasDisplayItems()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
aBuilder.MarkFrameForDisplayIfVisible(currentFrame,
|
||||
aBuilder.RootReferenceFrame());
|
||||
|
||||
// Store the stacking context relative dirty area such
|
||||
// that display list building will pick it up when it
|
||||
// gets to it.
|
||||
nsDisplayListBuilder::DisplayListBuildingData* data =
|
||||
currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
||||
if (!data) {
|
||||
data = new nsDisplayListBuilder::DisplayListBuildingData();
|
||||
currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingRect(), data);
|
||||
currentFrame->SetHasOverrideDirtyRegion(true);
|
||||
aOutFramesWithProps.AppendElement(currentFrame);
|
||||
}
|
||||
CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
|
||||
aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
|
||||
data->mDirtyRect.UnionRect(data->mDirtyRect, aOverflow);
|
||||
|
||||
if (!aStopAtStackingContext) {
|
||||
// Continue ascending the frame tree until we reach aStopAtFrame.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!data->mModifiedAGR) {
|
||||
data->mModifiedAGR = *aAGR;
|
||||
} else if (data->mModifiedAGR != *aAGR) {
|
||||
data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf();
|
||||
CRR_LOG("Found multiple modified AGRs within this stacking context, giving up\n");
|
||||
}
|
||||
|
||||
// Don't contribute to the root dirty area at all.
|
||||
aOverflow.SetEmpty();
|
||||
*aAGR = nullptr;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of frames that has been modified, computes the region that we need to
|
||||
* do display list building for in order to build all modified display items.
|
||||
|
@ -672,14 +838,14 @@ bool
|
|||
RetainedDisplayListBuilder::ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
|
||||
nsRect* aOutDirty,
|
||||
AnimatedGeometryRoot** aOutModifiedAGR,
|
||||
nsTArray<nsIFrame*>* aOutFramesWithProps)
|
||||
nsTArray<nsIFrame*>& aOutFramesWithProps)
|
||||
{
|
||||
CRR_LOG("Computing rebuild regions for %d frames:\n", aModifiedFrames.size());
|
||||
CRR_LOG("Computing rebuild regions for %zu frames:\n", aModifiedFrames.Length());
|
||||
for (nsIFrame* f : aModifiedFrames) {
|
||||
MOZ_ASSERT(f);
|
||||
|
||||
if (f->HasOverrideDirtyRegion()) {
|
||||
aOutFramesWithProps->AppendElement(f);
|
||||
aOutFramesWithProps.AppendElement(f);
|
||||
}
|
||||
|
||||
if (f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
|
||||
|
@ -692,7 +858,6 @@ RetainedDisplayListBuilder::ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedF
|
|||
|
||||
CRR_LOG("Processing frame %p with agr %p\n", f, agr->mFrame);
|
||||
|
||||
|
||||
// Convert the frame's overflow rect into the coordinate space
|
||||
// of the nearest stacking context that has an existing display item.
|
||||
// We store that as a dirty rect on that stacking context so that we build
|
||||
|
@ -702,109 +867,18 @@ RetainedDisplayListBuilder::ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedF
|
|||
// of the stacking context, since we know the stacking context item exists in
|
||||
// the old list, so we can trivially merge without needing other items.
|
||||
nsRect overflow = f->GetVisualOverflowRectRelativeToSelf();
|
||||
nsIFrame* currentFrame = f;
|
||||
|
||||
while (currentFrame != mBuilder.RootReferenceFrame()) {
|
||||
ProcessFrame(f, mBuilder, &agr, overflow, mBuilder.RootReferenceFrame(),
|
||||
aOutFramesWithProps, true);
|
||||
|
||||
// Preserve-3d frames don't have valid overflow areas, and they might
|
||||
// have singular transforms (despite still being visible when combined
|
||||
// with their ancestors). If we're at one, jump up to the root of the
|
||||
// preserve-3d context and use the whole overflow area.
|
||||
nsIFrame* last = currentFrame;
|
||||
while (currentFrame->Extend3DContext() ||
|
||||
currentFrame->Combines3DTransformWithAncestors()) {
|
||||
last = currentFrame;
|
||||
currentFrame = currentFrame->GetParent();
|
||||
}
|
||||
if (last != currentFrame) {
|
||||
overflow = last->GetVisualOverflowRectRelativeToParent();
|
||||
}
|
||||
|
||||
// Convert 'overflow' into the coordinate space of the nearest stacking context
|
||||
// or display port ancestor and update 'currentFrame' to point to that frame.
|
||||
overflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, overflow, mBuilder.RootReferenceFrame(),
|
||||
nullptr, nullptr,
|
||||
/* aStopAtStackingContextAndDisplayPort = */ true,
|
||||
¤tFrame);
|
||||
MOZ_ASSERT(currentFrame);
|
||||
|
||||
if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
|
||||
CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
|
||||
nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
|
||||
MOZ_ASSERT(sf);
|
||||
nsRect displayPort;
|
||||
DebugOnly<bool> hasDisplayPort =
|
||||
nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort, RelativeTo::ScrollPort);
|
||||
MOZ_ASSERT(hasDisplayPort);
|
||||
// get it relative to the scrollport (from the scrollframe)
|
||||
nsRect r = overflow - sf->GetScrollPortRect().TopLeft();
|
||||
r.IntersectRect(r, displayPort);
|
||||
if (!r.IsEmpty()) {
|
||||
nsRect* rect =
|
||||
currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
|
||||
if (!rect) {
|
||||
rect = new nsRect();
|
||||
currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
|
||||
currentFrame->SetHasOverrideDirtyRegion(true);
|
||||
}
|
||||
rect->UnionRect(*rect, r);
|
||||
aOutFramesWithProps->AppendElement(currentFrame);
|
||||
CRR_LOG("Adding area to displayport draw area: %d %d %d %d\n", r.x, r.y, r.width, r.height);
|
||||
|
||||
// TODO: Can we just use MarkFrameForDisplayIfVisible, plus MarkFramesForDifferentAGR to
|
||||
// ensure that this displayport, plus any items that move relative to it get rebuilt,
|
||||
// and then not contribute to the root dirty area?
|
||||
overflow = sf->GetScrollPortRect();
|
||||
} else {
|
||||
// Don't contribute to the root dirty area at all.
|
||||
overflow.SetEmpty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFrame->IsStackingContext()) {
|
||||
CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
|
||||
// If we found an intermediate stacking context with an existing display item
|
||||
// then we can store the dirty rect there and stop. If we couldn't find one then
|
||||
// we need to keep bubbling up to the next stacking context.
|
||||
if (currentFrame != mBuilder.RootReferenceFrame() &&
|
||||
currentFrame->HasDisplayItems()) {
|
||||
mBuilder.MarkFrameForDisplayIfVisible(currentFrame, mBuilder.RootReferenceFrame());
|
||||
|
||||
// Store the stacking context relative dirty area such
|
||||
// that display list building will pick it up when it
|
||||
// gets to it.
|
||||
nsDisplayListBuilder::DisplayListBuildingData* data =
|
||||
currentFrame->GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
|
||||
if (!data) {
|
||||
data = new nsDisplayListBuilder::DisplayListBuildingData;
|
||||
currentFrame->SetProperty(nsDisplayListBuilder::DisplayListBuildingRect(), data);
|
||||
currentFrame->SetHasOverrideDirtyRegion(true);
|
||||
aOutFramesWithProps->AppendElement(currentFrame);
|
||||
}
|
||||
data->mDirtyRect.UnionRect(data->mDirtyRect, overflow);
|
||||
CRR_LOG("Adding area to stacking context draw area: %d %d %d %d\n",
|
||||
overflow.x, overflow.y, overflow.width, overflow.height);
|
||||
if (!data->mModifiedAGR) {
|
||||
data->mModifiedAGR = agr;
|
||||
} else if (data->mModifiedAGR != agr) {
|
||||
data->mDirtyRect = currentFrame->GetVisualOverflowRectRelativeToSelf();
|
||||
CRR_LOG("Found multiple modified AGRs within this stacking context, giving up\n");
|
||||
}
|
||||
|
||||
// Don't contribute to the root dirty area at all.
|
||||
agr = nullptr;
|
||||
overflow.SetEmpty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
aOutDirty->UnionRect(*aOutDirty, overflow);
|
||||
CRR_LOG("Adding area to root draw area: %d %d %d %d\n", overflow.x, overflow.y, overflow.width, overflow.height);
|
||||
CRR_LOG("Adding area to root draw area: %d %d %d %d\n",
|
||||
overflow.x, overflow.y, overflow.width, overflow.height);
|
||||
|
||||
// If we get changed frames from multiple AGRS, then just give up as it gets really complex to
|
||||
// track which items would need to be marked in MarkFramesForDifferentAGR.
|
||||
if (!*aOutModifiedAGR) {
|
||||
CRR_LOG("Setting %p as root stacking context AGR\n", agr);
|
||||
*aOutModifiedAGR = agr;
|
||||
} else if (agr && *aOutModifiedAGR != agr) {
|
||||
CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
|
||||
|
@ -909,7 +983,7 @@ RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
|
|||
bool merged = false;
|
||||
if (shouldBuildPartial &&
|
||||
ComputeRebuildRegion(modifiedFrames, &modifiedDirty,
|
||||
&modifiedAGR, &framesWithProps)) {
|
||||
&modifiedAGR, framesWithProps)) {
|
||||
modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
|
||||
|
||||
PreProcessDisplayList(&mList, modifiedAGR);
|
||||
|
|
|
@ -48,7 +48,7 @@ private:
|
|||
bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
|
||||
nsRect* aOutDirty,
|
||||
AnimatedGeometryRoot** aOutModifiedAGR,
|
||||
nsTArray<nsIFrame*>* aOutFramesWithProps);
|
||||
nsTArray<nsIFrame*>& aOutFramesWithProps);
|
||||
|
||||
void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem);
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Retained display list test</title>
|
||||
<style type="text/css">
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Retained display list test</title>
|
||||
<style type="text/css">
|
||||
.box {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
button {
|
||||
position: fixed;
|
||||
outline: none;
|
||||
background-color: green;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.red {
|
||||
position: absolute;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.translate {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="box red"></div>
|
||||
<button class="box" id="green"></button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function doTest() {
|
||||
document.getElementById("green").classList.add("translate");
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
window.addEventListener("MozReftestInvalidate", doTest);
|
||||
|
||||
// setTimeout(doTest, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Retained display list test</title>
|
||||
<style type="text/css">
|
||||
#child {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
top: 300px;
|
||||
left: 0px;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
z-index: 1;
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="child"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Retained display list test</title>
|
||||
<style type="text/css">
|
||||
.back {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
#parent {
|
||||
position: fixed;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
#child {
|
||||
position: fixed;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
top: 300px;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.translate {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="back"></div>
|
||||
<div id="parent">
|
||||
<div id="child"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
function doTest() {
|
||||
document.getElementById("parent").classList.add("translate");
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
window.addEventListener("MozReftestInvalidate", doTest);
|
||||
|
||||
// setTimeout(doTest, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -14,3 +14,5 @@ fuzzy(1,235200) == 1413073.html 1413073-ref.html
|
|||
== 1416291.html 1416291-ref.html
|
||||
== 1417601-1.html 1417601-1-ref.html
|
||||
== 1418945-1.html 1418945-1-ref.html
|
||||
skip-if(Android) == 1428993-1.html 1428993-1-ref.html
|
||||
== 1428993-2.html 1428993-2-ref.html
|
||||
|
|
|
@ -275,11 +275,11 @@ with Files('view-source/**'):
|
|||
BUG_COMPONENT = ('Core', 'HTML: Parser')
|
||||
with Files('w3c-css/**'):
|
||||
BUG_COMPONENT = ('Core', 'Layout')
|
||||
with Files('w3c-css/received/css-conditional-3/**'):
|
||||
with Files('w3c-css/received/css-conditional/**'):
|
||||
BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
|
||||
with Files('w3c-css/received/css-namespaces-3/**'):
|
||||
with Files('w3c-css/received/css-namespaces/**'):
|
||||
BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
|
||||
with Files('w3c-css/received/css-values-3/**'):
|
||||
with Files('w3c-css/received/css-values/**'):
|
||||
BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
|
||||
with Files('w3c-css/submitted/conditional3/**'):
|
||||
BUG_COMPONENT = ('Core', 'CSS Parsing and Computation')
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# 2) A failure is denoted by <failure-type>* (as described in
|
||||
# layout/tools/reftest/README.txt) and a path pattern starting
|
||||
# with module. E.g.:
|
||||
# fails css-values-3/attr-*.html
|
||||
# fails css-values/attr-*.html
|
||||
#
|
||||
# If a test matches multiple path pattern, the last one wins. Because
|
||||
# of this, an item could have zero <failure-type>, which indicates it
|
||||
|
@ -15,301 +15,319 @@
|
|||
#### Selectors 4 ####################################################
|
||||
|
||||
# focus-within
|
||||
needs-focus selectors4/focus-within-0??.html
|
||||
needs-focus selectors/focus-within-0??.html
|
||||
|
||||
# Rely on Element.attachShadow which is not implemented yet
|
||||
skip selectors4/focus-within-shadow-*.html
|
||||
skip selectors/focus-within-shadow-*.html
|
||||
|
||||
#### CSS Values 3 ####################################################
|
||||
|
||||
# New failures need to be triged later
|
||||
fails css-values-3/ch-unit-003.html
|
||||
fails css-values/ch-unit-003.html
|
||||
|
||||
# Fuzzy
|
||||
fuzzy-if(OSX||Android,78,197) css-values-3/ch-unit-001.html
|
||||
fuzzy(50,160) css-values-3/ch-unit-002.html
|
||||
fuzzy(78,197) css-values-3/ch-unit-004.html
|
||||
fuzzy-if(OSX||Android,78,197) css-values/ch-unit-001.html
|
||||
fuzzy(50,160) css-values/ch-unit-002.html
|
||||
fuzzy(78,197) css-values/ch-unit-004.html
|
||||
|
||||
# Bug 435426
|
||||
fails css-values-3/attr-*.html
|
||||
css-values-3/attr-*-invalid-fallback.html
|
||||
css-values-3/attr-invalid-type-???.html
|
||||
fails css-values/attr-*.html
|
||||
css-values/attr-*-invalid-fallback.html
|
||||
css-values/attr-invalid-type-???.html
|
||||
|
||||
# Bug 1256575
|
||||
fails-if(!stylo) css-values-3/calc-in-media-queries-???.html
|
||||
fails-if(!stylo||styloVsGecko) css-values/calc-in-media-queries-???.html
|
||||
|
||||
# because of dynamic change
|
||||
skip css-values-3/vh_not_refreshing_on_chrome.html
|
||||
skip css-values-3/vh_not_refreshing_on_chrome_iframe.html
|
||||
skip css-values/vh_not_refreshing_on_chrome.html
|
||||
skip css-values/vh_not_refreshing_on_chrome_iframe.html
|
||||
|
||||
# because of support files (in iframe subdir) not being copied (bug 1256580)
|
||||
skip css-values-3/vh-support-transform-origin.html
|
||||
skip css-values-3/vh-support-transform-translate.html
|
||||
skip css-values/vh-support-transform-origin.html
|
||||
skip css-values/vh-support-transform-translate.html
|
||||
|
||||
css-values-3/calc-in-calc.html
|
||||
css-values/calc-in-calc.html
|
||||
|
||||
#### CSS Writing Modes 3 #############################################
|
||||
|
||||
# New failures need to be triged later
|
||||
fails css-writing-modes-3/float-lft-orthog-htb-in-vlr-002.xht
|
||||
fails css-writing-modes-3/float-lft-orthog-htb-in-vrl-002.xht
|
||||
fails css-writing-modes-3/float-lft-orthog-vlr-in-htb-002.xht
|
||||
fails css-writing-modes-3/float-lft-orthog-vrl-in-htb-002.xht
|
||||
fails css-writing-modes-3/float-rgt-orthog-htb-in-vlr-003.xht
|
||||
fails css-writing-modes-3/float-rgt-orthog-htb-in-vrl-003.xht
|
||||
fails css-writing-modes-3/float-rgt-orthog-vlr-in-htb-003.xht
|
||||
fails css-writing-modes-3/float-rgt-orthog-vrl-in-htb-003.xht
|
||||
fails css-writing-modes-3/sizing-orthog-htb-in-vrl-001.xht
|
||||
fails css-writing-modes-3/sizing-orthog-htb-in-vrl-004.xht
|
||||
fails css-writing-modes-3/sizing-orthog-htb-in-vrl-013.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes-3/sizing-orthog-htb-in-vlr-008.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes-3/sizing-orthog-htb-in-vlr-020.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes-3/sizing-orthog-htb-in-vrl-008.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes-3/sizing-orthog-htb-in-vrl-020.xht
|
||||
css-writing-modes-3/sizing-orthog-vlr-in-htb-008.xht
|
||||
css-writing-modes-3/sizing-orthog-vlr-in-htb-020.xht
|
||||
css-writing-modes-3/sizing-orthog-vrl-in-htb-008.xht
|
||||
css-writing-modes-3/sizing-orthog-vrl-in-htb-020.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vlr-003.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vlr-009.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vlr-015.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vlr-021.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vrl-003.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vrl-009.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vrl-015.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-htb-in-vrl-021.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vlr-in-htb-003.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vlr-in-htb-009.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vlr-in-htb-015.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vlr-in-htb-021.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vrl-in-htb-003.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vrl-in-htb-009.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vrl-in-htb-015.xht
|
||||
fails-if(Android) css-writing-modes-3/sizing-orthog-vrl-in-htb-021.xht
|
||||
fails css-writing-modes/float-lft-orthog-htb-in-vlr-002.xht
|
||||
fails css-writing-modes/float-lft-orthog-htb-in-vrl-002.xht
|
||||
fails css-writing-modes/float-lft-orthog-vlr-in-htb-002.xht
|
||||
fails css-writing-modes/float-lft-orthog-vrl-in-htb-002.xht
|
||||
fails css-writing-modes/float-rgt-orthog-htb-in-vlr-003.xht
|
||||
fails css-writing-modes/float-rgt-orthog-htb-in-vrl-003.xht
|
||||
fails css-writing-modes/float-rgt-orthog-vlr-in-htb-003.xht
|
||||
fails css-writing-modes/float-rgt-orthog-vrl-in-htb-003.xht
|
||||
fails css-writing-modes/sizing-orthog-htb-in-vrl-001.xht
|
||||
fails css-writing-modes/sizing-orthog-htb-in-vrl-004.xht
|
||||
fails css-writing-modes/sizing-orthog-htb-in-vrl-013.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes/sizing-orthog-htb-in-vlr-008.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes/sizing-orthog-htb-in-vlr-020.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes/sizing-orthog-htb-in-vrl-008.xht
|
||||
fails-if(OSX||winWidget) css-writing-modes/sizing-orthog-htb-in-vrl-020.xht
|
||||
css-writing-modes/sizing-orthog-vlr-in-htb-008.xht
|
||||
css-writing-modes/sizing-orthog-vlr-in-htb-020.xht
|
||||
css-writing-modes/sizing-orthog-vrl-in-htb-008.xht
|
||||
css-writing-modes/sizing-orthog-vrl-in-htb-020.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vlr-003.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vlr-009.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vlr-015.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vlr-021.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vrl-003.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vrl-009.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vrl-015.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-htb-in-vrl-021.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vlr-in-htb-003.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vlr-in-htb-009.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vlr-in-htb-015.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vlr-in-htb-021.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vrl-in-htb-003.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vrl-in-htb-009.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vrl-in-htb-015.xht
|
||||
fails-if(Android) css-writing-modes/sizing-orthog-vrl-in-htb-021.xht
|
||||
|
||||
# Fuzzy
|
||||
fuzzy-if(OSX||winWidget,255,480) css-writing-modes-3/abs-pos-non-replaced-v??-???.xht
|
||||
fuzzy-if(OSX||winWidget,93,600) css-writing-modes-3/baseline-inline-non-replaced-00?.xht
|
||||
fuzzy-if(OSX||winWidget,213,1540) css-writing-modes-3/block-flow-direction-???-0??.xht
|
||||
fuzzy-if(OSX,255,200) css-writing-modes-3/box-offsets-rel-pos-vlr-005.xht
|
||||
fuzzy-if(OSX,255,200) css-writing-modes-3/box-offsets-rel-pos-vrl-004.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes-3/caption-side-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,215,780) css-writing-modes-3/central-baseline-alignment-00?.xht
|
||||
fuzzy-if(OSX||winWidget,75,404) css-writing-modes-3/direction-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,135,902) css-writing-modes-3/float-contiguous-v??-01?.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes-3/float-shrink-to-fit-vlr-009.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes-3/float-shrink-to-fit-vrl-008.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes-3/float-v??-0??.xht
|
||||
fuzzy-if(OSX||winWidget,62,404) css-writing-modes-3/height-width-inline-non-replaced-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,218,621) css-writing-modes-3/inline-block-alignment-orthogonal-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,135,1080) css-writing-modes-3/inline-block-alignment-slr-009.xht
|
||||
fuzzy-if(OSX||winWidget,111,960) css-writing-modes-3/inline-block-alignment-srl-008.xht
|
||||
fuzzy-if(OSX||winWidget,213,1540) css-writing-modes-3/line-box-direction-???-0??.xht
|
||||
fuzzy-if(OSX||winWidget,110,1200) css-writing-modes-3/row-progression-???-0??.xht
|
||||
fuzzy-if(OSX||winWidget,110,1200) css-writing-modes-3/table-column-order-00?.xht
|
||||
fuzzy-if(winWidget,110,1200) css-writing-modes-3/table-column-order-slr-007.xht
|
||||
fuzzy-if(OSX||winWidget,110,1200) css-writing-modes-3/table-column-order-srl-006.xht
|
||||
fuzzy-if(OSX||winWidget,75,404) css-writing-modes-3/text-align-v??-0??.xht
|
||||
fuzzy-if(OSX||winWidget,215,780) css-writing-modes-3/text-baseline-???-00?.xht
|
||||
fuzzy-if(OSX,15,16) css-writing-modes-3/text-combine-upright-decorations-001.html
|
||||
fuzzy-if(OSX||winWidget,255,480) css-writing-modes-3/text-indent-v??-0??.xht
|
||||
fuzzy-if(OSX||winWidget,226,960) css-writing-modes-3/text-orientation-016.xht
|
||||
fuzzy-if(OSX||winWidget,223,720) css-writing-modes-3/vertical-alignment-*.xht
|
||||
fuzzy-if(OSX||winWidget,153,612) css-writing-modes-3/writing-mode-vertical-??-00?.*
|
||||
fuzzy(255,960) random-if(webrender) css-writing-modes-3/text-combine-upright-value-all-00?.html
|
||||
fuzzy-if(OSX||winWidget,255,480) css-writing-modes/abs-pos-non-replaced-v??-???.xht
|
||||
fuzzy-if(OSX||winWidget,93,600) css-writing-modes/baseline-inline-non-replaced-00?.xht
|
||||
fuzzy-if(OSX||winWidget,213,1540) css-writing-modes/block-flow-direction-???-0??.xht
|
||||
fuzzy-if(OSX,255,200) css-writing-modes/box-offsets-rel-pos-vlr-005.xht
|
||||
fuzzy-if(OSX,255,200) css-writing-modes/box-offsets-rel-pos-vrl-004.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes/caption-side-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,215,780) css-writing-modes/central-baseline-alignment-00?.xht
|
||||
fuzzy-if(OSX||winWidget,75,404) css-writing-modes/direction-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,135,902) css-writing-modes/float-contiguous-v??-01?.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes/float-shrink-to-fit-vlr-009.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes/float-shrink-to-fit-vrl-008.xht
|
||||
fuzzy-if(OSX||winWidget,93,300) css-writing-modes/float-v??-0??.xht
|
||||
fuzzy-if(OSX||winWidget,62,404) css-writing-modes/height-width-inline-non-replaced-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,218,621) css-writing-modes/inline-block-alignment-orthogonal-v??-00?.xht
|
||||
fuzzy-if(OSX||winWidget,135,1080) css-writing-modes/inline-block-alignment-slr-009.xht
|
||||
fuzzy-if(OSX||winWidget,111,960) css-writing-modes/inline-block-alignment-srl-008.xht
|
||||
fuzzy-if(OSX||winWidget,213,1540) css-writing-modes/line-box-direction-???-0??.xht
|
||||
fuzzy-if(OSX||winWidget,110,1200) css-writing-modes/row-progression-???-0??.xht
|
||||
fuzzy-if(OSX||winWidget,110,1200) css-writing-modes/table-column-order-00?.xht
|
||||
fuzzy-if(winWidget,110,1200) css-writing-modes/table-column-order-slr-007.xht
|
||||
fuzzy-if(OSX||winWidget,110,1200) css-writing-modes/table-column-order-srl-006.xht
|
||||
fuzzy-if(OSX||winWidget,75,404) css-writing-modes/text-align-v??-0??.xht
|
||||
fuzzy-if(OSX||winWidget,215,780) css-writing-modes/text-baseline-???-00?.xht
|
||||
fuzzy-if(OSX,15,16) css-writing-modes/text-combine-upright-decorations-001.html
|
||||
fuzzy-if(OSX||winWidget,255,480) css-writing-modes/text-indent-v??-0??.xht
|
||||
fuzzy-if(OSX||winWidget,226,960) css-writing-modes/text-orientation-016.xht
|
||||
fuzzy-if(OSX||winWidget,223,720) css-writing-modes/vertical-alignment-*.xht
|
||||
fuzzy-if(OSX||winWidget,153,612) css-writing-modes/writing-mode-vertical-??-00?.*
|
||||
fuzzy(255,960) random-if(webrender) css-writing-modes/text-combine-upright-value-all-00?.html
|
||||
|
||||
# Bug 1167911
|
||||
skip css-writing-modes-3/abs-pos-non-replaced-icb-vlr-021.xht
|
||||
skip css-writing-modes-3/abs-pos-non-replaced-icb-vrl-020.xht
|
||||
skip css-writing-modes/abs-pos-non-replaced-icb-vlr-021.xht
|
||||
skip css-writing-modes/abs-pos-non-replaced-icb-vrl-020.xht
|
||||
|
||||
# Bug 1244601
|
||||
fails css-writing-modes-3/block-flow-direction-slr-058.xht
|
||||
fails css-writing-modes-3/block-flow-direction-srl-057.xht
|
||||
fails css-writing-modes-3/block-flow-direction-vlr-018.xht
|
||||
fails css-writing-modes-3/block-flow-direction-vrl-017.xht
|
||||
fails css-writing-modes/block-flow-direction-slr-058.xht
|
||||
fails css-writing-modes/block-flow-direction-srl-057.xht
|
||||
fails css-writing-modes/block-flow-direction-vlr-018.xht
|
||||
fails css-writing-modes/block-flow-direction-vrl-017.xht
|
||||
|
||||
# Bug 1185430
|
||||
fails css-writing-modes-3/contiguous-floated-table-vlr-00?.xht
|
||||
fails css-writing-modes-3/contiguous-floated-table-vrl-00?.xht
|
||||
fails css-writing-modes-3/table-progression-slr-002.html
|
||||
fails css-writing-modes-3/table-progression-srl-002.html
|
||||
fails css-writing-modes-3/table-progression-vlr-00?.html
|
||||
css-writing-modes-3/table-progression-vlr-001.html
|
||||
fails css-writing-modes-3/table-progression-vrl-00?.html
|
||||
css-writing-modes-3/table-progression-vrl-001.html
|
||||
fails css-writing-modes/contiguous-floated-table-vlr-00?.xht
|
||||
fails css-writing-modes/contiguous-floated-table-vrl-00?.xht
|
||||
fails css-writing-modes/table-progression-slr-002.html
|
||||
fails css-writing-modes/table-progression-srl-002.html
|
||||
fails css-writing-modes/table-progression-vlr-00?.html
|
||||
css-writing-modes/table-progression-vlr-001.html
|
||||
fails css-writing-modes/table-progression-vrl-00?.html
|
||||
css-writing-modes/table-progression-vrl-001.html
|
||||
|
||||
fails css-writing-modes-3/flexbox_align-items-stretch-writing-modes.html
|
||||
fails css-writing-modes/flexbox_align-items-stretch-writing-modes.html
|
||||
|
||||
# Bug 1179952
|
||||
fails css-writing-modes-3/inline-block-alignment-00?.xht
|
||||
fuzzy-if(OSX||winWidget,111,960) css-writing-modes-3/inline-block-alignment-006.xht
|
||||
fails css-writing-modes-3/inline-table-alignment-00?.xht
|
||||
fails css-writing-modes/inline-block-alignment-00?.xht
|
||||
fuzzy-if(OSX||winWidget,111,960) css-writing-modes/inline-block-alignment-006.xht
|
||||
fails css-writing-modes/inline-table-alignment-00?.xht
|
||||
|
||||
# Bug 1227616
|
||||
random css-writing-modes-3/line-box-direction-slr-056.xht
|
||||
random css-writing-modes-3/line-box-direction-srl-055.xht
|
||||
random css-writing-modes-3/line-box-direction-vlr-016.xht
|
||||
random css-writing-modes-3/line-box-direction-vrl-015.xht
|
||||
random css-writing-modes/line-box-direction-slr-056.xht
|
||||
random css-writing-modes/line-box-direction-srl-055.xht
|
||||
random css-writing-modes/line-box-direction-vlr-016.xht
|
||||
random css-writing-modes/line-box-direction-vrl-015.xht
|
||||
|
||||
# Bug 1220352
|
||||
fails css-writing-modes-3/line-box-height-vlr-003.xht
|
||||
fails css-writing-modes-3/line-box-height-vlr-005.xht
|
||||
fails css-writing-modes-3/line-box-height-vlr-011.xht
|
||||
fails css-writing-modes-3/line-box-height-vlr-013.xht
|
||||
fails css-writing-modes-3/line-box-height-vlr-021.xht
|
||||
fails css-writing-modes-3/line-box-height-vlr-023.xht
|
||||
fails css-writing-modes-3/line-box-height-vrl-002.xht
|
||||
fails css-writing-modes-3/line-box-height-vrl-004.xht
|
||||
fails css-writing-modes-3/line-box-height-vrl-010.xht
|
||||
fails css-writing-modes-3/line-box-height-vrl-012.xht
|
||||
fails css-writing-modes/line-box-height-vlr-003.xht
|
||||
fails css-writing-modes/line-box-height-vlr-005.xht
|
||||
fails css-writing-modes/line-box-height-vlr-011.xht
|
||||
fails css-writing-modes/line-box-height-vlr-013.xht
|
||||
fails css-writing-modes/line-box-height-vlr-021.xht
|
||||
fails css-writing-modes/line-box-height-vlr-023.xht
|
||||
fails css-writing-modes/line-box-height-vrl-002.xht
|
||||
fails css-writing-modes/line-box-height-vrl-004.xht
|
||||
fails css-writing-modes/line-box-height-vrl-010.xht
|
||||
fails css-writing-modes/line-box-height-vrl-012.xht
|
||||
|
||||
# Bug 1258635 - text-combine-upright: digits
|
||||
# Using skip because there are mismatch tests which would unexpectedly
|
||||
# pass with "fails-if(!stylo)".
|
||||
skip css-writing-modes-3/full-width-003.html
|
||||
skip css-writing-modes-3/text-combine-upright-value-digits?-001.html
|
||||
skip css-writing-modes-3/text-combine-upright-value-digits?-002.html
|
||||
skip css-writing-modes/full-width-003.html
|
||||
skip css-writing-modes/text-combine-upright-value-digits?-001.html
|
||||
skip css-writing-modes/text-combine-upright-value-digits?-002.html
|
||||
|
||||
# Bug 1220353
|
||||
fails css-writing-modes-3/vertical-alignment-vlr-023.xht
|
||||
fails css-writing-modes-3/vertical-alignment-vlr-025.xht
|
||||
fails css-writing-modes-3/vertical-alignment-vrl-022.xht
|
||||
fails css-writing-modes-3/vertical-alignment-vrl-024.xht
|
||||
fails css-writing-modes/vertical-alignment-vlr-023.xht
|
||||
fails css-writing-modes/vertical-alignment-vlr-025.xht
|
||||
fails css-writing-modes/vertical-alignment-vrl-022.xht
|
||||
fails css-writing-modes/vertical-alignment-vrl-024.xht
|
||||
|
||||
# Bug 1102175
|
||||
fails css-writing-modes-3/wm-propagation-body-*.xht
|
||||
fails css-writing-modes/wm-propagation-body-*.xht
|
||||
|
||||
css-writing-modes-3/text-combine-upright-layout-rules-001.html
|
||||
css-writing-modes/text-combine-upright-layout-rules-001.html
|
||||
|
||||
#### CSS Multi-column 1 ##############################################
|
||||
|
||||
# Fuzzy annotations for multicol tests are due to AA differences.
|
||||
# fails-if(!stylo) annotations need to be triaged later. (Bug 1299006)
|
||||
fails-if(winWidget||OSX) css-multicol-1/multicol-block-no-clip-001.xht
|
||||
fails-if(winWidget||OSX) css-multicol-1/multicol-block-no-clip-002.xht
|
||||
fails css-multicol-1/multicol-br-inside-avoidcolumn-001.xht
|
||||
fails css-multicol-1/multicol-break-000.xht
|
||||
fails css-multicol-1/multicol-break-001.xht
|
||||
fuzzy(135,1008) css-multicol-1/multicol-clip-001.xht
|
||||
fuzzy(135,770) css-multicol-1/multicol-clip-002.xht
|
||||
fuzzy(135,467) css-multicol-1/multicol-collapsing-001.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-001.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-002.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-003.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-004.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-005.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-006.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-columns-007.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-columns-invalid-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-columns-invalid-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-columns-toolong-001.xht
|
||||
fuzzy(135,530) css-multicol-1/multicol-containing-001.xht
|
||||
fuzzy(215,241) css-multicol-1/multicol-containing-002.xht
|
||||
fuzzy(87,180) css-multicol-1/multicol-count-001.xht
|
||||
fails css-multicol-1/multicol-count-002.xht
|
||||
fails css-multicol-1/multicol-count-computed-001.xht
|
||||
fails css-multicol-1/multicol-count-computed-002.xht
|
||||
fails-if(winWidget||OSX||Android) css-multicol-1/multicol-count-computed-003.xht
|
||||
fuzzy-if(winWidget||OSX||gtkWidget,112,861) fails-if(Android) css-multicol-1/multicol-count-computed-004.xht
|
||||
fails-if(winWidget||OSX||Android) css-multicol-1/multicol-count-computed-005.xht
|
||||
fails css-multicol-1/multicol-count-large-001.xht
|
||||
fuzzy(255,132) css-multicol-1/multicol-count-large-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-count-negative-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-count-negative-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-count-non-integer-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-count-non-integer-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-count-non-integer-003.xht
|
||||
fuzzy(135,80) css-multicol-1/multicol-fill-auto-002.xht
|
||||
fuzzy(135,3270) css-multicol-1/multicol-fill-auto-003.xht
|
||||
fails css-multicol-1/multicol-fill-auto.xht
|
||||
fuzzy(135,80) css-multicol-1/multicol-fill-balance-001.xht
|
||||
fuzzy(135,821) css-multicol-1/multicol-gap-000.xht
|
||||
fuzzy(255,290) css-multicol-1/multicol-gap-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-gap-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-gap-003.xht
|
||||
fuzzy(107,1823) css-multicol-1/multicol-gap-fraction-001.xht
|
||||
fuzzy-if(winWidget||OSX||gtkWidget,204,1048) fuzzy-if(skiaContent,208,1048) fails-if(Android) css-multicol-1/multicol-gap-large-001.xht
|
||||
fuzzy(225,920) css-multicol-1/multicol-gap-large-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-gap-negative-001.xht
|
||||
fails css-multicol-1/multicol-height-block-child-001.xht
|
||||
fuzzy(255,3762) css-multicol-1/multicol-inherit-001.xht
|
||||
fuzzy(135,1893) css-multicol-1/multicol-inherit-002.xht
|
||||
fails css-multicol-1/multicol-inherit-003.xht
|
||||
fails css-multicol-1/multicol-inherit-004.xht
|
||||
fuzzy(96,264) css-multicol-1/multicol-list-item-001.xht
|
||||
fuzzy(73,1200) css-multicol-1/multicol-margin-001.xht
|
||||
fuzzy(73,1200) css-multicol-1/multicol-margin-002.xht
|
||||
fuzzy(243,3322) fuzzy-if(skiaContent,244,3322) css-multicol-1/multicol-margin-child-001.xht
|
||||
fuzzy(255,4008) css-multicol-1/multicol-nested-002.xht
|
||||
fuzzy(255,4109) css-multicol-1/multicol-nested-005.xht
|
||||
fuzzy(204,2463) fuzzy-if(skiaContent,208,2463) css-multicol-1/multicol-nested-margin-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-nested-margin-002.xht
|
||||
fuzzy(204,2371) fuzzy-if(skiaContent,208,2371) css-multicol-1/multicol-nested-margin-003.xht
|
||||
fuzzy(225,2529) css-multicol-1/multicol-nested-margin-004.xht
|
||||
fuzzy(225,2529) css-multicol-1/multicol-nested-margin-005.xht
|
||||
fuzzy(135,142) css-multicol-1/multicol-overflow-000.xht
|
||||
fuzzy(204,1844) fuzzy-if(skiaContent,208,1844) css-multicol-1/multicol-overflowing-001.xht
|
||||
fuzzy-if(OSX,61,2) fuzzy-if(skiaContent,64,2) css-multicol-1/multicol-reduce-000.xht
|
||||
fuzzy-if(OSX,8,20) css-multicol-1/multicol-rule-000.xht
|
||||
fuzzy(135,1584) css-multicol-1/multicol-rule-001.xht
|
||||
fails css-multicol-1/multicol-rule-002.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-rule-003.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-rule-color-001.xht
|
||||
fuzzy(106,354) css-multicol-1/multicol-rule-dashed-000.xht
|
||||
fuzzy(106,354) css-multicol-1/multicol-rule-dotted-000.xht
|
||||
fuzzy(106,354) css-multicol-1/multicol-rule-double-000.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-rule-fraction-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-rule-fraction-002.xht
|
||||
fails css-multicol-1/multicol-rule-fraction-003.xht
|
||||
fuzzy(127,500) css-multicol-1/multicol-rule-groove-000.xht
|
||||
fuzzy(94,256) css-multicol-1/multicol-rule-hidden-000.xht
|
||||
fuzzy(127,500) css-multicol-1/multicol-rule-inset-000.xht
|
||||
fuzzy(127,500) css-multicol-1/multicol-rule-outset-000.xht
|
||||
fails css-multicol-1/multicol-rule-px-001.xht
|
||||
fuzzy(127,500) css-multicol-1/multicol-rule-ridge-000.xht
|
||||
fuzzy(106,354) css-multicol-1/multicol-rule-solid-000.xht
|
||||
fails css-multicol-1/multicol-rule-stacking-001.xht
|
||||
css-multicol-1/multicol-rule-style-groove-001.xht
|
||||
css-multicol-1/multicol-rule-style-inset-001.xht
|
||||
css-multicol-1/multicol-rule-style-outset-001.xht
|
||||
css-multicol-1/multicol-rule-style-ridge-001.xht
|
||||
fails css-multicol-1/multicol-shorthand-001.xht
|
||||
fails css-multicol-1/multicol-span-000.xht
|
||||
fails css-multicol-1/multicol-span-all-001.xht
|
||||
fails css-multicol-1/multicol-span-all-002.xht
|
||||
fails css-multicol-1/multicol-span-all-003.xht
|
||||
fails css-multicol-1/multicol-span-all-child-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol-1/multicol-span-all-child-002.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-001.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-002.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-bottom-001.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-nested-001.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-nested-002.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-nested-003.xht
|
||||
fails css-multicol-1/multicol-span-all-margin-nested-firstchild-001.xht
|
||||
fails css-multicol-1/multicol-span-float-001.xht
|
||||
fails css-multicol-1/multicol-span-none-001.xht
|
||||
fails css-multicol-1/multicol-table-cell-001.xht
|
||||
fails css-multicol-1/multicol-table-cell-height-001.xht
|
||||
fails css-multicol-1/multicol-table-cell-height-002.xht
|
||||
fails css-multicol-1/multicol-table-cell-vertical-align-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol-1/multicol-width-002.xht
|
||||
fails css-multicol-1/multicol-width-count-002.xht
|
||||
fails css-multicol-1/multicol-width-ems-001.xht
|
||||
fails css-multicol-1/multicol-width-negative-001.xht
|
||||
fuzzy(225,1060) css-multicol-1/multicol-width-large-001.xht
|
||||
fails css-multicol-1/multicol-width-small-001.xht
|
||||
fuzzy(225,1060) css-multicol-1/multicol-width-invalid-001.xht
|
||||
fuzzy(225,1060) css-multicol-1/multicol-width-large-002.xht
|
||||
fails css-multicol-1/multicol-zero-height-001.xht
|
||||
fuzzy(225,13600) css-multicol-1/multicol-nested-column-rule-001.xht
|
||||
fuzzy(94,256) css-multicol-1/multicol-rule-none-000.xht
|
||||
fails-if(winWidget||OSX) css-multicol/multicol-block-no-clip-001.xht
|
||||
fails-if(winWidget||OSX) css-multicol/multicol-block-no-clip-002.xht
|
||||
fails css-multicol/multicol-br-inside-avoidcolumn-001.xht
|
||||
fails css-multicol/multicol-break-000.xht
|
||||
fails css-multicol/multicol-break-001.xht
|
||||
fuzzy(135,1008) css-multicol/multicol-clip-001.xht
|
||||
fuzzy(135,770) css-multicol/multicol-clip-002.xht
|
||||
fuzzy(135,467) css-multicol/multicol-collapsing-001.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-001.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-002.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-003.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-004.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-005.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-006.xht
|
||||
fuzzy(87,180) css-multicol/multicol-columns-007.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-columns-invalid-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol/multicol-columns-invalid-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-columns-toolong-001.xht
|
||||
fuzzy(135,530) css-multicol/multicol-containing-001.xht
|
||||
fuzzy(215,241) css-multicol/multicol-containing-002.xht
|
||||
fuzzy(87,180) css-multicol/multicol-count-001.xht
|
||||
fails css-multicol/multicol-count-002.xht
|
||||
fails-if(winWidget||OSX||Android) css-multicol/multicol-count-computed-003.xht
|
||||
fuzzy-if(winWidget||OSX||gtkWidget,112,861) fails-if(Android) css-multicol/multicol-count-computed-004.xht
|
||||
fails-if(winWidget||OSX||Android) css-multicol/multicol-count-computed-005.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-count-negative-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-count-negative-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-count-non-integer-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-count-non-integer-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-count-non-integer-003.xht
|
||||
fuzzy(135,80) css-multicol/multicol-fill-auto-002.xht
|
||||
fuzzy(135,3270) css-multicol/multicol-fill-auto-003.xht
|
||||
fuzzy(135,80) css-multicol/multicol-fill-balance-001.xht
|
||||
fuzzy(135,821) css-multicol/multicol-gap-000.xht
|
||||
fuzzy(255,290) css-multicol/multicol-gap-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-gap-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-gap-003.xht
|
||||
fuzzy(107,1823) css-multicol/multicol-gap-fraction-001.xht
|
||||
fuzzy-if(winWidget||OSX||gtkWidget,204,1048) fuzzy-if(skiaContent,208,1048) fails-if(Android) css-multicol/multicol-gap-large-001.xht
|
||||
fuzzy(225,920) css-multicol/multicol-gap-large-002.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-gap-negative-001.xht
|
||||
fails css-multicol/multicol-height-block-child-001.xht
|
||||
fuzzy(255,3762) css-multicol/multicol-inherit-001.xht
|
||||
fuzzy(135,1893) css-multicol/multicol-inherit-002.xht
|
||||
fails css-multicol/multicol-inherit-003.xht
|
||||
fuzzy(96,264) css-multicol/multicol-list-item-001.xht
|
||||
fuzzy(73,1200) css-multicol/multicol-margin-001.xht
|
||||
fuzzy(73,1200) css-multicol/multicol-margin-002.xht
|
||||
fuzzy(243,3322) fuzzy-if(skiaContent,244,3322) css-multicol/multicol-margin-child-001.xht
|
||||
fuzzy(255,4008) css-multicol/multicol-nested-002.xht
|
||||
fuzzy(255,4109) css-multicol/multicol-nested-005.xht
|
||||
fuzzy(204,2463) fuzzy-if(skiaContent,208,2463) css-multicol/multicol-nested-margin-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol/multicol-nested-margin-002.xht
|
||||
fuzzy(204,2371) fuzzy-if(skiaContent,208,2371) css-multicol/multicol-nested-margin-003.xht
|
||||
fuzzy(225,2529) css-multicol/multicol-nested-margin-004.xht
|
||||
fuzzy(225,2529) css-multicol/multicol-nested-margin-005.xht
|
||||
fuzzy(135,142) css-multicol/multicol-overflow-000.xht
|
||||
fuzzy(204,1844) fuzzy-if(skiaContent,208,1844) css-multicol/multicol-overflowing-001.xht
|
||||
fuzzy-if(OSX,61,2) fuzzy-if(skiaContent,64,2) css-multicol/multicol-reduce-000.xht
|
||||
fuzzy-if(OSX,8,20) css-multicol/multicol-rule-000.xht
|
||||
fuzzy(135,1584) css-multicol/multicol-rule-001.xht
|
||||
fails css-multicol/multicol-rule-002.xht
|
||||
fails-if(OSX||winWidget) css-multicol/multicol-rule-003.xht
|
||||
fails-if(OSX||winWidget) css-multicol/multicol-rule-color-001.xht
|
||||
fuzzy(106,354) css-multicol/multicol-rule-dashed-000.xht
|
||||
fuzzy(106,354) css-multicol/multicol-rule-dotted-000.xht
|
||||
fuzzy(106,354) css-multicol/multicol-rule-double-000.xht
|
||||
fails-if(OSX||winWidget) css-multicol/multicol-rule-fraction-001.xht
|
||||
fails-if(OSX||winWidget) css-multicol/multicol-rule-fraction-002.xht
|
||||
fuzzy-if(OSX||winWidget||Android,113,792) css-multicol/multicol-rule-fraction-003.xht
|
||||
fuzzy(127,500) css-multicol/multicol-rule-groove-000.xht
|
||||
fuzzy(94,256) css-multicol/multicol-rule-hidden-000.xht
|
||||
fuzzy(127,500) css-multicol/multicol-rule-inset-000.xht
|
||||
fuzzy(127,500) css-multicol/multicol-rule-outset-000.xht
|
||||
fails css-multicol/multicol-rule-px-001.xht
|
||||
fuzzy(127,500) css-multicol/multicol-rule-ridge-000.xht
|
||||
fuzzy(106,354) css-multicol/multicol-rule-solid-000.xht
|
||||
fails css-multicol/multicol-rule-stacking-001.xht
|
||||
fails css-multicol/multicol-shorthand-001.xht
|
||||
fails css-multicol/multicol-span-000.xht
|
||||
fails css-multicol/multicol-span-all-001.xht
|
||||
fails css-multicol/multicol-span-all-002.xht
|
||||
fails css-multicol/multicol-span-all-003.xht
|
||||
fails css-multicol/multicol-span-all-margin-001.xht
|
||||
fails css-multicol/multicol-span-all-margin-002.xht
|
||||
fails css-multicol/multicol-span-all-margin-bottom-001.xht
|
||||
fails css-multicol/multicol-span-all-margin-nested-001.xht
|
||||
fails css-multicol/multicol-span-all-margin-nested-002.xht
|
||||
fails css-multicol/multicol-span-all-margin-nested-firstchild-001.xht
|
||||
fails css-multicol/multicol-span-float-001.xht
|
||||
fails css-multicol/multicol-span-none-001.xht
|
||||
fails css-multicol/multicol-table-cell-001.xht
|
||||
fails css-multicol/multicol-table-cell-height-001.xht
|
||||
fails css-multicol/multicol-table-cell-height-002.xht
|
||||
fails css-multicol/multicol-table-cell-vertical-align-001.xht
|
||||
fuzzy(204,930) fuzzy-if(skiaContent,208,930) css-multicol/multicol-width-002.xht
|
||||
fails css-multicol/multicol-width-count-002.xht
|
||||
fails css-multicol/multicol-width-negative-001.xht
|
||||
fuzzy(225,1060) css-multicol/multicol-width-large-001.xht
|
||||
fails css-multicol/multicol-width-small-001.xht
|
||||
fuzzy(225,1060) css-multicol/multicol-width-invalid-001.xht
|
||||
fuzzy(225,1060) css-multicol/multicol-width-large-002.xht
|
||||
fuzzy(225,13600) css-multicol/multicol-nested-column-rule-001.xht
|
||||
fuzzy(94,256) css-multicol/multicol-rule-none-000.xht
|
||||
|
||||
#This test seems to pass only on Linux-opt build, on everything else
|
||||
#Therefore using fuzzy annotation as a catch all
|
||||
fuzzy(255,2808) css-multicol-1/multicol-rule-large-001.xht
|
||||
fuzzy(255,2808) css-multicol/multicol-rule-large-001.xht
|
||||
|
||||
# fails because column-span property not implemented (Bug 616436)
|
||||
fails css-multicol-1/multicol-fill-auto-block-children-001.xht
|
||||
fails css-multicol-1/multicol-fill-auto-block-children-002.xht
|
||||
fails css-multicol-1/multicol-span-all-block-sibling-003.xht
|
||||
fails css-multicol/multicol-fill-auto-block-children-001.xht
|
||||
fails css-multicol/multicol-fill-auto-block-children-002.xht
|
||||
fails css-multicol/multicol-span-all-block-sibling-003.xht
|
||||
|
||||
# skip these tests since they haven't been triaged yet.
|
||||
# These tests were added to the tree as part of an update for Bug 1430939.
|
||||
skip css-multicol/multicol-fill-balance-002.html
|
||||
skip css-multicol/multicol-gap-fraction-002.html
|
||||
skip css-multicol/multicol-rule-shorthand-2.xht
|
||||
skip css-multicol/multicol-width-ch-001.xht
|
||||
skip css-values/ex-calc-expression-001.html
|
||||
skip css-values/ic-unit-001.html
|
||||
skip css-values/ic-unit-002.html
|
||||
skip css-values/ic-unit-003.html
|
||||
skip css-values/ic-unit-004.html
|
||||
skip css-values/lh-unit-001.html
|
||||
skip css-values/lh-unit-002.html
|
||||
skip css-values/support/vh-support-transform-origin-iframe.html
|
||||
skip css-values/support/vh-support-transform-translate-iframe.html
|
||||
skip css-writing-modes/bidi-table-001.html
|
||||
skip css-writing-modes/block-plaintext-006.html
|
||||
skip css-writing-modes/table-cell-001.html
|
||||
skip css-writing-modes/table-cell-002.html
|
||||
skip selectors/any-link-dynamic-001.html
|
||||
skip selectors/root-siblings.htm
|
||||
skip selectors/scope-without-scoping.html
|
||||
skip selectors/selector-placeholder-shown-type-change-001.html
|
||||
skip selectors/selector-placeholder-shown-type-change-002.html
|
||||
skip selectors/selector-placeholder-shown-type-change-003.html
|
||||
skip selectors/selector-read-write-type-change-001.html
|
||||
skip selectors/selector-read-write-type-change-002.html
|
||||
skip selectors/selector-required-type-change-001.html
|
||||
skip selectors/selector-required-type-change-002.html
|
||||
skip selectors/selectors-attr-white-space-001.html
|
||||
skip selectors/selectors-empty-001.xml
|
||||
skip selectors/selectors-namespace-001.xml
|
||||
|
|
|
@ -30,12 +30,12 @@ import re
|
|||
# But for now, let's just import a few sets of tests.
|
||||
|
||||
gSubtrees = [
|
||||
os.path.join("css-namespaces-3"),
|
||||
os.path.join("css-conditional-3"),
|
||||
os.path.join("css-values-3"),
|
||||
os.path.join("css-multicol-1"),
|
||||
os.path.join("css-writing-modes-3"),
|
||||
os.path.join("selectors4"),
|
||||
os.path.join("css-namespaces"),
|
||||
os.path.join("css-conditional"),
|
||||
os.path.join("css-values"),
|
||||
os.path.join("css-multicol"),
|
||||
os.path.join("css-writing-modes"),
|
||||
os.path.join("selectors"),
|
||||
]
|
||||
|
||||
gPrefixedProperties = [
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<title>CSS Reftest Reference</title>
|
||||
|
||||
<link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" />
|
||||
|
||||
<style type="text/css"><![CDATA[
|
||||
p {color: green;}
|
||||
]]></style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p>This text should be green.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче