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

This commit is contained in:
Noemi Erli 2018-01-19 12:16:58 +02:00
Родитель f1a8db233f 1e1fdc2b3d
Коммит 74f7da5f13
2233 изменённых файлов: 13610 добавлений и 5434 удалений

Просмотреть файл

@ -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,
&currentFrame);
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,
&currentFrame);
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>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше