Merge fx-team to central, a=merge

This commit is contained in:
Wes Kocher 2015-09-18 13:58:09 -07:00
commit 3e0814bfd3
96 changed files with 995 additions and 2343 deletions

View File

@ -1380,8 +1380,6 @@ pref("devtools.inspector.showUserAgentStyles", false);
pref("devtools.inspector.showAllAnonymousContent", false);
// Enable the MDN docs tooltip
pref("devtools.inspector.mdnDocsTooltip.enabled", true);
// Show the new animation inspector UI
pref("devtools.inspector.animationInspectorV3", false);
// DevTools default color unit
pref("devtools.defaultColorUnit", "hex");

View File

@ -22,13 +22,8 @@ const kWhitelist = [
// Loop standalone client CSS uses placeholder cross browser pseudo-element
{sourceName: /loop\/.*\.css/i,
errorMessage: /Unknown pseudo-class.*placeholder/i},
// Loop issues that crept in since this test was broken, see bug ...
{sourceName: /loop\/.*shared\/css\/conversation.css/i,
errorMessage: /Error in parsing value for 'display'/i},
{sourceName: /loop\/.*shared\/css\/common.css/i,
errorMessage: /Unknown property 'user-select'/i},
{sourceName: /loop\/.*css\/panel.css/i,
errorMessage: /Expected color but found 'none'/i},
// Highlighter CSS uses chrome-only pseudo-class, see bug 985597.
{sourceName: /highlighter\.css/i,
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i},

View File

@ -1068,7 +1068,6 @@ html[dir="rtl"] .settings-menu .dropdown-menu {
border: none;
color: #fff;
background-color: #00a9dc;
border-color: none;
line-height: 43px;
margin: 0 15px;
padding: 0;

View File

@ -1386,7 +1386,6 @@ html[dir="rtl"] .room-context-btn-close {
.media-wrapper > .focus-stream > .local {
position: absolute;
display:fixed;
right: 0;
left: auto;
/* 30% is the height of the text chat. As we have a margin,

View File

@ -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/. */
Components.utils.import("resource://gre/modules/Services.jsm");
const TEST_LIST = "test-track-simple";
const TRACK_SUFFIX = "-track-digest256";
const TRACKING_TABLE_PREF = "urlclassifier.trackingTable";
const LISTS_PREF_BRANCH = "browser.safebrowsing.provider.mozilla.lists.";
let gBlocklistManager = {
_type: "",
_blockLists: [],
_brandShortName : null,
_bundle: null,
_tree: null,
_view: {
_rowCount: 0,
get rowCount() {
return this._rowCount;
},
getCellText: function (row, column) {
if (column.id == "listCol") {
let list = gBlocklistManager._blockLists[row];
let desc = list.description ? list.description : "";
let text = gBlocklistManager._bundle.getFormattedString("mozNameTemplate",
[list.name, desc]);
return text;
}
return "";
},
isSeparator: function(index) { return false; },
isSorted: function() { return false; },
isContainer: function(index) { return false; },
setTree: function(tree) {},
getImageSrc: function(row, column) {},
getProgressMode: function(row, column) {},
getCellValue: function(row, column) {
if (column.id == "selectionCol")
return gBlocklistManager._blockLists[row].selected;
return undefined;
},
cycleHeader: function(column) {},
getRowProperties: function(row) { return ""; },
getColumnProperties: function(column) { return ""; },
getCellProperties: function(row, column) {
if (column.id == "selectionCol") {
return "checkmark";
}
return "";
}
},
onWindowKeyPress: function (event) {
if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
window.close();
} else if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
gBlocklistManager.onApplyChanges();
}
},
onLoad: function () {
this._bundle = document.getElementById("bundlePreferences");
let params = window.arguments[0];
this.init(params);
},
init: function (params) {
if (this._type) {
// reusing an open dialog, clear the old observer
this.uninit();
}
this._type = "tracking";
this._brandShortName = params.brandShortName;
let blocklistsText = document.getElementById("blocklistsText");
while (blocklistsText.hasChildNodes()) {
blocklistsText.removeChild(blocklistsText.firstChild);
}
blocklistsText.appendChild(document.createTextNode(params.introText));
document.title = params.windowTitle;
let treecols = document.getElementsByTagName("treecols")[0];
treecols.addEventListener("click", event => {
if (event.target.nodeName != "treecol" || event.button != 0) {
return;
}
});
this._loadBlockLists();
},
uninit: function () {},
onListSelected: function () {
for (let list of this._blockLists) {
list.selected = false;
}
this._blockLists[this._tree.currentIndex].selected = true;
this._updateTree();
},
onApplyChanges: function () {
let activeList = this._getActiveList();
let selected = null;
for (let list of this._blockLists) {
if (list.selected) {
selected = list;
break;
}
}
if (activeList !== selected.id) {
const Cc = Components.classes, Ci = Components.interfaces;
let msg = this._bundle.getFormattedString("blocklistChangeRequiresRestart",
[this._brandShortName]);
let title = this._bundle.getFormattedString("shouldRestartTitle",
[this._brandShortName]);
let shouldProceed = Services.prompt.confirm(window, title, msg);
if (shouldProceed) {
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
.createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
"restart");
shouldProceed = !cancelQuit.data;
if (shouldProceed) {
let trackingTable = TEST_LIST + "," + selected.id + TRACK_SUFFIX;
Services.prefs.setCharPref(TRACKING_TABLE_PREF, trackingTable);
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |
Ci.nsIAppStartup.eRestart);
}
}
// Don't close the dialog in case we didn't quit.
return;
}
window.close();
},
_loadBlockLists: function () {
this._blockLists = [];
// Load blocklists into a table.
let branch = Services.prefs.getBranch(LISTS_PREF_BRANCH);
let itemArray = branch.getChildList("");
for (let itemName of itemArray) {
try {
this._createOrUpdateBlockList(itemName);
} catch (e) {
// Ignore bogus or missing list name.
continue;
}
}
this._updateTree();
},
_createOrUpdateBlockList: function (itemName) {
let branch = Services.prefs.getBranch(LISTS_PREF_BRANCH);
let key = branch.getCharPref(itemName);
let value = this._bundle.getString(key);
let suffix = itemName.slice(itemName.lastIndexOf("."));
let id = itemName.replace(suffix, "");
let list = this._blockLists.find(el => el.id === id);
if (!list) {
list = { id };
this._blockLists.push(list);
}
list.selected = this._getActiveList() === id;
// Get the property name from the suffix (e.g. ".name" -> "name").
let prop = suffix.slice(1);
list[prop] = value;
return list;
},
_updateTree: function () {
this._tree = document.getElementById("blocklistsTree");
this._view._rowCount = this._blockLists.length;
this._tree.view = this._view;
},
_getActiveList: function () {
let activeList = Services.prefs.getCharPref(TRACKING_TABLE_PREF);
activeList = activeList.replace(TEST_LIST, "");
activeList = activeList.replace(",", "");
activeList = activeList.replace(TRACK_SUFFIX, "");
return activeList.trim();
}
};
function initWithParams(params) {
gBlocklistManager.init(params);
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/blocklists.dtd" >
<window id="BlocklistsDialog" class="windowDialog"
windowtype="Browser:Blocklists"
title="&window.title;"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="width: &window.width;;"
onload="gBlocklistManager.onLoad();"
onunload="gBlocklistManager.uninit();"
persist="screenX screenY width height"
onkeypress="gBlocklistManager.onWindowKeyPress(event);">
<script src="chrome://global/content/treeUtils.js"/>
<script src="chrome://browser/content/preferences/blocklists.js"/>
<stringbundle id="bundlePreferences"
src="chrome://browser/locale/preferences/preferences.properties"/>
<keyset>
<key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
</keyset>
<vbox class="contentPane largeDialogContainer" flex="1">
<description id="blocklistsText" control="url"/>
<separator class="thin"/>
<tree id="blocklistsTree" flex="1" style="height: 18em;"
hidecolumnpicker="true"
onselect="gBlocklistManager.onListSelected();">
<treecols>
<treecol id="selectionCol" label="" flex="1" sortable="false"
type="checkbox"/>
<treecol id="listCol" label="&treehead.list.label;" flex="80"
sortable="false"/>
</treecols>
<treechildren/>
</tree>
</vbox>
<vbox>
<spacer flex="1"/>
<hbox class="actionButtons" align="right" flex="1">
<button oncommand="close();" icon="close"
label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
<button id="btnApplyChanges" oncommand="gBlocklistManager.onApplyChanges();" icon="save"
label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
</hbox>
</vbox>
</window>

View File

@ -116,6 +116,8 @@ var gPrivacyPane = {
gPrivacyPane.showCookies);
setEventListener("clearDataSettings", "command",
gPrivacyPane.showClearPrivateDataSettings);
setEventListener("changeBlockList", "command",
gPrivacyPane.showBlockLists);
},
// HISTORY MODE
@ -365,6 +367,21 @@ var gPrivacyPane = {
this._shouldPromptForRestart = true;
},
/**
* Displays the available block lists for tracking protection.
*/
showBlockLists: function ()
{
var bundlePreferences = document.getElementById("bundlePreferences");
let brandName = document.getElementById("bundleBrand")
.getString("brandShortName");
var params = { brandShortName: brandName,
windowTitle: bundlePreferences.getString("blockliststitle"),
introText: bundlePreferences.getString("blockliststext") };
gSubDialog.open("chrome://browser/content/preferences/blocklists.xul",
null, params);
},
// HISTORY
/*

View File

@ -27,6 +27,9 @@
<preference id="pref.privacy.disable_button.view_cookies"
name="pref.privacy.disable_button.view_cookies"
type="bool"/>
<preference id="pref.privacy.disable_button.change_blocklist"
name="pref.privacy.disable_button.change_blocklist"
type="bool"/>
<!-- Location Bar -->
<preference id="browser.urlbar.autocomplete.enabled"
@ -81,7 +84,7 @@
</hbox>
<!-- Tracking -->
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
<caption><label>&tracking.label;</label></caption>
<vbox>
<hbox align="center">
@ -116,6 +119,10 @@
<label id="trackingProtectionPBMLearnMore"
class="text-link"
value="&trackingProtectionPBMLearnMore.label;"/>
<spacer flex="1" />
<button id="changeBlockList"
label="&changeBlockList.label;" accesskey="&changeBlockList.accesskey;"
preference="pref.privacy.disable_button.change_blocklist"/>
</hbox>
</vbox>
</groupbox>

View File

@ -9,6 +9,8 @@ browser.jar:
content/browser/preferences/aboutPermissions.xml
content/browser/preferences/applicationManager.xul
* content/browser/preferences/applicationManager.js
content/browser/preferences/blocklists.xul
content/browser/preferences/blocklists.js
* content/browser/preferences/colors.xul
* content/browser/preferences/cookies.xul
* content/browser/preferences/cookies.js

View File

@ -1046,7 +1046,7 @@
class="search-panel-one-offs"/>
<xul:vbox anonid="add-engines"/>
<xul:button anonid="search-settings"
oncommand="BrowserUITelemetry.countSearchSettingsEvent('searchbar');openPreferences('paneSearch')"
oncommand="showSettings();"
class="search-setting-button search-panel-header"
label="&changeSearchSettings.button;"/>
</content>
@ -1091,6 +1091,16 @@
.engine = currentEngine;
]]></body>
</method>
<method name="showSettings">
<body><![CDATA[
BrowserUITelemetry.countSearchSettingsEvent("searchbar");
openPreferences("paneSearch");
// If the preference tab was already selected, the panel doesn't
// close itself automatically.
BrowserSearch.searchBar._textbox.closePopup();
]]></body>
</method>
</implementation>
<handlers>
<handler event="popuphidden"><![CDATA[

View File

@ -220,12 +220,6 @@ this.UITour = {
query: "#searchbar",
widgetName: "search-container",
}],
["searchProvider", {
query: (aDocument) => {
return null;
},
widgetName: "search-container",
}],
["searchIcon", {
query: (aDocument) => {
let searchbar = aDocument.getElementById("searchbar");
@ -987,11 +981,6 @@ this.UITour = {
return deferred.promise;
}
if (aTargetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
let engineID = aTargetName.slice(TARGET_SEARCHENGINE_PREFIX.length);
return this.getSearchEngineTarget(aWindow, engineID);
}
let targetObject = this.targets.get(aTargetName);
if (!targetObject) {
log.warn("getTarget: The specified target name is not in the allowed set");
@ -1331,18 +1320,6 @@ this.UITour = {
*/
showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
function showHighlightPanel() {
if (aTarget.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
// This won't affect normal higlights done via the panel, so we need to
// manually hide those.
this.hideHighlight(aChromeWindow);
aTarget.node.setAttribute("_moz-menuactive", true);
return;
}
// Conversely, highlights for search engines are highlighted via CSS
// rather than a panel, so need to be manually removed.
this._hideSearchEngineHighlight(aChromeWindow);
let highlighter = aChromeWindow.document.getElementById("UITourHighlight");
let effect = aEffect;
@ -1419,24 +1396,6 @@ this.UITour = {
highlighter.removeAttribute("active");
this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
this._hideSearchEngineHighlight(aWindow);
},
_hideSearchEngineHighlight: function(aWindow) {
// We special case highlighting items in the search engines dropdown,
// so just blindly remove any highlight there.
let searchMenuBtn = null;
try {
searchMenuBtn = this.targets.get("searchProvider").query(aWindow.document);
} catch (e) { /* This is ok to fail. */ }
if (searchMenuBtn) {
let searchPopup = aWindow.document
.getAnonymousElementByAttribute(searchMenuBtn,
"anonid",
"searchbar-popup");
for (let menuItem of searchPopup.children)
menuItem.removeAttribute("_moz-menuactive");
}
},
/**
@ -1558,11 +1517,6 @@ this.UITour = {
return;
}
// Due to a platform limitation, we can't anchor a panel to an element in a
// <menupopup>. So we can't support showing info panels for search engines.
if (aAnchor.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX))
return;
this._setAppMenuStateForAnnotation(aChromeWindow, "info",
this.targetIsInAppMenu(aAnchor),
showInfoPanel.bind(this, this._correctAnchor(aAnchor.node)));
@ -1670,10 +1624,6 @@ this.UITour = {
});
panel.addEventListener("popuphidden", this.onPanelHidden);
panel.addEventListener("popuphiding", this.hideLoopPanelAnnotations);
} else if (aMenuName == "searchEngines") {
this.getTarget(aWindow, "searchProvider").then(target => {
openMenuButton(target.node);
}).catch(log.error);
} else if (aMenuName == "pocket") {
this.getTarget(aWindow, "pocket").then(Task.async(function* onPocketTarget(target) {
let widgetGroupWrapper = CustomizableUI.getWidget(target.widgetName);
@ -1735,9 +1685,6 @@ this.UITour = {
} else if (aMenuName == "loop") {
let panel = aWindow.document.getElementById("loop-notification-panel");
panel.hidePopup();
} else if (aMenuName == "searchEngines") {
let menuBtn = this.targets.get("searchProvider").query(aWindow.document);
closeMenuButton(menuBtn);
}
},
@ -1882,17 +1829,22 @@ this.UITour = {
gettingStartedSeen: Services.prefs.getBoolPref("loop.gettingStarted.seen"),
});
break;
case "search":
case "selectedSearchEngine":
Services.search.init(rv => {
let engine;
let data;
if (Components.isSuccessCode(rv)) {
engine = Services.search.defaultEngine;
let engines = Services.search.getVisibleEngines();
data = {
searchEngineIdentifier: Services.search.defaultEngine.identifier,
engines: [TARGET_SEARCHENGINE_PREFIX + engine.identifier
for (engine of engines)
if (engine.identifier)]
};
} else {
engine = { identifier: "" };
data = {engines: [], searchEngineIdentifier: ""};
}
this.sendPageCallback(aMessageManager, aCallbackID, {
searchEngineIdentifier: engine.identifier
});
this.sendPageCallback(aMessageManager, aCallbackID, data);
});
break;
case "sync":
@ -1950,10 +1902,6 @@ this.UITour = {
targetNames.push(targetObject.targetName);
}
targetNames = targetNames.concat(
yield this.getAvailableSearchEngineTargets(window)
);
data = {
targets: targetNames,
};
@ -2060,55 +2008,6 @@ this.UITour = {
});
},
getAvailableSearchEngineTargets(aWindow) {
return new Promise(resolve => {
this.getTarget(aWindow, "search").then(searchTarget => {
if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
return resolve([]);
Services.search.init(() => {
let engines = Services.search.getVisibleEngines();
resolve([TARGET_SEARCHENGINE_PREFIX + engine.identifier
for (engine of engines)
if (engine.identifier)]);
});
}).catch(() => resolve([]));
});
},
// We only allow matching based on a search engine's identifier - this gives
// us a non-changing ID and guarentees we only match against app-provided
// engines.
getSearchEngineTarget(aWindow, aIdentifier) {
return new Promise((resolve, reject) => {
Task.spawn(function*() {
let searchTarget = yield this.getTarget(aWindow, "search");
// We're not supporting having the searchbar in the app-menu, because
// popups within popups gets crazy. This restriction should be lifted
// once bug 988151 is implemented, as the page can then be responsible
// for opening each menu when appropriate.
if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
return reject("Search engine not available");
yield Services.search.init();
let searchPopup = searchTarget.node._popup;
for (let engineNode of searchPopup.children) {
let engine = engineNode.engine;
if (engine && engine.identifier == aIdentifier) {
return resolve({
targetName: TARGET_SEARCHENGINE_PREFIX + engine.identifier,
node: engineNode,
});
}
}
reject("Search engine not available");
}.bind(this)).catch(() => {
reject("Search engine not available");
});
});
},
notify(eventName, params) {
let winEnum = Services.wm.getEnumerator("navigator:browser");
while (winEnum.hasMoreElements()) {

View File

@ -216,56 +216,6 @@ var tests = [
gContentAPI.showHighlight("urlbar");
waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
},
function test_highlight_search_engine(done) {
let highlight = document.getElementById("UITourHighlight");
gContentAPI.showHighlight("urlbar");
waitForElementToBeVisible(highlight, () => {
let searchbar = document.getElementById("searchbar");
done();
return; // The oneoffui removes the menu that's being tested here.
gContentAPI.showMenu("searchEngines", function() {
isnot(searchbar, null, "Should have found searchbar");
let searchPopup = document.getAnonymousElementByAttribute(searchbar,
"anonid",
"searchbar-popup");
isnot(searchPopup, null, "Should have found search popup");
function getEngineNode(identifier) {
let engineNode = null;
for (let node of searchPopup.children) {
if (node.engine.identifier == identifier) {
engineNode = node;
break;
}
}
isnot(engineNode, null, "Should have found search engine node in popup");
return engineNode;
}
let googleEngineNode = getEngineNode("google");
let bingEngineNode = getEngineNode("bing");
gContentAPI.showHighlight("searchEngine-google");
waitForCondition(() => googleEngineNode.getAttribute("_moz-menuactive") == "true", function() {
is_element_hidden(highlight, "Highlight panel should be hidden by highlighting search engine");
gContentAPI.showHighlight("searchEngine-bing");
waitForCondition(() => bingEngineNode.getAttribute("_moz-menuactive") == "true", function() {
isnot(googleEngineNode.getAttribute("_moz-menuactive"), "true", "Previous engine should no longer be highlighted");
gContentAPI.hideHighlight();
waitForCondition(() => bingEngineNode.getAttribute("_moz-menuactive") != "true", function() {
gContentAPI.hideMenu("searchEngines");
waitForCondition(() => searchPopup.state == "closed", function() {
done();
}, "Search dropdown should close");
}, "Menu item should get attribute removed");
}, "Menu item should get attribute to make it look active");
});
});
});
},
function test_highlight_effect_unsupported(done) {
function checkUnsupportedEffect() {
is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
@ -367,7 +317,7 @@ var tests = [
});
});
},
function test_select_search_engine(done) {
function test_search(done) {
Services.search.init(rv => {
if (!Components.isSuccessCode(rv)) {
ok(false, "search service init failed: " + rv);
@ -375,9 +325,21 @@ var tests = [
return;
}
let defaultEngine = Services.search.defaultEngine;
gContentAPI.getConfiguration("availableTargets", data => {
let searchEngines = data.targets.filter(t => t.startsWith("searchEngine-"));
let someOtherEngineID = searchEngines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
gContentAPI.getConfiguration("search", data => {
let visibleEngines = Services.search.getVisibleEngines();
let expectedEngines = ["searchEngine-" + engine.identifier
for (engine of visibleEngines)
if (engine.identifier)];
let engines = data.engines;
ok(Array.isArray(engines), "data.engines should be an array");
is(engines.sort().toString(), expectedEngines.sort().toString(),
"Engines should be as expected");
is(data.searchEngineIdentifier, defaultEngine.identifier,
"the searchEngineIdentifier property should contain the defaultEngine's identifier");
let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
let observe = function (subject, topic, verb) {

View File

@ -34,13 +34,6 @@ function test() {
UITourTest();
}
function searchEngineTargets() {
let engines = Services.search.getVisibleEngines();
return ["searchEngine-" + engine.identifier
for (engine of engines)
if (engine.identifier)];
}
var tests = [
function test_availableTargets(done) {
gContentAPI.getConfiguration("availableTargets", (data) => {
@ -63,7 +56,6 @@ var tests = [
"searchIcon",
"trackingProtection",
"urlbar",
...searchEngineTargets(),
...(hasWebIDE ? ["webide"] : [])
]);
@ -96,7 +88,6 @@ var tests = [
"searchIcon",
"trackingProtection",
"urlbar",
...searchEngineTargets(),
...(hasWebIDE ? ["webide"] : [])
]);
@ -114,7 +105,7 @@ var tests = [
// Make sure the callback still fires with the other available targets.
CustomizableUI.removeWidgetFromArea("search-container");
gContentAPI.getConfiguration("availableTargets", (data) => {
// Default minus "search" and "searchProvider" and "searchIcon"
// Default minus "search" and "searchIcon"
ok_targets(data, [
"accountStatus",
"addons",

View File

@ -23,7 +23,6 @@ loader.lazyRequireGetter(this, "AnimationsFront",
const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const V3_UI_PREF = "devtools.inspector.animationInspectorV3";
// Global toolbox/inspector, set when startup is called.
var gToolbox, gInspector;
@ -80,6 +79,8 @@ var getServerTraits = Task.async(function*(target) {
let config = [
{ name: "hasToggleAll", actor: "animations",
method: "toggleAll" },
{ name: "hasToggleSeveral", actor: "animations",
method: "toggleSeveral" },
{ name: "hasSetCurrentTime", actor: "animationplayer",
method: "setCurrentTime" },
{ name: "hasMutationEvents", actor: "animations",
@ -97,9 +98,6 @@ var getServerTraits = Task.async(function*(target) {
traits[name] = yield target.actorHasMethod(actor, method);
}
// Special pref-based UI trait.
traits.isNewUI = Services.prefs.getBoolPref(V3_UI_PREF);
return traits;
});
@ -111,9 +109,6 @@ var getServerTraits = Task.async(function*(target) {
*
* AnimationPlayerFronts are available in AnimationsController.animationPlayers.
*
* Note also that all AnimationPlayerFronts handled by the controller are set to
* auto-refresh (except when the sidebar panel is not visible).
*
* Usage example:
*
* AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
@ -180,7 +175,7 @@ var AnimationsController = {
startListeners: function() {
// Re-create the list of players when a new node is selected, except if the
// sidebar isn't visible. And set the players to auto-refresh when needed.
// sidebar isn't visible.
gInspector.selection.on("new-node-front", this.onNewNodeFront);
gInspector.sidebar.on("select", this.onPanelVisibilityChange);
gToolbox.on("select", this.onPanelVisibilityChange);
@ -204,9 +199,6 @@ var AnimationsController = {
onPanelVisibilityChange: Task.async(function*() {
if (this.isPanelVisible()) {
this.onNewNodeFront();
this.startAllAutoRefresh();
} else {
this.stopAllAutoRefresh();
}
}),
@ -245,11 +237,32 @@ var AnimationsController = {
return this.animationsFront.toggleAll().catch(e => console.error(e));
},
/**
* Similar to toggleAll except that it only plays/pauses the currently known
* animations (those listed in this.animationPlayers).
* @param {Boolean} shouldPause True if the animations should be paused, false
* if they should be played.
* @return {Promise} Resolves when the playState has been changed.
*/
toggleCurrentAnimations: Task.async(function*(shouldPause) {
if (this.traits.hasToggleSeveral) {
yield this.animationsFront.toggleSeveral(this.animationPlayers,
shouldPause);
} else {
// Fall back to pausing/playing the players one by one, which is bound to
// introduce some de-synchronization.
for (let player of this.animationPlayers) {
if (shouldPause) {
yield player.pause();
} else {
yield player.play();
}
}
}
}),
/**
* Set all known animations' currentTimes to the provided time.
* Note that depending on the server's capabilities, this might resolve in
* either one packet, or as many packets as there are animations. In the
* latter case, some time deltas might be introduced.
* @param {Number} time.
* @param {Boolean} shouldPause Should the animations be paused too.
* @return {Promise} Resolves when the current time has been set.
@ -259,6 +272,8 @@ var AnimationsController = {
yield this.animationsFront.setCurrentTimes(this.animationPlayers, time,
shouldPause);
} else {
// Fall back to pausing and setting the current time on each player, one
// by one, which is bound to introduce some de-synchronization.
for (let animation of this.animationPlayers) {
if (shouldPause) {
yield animation.pause();
@ -279,7 +294,6 @@ var AnimationsController = {
this.animationPlayers = yield this.animationsFront
.getAnimationPlayersForNode(nodeFront);
this.startAllAutoRefresh();
// Start listening for animation mutations only after the first method call
// otherwise events won't be sent.
@ -295,15 +309,9 @@ var AnimationsController = {
for (let {type, player} of changes) {
if (type === "added") {
this.animationPlayers.push(player);
if (!this.traits.isNewUI) {
player.startAutoRefresh();
}
}
if (type === "removed") {
if (!this.traits.isNewUI) {
player.stopAutoRefresh();
}
yield player.release();
let index = this.animationPlayers.indexOf(player);
this.animationPlayers.splice(index, 1);
@ -333,26 +341,6 @@ var AnimationsController = {
return time;
},
startAllAutoRefresh: function() {
if (this.traits.isNewUI) {
return;
}
for (let front of this.animationPlayers) {
front.startAutoRefresh();
}
},
stopAllAutoRefresh: function() {
if (this.traits.isNewUI) {
return;
}
for (let front of this.animationPlayers) {
front.stopAutoRefresh();
}
},
destroyAnimationPlayers: Task.async(function*() {
// Let the server know that we're not interested in receiving updates about
// players for the current node. We're either being destroyed or a new node
@ -360,7 +348,7 @@ var AnimationsController = {
if (this.traits.hasMutationEvents) {
yield this.animationsFront.stopAnimationPlayerUpdates();
}
this.stopAllAutoRefresh();
for (let front of this.animationPlayers) {
yield front.release();
}

View File

@ -14,10 +14,13 @@
<link rel="stylesheet" href="chrome://browser/skin/devtools/animationinspector.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
</head>
<body class="theme-sidebar devtools-monospace" role="application">
<div id="toolbar" class="theme-toolbar">
<body class="theme-sidebar devtools-monospace" role="application" empty="true">
<div id="global-toolbar" class="theme-toolbar">
<span class="label">&allAnimations;</span>
<button id="toggle-all" standalone="true" class="devtools-button"></button>
<button id="toggle-all" standalone="true" class="devtools-button pause-button"></button>
</div>
<div id="timeline-toolbar" class="theme-toolbar">
<button id="pause-resume-timeline" standalone="true" class="devtools-button pause-button paused"></button>
</div>
<div id="players"></div>
<div id="error-message">

View File

@ -8,13 +8,7 @@
"use strict";
const {createNode} = require("devtools/animationinspector/utils");
const {
PlayerMetaDataHeader,
PlaybackRateSelector,
AnimationTargetNode,
AnimationsTimeline
} = require("devtools/animationinspector/components");
const {AnimationsTimeline} = require("devtools/animationinspector/components");
/**
* The main animations panel UI.
@ -39,26 +33,25 @@ var AnimationsPanel = {
this.errorMessageEl = document.querySelector("#error-message");
this.pickerButtonEl = document.querySelector("#element-picker");
this.toggleAllButtonEl = document.querySelector("#toggle-all");
this.playTimelineButtonEl = document.querySelector("#pause-resume-timeline");
// If the server doesn't support toggling all animations at once, hide the
// whole bottom toolbar.
// whole global toolbar.
if (!AnimationsController.traits.hasToggleAll) {
document.querySelector("#toolbar").style.display = "none";
document.querySelector("#global-toolbar").style.display = "none";
}
// Binding functions that need to be called in scope.
for (let functionName of ["onPickerStarted", "onPickerStopped",
"refreshAnimations", "toggleAll", "onTabNavigated",
"onTimelineDataChanged", "playPauseTimeline"]) {
this[functionName] = this[functionName].bind(this);
}
let hUtils = gToolbox.highlighterUtils;
this.togglePicker = hUtils.togglePicker.bind(hUtils);
this.onPickerStarted = this.onPickerStarted.bind(this);
this.onPickerStopped = this.onPickerStopped.bind(this);
this.refreshAnimations = this.refreshAnimations.bind(this);
this.toggleAll = this.toggleAll.bind(this);
this.onTabNavigated = this.onTabNavigated.bind(this);
this.onTimelineTimeChanged = this.onTimelineTimeChanged.bind(this);
if (AnimationsController.traits.isNewUI) {
this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
this.animationsTimelineComponent.init(this.playersEl);
}
this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
this.animationsTimelineComponent.init(this.playersEl);
this.startListeners();
@ -82,14 +75,14 @@ var AnimationsPanel = {
this.stopListeners();
if (this.animationsTimelineComponent) {
this.animationsTimelineComponent.destroy();
this.animationsTimelineComponent = null;
}
this.animationsTimelineComponent.destroy();
this.animationsTimelineComponent = null;
yield this.destroyPlayerWidgets();
this.playersEl = this.errorMessageEl = null;
this.toggleAllButtonEl = this.pickerButtonEl = null;
this.playTimelineButtonEl = null;
this.destroyed.resolve();
}),
@ -98,46 +91,44 @@ var AnimationsPanel = {
AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
this.refreshAnimations);
this.pickerButtonEl.addEventListener("click", this.togglePicker, false);
this.pickerButtonEl.addEventListener("click", this.togglePicker);
gToolbox.on("picker-started", this.onPickerStarted);
gToolbox.on("picker-stopped", this.onPickerStopped);
this.toggleAllButtonEl.addEventListener("click", this.toggleAll, false);
this.toggleAllButtonEl.addEventListener("click", this.toggleAll);
this.playTimelineButtonEl.addEventListener("click", this.playPauseTimeline);
gToolbox.target.on("navigate", this.onTabNavigated);
if (this.animationsTimelineComponent) {
this.animationsTimelineComponent.on("current-time-changed",
this.onTimelineTimeChanged);
}
this.animationsTimelineComponent.on("timeline-data-changed",
this.onTimelineDataChanged);
},
stopListeners: function() {
AnimationsController.off(AnimationsController.PLAYERS_UPDATED_EVENT,
this.refreshAnimations);
this.pickerButtonEl.removeEventListener("click", this.togglePicker, false);
this.pickerButtonEl.removeEventListener("click", this.togglePicker);
gToolbox.off("picker-started", this.onPickerStarted);
gToolbox.off("picker-stopped", this.onPickerStopped);
this.toggleAllButtonEl.removeEventListener("click", this.toggleAll, false);
this.toggleAllButtonEl.removeEventListener("click", this.toggleAll);
this.playTimelineButtonEl.removeEventListener("click", this.playPauseTimeline);
gToolbox.target.off("navigate", this.onTabNavigated);
if (this.animationsTimelineComponent) {
this.animationsTimelineComponent.off("current-time-changed",
this.onTimelineTimeChanged);
this.animationsTimelineComponent.off("timeline-data-changed",
this.onTimelineDataChanged);
},
togglePlayers: function(isVisible) {
if (isVisible) {
document.body.removeAttribute("empty");
document.body.setAttribute("timeline", "true");
} else {
document.body.setAttribute("empty", "true");
document.body.removeAttribute("timeline");
}
},
displayErrorMessage: function() {
this.errorMessageEl.style.display = "block";
this.playersEl.style.display = "none";
},
hideErrorMessage: function() {
this.errorMessageEl.style.display = "none";
this.playersEl.style.display = "block";
},
onPickerStarted: function() {
this.pickerButtonEl.setAttribute("checked", "true");
},
@ -147,74 +138,71 @@ var AnimationsPanel = {
},
toggleAll: Task.async(function*() {
let btnClass = this.toggleAllButtonEl.classList;
if (!AnimationsController.traits.isNewUI) {
// Toggling all animations is async and it may be some time before each of
// the current players get their states updated, so toggle locally too, to
// avoid the timelines from jumping back and forth.
if (this.playerWidgets) {
let currentWidgetStateChange = [];
for (let widget of this.playerWidgets) {
currentWidgetStateChange.push(btnClass.contains("paused")
? widget.play() : widget.pause());
}
yield promise.all(currentWidgetStateChange)
.catch(error => console.error(error));
}
}
btnClass.toggle("paused");
this.toggleAllButtonEl.classList.toggle("paused");
yield AnimationsController.toggleAll();
}),
/**
* Depending on the state of the timeline either pause or play the animations
* displayed in it.
* If the animations are finished, this will play them from the start again.
* If the animations are playing, this will pause them.
* If the animations are paused, this will resume them.
*/
playPauseTimeline: Task.async(function*() {
yield AnimationsController.toggleCurrentAnimations(this.timelineData.isMoving);
// Now that the playState have been changed make sure the player (the
// fronts) are up to date, and then refresh the UI.
for (let player of AnimationsController.animationPlayers) {
yield player.refreshState();
}
yield this.refreshAnimations();
}),
onTabNavigated: function() {
this.toggleAllButtonEl.classList.remove("paused");
},
onTimelineTimeChanged: function(e, time) {
AnimationsController.setCurrentTimeAll(time, true)
.catch(error => console.error(error));
onTimelineDataChanged: function(e, data) {
this.timelineData = data;
let {isPaused, isMoving, time} = data;
this.playTimelineButtonEl.classList.toggle("paused", !isMoving);
// Pause all animations and set their currentTimes (but only do this after
// the previous currentTime setting is done, as this gets called many times
// when users drag the scrubber with the mouse, and we want the server-side
// requests to be sequenced).
if (isPaused && !this.setCurrentTimeAllPromise) {
this.setCurrentTimeAllPromise =
AnimationsController.setCurrentTimeAll(time, true)
.catch(error => console.error(error))
.then(() => this.setCurrentTimeAllPromise = null);
}
},
refreshAnimations: Task.async(function*() {
let done = gInspector.updating("animationspanel");
// Empty the whole panel first.
this.hideErrorMessage();
this.togglePlayers(true);
yield this.destroyPlayerWidgets();
// Re-render the timeline component.
if (this.animationsTimelineComponent) {
this.animationsTimelineComponent.render(
AnimationsController.animationPlayers,
AnimationsController.documentCurrentTime);
}
this.animationsTimelineComponent.render(
AnimationsController.animationPlayers,
AnimationsController.documentCurrentTime);
// If there are no players to show, show the error message instead and
// return.
if (!AnimationsController.animationPlayers.length) {
this.displayErrorMessage();
this.togglePlayers(false);
this.emit(this.UI_UPDATED_EVENT);
done();
return;
}
// Otherwise, create player widgets (only when isNewUI is false, the
// timeline has already been re-rendered).
if (!AnimationsController.traits.isNewUI) {
this.playerWidgets = [];
let initPromises = [];
for (let player of AnimationsController.animationPlayers) {
let widget = new PlayerWidget(player, this.playersEl);
initPromises.push(widget.initialize());
this.playerWidgets.push(widget);
}
yield initPromises;
}
this.emit(this.UI_UPDATED_EVENT);
done();
}),
@ -232,407 +220,3 @@ var AnimationsPanel = {
};
EventEmitter.decorate(AnimationsPanel);
/**
* An AnimationPlayer UI widget
*/
function PlayerWidget(player, containerEl) {
EventEmitter.decorate(this);
this.player = player;
this.containerEl = containerEl;
this.onStateChanged = this.onStateChanged.bind(this);
this.onPlayPauseBtnClick = this.onPlayPauseBtnClick.bind(this);
this.onRewindBtnClick = this.onRewindBtnClick.bind(this);
this.onFastForwardBtnClick = this.onFastForwardBtnClick.bind(this);
this.onCurrentTimeChanged = this.onCurrentTimeChanged.bind(this);
this.onPlaybackRateChanged = this.onPlaybackRateChanged.bind(this);
this.metaDataComponent = new PlayerMetaDataHeader();
if (AnimationsController.traits.hasSetPlaybackRate) {
this.rateComponent = new PlaybackRateSelector();
}
if (AnimationsController.traits.hasTargetNode) {
this.targetNodeComponent = new AnimationTargetNode(gInspector);
}
}
PlayerWidget.prototype = {
initialize: Task.async(function*() {
if (this.initialized) {
return;
}
this.initialized = true;
this.createMarkup();
this.startListeners();
}),
destroy: Task.async(function*() {
if (this.destroyed) {
return;
}
this.destroyed = true;
this.stopTimelineAnimation();
this.stopListeners();
this.metaDataComponent.destroy();
if (this.rateComponent) {
this.rateComponent.destroy();
}
if (this.targetNodeComponent) {
this.targetNodeComponent.destroy();
}
this.el.remove();
this.playPauseBtnEl = this.rewindBtnEl = this.fastForwardBtnEl = null;
this.currentTimeEl = this.timeDisplayEl = null;
this.containerEl = this.el = this.player = null;
}),
startListeners: function() {
this.player.on(this.player.AUTO_REFRESH_EVENT, this.onStateChanged);
this.playPauseBtnEl.addEventListener("click", this.onPlayPauseBtnClick);
if (AnimationsController.traits.hasSetCurrentTime) {
this.rewindBtnEl.addEventListener("click", this.onRewindBtnClick);
this.fastForwardBtnEl.addEventListener("click", this.onFastForwardBtnClick);
this.currentTimeEl.addEventListener("input", this.onCurrentTimeChanged);
}
if (this.rateComponent) {
this.rateComponent.on("rate-changed", this.onPlaybackRateChanged);
}
},
stopListeners: function() {
this.player.off(this.player.AUTO_REFRESH_EVENT, this.onStateChanged);
this.playPauseBtnEl.removeEventListener("click", this.onPlayPauseBtnClick);
if (AnimationsController.traits.hasSetCurrentTime) {
this.rewindBtnEl.removeEventListener("click", this.onRewindBtnClick);
this.fastForwardBtnEl.removeEventListener("click", this.onFastForwardBtnClick);
this.currentTimeEl.removeEventListener("input", this.onCurrentTimeChanged);
}
if (this.rateComponent) {
this.rateComponent.off("rate-changed", this.onPlaybackRateChanged);
}
},
createMarkup: function() {
let state = this.player.state;
this.el = createNode({
parent: this.containerEl,
attributes: {
"class": "player-widget " + state.playState
}
});
if (this.targetNodeComponent) {
this.targetNodeComponent.init(this.el);
this.targetNodeComponent.render(this.player);
}
this.metaDataComponent.init(this.el);
this.metaDataComponent.render(state);
// Timeline widget.
let timelineEl = createNode({
parent: this.el,
attributes: {
"class": "timeline"
}
});
// Playback control buttons container.
let playbackControlsEl = createNode({
parent: timelineEl,
attributes: {
"class": "playback-controls"
}
});
// Control buttons.
this.playPauseBtnEl = createNode({
parent: playbackControlsEl,
nodeType: "button",
attributes: {
"class": "toggle devtools-button"
}
});
if (AnimationsController.traits.hasSetCurrentTime) {
this.rewindBtnEl = createNode({
parent: playbackControlsEl,
nodeType: "button",
attributes: {
"class": "rw devtools-button"
}
});
this.fastForwardBtnEl = createNode({
parent: playbackControlsEl,
nodeType: "button",
attributes: {
"class": "ff devtools-button"
}
});
}
if (this.rateComponent) {
this.rateComponent.init(playbackControlsEl);
this.rateComponent.render(state);
}
// Sliders container.
let slidersContainerEl = createNode({
parent: timelineEl,
attributes: {
"class": "sliders-container",
}
});
let max = state.duration;
if (state.iterationCount) {
// If there's a finite nb of iterations.
max = state.iterationCount * state.duration;
}
// For now, keyframes aren't exposed by the actor. So the only range <input>
// displayed in the container is the currentTime. When keyframes are
// available, one input per keyframe can be added here.
this.currentTimeEl = createNode({
nodeType: "input",
parent: slidersContainerEl,
attributes: {
"type": "range",
"class": "current-time",
"min": "0",
"max": max,
"step": "10",
"value": "0"
}
});
if (!AnimationsController.traits.hasSetCurrentTime) {
this.currentTimeEl.setAttribute("disabled", "true");
}
// Time display
this.timeDisplayEl = createNode({
parent: timelineEl,
attributes: {
"class": "time-display"
}
});
// Show the initial time.
this.displayTime(state.currentTime);
},
/**
* Executed when the playPause button is clicked.
* Note that tests may want to call this callback directly rather than
* simulating a click on the button since it returns the promise returned by
* play and paused.
* @return {Promise}
*/
onPlayPauseBtnClick: function() {
if (this.player.state.playState === "running") {
return this.pause();
}
return this.play();
},
onRewindBtnClick: function() {
this.setCurrentTime(0, true);
},
onFastForwardBtnClick: function() {
let state = this.player.state;
let time = state.duration;
if (state.iterationCount) {
time = state.iterationCount * state.duration;
}
this.setCurrentTime(time, true);
},
/**
* Executed when the current-time range input is changed.
*/
onCurrentTimeChanged: function(e) {
let time = e.target.value;
this.setCurrentTime(parseFloat(time), true);
},
/**
* Executed when the playback rate dropdown value changes in the playbackrate
* component.
*/
onPlaybackRateChanged: function(e, rate) {
this.setPlaybackRate(rate);
},
/**
* Whenever a player state update is received.
*/
onStateChanged: function() {
let state = this.player.state;
this.updateWidgetState(state);
this.metaDataComponent.render(state);
if (this.rateComponent) {
this.rateComponent.render(state);
}
switch (state.playState) {
case "finished":
this.stopTimelineAnimation();
this.displayTime(this.player.state.currentTime);
break;
case "running":
this.startTimelineAnimation();
break;
case "paused":
this.stopTimelineAnimation();
this.displayTime(this.player.state.currentTime);
break;
case "idle":
this.stopTimelineAnimation();
this.displayTime(0);
break;
}
},
/**
* Set the current time of the animation.
* @param {Number} time.
* @param {Boolean} shouldPause Should the player be paused too.
* @return {Promise} Resolves when the current time has been set.
*/
setCurrentTime: Task.async(function*(time, shouldPause) {
if (!AnimationsController.traits.hasSetCurrentTime) {
throw new Error("This server version doesn't support setting " +
"animations' currentTime");
}
if (shouldPause) {
this.stopTimelineAnimation();
yield this.pause();
}
if (this.player.state.delay) {
time += this.player.state.delay;
}
// Set the time locally first so it feels instant, even if the request to
// actually set the time is async.
this.displayTime(time);
yield this.player.setCurrentTime(time);
}),
/**
* Set the playback rate of the animation.
* @param {Number} rate.
* @return {Promise} Resolves when the rate has been set.
*/
setPlaybackRate: function(rate) {
if (!AnimationsController.traits.hasSetPlaybackRate) {
throw new Error("This server version doesn't support setting " +
"animations' playbackRate");
}
return this.player.setPlaybackRate(rate);
},
/**
* Pause the animation player via this widget.
* @return {Promise} Resolves when the player is paused, the button is
* switched to the right state, and the timeline animation is stopped.
*/
pause: function() {
// Switch to the right className on the element right away to avoid waiting
// for the next state update to change the playPause icon.
this.updateWidgetState({playState: "paused"});
this.stopTimelineAnimation();
return this.player.pause();
},
/**
* Play the animation player via this widget.
* @return {Promise} Resolves when the player is playing, the button is
* switched to the right state, and the timeline animation is started.
*/
play: function() {
// Switch to the right className on the element right away to avoid waiting
// for the next state update to change the playPause icon.
this.updateWidgetState({playState: "running"});
this.startTimelineAnimation();
return this.player.play();
},
updateWidgetState: function({playState}) {
this.el.className = "player-widget " + playState;
},
/**
* Make the timeline progress smoothly, even though the currentTime is only
* updated at some intervals. This uses a local animation loop.
*/
startTimelineAnimation: function() {
this.stopTimelineAnimation();
let state = this.player.state;
let start = performance.now();
let loop = () => {
this.rafID = requestAnimationFrame(loop);
let delta = (performance.now() - start) * state.playbackRate;
let now = state.currentTime + delta;
this.displayTime(now);
};
loop();
},
/**
* Display the time in the timeDisplayEl and in the currentTimeEl slider.
*/
displayTime: function(time) {
let state = this.player.state;
// If the animation is delayed, don't start displaying the time until the
// delay has passed.
if (state.delay) {
time = Math.max(0, time - state.delay);
}
// For finite animations, make sure the displayed time does not go beyond
// the animation total duration (this may happen due to the local
// requestAnimationFrame loop).
if (state.iterationCount) {
time = Math.min(time, state.iterationCount * state.duration);
}
// Set the time label value.
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(time / 1000, 2));
// Set the timeline slider value.
if (!state.iterationCount && time !== state.duration) {
time = time % state.duration;
}
this.currentTimeEl.value = time;
},
/**
* Stop the animation loop that makes the timeline progress.
*/
stopTimelineAnimation: function() {
if (this.rafID) {
cancelAnimationFrame(this.rafID);
this.rafID = null;
}
}
};

View File

@ -35,268 +35,6 @@ const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
// The minimum spacing between 2 time graduation headers in the timeline (px).
const TIME_GRADUATION_MIN_SPACING = 40;
/**
* UI component responsible for displaying and updating the player meta-data:
* name, duration, iterations, delay.
* The parent UI component for this should drive its updates by calling
* render(state) whenever it wants the component to update.
*/
function PlayerMetaDataHeader() {
// Store the various state pieces we need to only refresh the UI when things
// change.
this.state = {};
}
exports.PlayerMetaDataHeader = PlayerMetaDataHeader;
PlayerMetaDataHeader.prototype = {
init: function(containerEl) {
// The main title element.
this.el = createNode({
parent: containerEl,
attributes: {
"class": "animation-title"
}
});
// Animation name.
this.nameLabel = createNode({
parent: this.el,
nodeType: "span"
});
this.nameValue = createNode({
parent: this.el,
nodeType: "strong",
attributes: {
"style": "display:none;"
}
});
// Animation duration, delay and iteration container.
let metaData = createNode({
parent: this.el,
nodeType: "span",
attributes: {
"class": "meta-data"
}
});
// Animation is running on compositor
this.compositorIcon = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"class": "compositor-icon",
"title": L10N.getStr("player.runningOnCompositorTooltip")
}
});
// Animation duration.
this.durationLabel = createNode({
parent: metaData,
nodeType: "span",
textContent: L10N.getStr("player.animationDurationLabel")
});
this.durationValue = createNode({
parent: metaData,
nodeType: "strong"
});
// Animation delay (hidden by default since there may not be a delay).
this.delayLabel = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"style": "display:none;"
},
textContent: L10N.getStr("player.animationDelayLabel")
});
this.delayValue = createNode({
parent: metaData,
nodeType: "strong"
});
// Animation iteration count (also hidden by default since we don't display
// single iterations).
this.iterationLabel = createNode({
parent: metaData,
nodeType: "span",
attributes: {
"style": "display:none;"
},
textContent: L10N.getStr("player.animationIterationCountLabel")
});
this.iterationValue = createNode({
parent: metaData,
nodeType: "strong",
attributes: {
"style": "display:none;"
}
});
},
destroy: function() {
this.state = null;
this.el.remove();
this.el = null;
this.nameLabel = this.nameValue = null;
this.durationLabel = this.durationValue = null;
this.delayLabel = this.delayValue = null;
this.iterationLabel = this.iterationValue = null;
this.compositorIcon = null;
},
render: function(state) {
// Update the name if needed.
if (state.name !== this.state.name) {
if (state.name) {
// Animations (and transitions since bug 1122414) have names.
this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
this.nameValue.style.display = "inline";
this.nameValue.textContent = state.name;
} else {
// With older actors, Css transitions don't have names.
this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
this.nameValue.style.display = "none";
}
}
// update the duration value if needed.
if (state.duration !== this.state.duration) {
this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(state.duration / 1000, 2));
}
// Update the delay if needed.
if (state.delay !== this.state.delay) {
if (state.delay) {
this.delayLabel.style.display = "inline";
this.delayValue.style.display = "inline";
this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(state.delay / 1000, 2));
} else {
// Hide the delay elements if there is no delay defined.
this.delayLabel.style.display = "none";
this.delayValue.style.display = "none";
}
}
// Update the iterationCount if needed.
if (state.iterationCount !== this.state.iterationCount) {
if (state.iterationCount !== 1) {
this.iterationLabel.style.display = "inline";
this.iterationValue.style.display = "inline";
let count = state.iterationCount ||
L10N.getStr("player.infiniteIterationCount");
this.iterationValue.innerHTML = count;
} else {
// Hide the iteration elements if iteration is 1.
this.iterationLabel.style.display = "none";
this.iterationValue.style.display = "none";
}
}
// Show the Running on compositor icon if needed.
if (state.isRunningOnCompositor !== this.state.isRunningOnCompositor) {
if (state.isRunningOnCompositor) {
this.compositorIcon.style.display = "inline";
} else {
// Hide the compositor icon
this.compositorIcon.style.display = "none";
}
}
this.state = state;
}
};
/**
* UI component responsible for displaying the playback rate drop-down in each
* player widget, updating it when the state changes, and emitting events when
* the user selects a new value.
* The parent UI component for this should drive its updates by calling
* render(state) whenever it wants the component to update.
*/
function PlaybackRateSelector() {
this.currentRate = null;
this.onSelectionChanged = this.onSelectionChanged.bind(this);
EventEmitter.decorate(this);
}
exports.PlaybackRateSelector = PlaybackRateSelector;
PlaybackRateSelector.prototype = {
PRESETS: [.1, .5, 1, 2, 5, 10],
init: function(containerEl) {
// This component is simple enough that we can re-create the markup every
// time it's rendered. So here we only store the parentEl.
this.parentEl = containerEl;
},
destroy: function() {
this.removeSelect();
this.parentEl = this.el = null;
},
removeSelect: function() {
if (this.el) {
this.el.removeEventListener("change", this.onSelectionChanged);
this.el.remove();
}
},
/**
* Get the ordered list of presets, including the current playbackRate if
* different from the existing presets.
*/
getCurrentPresets: function({playbackRate}) {
return [...new Set([...this.PRESETS, playbackRate])].sort((a, b) => a > b);
},
render: function(state) {
if (state.playbackRate === this.currentRate) {
return;
}
this.removeSelect();
this.el = createNode({
parent: this.parentEl,
nodeType: "select",
attributes: {
"class": "rate devtools-button"
}
});
for (let preset of this.getCurrentPresets(state)) {
let option = createNode({
parent: this.el,
nodeType: "option",
attributes: {
value: preset,
},
textContent: L10N.getFormatStr("player.playbackRateLabel", preset)
});
if (preset === state.playbackRate) {
option.setAttribute("selected", "");
}
}
this.el.addEventListener("change", this.onSelectionChanged);
this.currentRate = state.playbackRate;
},
onSelectionChanged: function() {
this.emit("rate-changed", parseFloat(this.el.value));
}
};
/**
* UI component responsible for displaying a preview of the target dom node of
* a given animation.
@ -565,18 +303,21 @@ var TimeScale = {
* @param {Object} state A PlayerFront.state object.
*/
addAnimation: function(state) {
let {startTime, delay, duration, iterationCount, playbackRate} = state;
let {previousStartTime, delay, duration,
iterationCount, playbackRate} = state;
// Negative-delayed animations have their startTimes set such that we would
// be displaying the delay outside the time window if we didn't take it into
// account here.
let relevantDelay = delay < 0 ? delay / playbackRate : 0;
previousStartTime = previousStartTime || 0;
this.minStartTime = Math.min(this.minStartTime, startTime + relevantDelay);
this.minStartTime = Math.min(this.minStartTime,
previousStartTime + relevantDelay);
let length = (delay / playbackRate) +
((duration / playbackRate) *
(!iterationCount ? 1 : iterationCount));
this.maxEndTime = Math.max(this.maxEndTime, startTime + length);
this.maxEndTime = Math.max(this.maxEndTime, previousStartTime + length);
},
/**
@ -661,8 +402,8 @@ exports.TimeScale = TimeScale;
* Animations are organized by lines, with a left margin containing the preview
* of the target DOM element the animation applies to.
* The current time play head can be moved by clicking/dragging in the header.
* when this happens, the component emits "current-time-changed" events with the
* new time.
* when this happens, the component emits "current-data-changed" events with the
* new time and state of the timeline.
*
* @param {InspectorPanel} inspector.
*/
@ -793,7 +534,12 @@ AnimationsTimeline.prototype = {
let time = TimeScale.distanceToRelativeTime(offset,
this.timeHeaderEl.offsetWidth);
this.emit("current-time-changed", time);
this.emit("timeline-data-changed", {
isPaused: true,
isMoving: false,
time: time
});
},
render: function(animations, documentCurrentTime) {
@ -862,17 +608,38 @@ AnimationsTimeline.prototype = {
}
},
isAtLeastOneAnimationPlaying: function() {
return this.animations.some(({state}) => state.playState === "running");
},
startAnimatingScrubber: function(time) {
let x = TimeScale.startTimeToDistance(time, this.timeHeaderEl.offsetWidth);
this.scrubberEl.style.left = x + "px";
if (time < TimeScale.minStartTime ||
time > TimeScale.maxEndTime) {
time > TimeScale.maxEndTime ||
!this.isAtLeastOneAnimationPlaying()) {
this.stopAnimatingScrubber();
this.emit("timeline-data-changed", {
isPaused: false,
isMoving: false,
time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth)
});
return;
}
this.emit("timeline-data-changed", {
isPaused: false,
isMoving: true,
time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth)
});
let now = this.win.performance.now();
this.rafID = this.win.requestAnimationFrame(() => {
if (!this.rafID) {
// In case the scrubber was stopped in the meantime.
return;
}
this.startAnimatingScrubber(time + this.win.performance.now() - now);
});
},
@ -880,6 +647,7 @@ AnimationsTimeline.prototype = {
stopAnimatingScrubber: function() {
if (this.rafID) {
this.win.cancelAnimationFrame(this.rafID);
this.rafID = null;
}
},
@ -935,7 +703,7 @@ AnimationsTimeline.prototype = {
// Create a container element to hold the delay and iterations.
// It is positioned according to its delay (divided by the playbackrate),
// and its width is according to its duration (divided by the playbackrate).
let start = state.startTime;
let start = state.previousStartTime || 0;
let duration = state.duration;
let rate = state.playbackRate;
let count = state.iterationCount;

View File

@ -11,35 +11,20 @@ support-files =
[browser_animation_controller_exposes_document_currentTime.js]
[browser_animation_empty_on_invalid_nodes.js]
[browser_animation_iterationCount_hidden_by_default.js]
[browser_animation_mutations_with_same_names.js]
[browser_animation_panel_exists.js]
[browser_animation_participate_in_inspector_update.js]
[browser_animation_play_pause_button.js]
[browser_animation_playerFronts_are_refreshed.js]
[browser_animation_playerWidgets_appear_on_panel_init.js]
[browser_animation_playerWidgets_compositor_icon.js]
[browser_animation_playerWidgets_destroy.js]
[browser_animation_playerWidgets_disables_on_finished.js]
[browser_animation_playerWidgets_dont_show_time_after_duration.js]
[browser_animation_playerWidgets_have_control_buttons.js]
[browser_animation_playerWidgets_meta_data.js]
[browser_animation_playerWidgets_scrubber_delayed.js]
[browser_animation_playerWidgets_scrubber_enabled.js]
[browser_animation_playerWidgets_scrubber_moves.js]
[browser_animation_playerWidgets_state_after_pause.js]
[browser_animation_playerWidgets_target_nodes.js]
[browser_animation_rate_select_shows_presets.js]
[browser_animation_refresh_on_added_animation.js]
[browser_animation_refresh_on_removed_animation.js]
[browser_animation_refresh_when_active.js]
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
[browser_animation_setting_currentTime_works_and_pauses.js]
[browser_animation_setting_playbackRate_works.js]
[browser_animation_shows_player_on_valid_node.js]
[browser_animation_target_highlight_select.js]
[browser_animation_timeline_displays_with_pref.js]
[browser_animation_timeline_header.js]
[browser_animation_timeline_pause_button.js]
[browser_animation_timeline_scrubber_exists.js]
[browser_animation_timeline_scrubber_movable.js]
[browser_animation_timeline_scrubber_moves.js]
@ -50,9 +35,5 @@ support-files =
[browser_animation_timeline_ui.js]
[browser_animation_toggle_button_resets_on_navigate.js]
[browser_animation_toggle_button_toggles_animations.js]
[browser_animation_toggle_button_updates_playerWidgets.js]
[browser_animation_toolbar_exists.js]
[browser_animation_ui_updates_when_animation_changes.js]
[browser_animation_ui_updates_when_animation_data_changes.js]
[browser_animation_ui_updates_when_animation_rate_changes.js]
[browser_animation_ui_updates_when_animation_time_changes.js]

View File

@ -9,7 +9,7 @@
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel, controller} = yield openAnimationInspectorNewUI();
let {panel, controller} = yield openAnimationInspector();
ok(controller.documentCurrentTime, "The documentCurrentTime getter exists");
checkDocumentTimeIsCorrect(controller);

View File

@ -11,27 +11,19 @@ add_task(function*() {
let {inspector, panel} = yield openAnimationInspector();
yield testEmptyPanel(inspector, panel);
({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
yield testEmptyPanel(inspector, panel, true);
});
function* testEmptyPanel(inspector, panel, isNewUI=false) {
function* testEmptyPanel(inspector, panel) {
info("Select node .still and check that the panel is empty");
let stillNode = yield getNodeFront(".still", inspector);
let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield selectNode(stillNode, inspector);
yield onUpdated;
if (isNewUI) {
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a still node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a still node");
} else {
ok(!panel.playerWidgets || !panel.playerWidgets.length,
"No player widgets displayed for a still node");
}
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a still node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a still node");
info("Select the comment text node and check that the panel is empty");
let commentNode = yield inspector.walker.previousSibling(stillNode);
@ -39,13 +31,7 @@ function* testEmptyPanel(inspector, panel, isNewUI=false) {
yield selectNode(commentNode, inspector);
yield onUpdated;
if (isNewUI) {
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a text node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a text node");
} else {
ok(!panel.playerWidgets || !panel.playerWidgets.length,
"No player widgets displayed for a text node");
}
}
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a text node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a text node");}

View File

@ -1,34 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that iteration count is only shown in the UI when it's different than 1
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting a node with an animation that doesn't repeat");
yield selectNode(".long", inspector);
let widget = panel.playerWidgets[0];
ok(isNodeVisible(widget.metaDataComponent.durationValue),
"The duration value is shown");
ok(!isNodeVisible(widget.metaDataComponent.delayValue),
"The delay value is hidden");
ok(!isNodeVisible(widget.metaDataComponent.iterationValue),
"The iteration count is hidden");
info("Selecting a node with an animation that repeats several times");
yield selectNode(".delayed", inspector);
widget = panel.playerWidgets[0];
ok(isNodeVisible(widget.metaDataComponent.durationValue),
"The duration value is shown");
ok(isNodeVisible(widget.metaDataComponent.delayValue),
"The delay value is shown");
ok(isNodeVisible(widget.metaDataComponent.iterationValue),
"The iteration count is shown");
});

View File

@ -11,7 +11,7 @@
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_negative_animation.html");
let {controller, panel} = yield openAnimationInspectorNewUI();
let {controller, panel} = yield openAnimationInspector();
info("Wait until all animations have been added " +
"(they're added with setTimeout)");

View File

@ -13,14 +13,6 @@ add_task(function*() {
ok(controller, "The animation controller exists");
ok(controller.animationsFront, "The animation controller has been initialized");
ok(panel, "The animation panel exists");
ok(panel.playersEl, "The animation panel has been initialized");
({panel, controller} = yield closeAnimationInspectorAndRestartWithNewUI());
ok(controller, "The animation controller exists");
ok(controller.animationsFront, "The animation controller has been initialized");
ok(panel, "The animation panel exists");
ok(panel.playersEl, "The animation panel has been initialized");
ok(panel.animationsTimelineComponent, "The animation panel has been initialized");

View File

@ -13,9 +13,6 @@ add_task(function*() {
let ui = yield openAnimationInspector();
yield testEventsOrder(ui);
ui = yield closeAnimationInspectorAndRestartWithNewUI();
yield testEventsOrder(ui);
});
function* testEventsOrder({inspector, panel, controller}) {

View File

@ -1,32 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that the play/pause button actually plays and pauses the player.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Selecting an animated node");
yield selectNode(".animated", inspector);
let player = controller.animationPlayers[0];
let widget = panel.playerWidgets[0];
info("Click the pause button");
yield togglePlayPauseButton(widget);
is(player.state.playState, "paused", "The AnimationPlayerFront is paused");
ok(widget.el.classList.contains("paused"), "The button's state has changed");
ok(!widget.rafID, "The smooth timeline animation has been stopped");
info("Click on the play button");
yield togglePlayPauseButton(widget);
is(player.state.playState, "running", "The AnimationPlayerFront is running");
ok(widget.el.classList.contains("running"), "The button's state has changed");
ok(widget.rafID, "The smooth timeline animation has been started");
});

View File

@ -19,17 +19,12 @@ add_task(function*() {
is(controller.animationPlayers.length, 1,
"One AnimationPlayerFront has been created");
ok(controller.animationPlayers[0].autoRefreshTimer,
"The AnimationPlayerFront has been set to auto-refresh");
info("Selecting a node with mutliple animations");
yield selectNode(".multi", inspector);
is(controller.animationPlayers.length, 2,
"2 AnimationPlayerFronts have been created");
ok(controller.animationPlayers[0].autoRefreshTimer &&
controller.animationPlayers[1].autoRefreshTimer,
"The AnimationPlayerFronts have been set to auto-refresh");
// Hold on to one of the AnimationPlayerFront objects and mock its release
// method to test that it is released correctly and that its auto-refresh is
@ -49,7 +44,6 @@ add_task(function*() {
info("Checking the destroyed AnimationPlayerFront object");
ok(releaseCalled, "The AnimationPlayerFront has been released");
ok(!retainedFront.autoRefreshTimer,
"The released AnimationPlayerFront's auto-refresh mode has been turned off");
yield oldRelease.call(retainedFront);
});

View File

@ -11,10 +11,6 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_body_animation.html");
let {panel} = yield openAnimationInspector();
is(panel.playerWidgets.length, 1,
"One animation player is displayed after init");
({panel} = yield closeAnimationInspectorAndRestartWithNewUI());
is(panel.animationsTimelineComponent.animations.length, 1,
"One animation is handled by the timeline after init");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 1,

View File

@ -1,24 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that player widgets show the right player meta-data for the
// isRunningOnCompositor property.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let compositorEl = panel.playerWidgets[0]
.el.querySelector(".compositor-icon");
ok(compositorEl, "The compositor-icon element exists");
ok(isNodeVisible(compositorEl),
"The compositor icon is visible, since the animation is running on " +
"compositor thread");
});

View File

@ -1,23 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that player widgets are destroyed correctly when needed.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select an animated node");
yield selectNode(".multi", inspector);
info("Hold on to one of the player widget instances to test it after destroy");
let widget = panel.playerWidgets[0];
info("Select another node to get the previous widgets destroyed");
yield selectNode(".animated", inspector);
ok(widget.destroyed, "The widget's destroyed flag is true");
});

View File

@ -1,38 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that when animations end, the corresponding player widgets are disabled.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Select the test node");
yield selectNode(".still", inspector);
info("Apply 2 finite animations to the test node and wait for the widgets to appear");
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", {
selector: ".still",
attributeName: "class",
attributeValue: "ball still multi-finite"
});
yield onUiUpdated;
is(controller.animationPlayers.length, 2, "2 animation players exist");
info("Wait for both animations to end");
let promises = controller.animationPlayers.map(front => {
return waitForPlayState(front, "finished");
});
yield promise.all(promises);
for (let widgetEl of panel.playersEl.querySelectorAll(".player-widget")) {
ok(widgetEl.classList.contains("finished"), "The player widget has the right class");
}
});

View File

@ -1,40 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that after the animation has ended, the current time label and timeline
// slider don't show values bigger than the animation duration (which would
// happen if the local requestAnimationFrame loop didn't stop correctly).
var L10N = new ViewHelpers.L10N();
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the test node");
yield selectNode(".still", inspector);
info("Start an animation on the test node and wait for the widget to appear");
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield executeInContent("devtools:test:setAttribute", {
selector: ".still",
attributeName: "class",
attributeValue: "ball still short"
});
yield onUiUpdated;
info("Wait until the animation ends");
let widget = panel.playerWidgets[0];
let front = widget.player;
yield waitForPlayState(front, "finished");
is(widget.currentTimeEl.value, front.state.duration,
"The timeline slider has the right value");
is(widget.timeDisplayEl.textContent,
L10N.numberWithDecimals(front.state.duration / 1000, 2) + "s",
"The timeline slider has the right value");
});

View File

@ -1,70 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that playerWidgets have control buttons: play/pause, rewind, fast-forward.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {controller, inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let widget = panel.playerWidgets[0];
let container = widget.el.querySelector(".playback-controls");
ok(container, "The control buttons container exists");
is(container.querySelectorAll("button").length, 3,
"The container contains 3 buttons");
ok(container.children[0].classList.contains("toggle"),
"The first button is the play/pause button");
ok(container.children[1].classList.contains("rw"),
"The second button is the rewind button");
ok(container.children[2].classList.contains("ff"),
"The third button is the fast-forward button");
ok(container.querySelector("select"),
"The container contains the playback rate select");
info("Faking an older server version by setting " +
"AnimationsController.traits.hasSetCurrentTime to false");
// Selecting <div.still> to make sure no widgets are displayed in the panel.
yield selectNode(".still", inspector);
controller.traits.hasSetCurrentTime = false;
info("Selecting the animated node again");
yield selectNode(".animated", inspector);
widget = panel.playerWidgets[0];
container = widget.el.querySelector(".playback-controls");
ok(container, "The control buttons container still exists");
is(container.querySelectorAll("button").length, 1,
"The container only contains 1 button");
ok(container.children[0].classList.contains("toggle"),
"The first button is the play/pause button");
yield selectNode(".still", inspector);
controller.traits.hasSetCurrentTime = true;
info("Faking an older server version by setting " +
"AnimationsController.traits.hasSetPlaybackRate to false");
controller.traits.hasSetPlaybackRate = false;
info("Selecting the animated node again");
yield selectNode(".animated", inspector);
widget = panel.playerWidgets[0];
container = widget.el.querySelector(".playback-controls");
ok(container, "The control buttons container still exists");
ok(!container.querySelector("select"),
"The playback rate select does not exist");
yield selectNode(".still", inspector);
controller.traits.hasSetPlaybackRate = true;
});

View File

@ -1,54 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that player widgets show the right player meta-data (name, duration,
// iteration count, delay).
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
ok(titleEl,
"The player widget has a title element, where meta-data should be displayed");
let nameEl = titleEl.querySelector("strong");
ok(nameEl, "The first <strong> tag was retrieved, it should contain the name");
is(nameEl.textContent, "simple-animation", "The animation name is correct");
let metaDataEl = titleEl.querySelector(".meta-data");
ok(metaDataEl, "The meta-data element exists");
let metaDataEls = metaDataEl.querySelectorAll("strong");
is(metaDataEls.length, 3, "3 meta-data elements were found");
is(metaDataEls[0].textContent, "2s",
"The first meta-data is the duration, and is correct");
ok(!isNodeVisible(metaDataEls[1]),
"The second meta-data is hidden, since there's no delay on the animation");
info("Select the node with the delayed animation");
yield selectNode(".delayed", inspector);
titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
nameEl = titleEl.querySelector("strong");
is(nameEl.textContent, "simple-animation", "The animation name is correct");
metaDataEls = titleEl.querySelectorAll(".meta-data strong");
is(metaDataEls.length, 3,
"3 meta-data elements were found for the delayed animation");
is(metaDataEls[0].textContent, "3s",
"The first meta-data is the duration, and is correct");
ok(isNodeVisible(metaDataEls[0]), "The duration is shown");
is(metaDataEls[1].textContent, "60s",
"The second meta-data is the delay, and is correct");
ok(isNodeVisible(metaDataEls[1]), "The delay is shown");
is(metaDataEls[2].textContent, "10",
"The third meta-data is the iteration count, and is correct");
ok(isNodeVisible(metaDataEls[2]), "The iteration count is shown");
});

View File

@ -1,24 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the currentTime timeline doesn't move if the animation is currently
// waiting for an animation-delay.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the delayed animation node");
yield selectNode(".delayed", inspector);
let widget = panel.playerWidgets[0];
let timeline = widget.currentTimeEl;
is(timeline.value, 0, "The timeline is at 0 since the animation hasn't started");
let timeLabel = widget.timeDisplayEl;
is(timeLabel.textContent, "0s", "The current time is 0");
});

View File

@ -1,39 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the currentTime timeline widget is enabled and that the associated
// player front supports setting the current time.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {controller, inspector, panel} = yield openAnimationInspector();
info("Select the animated node");
yield selectNode(".animated", inspector);
info("Get the player widget's timeline element");
let widget = panel.playerWidgets[0];
let timeline = widget.currentTimeEl;
ok(!timeline.hasAttribute("disabled"), "The timeline input[range] is enabled");
ok(widget.setCurrentTime, "The widget has the setCurrentTime method");
ok(widget.player.setCurrentTime,
"The associated player front has the setCurrentTime method");
info("Faking an older server version by setting " +
"AnimationsController.traits.hasSetCurrentTime to false");
yield selectNode("body", inspector);
controller.traits.hasSetCurrentTime = false;
yield selectNode(".animated", inspector);
info("Get the player widget's timeline element");
widget = panel.playerWidgets[0];
timeline = widget.currentTimeEl;
ok(timeline.hasAttribute("disabled"), "The timeline input[range] is disabled");
});

View File

@ -1,28 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the currentTime timeline widget actually progresses with the
// animation itself.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select the animated node");
yield selectNode(".animated", inspector);
info("Get the player widget's timeline element and its current position");
let widget = panel.playerWidgets[0];
let timeline = widget.currentTimeEl;
yield onceNextPlayerRefresh(widget.player);
ok(widget.rafID, "The widget is updating the timeline with a rAF loop");
info("Pause the animation");
yield togglePlayPauseButton(widget);
ok(!widget.rafID, "The rAF loop has been stopped after the animation was paused");
});

View File

@ -1,33 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that once an animation is paused and its widget is refreshed, the right
// initial time is displayed.
var L10N = new ViewHelpers.L10N();
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting the test node");
yield selectNode(".animated", inspector);
info("Pausing the animation by using the widget");
let widget = panel.playerWidgets[0];
yield widget.pause();
info("Selecting another node and then the same node again to refresh the widget");
yield selectNode(".still", inspector);
yield selectNode(".animated", inspector);
widget = panel.playerWidgets[0];
ok(widget.el.classList.contains("paused"), "The widget is still in paused mode");
is(widget.timeDisplayEl.textContent,
L10N.numberWithDecimals(widget.player.state.currentTime / 1000, 2) + "s",
"The initial time has been set to the player's");
});

View File

@ -13,29 +13,6 @@ add_task(function*() {
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let widget = panel.playerWidgets[0];
// Make sure to wait for the target-retrieved event if the nodeFront hasn't
// yet been retrieved by the TargetNodeComponent.
if (!widget.targetNodeComponent.nodeFront) {
yield widget.targetNodeComponent.once("target-retrieved");
}
let targetEl = widget.el.querySelector(".animation-target");
ok(targetEl, "The player widget has a target element");
is(targetEl.textContent, "<divid=\"\"class=\"ball animated\">",
"The target element's content is correct");
let selectorEl = targetEl.querySelector(".node-selector");
ok(selectorEl,
"The icon to select the target element in the inspector exists");
info("Test again with the new timeline UI");
({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
info("Select the simple animated node");
yield selectNode(".animated", inspector);
let targetNodeComponent = panel.animationsTimelineComponent.targetNodes[0];
// Make sure to wait for the target-retrieved event if the nodeFront hasn't
// yet been retrieved by the TargetNodeComponent.
@ -46,7 +23,7 @@ add_task(function*() {
is(targetNodeComponent.el.textContent, "div#.ball.animated",
"The target element's content is correct");
selectorEl = targetNodeComponent.el.querySelector(".node-selector");
let selectorEl = targetNodeComponent.el.querySelector(".node-selector");
ok(selectorEl,
"The icon to select the target element in the inspector exists");
});

View File

@ -1,49 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the playbackRate select element contains a list of presets and
// and that if the animation has a current rate that is not part of this list,
// it is added.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting the test node");
yield selectNode(".animated", inspector);
info("Get the playback rate UI component");
let widget = panel.playerWidgets[0];
let rateComponent = widget.rateComponent;
let options = rateComponent.el.querySelectorAll("option");
is(options.length, rateComponent.PRESETS.length,
"The playback rate select contains the right number of options");
for (let i = 0; i < rateComponent.PRESETS.length; i ++) {
is(options[i].value, rateComponent.PRESETS[i] + "",
"The playback rate option " + i + " has the right preset value " +
rateComponent.PRESETS[i]);
}
info("Set a custom rate (not part of the presets) via the DOM");
let onRateChanged = waitForStateCondition(widget.player, state => {
return state.playbackRate === 3.6
});
yield executeInContent("Test:SetAnimationPlayerPlaybackRate", {
selector: ".animated",
animationIndex: 0,
playbackRate: 3.6
});
yield onRateChanged;
options = rateComponent.el.querySelectorAll("option");
is(options.length, rateComponent.PRESETS.length + 1,
"The playback rate select contains the right number of options (presets + 1)");
ok([...options].find(option => option.value === "3.6"),
"The custom rate is part of the select");
});

View File

@ -11,9 +11,6 @@ add_task(function*() {
let {inspector, panel} = yield openAnimationInspector();
yield testRefreshOnNewAnimation(inspector, panel);
({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
yield testRefreshOnNewAnimation(inspector, panel);
});
function* testRefreshOnNewAnimation(inspector, panel) {

View File

@ -11,11 +11,6 @@ add_task(function*() {
let {inspector, panel} = yield openAnimationInspector();
yield testRefreshOnRemove(inspector, panel);
yield testAddedAnimationWorks(inspector, panel);
info("Reload and test again with the new UI");
({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI(true));
yield testRefreshOnRemove(inspector, panel, true);
});
function* testRefreshOnRemove(inspector, panel) {
@ -51,22 +46,3 @@ function* testRefreshOnRemove(inspector, panel) {
assertAnimationsDisplayed(panel, 1);
}
function* testAddedAnimationWorks(inspector, panel) {
info("Now wait until the animation finishes");
let widget = panel.playerWidgets[0];
yield waitForPlayState(widget.player, "finished");
is(panel.playersEl.querySelectorAll(".player-widget").length, 1,
"There is still a player widget in the panel after the animation finished");
info("Checking that the animation's currentTime can still be set");
info("Click at the center of the slider input");
let onPaused = waitForPlayState(widget.player, "paused");
let input = widget.currentTimeEl;
let win = input.ownerDocument.defaultView;
EventUtils.synthesizeMouseAtCenter(input, {type: "mousedown"}, win);
yield onPaused;
ok(widget.el.classList.contains("paused"), "The widget is in paused mode");
}

View File

@ -11,9 +11,6 @@ add_task(function*() {
let {inspector, panel} = yield openAnimationInspector();
yield testRefresh(inspector, panel);
({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
yield testRefresh(inspector, panel);
});
function* testRefresh(inspector, panel) {

View File

@ -10,22 +10,6 @@
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Selecting the test animated node");
yield selectNode(".multi", inspector);
is(controller.animationPlayers.length, panel.playerWidgets.length,
"As many playerWidgets were created as there are playerFronts");
for (let widget of panel.playerWidgets) {
ok(widget.initialized, "The player widget is initialized");
is(widget.el.parentNode, panel.playersEl,
"The player widget has been appended to the panel");
}
info("Test again with the new UI, making sure the same number of " +
"animation timelines is created");
({inspector, panel, controller} = yield closeAnimationInspectorAndRestartWithNewUI());
let timeline = panel.animationsTimelineComponent;
info("Selecting the test animated node again");

View File

@ -1,43 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that setting an animation's current time by clicking in the input[range]
// or rewind or fast-forward buttons pauses the animation and sets it to the
// right time.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {toolbox, inspector, panel} = yield openAnimationInspector();
info("Select an animated node");
yield selectNode(".animated", inspector);
info("Get the player widget for this node");
let widget = panel.playerWidgets[0];
let input = widget.currentTimeEl;
let rwBtn = widget.rewindBtnEl;
let ffBtn = widget.fastForwardBtnEl;
let win = input.ownerDocument.defaultView;
info("Click at the center of the input")
EventUtils.synthesizeMouseAtCenter(input, {type: "mousedown"}, win);
yield checkPausedAt(widget, 1000);
info("Resume the player and wait for an auto-refresh event");
yield widget.player.play();
yield onceNextPlayerRefresh(widget.player);
info("Click on the rewind button");
EventUtils.sendMouseEvent({type: "click"}, rwBtn, win);
yield checkPausedAt(widget, 0);
info("Click on the fast-forward button");
EventUtils.sendMouseEvent({type: "click"}, ffBtn, win);
yield checkPausedAt(widget, 2000);
});

View File

@ -1,34 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that setting an animation's playback rate by selecting a rate in the
// presets drop-down sets the rate accordingly.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {toolbox, inspector, panel} = yield openAnimationInspector();
info("Select an animated node");
yield selectNode(".animated", inspector);
info("Get the player widget for this node");
let widget = panel.playerWidgets[0];
let select = widget.rateComponent.el;
let win = select.ownerDocument.defaultView;
info("Click on the rate drop-down");
EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"}, win);
info("Click on a rate option");
let option = select.options[select.options.length - 1];
EventUtils.synthesizeMouseAtCenter(option, {type: "mouseup"}, win);
let selectedRate = parseFloat(option.value);
info("Check that the rate was changed on the player at the next update");
yield waitForStateCondition(widget.player, ({playbackRate}) => playbackRate === selectedRate);
is(widget.player.state.playbackRate, selectedRate,
"The rate was changed successfully");
});

View File

@ -9,18 +9,11 @@
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
yield testShowsAnimations(inspector, panel);
({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
yield testShowsAnimations(inspector, panel);
});
function* testShowsAnimations(inspector, panel) {
info("Select node .animated and check that the panel is not empty");
let node = yield getNodeFront(".animated", inspector);
yield selectNode(node, inspector);
assertAnimationsDisplayed(panel, 1);
}
});

View File

@ -12,12 +12,9 @@ add_task(function*() {
let ui = yield openAnimationInspector();
yield testTargetNode(ui);
ui = yield closeAnimationInspectorAndRestartWithNewUI();
yield testTargetNode(ui, true);
});
function* testTargetNode({toolbox, inspector, panel}, isNewUI) {
function* testTargetNode({toolbox, inspector, panel}) {
info("Select the simple animated node");
let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);

View File

@ -1,24 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that the timeline-based UI is displayed instead of the playerwidget-
// based UI when the "devtools.inspector.animationInspectorV3" is set.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspectorNewUI();
info("Selecting the test node");
yield selectNode(".animated", inspector);
let timeline = panel.animationsTimelineComponent;
ok(timeline, "The timeline components was created");
is(timeline.rootWrapperEl.parentNode, panel.playersEl,
"The timeline component was appended in the DOM");
is(panel.playersEl.querySelectorAll(".player-widget").length, 0,
"There are no playerWidgets in the DOM");
});

View File

@ -4,8 +4,7 @@
"use strict";
// Check that the timeline-based UI shows correct time graduations in the
// header.
// Check that the timeline shows correct time graduations in the header.
const {findOptimalTimeInterval} = require("devtools/animationinspector/utils");
const {TimeScale} = require("devtools/animationinspector/components");
@ -14,7 +13,7 @@ const TIME_GRADUATION_MIN_SPACING = 40;
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let headerEl = timeline.timeHeaderEl;

View File

@ -0,0 +1,67 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that the timeline toolbar contains a pause button and that this pause
// button can be clicked. Check that when it is, the current animations
// displayed in the timeline get their playstates changed accordingly, and check
// that the scrubber resumes/stops moving.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let btn = panel.playTimelineButtonEl;
ok(btn, "The play/pause button exists");
ok(!btn.classList.contains("paused"),
"The play/pause button is in its playing state");
info("Click on the button to pause all timeline animations");
yield clickPlayPauseButton(panel);
ok(btn.classList.contains("paused"),
"The play/pause button is in its paused state");
yield checkIfScrubberMoving(panel, false);
info("Click again on the button to play all timeline animations");
yield clickPlayPauseButton(panel);
ok(!btn.classList.contains("paused"),
"The play/pause button is in its playing state again");
yield checkIfScrubberMoving(panel, true);
});
function* clickPlayPauseButton(panel) {
let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
let btn = panel.playTimelineButtonEl;
let win = btn.ownerDocument.defaultView;
EventUtils.sendMouseEvent({type: "click"}, btn, win);
yield onUiUpdated;
yield waitForAllAnimationTargets(panel);
}
function* checkIfScrubberMoving(panel, isMoving) {
let timeline = panel.animationsTimelineComponent;
let scrubberEl = timeline.scrubberEl;
if (isMoving) {
// If we expect the scrubber to move, just wait for a couple of
// timeline-data-changed events and compare times.
let {time: time1} = yield timeline.once("timeline-data-changed");
let {time: time2} = yield timeline.once("timeline-data-changed");
ok(time2 > time1, "The scrubber is moving");
} else {
// If instead we expect the scrubber to remain at its position, just wait
// for some time. A relatively long timeout is used because the test page
// has long running animations, so the scrubber doesn't move that quickly.
let startOffset = scrubberEl.offsetLeft;
yield new Promise(r => setTimeout(r, 2000));
let endOffset = scrubberEl.offsetLeft;
is(startOffset, endOffset, "The scrubber is not moving");
}
}

View File

@ -4,11 +4,11 @@
"use strict";
// Check that the timeline-based UI does have a scrubber element.
// Check that the timeline does have a scrubber element.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let scrubberEl = timeline.scrubberEl;

View File

@ -4,32 +4,50 @@
"use strict";
// Check that the scrubber in the timeline-based UI can be moved by clicking &
// dragging in the header area.
// Check that the scrubber in the timeline can be moved by clicking & dragging
// in the header area.
// Also check that doing so changes the timeline's play/pause button to paused
// state.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let win = timeline.win;
let timeHeaderEl = timeline.timeHeaderEl;
let scrubberEl = timeline.scrubberEl;
let playTimelineButtonEl = panel.playTimelineButtonEl;
ok(!playTimelineButtonEl.classList.contains("paused"),
"The timeline play button is in its playing state by default");
info("Mousedown in the header to move the scrubber");
EventUtils.synthesizeMouse(timeHeaderEl, 50, 1, {type: "mousedown"}, win);
yield synthesizeMouseAndWaitForTimelineChange(timeline, 50, 1, "mousedown");
let newPos = parseInt(scrubberEl.style.left, 10);
is(newPos, 50, "The scrubber moved on mousedown");
ok(playTimelineButtonEl.classList.contains("paused"),
"The timeline play button is in its paused state after mousedown");
info("Continue moving the mouse and verify that the scrubber tracks it");
EventUtils.synthesizeMouse(timeHeaderEl, 100, 1, {type: "mousemove"}, win);
yield synthesizeMouseAndWaitForTimelineChange(timeline, 100, 1, "mousemove");
newPos = parseInt(scrubberEl.style.left, 10);
is(newPos, 100, "The scrubber followed the mouse");
ok(playTimelineButtonEl.classList.contains("paused"),
"The timeline play button is in its paused state after mousemove");
info("Release the mouse and move again and verify that the scrubber stays");
EventUtils.synthesizeMouse(timeHeaderEl, 100, 1, {type: "mouseup"}, win);
EventUtils.synthesizeMouse(timeHeaderEl, 200, 1, {type: "mousemove"}, win);
newPos = parseInt(scrubberEl.style.left, 10);
is(newPos, 100, "The scrubber stopped following the mouse");
});
function* synthesizeMouseAndWaitForTimelineChange(timeline, x, y, type) {
let onDataChanged = timeline.once("timeline-data-changed");
EventUtils.synthesizeMouse(timeline.timeHeaderEl, x, y, {type}, timeline.win);
yield onDataChanged;
}

View File

@ -4,15 +4,14 @@
"use strict";
// Check that the scrubber in the timeline-based UI moves when animations are
// playing.
// Check that the scrubber in the timeline moves when animations are playing.
// The animations in the test page last for a very long time, so the test just
// measures the position of the scrubber once, then waits for some time to pass
// and measures its position again.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let scrubberEl = timeline.scrubberEl;

View File

@ -4,14 +4,14 @@
"use strict";
// Check that animation delay is visualized in the timeline-based UI when the
// animation is delayed.
// Check that animation delay is visualized in the timeline when the animation
// is delayed.
// Also check that negative delays do not overflow the UI, and are shown like
// positive delays.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspectorNewUI();
let {inspector, panel} = yield openAnimationInspector();
info("Selecting a delayed animated node");
yield selectNode(".delayed", inspector);

View File

@ -4,12 +4,12 @@
"use strict";
// Check that the timeline-based UI is displays as many iteration elements as
// there are iterations in an animation.
// Check that the timeline is displays as many iteration elements as there are
// iterations in an animation.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspectorNewUI();
let {inspector, panel} = yield openAnimationInspector();
info("Selecting the test node");
yield selectNode(".delayed", inspector);

View File

@ -4,12 +4,12 @@
"use strict";
// Check that the timeline-based UI displays animations' duration, delay and
// iteration counts in tooltips.
// Check that the timeline displays animations' duration, delay and iteration
// counts in tooltips.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
info("Getting the animation element from the panel");
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;

View File

@ -14,7 +14,7 @@
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_modify_playbackRate.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;

View File

@ -4,11 +4,11 @@
"use strict";
// Check that the timeline-based UI contains the right elements.
// Check that the timeline contains the right elements.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspectorNewUI();
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let el = timeline.rootWrapperEl;

View File

@ -1,38 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that pressing the main toggle button also updates the displayed
// player widgets.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Select an animated node");
yield selectNode(".animated", inspector);
let widget = panel.playerWidgets[0];
let player = widget.player;
info("Listen to animation state changes and click the toggle button to " +
"pause all animations");
let onPaused = waitForPlayState(player, "paused");
yield panel.toggleAll();
yield onPaused;
info("Checking the selected node's animation player widget's state");
is(player.state.playState, "paused", "The player front's state is paused");
ok(widget.el.classList.contains("paused"), "The widget's UI is in paused state");
info("Listen to animation state changes and click the toggle button to " +
"play all animations");
let onRunning = waitForPlayState(player, "running");
yield panel.toggleAll();
yield onRunning;
info("Checking the selected node's animation player widget's state again");
is(player.state.playState, "running", "The player front's state is running");
ok(widget.el.classList.contains("running"), "The widget's UI is in running state");
});

View File

@ -6,21 +6,29 @@
// Test that the animation panel has a top toolbar that contains the play/pause
// button and that is displayed at all times.
// Also test that this toolbar gets replaced by the timeline toolbar when there
// are animations to be displayed.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, window} = yield openAnimationInspector();
let doc = window.document;
let toolbar = doc.querySelector("#global-toolbar");
let toolbar = doc.querySelector("#toolbar");
ok(toolbar, "The panel contains the toolbar element");
ok(toolbar.querySelector("#toggle-all"), "The toolbar contains the toggle button");
ok(isNodeVisible(toolbar), "The toolbar is visible");
ok(toolbar, "The panel contains the toolbar element with the new UI");
ok(!isNodeVisible(toolbar),
"The toolbar is hidden while there are animations");
info("Select an animated node");
yield selectNode(".animated", inspector);
let timelineToolbar = doc.querySelector("#timeline-toolbar");
ok(timelineToolbar, "The panel contains a timeline toolbar element");
ok(isNodeVisible(timelineToolbar),
"The timeline toolbar is visible when there are animations");
toolbar = doc.querySelector("#toolbar");
ok(toolbar, "The panel still contains the toolbar element");
ok(isNodeVisible(toolbar), "The toolbar is still visible");
info("Select a node that has no animations");
yield selectNode(".still", inspector);
ok(isNodeVisible(toolbar),
"The toolbar is shown when there are no animations");
ok(!isNodeVisible(timelineToolbar),
"The timeline toolbar is hidden when there are no animations");
});

View File

@ -1,52 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Verify that if the animation object changes in content, then the widget
// reflects that change.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel, inspector} = yield openAnimationInspector();
info("Select the test node");
yield selectNode(".animated", inspector);
info("Get the player widget");
let widget = panel.playerWidgets[0];
let player = widget.player;
info("Wait for paused playState");
let onPaused = waitForPlayState(player, "paused");
info("Pause the animation via the content DOM");
yield executeInContent("Test:ToggleAnimationPlayer", {
selector: ".animated",
animationIndex: 0,
pause: true
});
yield onPaused;
is(player.state.playState, "paused", "The AnimationPlayerFront is paused");
ok(widget.el.classList.contains("paused"), "The button's state has changed");
ok(!widget.rafID, "The smooth timeline animation has been stopped");
info("Wait for running playState");
let onRunning = waitForPlayState(player, "running");
info("Play the animation via the content DOM");
yield executeInContent("Test:ToggleAnimationPlayer", {
selector: ".animated",
animationIndex: 0,
pause: false
});
yield onRunning;
is(player.state.playState, "running", "The AnimationPlayerFront is running");
ok(widget.el.classList.contains("running"), "The button's state has changed");
ok(widget.rafID, "The smooth timeline animation has been started");
});

View File

@ -12,47 +12,33 @@ add_task(function*() {
let ui = yield openAnimationInspector();
yield testDataUpdates(ui);
info("Close the toolbox, reload the tab, and try again with the new UI");
ui = yield closeAnimationInspectorAndRestartWithNewUI(true);
yield testDataUpdates(ui, true);
});
function* testDataUpdates({panel, controller, inspector}, isNewUI=false) {
function* testDataUpdates({panel, controller, inspector}) {
info("Select the test node");
yield selectNode(".animated", inspector);
let animation = controller.animationPlayers[0];
yield setStyle(animation, panel, "animationDuration", "5.5s", isNewUI);
yield setStyle(animation, panel, "animationIterationCount", "300", isNewUI);
yield setStyle(animation, panel, "animationDelay", "45s", isNewUI);
yield setStyle(animation, panel, "animationDuration", "5.5s");
yield setStyle(animation, panel, "animationIterationCount", "300");
yield setStyle(animation, panel, "animationDelay", "45s");
if (isNewUI) {
let animationsEl = panel.animationsTimelineComponent.animationsEl;
let timeBlockEl = animationsEl.querySelector(".time-block");
let animationsEl = panel.animationsTimelineComponent.animationsEl;
let timeBlockEl = animationsEl.querySelector(".time-block");
// 45s delay + (300 * 5.5)s duration
let expectedTotalDuration = 1695 * 1000;
let timeRatio = expectedTotalDuration / timeBlockEl.offsetWidth;
// 45s delay + (300 * 5.5)s duration
let expectedTotalDuration = 1695 * 1000;
let timeRatio = expectedTotalDuration / timeBlockEl.offsetWidth;
// XXX: the nb and size of each iteration cannot be tested easily (displayed
// using a linear-gradient background and capped at 2px wide). They should
// be tested in bug 1173761.
let delayWidth = parseFloat(timeBlockEl.querySelector(".delay").style.width);
is(Math.round(delayWidth * timeRatio), 45 * 1000,
"The timeline has the right delay");
} else {
let widget = panel.playerWidgets[0];
is(widget.metaDataComponent.durationValue.textContent, "5.50s",
"The widget shows the new duration");
is(widget.metaDataComponent.iterationValue.textContent, "300",
"The widget shows the new iteration count");
is(widget.metaDataComponent.delayValue.textContent, "45s",
"The widget shows the new delay");
}
// XXX: the nb and size of each iteration cannot be tested easily (displayed
// using a linear-gradient background and capped at 2px wide). They should
// be tested in bug 1173761.
let delayWidth = parseFloat(timeBlockEl.querySelector(".delay").style.width);
is(Math.round(delayWidth * timeRatio), 45 * 1000,
"The timeline has the right delay");
}
function* setStyle(animation, panel, name, value, isNewUI=false) {
function* setStyle(animation, panel, name, value) {
info("Change the animation style via the content DOM. Setting " +
name + " to " + value);
@ -67,10 +53,4 @@ function* setStyle(animation, panel, name, value, isNewUI=false) {
// Also wait for the target node previews to be loaded if the panel got
// refreshed as a result of this animation mutation.
yield waitForAllAnimationTargets(panel);
// If this is the playerWidget-based UI, wait for the auto-refresh event too
// to make sure the UI has updated.
if (!isNewUI) {
yield once(animation, animation.AUTO_REFRESH_EVENT);
}
}

View File

@ -1,47 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that setting an animation's playbackRate via the WebAnimations API (from
// content), actually changes the rate in the corresponding widget too.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting the test node");
yield selectNode(".animated", inspector);
info("Get the player widget");
let widget = panel.playerWidgets[0];
info("Change the animation's playbackRate via the content DOM");
let onRateChanged = waitForStateCondition(widget.player, state => {
return state.playbackRate === 2;
}, "playbackRate === 2");
yield executeInContent("Test:SetAnimationPlayerPlaybackRate", {
selector: ".animated",
animationIndex: 0,
playbackRate: 2
});
yield onRateChanged;
is(widget.rateComponent.el.value, "2",
"The playbackRate select value was changed");
info("Change the animation's playbackRate again via the content DOM");
onRateChanged = waitForStateCondition(widget.player, state => {
return state.playbackRate === 0.3;
}, "playbackRate === 0.3");
yield executeInContent("Test:SetAnimationPlayerPlaybackRate", {
selector: ".animated",
animationIndex: 0,
playbackRate: 0.3
});
yield onRateChanged;
is(widget.rateComponent.el.value, "0.3",
"The playbackRate select value was changed again");
});

View File

@ -1,52 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that setting an animation's currentTime via the WebAnimations API (from
// content), actually changes the time in the corresponding widget too.
var L10N = new ViewHelpers.L10N();
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting the test node");
yield selectNode(".animated", inspector);
info("Get the player widget");
let widget = panel.playerWidgets[0];
info("Pause the player so we can compare times easily");
yield executeInContent("Test:ToggleAnimationPlayer", {
selector: ".animated",
animationIndex: 0,
pause: true
});
yield onceNextPlayerRefresh(widget.player);
ok(widget.el.classList.contains("paused"), "The widget is in pause mode");
info("Change the animation's currentTime via the content DOM");
yield executeInContent("Test:SetAnimationPlayerCurrentTime", {
selector: ".animated",
animationIndex: 0,
currentTime: 0
});
yield onceNextPlayerRefresh(widget.player);
is(widget.currentTimeEl.value, 0, "The currentTime slider's value was changed");
info("Change the animation's currentTime again via the content DOM");
yield executeInContent("Test:SetAnimationPlayerCurrentTime", {
selector: ".animated",
animationIndex: 0,
currentTime: 300
});
yield onceNextPlayerRefresh(widget.player);
is(widget.currentTimeEl.value, 300,
"The currentTime slider's value was changed again");
});

View File

@ -83,13 +83,6 @@ function addTab(url) {
return def.promise;
}
/**
* Switch ON the new UI pref.
*/
function enableNewUI() {
Services.prefs.setBoolPref(NEW_UI_PREF, true);
}
/**
* Reload the current tab location.
*/
@ -147,15 +140,9 @@ var selectNode = Task.async(function*(data, inspector, reason="test") {
* @param {String} msg An optional string to be used as the assertion message.
*/
function assertAnimationsDisplayed(panel, nbAnimations, msg="") {
let isNewUI = Services.prefs.getBoolPref(NEW_UI_PREF);
msg = msg || `There are ${nbAnimations} animations in the panel`;
if (isNewUI) {
is(panel.animationsTimelineComponent.animationsEl.childNodes.length,
nbAnimations, msg);
} else {
is(panel.playersEl.querySelectorAll(".player-widget").length,
nbAnimations, msg);
}
is(panel.animationsTimelineComponent.animationsEl.childNodes.length,
nbAnimations, msg);
}
/**
@ -229,16 +216,6 @@ var openAnimationInspector = Task.async(function*() {
};
});
/**
* Turn on the new timeline-based UI pref ON, and then open the toolbox, with
* the inspector tool visible and the animationinspector sidebar selected.
* @return a promise that resolves when the inspector is ready.
*/
function openAnimationInspectorNewUI() {
enableNewUI();
return openAnimationInspector();
}
/**
* Close the toolbox.
* @return a promise that resolves when the toolbox has closed.
@ -248,24 +225,6 @@ var closeAnimationInspector = Task.async(function*() {
yield gDevTools.closeToolbox(target);
});
/**
* During the time period we migrate from the playerWidgets-based UI to the new
* AnimationTimeline UI, we'll want to run certain tests against both UI.
* This closes the toolbox, switch the new UI pref ON, and opens the toolbox
* again, with the animation inspector panel selected.
* @param {Boolean} reload Optionally reload the page after the toolbox was
* closed and before it is opened again.
* @return a promise that resolves when the animation inspector is ready.
*/
var closeAnimationInspectorAndRestartWithNewUI = Task.async(function*(reload) {
info("Close the toolbox and test again with the new UI");
yield closeAnimationInspector();
if (reload) {
yield reloadTab();
}
return yield openAnimationInspectorNewUI();
});
/**
* Wait for the toolbox frame to receive focus after it loads
* @param {Toolbox} toolbox
@ -481,13 +440,7 @@ function isNodeVisible(node) {
* @return {Array} all AnimationTargetNode instances
*/
var waitForAllAnimationTargets = Task.async(function*(panel) {
let targets = [];
if (panel.animationsTimelineComponent) {
targets = targets.concat(panel.animationsTimelineComponent.targetNodes);
}
if (panel.playerWidgets) {
targets = targets.concat(panel.playerWidgets.map(w => w.targetNodeComponent));
}
let targets = panel.animationsTimelineComponent.targetNodes;
yield promise.all(targets.map(t => {
if (!t.nodeFront) {
return t.once("target-retrieved");

View File

@ -12,19 +12,19 @@ const {TimeScale} = require("devtools/animationinspector/components");
const TEST_ANIMATIONS = [{
desc: "Testing a few standard animations",
animations: [{
startTime: 500,
previousStartTime: 500,
delay: 0,
duration: 1000,
iterationCount: 1,
playbackRate: 1
}, {
startTime: 400,
previousStartTime: 400,
delay: 100,
duration: 10,
iterationCount: 100,
playbackRate: 1
}, {
startTime: 50,
previousStartTime: 50,
delay: 1000,
duration: 100,
iterationCount: 20,
@ -35,7 +35,7 @@ const TEST_ANIMATIONS = [{
}, {
desc: "Testing a single negative-delay animation",
animations: [{
startTime: 100,
previousStartTime: 100,
delay: -100,
duration: 100,
iterationCount: 1,
@ -46,7 +46,7 @@ const TEST_ANIMATIONS = [{
}, {
desc: "Testing a single negative-delay animation with a different rate",
animations: [{
startTime: 3500,
previousStartTime: 3500,
delay: -1000,
duration: 10000,
iterationCount: 2,

View File

@ -46,6 +46,7 @@ function SelectorSearch(aInspector, aInputNode) {
this._onHTMLSearch = this._onHTMLSearch.bind(this);
this._onSearchKeypress = this._onSearchKeypress.bind(this);
this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
this._onMarkupMutation = this._onMarkupMutation.bind(this);
// Options for the AutocompletePopup.
let options = {
@ -63,6 +64,7 @@ function SelectorSearch(aInspector, aInputNode) {
// event listeners.
this.searchBox.addEventListener("command", this._onHTMLSearch, true);
this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
this.inspector.on("markupmutation", this._onMarkupMutation);
// For testing, we need to be able to wait for the most recent node request
// to finish. Tests can watch this promise for that.
@ -170,6 +172,7 @@ SelectorSearch.prototype = {
// event listeners.
this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
this.inspector.off("markupmutation", this._onMarkupMutation);
this.searchPopup.destroy();
this.searchPopup = null;
this.searchBox = null;
@ -423,6 +426,15 @@ SelectorSearch.prototype = {
this.emit("processing-done");
},
/**
* Reset previous search results on markup-mutations to make sure we search
* again after nodes have been added/removed/changed.
*/
_onMarkupMutation: function() {
this._searchResults = null;
this._lastSearched = null;
},
/**
* Populates the suggestions list and show the suggestion popup.
*/

View File

@ -102,6 +102,7 @@ skip-if = e10s # Test synthesize scrolling events in content. Also, see bug 1035
[browser_inspector_search-03.js]
[browser_inspector_search-04.js]
[browser_inspector_search-05.js]
[browser_inspector_search-06.js]
[browser_inspector_search-reserved.js]
[browser_inspector_select-docshell.js]
[browser_inspector_select-last-selected.js]

View File

@ -0,0 +1,76 @@
/* 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";
// Check that searching again for nodes after they are removed or added from the
// DOM works correctly.
const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html";
add_task(function* () {
let { inspector, testActor } = yield openInspectorForURL(TEST_URL);
info("Searching for test node #d1");
yield focusSearchBoxUsingShortcut(inspector.panelWin);
yield synthesizeKeys(["#", "d", "1"], inspector);
assertHasResult(inspector, true);
info("Removing node #d1");
yield mutatePage(inspector, testActor,
"document.getElementById(\"d1\").remove()");
info("Pressing return button to search again for node #d1.");
yield synthesizeKeys("VK_RETURN", inspector);
assertHasResult(inspector, false);
info("Emptying the field and searching for a node that doesn't exist: #d3");
let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3"];
yield synthesizeKeys(keys, inspector);
assertHasResult(inspector, false);
info("Create the #d3 node in the page");
yield mutatePage(inspector, testActor,
`document.getElementById("d2").insertAdjacentHTML(
"afterend", "<div id=d3></div>")`);
info("Pressing return button to search again for node #d3.");
yield synthesizeKeys("VK_RETURN", inspector);
assertHasResult(inspector, true);
// Catch-all event for remaining server requests when searching for the new
// node.
yield inspector.once("inspector-updated");
});
function* synthesizeKeys(keys, inspector) {
if (typeof keys === "string") {
keys = [keys];
}
for (let key of keys) {
info("Synthesizing key " + key + " in the search box");
let eventHandled = once(inspector.searchBox, "keypress", true);
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
yield eventHandled;
info("Waiting for the search query to complete");
yield inspector.searchSuggestions._lastQuery;
}
}
function assertHasResult(inspector, expectResult) {
is(inspector.searchBox.classList.contains("devtools-no-search-result"),
!expectResult,
"There are" + (expectResult ? "" : " no") + " search results");
}
function* mutatePage(inspector, testActor, expression) {
let onUpdated = inspector.once("inspector-updated");
yield testActor.eval(expression);
yield onUpdated;
}

View File

@ -85,6 +85,8 @@ var UI = {
projectNode.setAttribute("hidden", "true");
let runtimeNode = document.querySelector("#runtime-panel-button");
runtimeNode.setAttribute("hidden", "true");
let openAppNode = document.querySelector("#menuitem-show_projectPanel");
openAppNode.setAttribute("hidden", "true");
}
runtimeList = new RuntimeList(window, window);
if (runtimeList.sidebarsEnabled) {

View File

@ -67,7 +67,7 @@
<menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
<menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
<menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
<menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
<menuitem id="menuitem-show_projectPanel" command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
<menuseparator/>
<menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
<menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>

View File

@ -706,6 +706,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY identity.connectionFile "This page is stored on your computer.">
<!ENTITY identity.connectionVerified1 "You are securely connected to this site, run by:">
<!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
<!ENTITY identity.insecureLoginForms "Your login could be compromised.">
<!-- Strings for connection state warnings. -->
<!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
@ -715,6 +716,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!-- Strings for connection state warnings in the subview. -->
<!ENTITY identity.description.insecure "Your connection to this site is not private. Information you submit could be viewed by others (like passwords, messages, credit cards, etc.).">
<!ENTITY identity.description.insecureLoginForms "The login information you enter on this page is not secure and could be compromised.">
<!ENTITY identity.description.weakCipher "Your connection to this website uses weak encryption and is not private.">
<!ENTITY identity.description.weakCipher2 "Other people can view your information or modify the website's behavior.">
<!ENTITY identity.description.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">

View File

@ -0,0 +1,14 @@
<!-- 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/. -->
<!ENTITY window.title "Block Lists">
<!ENTITY window.width "50em">
<!ENTITY treehead.list.label "List">
<!ENTITY windowClose.key "w">
<!ENTITY button.cancel.label "Cancel">
<!ENTITY button.cancel.accesskey "C">
<!ENTITY button.ok.label "Save Changes">
<!ENTITY button.ok.accesskey "S">

View File

@ -28,6 +28,28 @@ popuppermissionstitle=Allowed Sites - Pop-ups
invalidURI=Please enter a valid hostname
invalidURITitle=Invalid Hostname Entered
#### Block List Manager
blockliststext=You can choose which list Firefox will use to block Web elements that may track your browsing activity.
blockliststitle=Block Lists
# LOCALIZATION NOTE (mozNameTemplate): This template constructs the name of the
# block list in the block lists dialog. It combines the list name and
# description.
# e.g. mozNameTemplate : "Standard (Recommended). This list does a pretty good job."
# %1$S = list name (fooName), %2$S = list descriptive text (fooDesc)
mozNameTemplate=%1$S %2$S
# LOCALIZATION NOTE (mozstdName, etc.): These labels appear in the tracking
# protection block lists dialog, mozNameTemplate is used to create the final
# string. Note that in the future these two strings (name, desc) could be
# displayed on two different lines.
mozstdName=Disconnect.me basic protection (Recommended).
mozstdDesc=Allows some trackers so websites function properly.
mozfullName=Disconnect.me strict protection.
mozfullDesc=Blocks known trackers. Some sites may not function properly.
# LOCALIZATION NOTE (blocklistChangeRequiresRestart, restartTitle): %S = brandShortName
blocklistChangeRequiresRestart=%S must restart to change block lists.
shouldRestartTitle=Restart %S
#### Master Password
pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.

View File

@ -13,6 +13,8 @@
<!ENTITY trackingProtectionPBM5.label "Use Tracking Protection in Private Windows">
<!ENTITY trackingProtectionPBM5.accesskey "v">
<!ENTITY trackingProtectionPBMLearnMore.label "Learn more">
<!ENTITY changeBlockList.label "Change Block List">
<!ENTITY changeBlockList.accesskey "C">
<!ENTITY history.label "History">

View File

@ -122,6 +122,7 @@
locale/browser/preferences/advanced.dtd (%chrome/browser/preferences/advanced.dtd)
locale/browser/preferences/applicationManager.dtd (%chrome/browser/preferences/applicationManager.dtd)
locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
locale/browser/preferences/blocklists.dtd (%chrome/browser/preferences/blocklists.dtd)
locale/browser/preferences/colors.dtd (%chrome/browser/preferences/colors.dtd)
locale/browser/preferences/cookies.dtd (%chrome/browser/preferences/cookies.dtd)
locale/browser/preferences/content.dtd (%chrome/browser/preferences/content.dtd)

View File

@ -35,9 +35,12 @@ body {
color: var(--theme-content-color3);
}
/* The top toolbar, containing the toggle-all button */
/* The top toolbar, containing the toggle-all button. And the timeline toolbar,
containing playback control buttons, shown only when there are animations
displayed in the timeline */
#toolbar {
#global-toolbar,
#timeline-toolbar {
border-bottom: 1px solid var(--theme-splitter-color);
display: flex;
flex-direction: row;
@ -46,13 +49,20 @@ body {
height: var(--toolbar-height);
}
#toolbar .label {
padding: 1px 4px;
#timeline-toolbar {
display: none;
}
#toggle-all {
border-width: 0 0 0 1px;
min-height: var(--toolbar-height);
[timeline] #global-toolbar {
display: none;
}
[timeline] #timeline-toolbar {
display: flex;
}
#global-toolbar .label {
padding: 1px 4px;
}
/* The main animations container */
@ -62,6 +72,10 @@ body {
overflow: auto;
}
[empty] #players {
display: none;
}
/* The error message, shown when an invalid/unanimated element is selected */
#error-message {
@ -74,16 +88,23 @@ body {
display: none;
}
[empty] #error-message {
display: block;
}
/* Element picker and toggle-all buttons */
/* Element picker, toggle-all buttons, timeline pause button, ... */
#element-picker,
#toggle-all {
#global-toolbar .devtools-button,
#timeline-toolbar .devtools-button {
border-width: 0 0 0 1px;
min-height: var(--toolbar-height);
}
.devtools-button {
position: relative;
}
#element-picker::before,
#toggle-all::before {
.devtools-button::before {
content: "";
display: block;
width: 16px;
@ -92,10 +113,13 @@ body {
left: 50%;
top: 50%;
margin: -8px 0 0 -8px;
}
#element-picker::before {
background-image: url("chrome://browser/skin/devtools/command-pick.png");
}
#toggle-all::before {
.pause-button::before {
background-image: url("debugger-pause.png");
}
@ -104,7 +128,7 @@ body {
filter: none; /* Icon is blue when checked, don't invert for light theme */
}
#toggle-all.paused::before {
.pause-button.paused::before {
background-image: url("debugger-play.png");
}
@ -114,11 +138,11 @@ body {
background-size: 64px;
}
#toggle-all::before {
.pause-button::before {
background-image: url("debugger-pause@2x.png");
}
#toggle-all.paused::before {
.pause-button.paused::before {
background-image: url("debugger-play@2x.png");
}
}
@ -128,6 +152,7 @@ body {
.animation-timeline {
height: 100%;
overflow: hidden;
position: relative;
/* The timeline gets its background-image from a canvas element created in
/browser/devtools/animationinspector/utils.js drawGraphElementBackground
thanks to document.mozSetImageElement("time-graduations", canvas)

View File

@ -22,6 +22,27 @@ treecol {
-moz-user-select: none;
}
#engineList treechildren::-moz-tree-image(engineShown, checked),
#blocklistsTree treechildren::-moz-tree-image(selectionCol, checked) {
list-style-image: url("chrome://global/skin/in-content/check.svg#check");
width: 21px;
height: 21px;
}
#engineList treechildren::-moz-tree-image(engineShown, checked, selected),
#blocklistsTree treechildren::-moz-tree-image(selectionCol, checked, selected) {
list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
}
#engineList treechildren::-moz-tree-row,
#blocklistsTree treechildren::-moz-tree-row {
min-height: 36px;
}
#selectionCol {
min-width: 26px;
}
/* Category List */
#categories {

View File

@ -19,16 +19,6 @@
margin: .5em 0;
}
#engineList treechildren::-moz-tree-image(engineShown, checked) {
list-style-image: url("chrome://global/skin/in-content/check.svg#check");
width: 21px;
height: 21px;
}
#engineList treechildren::-moz-tree-image(engineShown, checked, selected) {
list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
}
#engineList treechildren::-moz-tree-image(engineName) {
-moz-margin-end: 10px;
-moz-margin-start: 1px;
@ -36,10 +26,6 @@
height: 16px;
}
#engineList treechildren::-moz-tree-row {
min-height: 36px;
}
#engineList treechildren::-moz-tree-drop-feedback {
background-color: Highlight;
width: 10000px; /* 100% doesn't work; 10k is hopefully larger than any window

View File

@ -6,37 +6,21 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.home.TwoLinePageRow;
import org.mozilla.gecko.util.StringUtils;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ReadingListRow extends LinearLayout {
private final Resources resources;
private final TextView title;
private final TextView excerpt;
private final TextView readTime;
private final ImageView indicator;
// Average reading speed in words per minute.
private static final int AVERAGE_READING_SPEED = 250;
// Length of average word.
private static final float AVERAGE_WORD_LENGTH = 5.1f;
private TextView title;
private TextView excerpt;
private ImageView indicator;
public ReadingListRow(Context context) {
this(context, null);
@ -44,14 +28,13 @@ public class ReadingListRow extends LinearLayout {
public ReadingListRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
LayoutInflater.from(context).inflate(R.layout.reading_list_row_view, this);
resources = context.getResources();
@Override
protected void onFinishInflate() {
super.onFinishInflate();
title = (TextView) findViewById(R.id.title);
excerpt = (TextView) findViewById(R.id.excerpt);
readTime = (TextView) findViewById(R.id.read_time);
indicator = (ImageView) findViewById(R.id.indicator);
}
@ -73,28 +56,6 @@ public class ReadingListRow extends LinearLayout {
excerpt.setTextAppearance(getContext(), isUnread ? R.style.Widget_ReadingListRow_Title_Unread : R.style.Widget_ReadingListRow_Title_Read);
indicator.setImageResource(isUnread ? R.drawable.reading_list_indicator_unread : R.drawable.reading_list_indicator_read);
/* Disabled until UX issues are fixed (see bug 1110461).
final int lengthIndex = cursor.getColumnIndexOrThrow(ReadingListItems.LENGTH);
final int minutes = getEstimatedReadTime(cursor.getInt(lengthIndex));
if (minutes <= 60) {
readTime.setText(resources.getString(R.string.reading_list_time_minutes, minutes));
} else {
readTime.setText(resources.getString(R.string.reading_list_time_over_an_hour));
}
*/
}
/**
* Calculates the estimated time to read an article based on its length.
*
* @param length of the article (in characters)
* @return estimated time to read the article (in minutes)
*/
private static int getEstimatedReadTime(int length) {
final int minutes = (int) Math.ceil((length / AVERAGE_WORD_LENGTH) / AVERAGE_READING_SPEED);
// Minimum of one minute.
return Math.max(minutes, 1);
}
}

View File

@ -342,6 +342,11 @@ public class TopSitesPanel extends HomeFragment {
// Long pressed item was not a Top Sites GridView item. Superclass
// can handle this.
super.onCreateContextMenu(menu, view, menuInfo);
if (!RestrictedProfiles.isAllowed(view.getContext(), Restriction.DISALLOW_CLEAR_HISTORY)) {
menu.findItem(R.id.home_remove).setVisible(false);
}
return;
}

View File

@ -6,4 +6,31 @@
<org.mozilla.gecko.home.ReadingListRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/reading_list_row_height"
android:layout_gravity="center_vertical"/>
android:layout_gravity="center_vertical">
<ImageView android:id="@+id/indicator"
android:layout_width="64dp"
android:layout_height="match_parent"
android:scaleType="center" />
<LinearLayout android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingRight="@dimen/reading_list_row_padding_right"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="4dp"
style="@style/Widget.ReadingListRow.Title" />
<TextView android:id="@+id/excerpt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.ReadingListRow.Description" />
</LinearLayout>
</org.mozilla.gecko.home.ReadingListRow>

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/indicator"
android:layout_width="64dp"
android:layout_height="match_parent"
android:scaleType="center" />
<LinearLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingRight="@dimen/reading_list_row_padding_right"
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="4dp"
style="@style/Widget.ReadingListRow.Title" />
<TextView
android:id="@+id/excerpt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.ReadingListRow.Description" />
</LinearLayout>
<TextView
android:id="@+id/read_time"
android:layout_width="64dp"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
style="@style/Widget.ReadingListRow.ReadTime" />
</merge>

View File

@ -4767,7 +4767,7 @@ pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,tes
pref("urlclassifier.phishTable", "goog-phish-shavar,test-phish-simple");
pref("urlclassifier.downloadBlockTable", "");
pref("urlclassifier.downloadAllowTable", "");
pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256");
pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256");
// The table and update/gethash URLs for Safebrowsing phishing and malware
// checks.
@ -4777,6 +4777,12 @@ pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trac
pref("browser.safebrowsing.provider.mozilla.lists", "mozstd-track-digest256,mozstd-trackwhite-digest256");
pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
// Block lists for tracking protection. The name values will be used as the keys
// to lookup the localized name in preferences.properties.
pref("browser.safebrowsing.provider.mozilla.lists.mozstd.name", "mozstdName");
pref("browser.safebrowsing.provider.mozilla.lists.mozstd.description", "mozstdDesc");
pref("browser.safebrowsing.provider.mozilla.lists.mozfull.name", "mozfullName");
pref("browser.safebrowsing.provider.mozilla.lists.mozfull.description", "mozfullDesc");
// Turn off Spatial navigation by default.
pref("snav.enabled", false);

View File

@ -184,16 +184,17 @@ var TelemetryReportingPolicyImpl = {
* @return {Object} A date object or null on errors.
*/
get dataSubmissionPolicyNotifiedDate() {
if (!Preferences.has(PREF_ACCEPTED_POLICY_DATE)) {
let prefString = Preferences.get(PREF_ACCEPTED_POLICY_DATE, "0");
let valueInteger = parseInt(prefString, 10);
// Bail out if we didn't store any value yet.
if (valueInteger == 0) {
this._log.info("get dataSubmissionPolicyNotifiedDate - No date stored yet.");
return null;
}
let prefString = Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0);
let valueInteger = parseInt(prefString, 10);
// If nothing or an invalid value is saved in the prefs, bail out.
if (Number.isNaN(valueInteger) || valueInteger == 0) {
// If an invalid value is saved in the prefs, bail out too.
if (Number.isNaN(valueInteger)) {
this._log.error("get dataSubmissionPolicyNotifiedDate - Invalid date stored.");
return null;
}

View File

@ -28,7 +28,6 @@
const {Cu} = require("chrome");
const promise = require("promise");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {setInterval, clearInterval} = require("sdk/timers");
const protocol = require("devtools/server/protocol");
const {ActorClass, Actor, FrontClass, Front,
Arg, method, RetVal, types} = protocol;
@ -36,10 +35,6 @@ const {ActorClass, Actor, FrontClass, Front,
const {NodeActor} = require("devtools/server/actors/inspector");
const events = require("sdk/event/core");
// How long (in ms) should we wait before polling again the state of an
// animationPlayer.
const PLAYER_DEFAULT_AUTO_REFRESH_TIMEOUT = 500;
// Types of animations.
const ANIMATION_TYPES = {
CSS_ANIMATION: "cssanimation",
@ -267,6 +262,15 @@ var AnimationPlayerActor = ActorClass({
* @return {Object}
*/
getCurrentState: method(function() {
// Remember the startTime each time getCurrentState is called, it may be
// useful when animations get paused. As in, when an animation gets paused,
// it's startTime goes back to null, but the front-end might still be
// interested in knowing what the previous startTime was. So everytime it
// is set, remember it and send it along with the newState.
if (this.player.startTime) {
this.previousStartTime = this.player.startTime;
}
// Note that if you add a new property to the state object, make sure you
// add the corresponding property in the AnimationPlayerFront' initialState
// getter.
@ -274,6 +278,7 @@ var AnimationPlayerActor = ActorClass({
type: this.getType(),
// startTime is null whenever the animation is paused or waiting to start.
startTime: this.player.startTime,
previousStartTime: this.previousStartTime,
currentTime: this.player.currentTime,
playState: this.player.playState,
playbackRate: this.player.playbackRate,
@ -413,8 +418,6 @@ var AnimationPlayerActor = ActorClass({
});
var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
AUTO_REFRESH_EVENT: "updated-state",
initialize: function(conn, form, detail, ctx) {
Front.prototype.initialize.call(this, conn, form, detail, ctx);
@ -431,7 +434,6 @@ var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
},
destroy: function() {
this.stopAutoRefresh();
Front.prototype.destroy.call(this);
},
@ -443,6 +445,7 @@ var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
return {
type: this._form.type,
startTime: this._form.startTime,
previousStartTime: this._form.previousStartTime,
currentTime: this._form.currentTime,
playState: this._form.playState,
playbackRate: this._form.playbackRate,
@ -464,72 +467,14 @@ var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
this.state = state;
}),
// About auto-refresh:
//
// The AnimationPlayerFront is capable of automatically refreshing its state
// by calling the getCurrentState method at regular intervals. This allows
// consumers to update their knowledge of the player's currentTime, playState,
// ... dynamically.
//
// Calling startAutoRefresh will start the automatic refreshing of the state,
// and calling stopAutoRefresh will stop it.
// Once the automatic refresh has been started, the AnimationPlayerFront emits
// "updated-state" events everytime the state changes.
//
// Note that given the time-related nature of animations, the actual state
// changes a lot more often than "updated-state" events are emitted. This is
// to avoid making many protocol requests.
/**
* Start auto-refreshing this player's state.
* @param {Number} interval Optional auto-refresh timer interval to override
* the default value.
*/
startAutoRefresh: function(interval=PLAYER_DEFAULT_AUTO_REFRESH_TIMEOUT) {
if (this.autoRefreshTimer) {
return;
}
this.autoRefreshTimer = setInterval(() => {
// Save the refresh promise for tests. The tests need to detect when the
// last request completes or they might finish too early.
// Storing the latest Promise is enough to know that there is no pending
// requests left as p.js guarantees the last request will get the reply
// last.
this.pendingRefreshStatePromise = this.refreshState();
this.pendingRefreshStatePromise.then(() => {
this.pendingRefreshStatePromise = null;
});
}, interval);
},
/**
* Stop auto-refreshing this player's state.
*/
stopAutoRefresh: function() {
if (!this.autoRefreshTimer) {
return;
}
clearInterval(this.autoRefreshTimer);
this.autoRefreshTimer = null;
},
/**
* Called automatically when auto-refresh is on. Doesn't return anything, but
* emits the "updated-state" event.
* Refresh the current state of this animation on the client from information
* found on the server. Doesn't return anything, just stores the new state.
*/
refreshState: Task.async(function*() {
let data = yield this.getCurrentState();
// By the time the new state is received, auto-refresh might be stopped.
if (!this.autoRefreshTimer) {
return;
}
if (this.currentStateHasChanged) {
this.state = data;
events.emit(this, this.AUTO_REFRESH_EVENT, this.state);
}
}),
@ -840,6 +785,24 @@ var AnimationsActor = exports.AnimationsActor = ActorClass({
response: {}
}),
/**
* Toggle (play/pause) several animations at the same time.
* @param {Array} players A list of AnimationPlayerActor objects.
* @param {Boolean} shouldPause If set to true, the players will be paused,
* otherwise they will be played.
*/
toggleSeveral: method(function(players, shouldPause) {
return promise.all(players.map(player => {
return shouldPause ? player.pause() : player.play();
}));
}, {
request: {
players: Arg(0, "array:animationplayer"),
shouldPause: Arg(1, "boolean")
},
response: {}
}),
/**
* Set the current time of several animations at the same time.
* @param {Array} players A list of AnimationPlayerActor.

View File

@ -26,8 +26,6 @@ support-files =
[browser_animation_actors_03.js]
[browser_animation_actors_04.js]
skip-if = e10s # Bug 1183605 - toolkit/devtools/server/tests/browser/ tests are still disabled in E10S
[browser_animation_actors_05.js]
skip-if = e10s # Bug 1183605 - toolkit/devtools/server/tests/browser/ tests are still disabled in E10S
[browser_animation_actors_06.js]
[browser_animation_actors_07.js]
[browser_animation_actors_08.js]

View File

@ -1,64 +0,0 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that AnimationPlayers can auto-refresh their states.
const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
let form = yield connectDebuggerClient(client);
let inspector = InspectorFront(client, form);
let walker = yield inspector.getWalker();
let front = AnimationsFront(client, form);
let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
let [player] = yield front.getAnimationPlayersForNode(node);
ok(player.startAutoRefresh, "The startAutoRefresh function is available");
ok(player.stopAutoRefresh, "The stopAutoRefresh function is available");
ok(player.state, "The current state is stored on the player");
info("Subscribe to the refresh event, start the auto-refresh and wait for " +
"a few events to be received");
player.startAutoRefresh();
let onAllEventsReceived = new Promise(resolve => {
let expected = 5;
let previousState = player.initialState;
let onNewState = state => {
ok(state.currentTime !== previousState.currentTime,
"The time has changed since the last update");
expected --;
previousState = state;
if (expected === 0) {
player.off(player.AUTO_REFRESH_EVENT, onNewState);
info("Stop the auto-refresh");
player.stopAutoRefresh();
if (player.pendingRefreshStatePromise) {
// A new request was fired before we had chance to stop it. Wait for
// it to complete.
player.pendingRefreshStatePromise.then(resolve);
} else {
resolve();
}
}
};
player.on(player.AUTO_REFRESH_EVENT, onNewState);
});
yield onAllEventsReceived;
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();
});

View File

@ -11,7 +11,7 @@ const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
@ -37,10 +37,11 @@ function* playerHasCompleteStateAtAllTimes(walker, front) {
// Get the state over and over again and check that the object returned
// contains all keys.
// Normally, only the currentTime will have changed in between 2 calls.
for (let i = 0; i < 10; i ++) {
for (let i = 0; i < 10; i++) {
let state = yield player.getCurrentState();
keys.forEach(key => {
ok(typeof state[key] !== "undefined", "The state retrieved has key " + key);
ok(typeof state[key] !== "undefined",
"The state retrieved has key " + key);
});
}
}

View File

@ -4,13 +4,24 @@
"use strict";
// Check that the AnimationsActor can pause/play all animations at once.
// Check that the AnimationsActor can pause/play all animations at once, and
// check that it can also pause/play a given list of animations at once.
const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
// List of selectors that match "all" animated nodes in the test page.
// This list misses a bunch of animated nodes on purpose. Only the ones that
// have infinite animations are listed. This is done to avoid intermittents
// caused when finite animations are already done playing by the time the test
// runs.
const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations",
".delayed-animation"];
// List of selectors that match some animated nodes in the test page only.
const SOME_ANIMATED_NODES = [".simple-animation", ".delayed-animation"];
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
@ -21,35 +32,57 @@ add_task(function*() {
info("Pause all animations in the test document");
yield front.pauseAll();
yield checkAllAnimationsStates(walker, front, "paused");
yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "paused");
info("Play all animations in the test document");
yield front.playAll();
yield checkAllAnimationsStates(walker, front, "running");
yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "running");
info("Pause all animations in the test document using toggleAll");
yield front.toggleAll();
yield checkAllAnimationsStates(walker, front, "paused");
yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "paused");
info("Play all animations in the test document using toggleAll");
yield front.toggleAll();
yield checkAllAnimationsStates(walker, front, "running");
yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "running");
info("Pause a given list of animations only");
let players = [];
for (let selector of SOME_ANIMATED_NODES) {
let [player] = yield getPlayersFor(walker, front, selector);
players.push(player);
}
yield front.toggleSeveral(players, true);
yield checkAnimationsStates(walker, front, SOME_ANIMATED_NODES, "paused");
yield checkAnimationsStates(walker, front, [".multiple-animations"], "running");
info("Play the same list of animations");
yield front.toggleSeveral(players, false);
yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "running");
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();
});
function* checkAllAnimationsStates(walker, front, playState) {
info("Checking the playState of all the nodes that have infinite running animations");
function* checkAnimationsStates(walker, front, selectors, playState) {
info("Checking the playState of all the nodes that have infinite running " +
"animations");
let selectors = [".simple-animation", ".multiple-animations", ".delayed-animation"];
for (let selector of selectors) {
info("Getting the AnimationPlayerFront for node " + selector);
let node = yield walker.querySelector(walker.rootNode, selector);
let [player] = yield front.getAnimationPlayersForNode(node);
yield player.ready;
let state = yield player.getCurrentState();
is(state.playState, playState,
"The playState of node " + selector + " is " + playState);
let [player] = yield getPlayersFor(walker, front, selector);
yield player.ready();
yield checkPlayState(player, selector, playState);
}
}
function* getPlayersFor(walker, front, selector) {
let node = yield walker.querySelector(walker.rootNode, selector);
return front.getAnimationPlayersForNode(node);
}
function* checkPlayState(player, selector, expectedState) {
let state = yield player.getCurrentState();
is(state.playState, expectedState,
"The playState of node " + selector + " is " + expectedState);
}

View File

@ -14,7 +14,7 @@ const URL = MAIN_DOMAIN + "animation.html";
add_task(function*() {
info("Creating a test document with 2 iframes containing animated nodes");
let doc = yield addTab("data:text/html;charset=utf-8," +
yield addTab("data:text/html;charset=utf-8," +
"<iframe id='i1' src='" + URL + "'></iframe>" +
"<iframe id='i2' src='" + URL + "'></iframe>");
@ -48,7 +48,8 @@ function* checkState(front, nodeFront, playState) {
let [player] = yield front.getAnimationPlayersForNode(nodeFront);
yield player.ready;
let state = yield player.getCurrentState();
is(state.playState, playState, "The playState of the test node is " + playState);
is(state.playState, playState,
"The playState of the test node is " + playState);
}
function* getNodeInFrame(walker, frameSelector, nodeSelector) {

View File

@ -14,7 +14,7 @@ const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
@ -31,8 +31,8 @@ add_task(function*() {
{attributeName: "class", newValue: "multiple-animations-2"}
]);
info("Get the list of players, by the time this executes, the first, short, " +
"animation should have ended.");
info("Get the list of players, by the time this executes, the first, " +
"short, animation should have ended.");
let players = yield front.getAnimationPlayersForNode(node);
if (players.length === 3) {
info("The short animation hasn't ended yet, wait for a bit.");
@ -45,13 +45,19 @@ add_task(function*() {
is(players.length, 2, "2 animations remain on the node");
is(players[0].state.duration, 1000, "The duration of the first animation is correct");
is(players[0].state.delay, 2000, "The delay of the first animation is correct");
is(players[0].state.iterationCount, null, "The iterationCount of the first animation is correct");
is(players[0].state.duration, 1000,
"The duration of the first animation is correct");
is(players[0].state.delay, 2000,
"The delay of the first animation is correct");
is(players[0].state.iterationCount, null,
"The iterationCount of the first animation is correct");
is(players[1].state.duration, 3000, "The duration of the second animation is correct");
is(players[1].state.delay, 1000, "The delay of the second animation is correct");
is(players[1].state.iterationCount, 100, "The iterationCount of the second animation is correct");
is(players[1].state.duration, 3000,
"The duration of the second animation is correct");
is(players[1].state.delay, 1000,
"The delay of the second animation is correct");
is(players[1].state.iterationCount, 100,
"The iterationCount of the second animation is correct");
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();

View File

@ -10,7 +10,7 @@ const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());

View File

@ -11,7 +11,7 @@ const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());

View File

@ -13,7 +13,7 @@ const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());

View File

@ -14,8 +14,8 @@ const URL = MAIN_DOMAIN + "animation.html";
add_task(function*() {
info("Creating a test document with 2 iframes containing animated nodes");
let doc = yield addTab("data:text/html;charset=utf-8," +
"<iframe id='iframe' src='" + URL + "'></iframe>");
yield addTab("data:text/html;charset=utf-8," +
"<iframe id='iframe' src='" + URL + "'></iframe>");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());