diff --git a/.eslintignore b/.eslintignore index 0bcd8ce43861..f12fbc099222 100644 --- a/.eslintignore +++ b/.eslintignore @@ -65,11 +65,9 @@ browser/base/content/test/general/file_csp_block_all_mixedcontent.html browser/base/content/test/urlbar/file_blank_but_not_blank.html browser/base/content/newtab/** browser/components/downloads/** -browser/components/feeds/** browser/components/privatebrowsing/** browser/components/sessionstore/** browser/components/tabview/** -browser/components/translation/** # generated files in cld2 browser/components/translation/cld2/cld-worker.js browser/extensions/pdfjs/** diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js index bceb49e6a579..b470efd3d5e7 100644 --- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -399,14 +399,16 @@ SocialShare = { if (!SocialUI.canSharePage(sharedURI)) return; + let browserMM = gBrowser.selectedBrowser.messageManager; + // the point of this action type is that we can use existing share // endpoints (e.g. oexchange) that do not support additional // socialapi functionality. One tweak is that we shoot an event // containing the open graph data. let _dataFn; if (!pageData || sharedURI == gBrowser.currentURI) { - messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => { - messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn); + browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => { + browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn); let pageData = msg.json; if (graphData) { // overwrite data retreived from page with data given to us as a param @@ -416,17 +418,17 @@ SocialShare = { } this.sharePage(providerOrigin, pageData, target, anchor); }); - gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData", null, { target }); + browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target }); return; } // if this is a share of a selected item, get any microformats if (!pageData.microformats && target) { - messageManager.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => { - messageManager.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn); + browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => { + browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn); pageData.microformats = msg.data; this.sharePage(providerOrigin, pageData, target, anchor); }); - gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target }); + browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target }); return; } this.currentShare = pageData; diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js index 0321d694972b..b1906537128b 100644 --- a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js +++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js @@ -49,10 +49,12 @@ var gTests = [ // We need to load the content script in the first window so that we can // catch the notifications fired globally when closing the second window. gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true); - yield BrowserTestUtils.closeWindow(win); - yield expectObserverCalled("recording-window-ended"); - yield expectObserverCalled("recording-device-events"); + let promises = [promiseObserverCalled("recording-device-events"), + promiseObserverCalled("recording-window-ended")]; + yield BrowserTestUtils.closeWindow(win); + yield Promise.all(promises); + yield expectNoObserverCalled(); yield checkNotSharing(); } diff --git a/browser/components/feeds/FeedConverter.js b/browser/components/feeds/FeedConverter.js index 5e18c0982e93..9e6fc17e71fe 100644 --- a/browser/components/feeds/FeedConverter.js +++ b/browser/components/feeds/FeedConverter.js @@ -1,4 +1,4 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* 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/. */ @@ -227,7 +227,7 @@ FeedConverter.prototype = { let feedReader = safeGetCharPref(getPrefActionForType(feedType), "bookmarks"); feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader); return; - } catch(ex) { /* fallback to preview mode */ } + } catch (ex) { /* fallback to preview mode */ } } } } diff --git a/browser/components/feeds/FeedWriter.js b/browser/components/feeds/FeedWriter.js index 0c99f58d7fab..51bf53d5b6a9 100644 --- a/browser/components/feeds/FeedWriter.js +++ b/browser/components/feeds/FeedWriter.js @@ -611,7 +611,7 @@ FeedWriter.prototype = { if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask") alwaysUse = true; } - catch(ex) { } + catch (ex) { } this._setCheckboxCheckedState(checkbox, alwaysUse); } }, @@ -949,7 +949,7 @@ FeedWriter.prototype = { }, receiveMessage(msg) { - switch(msg.name) { + switch (msg.name) { case "FeedWriter:SetApplicationLauncherMenuItem": let menuItem = null; diff --git a/browser/components/feeds/WebContentConverter.js b/browser/components/feeds/WebContentConverter.js index 0fef8b1d2f13..76fe4e7bf554 100644 --- a/browser/components/feeds/WebContentConverter.js +++ b/browser/components/feeds/WebContentConverter.js @@ -920,7 +920,7 @@ WebContentConverterRegistrarContent.prototype = { let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + "."); try { this._registerContentHandlerHavingBranch(branch); - } catch(ex) { + } catch (ex) { // do nothing, the next branch might have values } } diff --git a/browser/components/feeds/content/subscribe.js b/browser/components/feeds/content/subscribe.js index ab2eac4ebc54..99c6d4e9e89a 100644 --- a/browser/components/feeds/content/subscribe.js +++ b/browser/components/feeds/content/subscribe.js @@ -8,7 +8,7 @@ var SubscribeHandler = { * The nsIFeedWriter object that produces the UI */ _feedWriter: null, - + init: function SH_init() { this._feedWriter = new BrowserFeedWriter(); }, diff --git a/browser/components/feeds/test/test_bug436801.html b/browser/components/feeds/test/test_bug436801.html index a72d2c11a059..29fb5acf0a69 100644 --- a/browser/components/feeds/test/test_bug436801.html +++ b/browser/components/feeds/test/test_bug436801.html @@ -86,7 +86,7 @@ function checkNode(node, schema) { var tag = schema.shift(); is(node.localName, tag, "Element should have expected tag"); while (schema.length) { - var val = schema.shift(); + let val = schema.shift(); if (Array.isArray(val)) var childSchema = val; else @@ -98,7 +98,7 @@ function checkNode(node, schema) { }; for (var name in attrSchema) { var [ns, nsName] = name.split(":"); - var val = nsName ? node.getAttributeNS(nsTable[ns], nsName) : + let val = nsName ? node.getAttributeNS(nsTable[ns], nsName) : node.getAttribute(name); is(val, attrSchema[name], "Attribute " + name + " should match"); } diff --git a/browser/components/feeds/test/test_registerHandler.html b/browser/components/feeds/test/test_registerHandler.html index 5241caf0e5d6..34e61d034f9e 100644 --- a/browser/components/feeds/test/test_registerHandler.html +++ b/browser/components/feeds/test/test_registerHandler.html @@ -28,7 +28,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402788 else navigator.registerContentHandler(aTxt, aUri, aTitle); } - catch(e) { + catch (e) { return false; } @@ -61,10 +61,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=402788 // ftp should not work is(testRegisterHandler(true, "foo", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with ftp scheme should not work"); is(testRegisterHandler(false, "application/rss+xml", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with ftp scheme should not work"); - // chrome should not work + // chrome should not work is(testRegisterHandler(true, "foo", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with chrome scheme should not work"); is(testRegisterHandler(false, "application/rss+xml", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with chrome scheme should not work"); - // foo should not work + // foo should not work is(testRegisterHandler(true, "foo", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with foo scheme should not work"); is(testRegisterHandler(false, "application/rss+xml", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with foo scheme should not work"); diff --git a/browser/components/translation/BingTranslator.jsm b/browser/components/translation/BingTranslator.jsm index bb498f0464ea..6c23935b8464 100644 --- a/browser/components/translation/BingTranslator.jsm +++ b/browser/components/translation/BingTranslator.jsm @@ -286,7 +286,7 @@ BingRequest.prototype = { * Initiates the request */ fireRequest: function() { - return Task.spawn(function *(){ + return Task.spawn(function *() { // Prepare authentication. let token = yield BingTokenManager.getToken(); let auth = "Bearer " + token; diff --git a/browser/components/translation/TranslationDocument.jsm b/browser/components/translation/TranslationDocument.jsm index a47af78af8fe..98bcaf261b6b 100644 --- a/browser/components/translation/TranslationDocument.jsm +++ b/browser/components/translation/TranslationDocument.jsm @@ -166,7 +166,7 @@ this.TranslationDocument.prototype = { item.original.push(objInMap); str += this.generateTextForItem(objInMap); wasLastItemPlaceholder = false; - } else { + } else if (!wasLastItemPlaceholder) { // Otherwise, if this node doesn't contain any useful content, // or if it is a root itself, we can replace it with a placeholder node. // We can't simply eliminate this node from our string representation @@ -174,11 +174,9 @@ this.TranslationDocument.prototype = { // probably merge two separate text nodes). // It's not necessary to add more than one placeholder in sequence; // we can optimize them away. - if (!wasLastItemPlaceholder) { - item.original.push(TranslationItem_NodePlaceholder); - str += '
'; - wasLastItemPlaceholder = true; - } + item.original.push(TranslationItem_NodePlaceholder); + str += '
'; + wasLastItemPlaceholder = true; } } @@ -277,9 +275,15 @@ TranslationItem.prototype = { isSimpleRoot: false, toString: function() { - let rootType = this.isRoot - ? (this.isSimpleRoot ? ' (simple root)' : ' (non simple root)') - : ''; + let rootType = ""; + if (this.isRoot) { + if (this.isSimpleRoot) { + rootType = " (simple root)"; + } + else { + rootType = " (non simple root)"; + } + } return "[object TranslationItem: <" + this.nodeRef.localName + ">" + rootType + "]"; }, diff --git a/browser/components/translation/YandexTranslator.jsm b/browser/components/translation/YandexTranslator.jsm index 6d471a00f354..ab92e09625c9 100644 --- a/browser/components/translation/YandexTranslator.jsm +++ b/browser/components/translation/YandexTranslator.jsm @@ -293,7 +293,7 @@ YandexRequest.prototype = { * Initiates the request */ fireRequest: function() { - return Task.spawn(function *(){ + return Task.spawn(function *() { // Prepare URL. let url = getUrlParam("https://translate.yandex.net/api/v1.5/tr.json/translate", "browser.translation.yandex.translateURLOverride"); diff --git a/browser/components/translation/cld2/post.js b/browser/components/translation/cld2/post.js index 99fa4d6084a5..a3e8b8522c03 100644 --- a/browser/components/translation/cld2/post.js +++ b/browser/components/translation/cld2/post.js @@ -125,7 +125,7 @@ for (let code of Object.keys(Encodings)) { addOnPreMain(function() { - onmessage = function(aMsg){ + onmessage = function(aMsg) { let data = aMsg['data']; let langInfo; diff --git a/browser/components/translation/test/browser_translation_bing.js b/browser/components/translation/test/browser_translation_bing.js index 662df8e4ff3d..399a67022dd1 100644 --- a/browser/components/translation/test/browser_translation_bing.js +++ b/browser/components/translation/test/browser_translation_bing.js @@ -103,7 +103,7 @@ add_task(function* test_handling_out_of_valid_key_error() { * * @param filename Name of a fixture file. */ -function constructFixtureURL(filename){ +function constructFixtureURL(filename) { // Deduce the Mochitest server address in use from a pref that was pre-processed. let server = Services.prefs.getCharPref("browser.translation.bing.authURL") .replace("http://", ""); diff --git a/browser/components/translation/test/browser_translation_telemetry.js b/browser/components/translation/test/browser_translation_telemetry.js index 2fdda7459b7b..7d87da12933f 100644 --- a/browser/components/translation/test/browser_translation_telemetry.js +++ b/browser/components/translation/test/browser_translation_telemetry.js @@ -24,7 +24,7 @@ var MetricsChecker = { DETECT_LANG : Services.telemetry.getHistogramById("SHOULD_AUTO_DETECT_LANGUAGE"), }, - reset: function(){ + reset: function() { for (let i of Object.keys(this.HISTOGRAMS)) { this.HISTOGRAMS[i].clear(); } @@ -65,7 +65,7 @@ var MetricsChecker = { /** * A recurrent loop for making assertions about collected metrics. */ - _assertionLoop: function (prevMetrics, metrics, additions){ + _assertionLoop: function (prevMetrics, metrics, additions) { for (let metric of Object.keys(additions)) { let addition = additions[metric]; // Allows nesting metrics. Useful for keyed histograms. @@ -117,9 +117,9 @@ var translate = Task.async(function*(text, from, closeTab = true) { yield acceptTranslationOffer(tab); if (closeTab) { gBrowser.removeTab(tab); - } else { - return tab; + return null; } + return tab; }); function waitForMessage({messageManager}, name) { diff --git a/browser/components/translation/test/browser_translation_yandex.js b/browser/components/translation/test/browser_translation_yandex.js index 4d66126dfb66..6e0af18e6c39 100644 --- a/browser/components/translation/test/browser_translation_yandex.js +++ b/browser/components/translation/test/browser_translation_yandex.js @@ -91,7 +91,7 @@ add_task(function* test_preference_attribution() { * * @param filename Name of a fixture file. */ -function constructFixtureURL(filename){ +function constructFixtureURL(filename) { // Deduce the Mochitest server address in use from a pref that was pre-processed. let server = Services.prefs.getCharPref("browser.translation.yandex.translateURLOverride") .replace("http://", ""); diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index c8db2bc006d1..5c3b2b2a7973 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -61,7 +61,7 @@ STUB_HOOK = $(NSINSTALL) -D '$(ABS_DIST)/$(PKG_INST_PATH)'; \ $(NULL) endif -SEARCHPLUGINS_FILENAMES := $(shell $(PYTHON) $(srcdir)/searchplugins.py $(srcdir)/search/list.json $(AB_CD)) +SEARCHPLUGINS_FILENAMES := $(shell $(call py_action,output_searchplugins_list,$(srcdir)/search/list.json $(AB_CD))) SEARCHPLUGINS_PATH := .deps/generated_$(AB_CD) SEARCHPLUGINS_TARGET := libs searchplugins SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(warning Missing searchplugin: $(plugin)))) @@ -84,7 +84,7 @@ include $(topsrcdir)/config/rules.mk include $(topsrcdir)/toolkit/locales/l10n.mk $(list-json): $(call mkdir_deps,$(SEARCHPLUGINS_PATH)) $(if $(IS_LANGUAGE_REPACK),FORCE) - $(shell $(PYTHON) $(srcdir)/searchjson.py $(srcdir)/search/list.json $(AB_CD) $(list-json)) + $(call py_action,generate_searchjson,$(srcdir)/search/list.json $(AB_CD) $(list-json)) searchplugins:: $(list-json) $(STAGEDIST): $(DIST)/branding diff --git a/devtools/client/animationinspector/components/animation-time-block.js b/devtools/client/animationinspector/components/animation-time-block.js index 2a68a8344b10..65c9e78aef55 100644 --- a/devtools/client/animationinspector/components/animation-time-block.js +++ b/devtools/client/animationinspector/components/animation-time-block.js @@ -12,6 +12,23 @@ const {createNode, TimeScale} = require("devtools/client/animationinspector/util const { LocalizationHelper } = require("devtools/shared/l10n"); const L10N = new LocalizationHelper("devtools/locale/animationinspector.properties"); +// In the createPathSegments function, an animation duration is divided by +// DURATION_RESOLUTION in order to draw the way the animation progresses. +// But depending on the timing-function, we may be not able to make the graph +// smoothly progress if this resolution is not high enough. +// So, if the difference of animation progress between 2 divisions is more than +// MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides +// by DURATION_RESOLUTION. +// DURATION_RESOLUTION shoud be integer and more than 2. +const DURATION_RESOLUTION = 4; +// MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1. +const MIN_PROGRESS_THRESHOLD = 0.1; +// Show max 10 iterations for infinite animations +// to give users a clue that the animation does repeat. +const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10; +// SVG namespace +const SVG_NS = "http://www.w3.org/2000/svg"; + /** * UI component responsible for displaying a single animation timeline, which * basically looks like a rectangle that shows the delay and iterations. @@ -51,41 +68,152 @@ AnimationTimeBlock.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 {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} = + const {x, delayX, delayW, endDelayX, endDelayW} = TimeScale.getAnimationDimensions(animation); - // background properties for .iterations element - let backgroundIterations = TimeScale.getIterationsBackgroundData(animation); - - createNode({ + // Animation summary graph element. + const summaryEl = createNode({ parent: this.containerEl, + namespace: "http://www.w3.org/2000/svg", + nodeType: "svg", attributes: { - "class": "iterations" + (state.iterationCount ? "" : " infinite"), - // Individual iterations are represented by setting the size of the - // repeating linear-gradient. - // The background-size, background-position, background-repeat represent - // iterationCount and iterationStart. - "style": `left:${x}%; - width:${iterationW}%; - background-size:${backgroundIterations.size}% 100%; - background-position:${backgroundIterations.position}% 0; - background-repeat:${backgroundIterations.repeat};` + "class": "summary", + "preserveAspectRatio": "none", + "style": `left: ${ x - (state.delay > 0 ? delayW : 0) }%` } }); - // The animation name is displayed over the iterations. - // Note that in case of negative delay, it is pushed towards the right so - // the delay element does not overlap. + // Total displayed duration + const totalDisplayedDuration = state.playbackRate * TimeScale.getDuration(); + + // Calculate stroke height in viewBox to display stroke of path. + const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight; + + // Set viewBox + summaryEl.setAttribute("viewBox", + `${ state.delay < 0 ? state.delay : 0 } + -${ 1 + strokeHeightForViewBox } + ${ totalDisplayedDuration } + ${ 1 + strokeHeightForViewBox * 2 }`); + + // Get a helper function that returns the path segment of timing-function. + const segmentHelper = getSegmentHelper(state, this.win); + + // Minimum segment duration is the duration of one pixel. + const minSegmentDuration = + totalDisplayedDuration / this.containerEl.clientWidth; + // Minimum progress threshold. + let minProgressThreshold = MIN_PROGRESS_THRESHOLD; + // If the easing is step function, + // minProgressThreshold should be changed by the steps. + const stepFunction = state.easing.match(/steps\((\d+)/); + if (stepFunction) { + minProgressThreshold = 1 / (parseInt(stepFunction[1], 10) + 1); + } + + // Starting time of main iteration. + let mainIterationStartTime = 0; + let iterationStart = state.iterationStart; + let iterationCount = state.iterationCount ? state.iterationCount : Infinity; + + // Append delay. + if (state.delay > 0) { + renderDelay(summaryEl, state, segmentHelper); + mainIterationStartTime = state.delay; + } else { + const negativeDelayCount = -state.delay / state.duration; + // Move to forward the starting point for negative delay. + iterationStart += negativeDelayCount; + // Consume iteration count by negative delay. + if (iterationCount !== Infinity) { + iterationCount -= negativeDelayCount; + } + } + + // Append 1st section of iterations, + // This section is only useful in cases where iterationStart has decimals. + // e.g. + // if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75. + const firstSectionCount = + iterationStart % 1 === 0 + ? 0 : Math.min(iterationCount, 1) - iterationStart % 1; + if (firstSectionCount) { + renderFirstIteration(summaryEl, state, mainIterationStartTime, + firstSectionCount, minSegmentDuration, + minProgressThreshold, segmentHelper); + } + + if (iterationCount === Infinity) { + // If the animation repeats infinitely, + // we fill the remaining area with iteration paths. + renderInfinity(summaryEl, state, mainIterationStartTime, + firstSectionCount, totalDisplayedDuration, + minSegmentDuration, minProgressThreshold, segmentHelper); + } else { + // Otherwise, we show remaining iterations, endDelay and fill. + + // Append forwards fill-mode. + if (state.fill === "both" || state.fill === "forwards") { + renderForwardsFill(summaryEl, state, mainIterationStartTime, + iterationCount, totalDisplayedDuration, + segmentHelper); + } + + // Append middle section of iterations. + // e.g. + // if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2. + const middleSectionCount = + Math.floor(iterationCount - firstSectionCount); + renderMiddleIterations(summaryEl, state, mainIterationStartTime, + firstSectionCount, middleSectionCount, + minSegmentDuration, minProgressThreshold, + segmentHelper); + + // Append last section of iterations, if there is remaining iteration. + // e.g. + // if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25. + const lastSectionCount = + iterationCount - middleSectionCount - firstSectionCount; + if (lastSectionCount) { + renderLastIteration(summaryEl, state, mainIterationStartTime, + firstSectionCount, middleSectionCount, + lastSectionCount, minSegmentDuration, + minProgressThreshold, segmentHelper); + } + + // Append endDelay. + if (state.endDelay > 0) { + renderEndDelay(summaryEl, state, + mainIterationStartTime, iterationCount, segmentHelper); + } + } + + // Append negative delay (which overlap the animation). + if (state.delay < 0) { + segmentHelper.animation.effect.timing.fill = "both"; + segmentHelper.asOriginalBehavior = false; + renderNegativeDelayHiddenProgress(summaryEl, state, minSegmentDuration, + minProgressThreshold, segmentHelper); + } + // Append negative endDelay (which overlap the animation). + if (state.iterationCount && state.endDelay < 0) { + if (segmentHelper.asOriginalBehavior) { + segmentHelper.animation.effect.timing.fill = "both"; + segmentHelper.asOriginalBehavior = false; + } + renderNegativeEndDelayHiddenProgress(summaryEl, state, + minSegmentDuration, + minProgressThreshold, + segmentHelper); + } + + // The animation name is displayed over the animation. createNode({ parent: createNode({ parent: this.containerEl, attributes: { "class": "name", - "title": this.getTooltipText(state), - // Place the name at the same position as the iterations, but make - // space for the negative delay if any. - "style": `left:${x + negativeDelayW}%; - width:${iterationW - negativeDelayW}%;` + "title": this.getTooltipText(state) }, }), textContent: state.name @@ -97,21 +225,25 @@ AnimationTimeBlock.prototype = { createNode({ parent: this.containerEl, attributes: { - "class": "delay" + (state.delay < 0 ? " negative" : ""), - "style": `left:${delayX}%; - width:${delayW}%;` + "class": "delay" + + (state.delay < 0 ? " negative" : " positive") + + (state.fill === "both" || + state.fill === "backwards" ? " fill" : ""), + "style": `left:${ delayX }%; width:${ delayW }%;` } }); } // endDelay - if (state.endDelay) { + if (state.iterationCount && state.endDelay) { createNode({ parent: this.containerEl, attributes: { - "class": "end-delay" + (state.endDelay < 0 ? " negative" : ""), - "style": `left:${endDelayX}%; - width:${endDelayW}%;` + "class": "end-delay" + + (state.endDelay < 0 ? " negative" : " positive") + + (state.fill === "both" || + state.fill === "forwards" ? " fill" : ""), + "style": `left:${ endDelayX }%; width:${ endDelayW }%;` } }); } @@ -119,7 +251,7 @@ AnimationTimeBlock.prototype = { getTooltipText: function (state) { let getTime = time => L10N.getFormatStr("player.timeLabel", - L10N.numberWithDecimals(time / 1000, 2)); + L10N.numberWithDecimals(time / 1000, 2)); let text = ""; @@ -163,6 +295,27 @@ AnimationTimeBlock.prototype = { text += "\n"; } + // Adding the easing. + if (state.easing) { + text += L10N.getStr("player.animationEasingLabel") + " "; + text += state.easing; + text += "\n"; + } + + // Adding the fill mode. + if (state.fill) { + text += L10N.getStr("player.animationFillLabel") + " "; + text += state.fill; + text += "\n"; + } + + // Adding the direction mode. + if (state.direction) { + text += L10N.getStr("player.animationDirectionLabel") + " "; + text += state.direction; + text += "\n"; + } + // Adding the playback rate if it's different than 1. if (state.playbackRate !== 1) { text += L10N.getStr("player.animationRateLabel") + " "; @@ -174,10 +327,10 @@ AnimationTimeBlock.prototype = { // needed. if (state.propertyState) { if (state.propertyState - .every(propState => propState.runningOnCompositor)) { + .every(propState => propState.runningOnCompositor)) { text += L10N.getStr("player.allPropertiesOnCompositorTooltip"); } else if (state.propertyState - .some(propState => propState.runningOnCompositor)) { + .some(propState => propState.runningOnCompositor)) { text += L10N.getStr("player.somePropertiesOnCompositorTooltip"); } } else if (state.isRunningOnCompositor) { @@ -190,6 +343,10 @@ AnimationTimeBlock.prototype = { onClick: function (e) { e.stopPropagation(); this.emit("selected", this.animation); + }, + + get win() { + return this.containerEl.ownerDocument.defaultView; } }; @@ -216,3 +373,346 @@ function getFormattedAnimationTitle({state}) { return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name); } + +/** + * Render delay section. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderDelay(parentEl, state, segmentHelper) { + const startSegment = segmentHelper.getSegment(0); + const endSegment = { x: state.delay, y: startSegment.y }; + appendPathElement(parentEl, [startSegment, endSegment], "delay-path"); +} + +/** + * Render first iteration section. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} mainIterationStartTime - Starting time of main iteration. + * @param {Number} firstSectionCount - Iteration count of first section. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderFirstIteration(parentEl, state, mainIterationStartTime, + firstSectionCount, minSegmentDuration, + minProgressThreshold, segmentHelper) { + const startTime = mainIterationStartTime; + const endTime = startTime + firstSectionCount * state.duration; + const segments = + createPathSegments(startTime, endTime, minSegmentDuration, + minProgressThreshold, segmentHelper); + appendPathElement(parentEl, segments, "iteration-path"); +} + +/** + * Render middle iterations section. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} mainIterationStartTime - Starting time of main iteration. + * @param {Number} firstSectionCount - Iteration count of first section. + * @param {Number} middleSectionCount - Iteration count of middle section. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderMiddleIterations(parentEl, state, mainIterationStartTime, + firstSectionCount, middleSectionCount, + minSegmentDuration, minProgressThreshold, + segmentHelper) { + const offset = mainIterationStartTime + firstSectionCount * state.duration; + for (let i = 0; i < middleSectionCount; i++) { + // Get the path segments of each iteration. + const startTime = offset + i * state.duration; + const endTime = startTime + state.duration; + const segments = + createPathSegments(startTime, endTime, minSegmentDuration, + minProgressThreshold, segmentHelper); + appendPathElement(parentEl, segments, "iteration-path"); + } +} + +/** + * Render last iteration section. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} mainIterationStartTime - Starting time of main iteration. + * @param {Number} firstSectionCount - Iteration count of first section. + * @param {Number} middleSectionCount - Iteration count of middle section. + * @param {Number} lastSectionCount - Iteration count of last section. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderLastIteration(parentEl, state, mainIterationStartTime, + firstSectionCount, middleSectionCount, + lastSectionCount, minSegmentDuration, + minProgressThreshold, segmentHelper) { + const startTime = mainIterationStartTime + + (firstSectionCount + middleSectionCount) * state.duration; + const endTime = startTime + lastSectionCount * state.duration; + const segments = + createPathSegments(startTime, endTime, minSegmentDuration, + minProgressThreshold, segmentHelper); + appendPathElement(parentEl, segments, "iteration-path"); +} + +/** + * Render Infinity iterations. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} mainIterationStartTime - Starting time of main iteration. + * @param {Number} firstSectionCount - Iteration count of first section. + * @param {Number} totalDuration - Displayed max duration. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderInfinity(parentEl, state, mainIterationStartTime, + firstSectionCount, totalDuration, minSegmentDuration, + minProgressThreshold, segmentHelper) { + // Calculate the number of iterations to display, + // with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS + let uncappedInfinityIterationCount = + (totalDuration - firstSectionCount * state.duration) / state.duration; + // If there is a small floating point error resulting in, e.g. 1.0000001 + // ceil will give us 2 so round first. + uncappedInfinityIterationCount = + parseFloat(uncappedInfinityIterationCount.toPrecision(6)); + const infinityIterationCount = + Math.min(MAX_INFINITE_ANIMATIONS_ITERATIONS, + Math.ceil(uncappedInfinityIterationCount)); + + // Append first full iteration path. + const firstStartTime = + mainIterationStartTime + firstSectionCount * state.duration; + const firstEndTime = firstStartTime + state.duration; + const firstSegments = + createPathSegments(firstStartTime, firstEndTime, minSegmentDuration, + minProgressThreshold, segmentHelper); + appendPathElement(parentEl, firstSegments, "iteration-path infinity"); + + // Append other iterations. We can copy first segments. + const isAlternate = state.direction.match(/alternate/); + for (let i = 1; i < infinityIterationCount; i++) { + const startTime = firstStartTime + i * state.duration; + let segments; + if (isAlternate && i % 2) { + // Copy as reverse. + segments = firstSegments.map(segment => { + return { x: firstEndTime - segment.x + startTime, y: segment.y }; + }); + } else { + // Copy as is. + segments = firstSegments.map(segment => { + return { x: segment.x - firstStartTime + startTime, y: segment.y }; + }); + } + appendPathElement(parentEl, segments, "iteration-path infinity copied"); + } +} + +/** + * Render endDelay section. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} mainIterationStartTime - Starting time of main iteration. + * @param {Number} iterationCount - Whole iteration count. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderEndDelay(parentEl, state, + mainIterationStartTime, iterationCount, segmentHelper) { + const startTime = mainIterationStartTime + iterationCount * state.duration; + const startSegment = segmentHelper.getSegment(startTime); + const endSegment = { x: startTime + state.endDelay, y: startSegment.y }; + appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path"); +} + +/** + * Render forwards fill section. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} mainIterationStartTime - Starting time of main iteration. + * @param {Number} iterationCount - Whole iteration count. + * @param {Number} totalDuration - Displayed max duration. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderForwardsFill(parentEl, state, mainIterationStartTime, + iterationCount, totalDuration, segmentHelper) { + const startTime = mainIterationStartTime + iterationCount * state.duration + + (state.endDelay > 0 ? state.endDelay : 0); + const startSegment = segmentHelper.getSegment(startTime); + const endSegment = { x: totalDuration, y: startSegment.y }; + appendPathElement(parentEl, [startSegment, endSegment], "fill-forwards-path"); +} + +/** + * Render hidden progress of negative delay. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderNegativeDelayHiddenProgress(parentEl, state, minSegmentDuration, + minProgressThreshold, + segmentHelper) { + const startTime = state.delay; + const endTime = 0; + const segments = + createPathSegments(startTime, endTime, minSegmentDuration, + minProgressThreshold, segmentHelper); + appendPathElement(parentEl, segments, "delay-path negative"); +} + +/** + * Render hidden progress of negative endDelay. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Object} state - State of animation. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object returned by getSegmentHelper. + */ +function renderNegativeEndDelayHiddenProgress(parentEl, state, + minSegmentDuration, + minProgressThreshold, + segmentHelper) { + const endTime = state.delay + state.iterationCount * state.duration; + const startTime = endTime + state.endDelay; + const segments = + createPathSegments(startTime, endTime, minSegmentDuration, + minProgressThreshold, segmentHelper); + appendPathElement(parentEl, segments, "enddelay-path negative"); +} + +/** + * Get a helper function which returns the segment coord from given time. + * @param {Object} state - animation state + * @param {Object} win - window object + * @return {Object} A segmentHelper object that has the following properties: + * - animation: The script animation used to get the progress + * - endTime: The end time of the animation + * - asOriginalBehavior: The spec is that the progress of animation is changed + * if the time of setCurrentTime is during the endDelay. + * Likewise, in case the time is less than 0. + * If this flag is true, we prevent the time + * to make the same animation behavior as the original. + * - getSegment: Helper function that, given a time, + * will calculate the progress through the dummy animation. + */ +function getSegmentHelper(state, win) { + // Create a dummy Animation timing data as the + // state object we're being passed in. + const timing = Object.assign({}, state, { + iterations: state.iterationCount ? state.iterationCount : Infinity + }); + + // Create a dummy Animation with the given timing. + const dummyAnimation = + new win.Animation(new win.KeyframeEffect(null, null, timing), null); + + // Returns segment helper object. + return { + animation: dummyAnimation, + endTime: dummyAnimation.effect.getComputedTiming().endTime, + asOriginalBehavior: true, + getSegment: function (time) { + if (this.asOriginalBehavior) { + // If the given time is less than 0, returned progress is 0. + if (time < 0) { + return { x: time, y: 0 }; + } + // Avoid to apply over endTime. + this.animation.currentTime = time < this.endTime ? time : this.endTime; + } else { + this.animation.currentTime = time; + } + const progress = this.animation.effect.getComputedTiming().progress; + return { x: time, y: Math.max(progress, 0) }; + } + }; +} + +/** + * Create the path segments from given parameters. + * @param {Number} startTime - Starting time of animation. + * @param {Number} endTime - Ending time of animation. + * @param {Number} minSegmentDuration - Minimum segment duration. + * @param {Number} minProgressThreshold - Minimum progress threshold. + * @param {Object} segmentHelper - The object of getSegmentHelper. + * @return {Array} path segments - + * [{x: {Number} time, y: {Number} progress}, ...] + */ +function createPathSegments(startTime, endTime, minSegmentDuration, + minProgressThreshold, segmentHelper) { + // If the duration is too short, early return. + if (endTime - startTime < minSegmentDuration) { + return [segmentHelper.getSegment(startTime), + segmentHelper.getSegment(endTime)]; + } + + // Otherwise, start creating segments. + let pathSegments = []; + + // Append the segment for the startTime position. + const startTimeSegment = segmentHelper.getSegment(startTime); + pathSegments.push(startTimeSegment); + let previousSegment = startTimeSegment; + + // Split the duration in equal intervals, and iterate over them. + // See the definition of DURATION_RESOLUTION for more information about this. + const interval = (endTime - startTime) / DURATION_RESOLUTION; + for (let index = 1; index <= DURATION_RESOLUTION; index++) { + // Create a segment for this interval. + const currentSegment = + segmentHelper.getSegment(startTime + index * interval); + + // If the distance between the Y coordinate (the animation's progress) of + // the previous segment and the Y coordinate of the current segment is too + // large, then recurse with a smaller duration to get more details + // in the graph. + if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) { + // Divide the current interval (excluding start and end bounds + // by adding/subtracting 1ms). + pathSegments = pathSegments.concat( + createPathSegments(previousSegment.x + 1, currentSegment.x - 1, + minSegmentDuration, minProgressThreshold, + segmentHelper)); + } + + pathSegments.push(currentSegment); + previousSegment = currentSegment; + } + + return pathSegments; +} + +/** + * Append path element. + * @param {Element} parentEl - Parent element of this appended path element. + * @param {Array} pathSegments - Path segments. Please see createPathSegments. + * @param {String} cls - Class name. + * @return {Element} path element. + */ +function appendPathElement(parentEl, pathSegments, cls) { + // Create path string. + let path = `M${ pathSegments[0].x },0`; + pathSegments.forEach(pathSegment => { + path += ` L${ pathSegment.x },${ pathSegment.y }`; + }); + path += ` L${ pathSegments[pathSegments.length - 1].x },0 Z`; + // Append and return the path element. + return createNode({ + parent: parentEl, + namespace: SVG_NS, + nodeType: "path", + attributes: { + "d": path, + "class": cls, + "vector-effect": "non-scaling-stroke", + "transform": "scale(1, -1)" + } + }); +} diff --git a/devtools/client/animationinspector/test/browser.ini b/devtools/client/animationinspector/test/browser.ini index 8d6343612f24..f13f382443a1 100644 --- a/devtools/client/animationinspector/test/browser.ini +++ b/devtools/client/animationinspector/test/browser.ini @@ -12,6 +12,7 @@ support-files = doc_script_animation.html doc_simple_animation.html doc_multiple_animation_types.html + doc_timing_combination_animation.html head.js !/devtools/client/commandline/test/helpers.js !/devtools/client/framework/test/shared-head.js @@ -56,6 +57,7 @@ skip-if = os == "linux" && !debug # Bug 1227792 [browser_animation_timeline_scrubber_exists.js] [browser_animation_timeline_scrubber_movable.js] [browser_animation_timeline_scrubber_moves.js] +[browser_animation_timeline_setCurrentTime.js] [browser_animation_timeline_shows_delay.js] [browser_animation_timeline_shows_endDelay.js] [browser_animation_timeline_shows_iterations.js] diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js index 5e2d847c2e6b..c05f15d27d99 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js @@ -21,7 +21,7 @@ add_task(function* () { let {containerEl, animation: {state}} = timeBlockComponents[i]; checkAnimationTooltip(containerEl, state); - checkIterationBackground(containerEl, state); + checkProgressAtStartingTime(containerEl, state); // Get the first set of keyframes (there's only one animated property // anyway), and the first frame element from there, we're only interested in @@ -48,27 +48,14 @@ function checkAnimationTooltip(el, {iterationStart, duration}) { ok(title.match(regex), "The tooltip shows the expected iteration start"); } -function checkIterationBackground(el, {iterationCount, iterationStart}) { - info("Check the background-image used to display iterations is offset " + - "correctly to represent the iterationStart"); - - let iterationsEl = el.querySelector(".iterations"); - let start = getIterationStartFromBackground(iterationsEl, iterationCount); - is(start, iterationStart % 1, - "The right background-position for iteration start"); -} - -function getIterationStartFromBackground(el, iterationCount) { - if (iterationCount == 1) { - let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]); - return 1 - size / 100; - } - - let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]); - let position = parseFloat(/([-\d]+)%/.exec(el.style.backgroundPosition)[1]); - let iterationStartW = -position / size * (100 - size); - let rounded = Math.round(iterationStartW * 100); - return rounded / 10000; +function checkProgressAtStartingTime(el, { iterationStart }) { + info("Check the progress of starting time"); + const pathEl = el.querySelector(".iteration-path"); + const pathSegList = pathEl.pathSegList; + const pathSeg = pathSegList.getItem(1); + const progress = pathSeg.y; + is(progress, iterationStart % 1, + `The progress at starting point should be ${ iterationStart % 1 }`); } function checkKeyframeOffset(timeBlockEl, frameEl, {iterationStart}) { diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js b/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js new file mode 100644 index 000000000000..efc32c001352 --- /dev/null +++ b/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js @@ -0,0 +1,88 @@ +/* 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"; + +requestLongerTimeout(2); + +// Animation.currentTime ignores neagtive delay and positive/negative endDelay +// during fill-mode, even if they are set. +// For example, when the animation timing is +// { duration: 1000, iterations: 1, endDelay: -500, easing: linear }, +// the animation progress is 0.5 at 700ms because the progress stops as 0.5 at +// 500ms in original animation. However, if you set as +// animation.currentTime = 700 manually, the progress will be 0.7. +// So we modify setCurrentTime method since +// AnimationInspector should re-produce same as original animation. +// In these tests, +// we confirm the behavior of setCurrentTime by delay and endDelay. + +add_task(function* () { + yield addTab(URL_ROOT + "doc_timing_combination_animation.html"); + const { panel, controller } = yield openAnimationInspector(); + + yield clickTimelinePlayPauseButton(panel); + + const timelineComponent = panel.animationsTimelineComponent; + const timeBlockComponents = timelineComponent.timeBlocks; + + // Test -5000ms. + let time = -5000; + yield controller.setCurrentTimeAll(time, true); + for (let i = 0; i < timeBlockComponents.length; i++) { + yield timeBlockComponents[i].animation.refreshState(); + const state = yield timeBlockComponents[i].animation.state; + info(`Check the state at ${ time }ms with ` + + `delay:${ state.delay } and endDelay:${ state.endDelay }`); + is(state.currentTime, 0, + `The currentTime should be 0 at setCurrentTime(${ time })`); + } + + // Test 10000ms. + time = 10000; + yield controller.setCurrentTimeAll(time, true); + for (let i = 0; i < timeBlockComponents.length; i++) { + yield timeBlockComponents[i].animation.refreshState(); + const state = yield timeBlockComponents[i].animation.state; + info(`Check the state at ${ time }ms with ` + + `delay:${ state.delay } and endDelay:${ state.endDelay }`); + const expected = state.delay < 0 ? 0 : time; + is(state.currentTime, expected, + `The currentTime should be ${ expected } at setCurrentTime(${ time }).` + + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`); + } + + // Test 60000ms. + time = 60000; + yield controller.setCurrentTimeAll(time, true); + for (let i = 0; i < timeBlockComponents.length; i++) { + yield timeBlockComponents[i].animation.refreshState(); + const state = yield timeBlockComponents[i].animation.state; + info(`Check the state at ${ time }ms with ` + + `delay:${ state.delay } and endDelay:${ state.endDelay }`); + const expected = state.delay < 0 ? time + state.delay : time; + is(state.currentTime, expected, + `The currentTime should be ${ expected } at setCurrentTime(${ time }).` + + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`); + } + + // Test 150000ms. + time = 150000; + yield controller.setCurrentTimeAll(time, true); + for (let i = 0; i < timeBlockComponents.length; i++) { + yield timeBlockComponents[i].animation.refreshState(); + const state = yield timeBlockComponents[i].animation.state; + info(`Check the state at ${ time }ms with ` + + `delay:${ state.delay } and endDelay:${ state.endDelay }`); + const currentTime = state.delay < 0 ? time + state.delay : time; + const endTime = + state.delay + state.iterationCount * state.duration + state.endDelay; + const expected = + state.endDelay < 0 && state.fill === "both" && currentTime > endTime + ? endTime : currentTime; + is(state.currentTime, expected, + `The currentTime should be ${ expected } at setCurrentTime(${ time }).` + + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`); + } +}); diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js index 8082eb0cf9ce..8c9b0653dbf2 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js @@ -19,14 +19,23 @@ add_task(function* () { yield selectNodeAndWaitForAnimations(".delayed", inspector); let timelineEl = panel.animationsTimelineComponent.rootWrapperEl; checkDelayAndName(timelineEl, true); + let animationEl = timelineEl.querySelector(".animation"); + let state = panel.animationsTimelineComponent.timeBlocks[0].animation.state; + checkPath(animationEl, state); info("Selecting a no-delay animated node"); yield selectNodeAndWaitForAnimations(".animated", inspector); checkDelayAndName(timelineEl, false); + animationEl = timelineEl.querySelector(".animation"); + state = panel.animationsTimelineComponent.timeBlocks[0].animation.state; + checkPath(animationEl, state); info("Selecting a negative-delay animated node"); yield selectNodeAndWaitForAnimations(".negative-delay", inspector); checkDelayAndName(timelineEl, true); + animationEl = timelineEl.querySelector(".animation"); + state = panel.animationsTimelineComponent.timeBlocks[0].animation.state; + checkPath(animationEl, state); }); function checkDelayAndName(timelineEl, hasDelay) { @@ -37,7 +46,6 @@ function checkDelayAndName(timelineEl, hasDelay) { " a delay element, as expected"); if (hasDelay) { - let name = timelineEl.querySelector(".name"); let targetNode = timelineEl.querySelector(".target"); // Check that the delay element does not cause the timeline to overflow. @@ -45,11 +53,44 @@ function checkDelayAndName(timelineEl, hasDelay) { let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width); ok(delayLeft >= sidebarWidth, "The delay element isn't displayed over the sidebar"); - - // Check that the delay is not displayed on top of the name. - let delayRight = Math.round(delay.getBoundingClientRect().right); - let nameLeft = Math.round(name.getBoundingClientRect().left); - ok(delayRight <= nameLeft, - "The delay element does not span over the name element"); + } +} + +function checkPath(animationEl, state) { + // Check existance of delay path. + const delayPathEl = animationEl.querySelector(".delay-path"); + if (!state.iterationCount && state.delay < 0) { + // Infinity + ok(!delayPathEl, "The delay path for Infinity should not exist"); + return; + } + if (state.delay === 0) { + ok(!delayPathEl, "The delay path for zero delay should not exist"); + return; + } + ok(delayPathEl, "The delay path should exist"); + + // Check delay path coordinates. + const pathSegList = delayPathEl.pathSegList; + const startingPathSeg = pathSegList.getItem(0); + const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2); + if (state.delay < 0) { + ok(delayPathEl.classList.contains("negative"), + "The delay path should have 'negative' class"); + const startingX = state.delay; + const endingX = 0; + is(startingPathSeg.x, startingX, + `The x of starting point should be ${ startingX }`); + is(endingPathSeg.x, endingX, + `The x of ending point should be ${ endingX }`); + } else { + ok(!delayPathEl.classList.contains("negative"), + "The delay path should not have 'negative' class"); + const startingX = 0; + const endingX = state.delay; + is(startingPathSeg.x, startingX, + `The x of starting point should be ${ startingX }`); + is(endingPathSeg.x, endingX, + `The x of ending point should be ${ endingX }`); } } diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js index 2c9f60746e2d..0aa5c16c0777 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js @@ -20,8 +20,11 @@ add_task(function* () { let selector = selectors[i]; yield selectNode(selector, inspector); let timelineEl = panel.animationsTimelineComponent.rootWrapperEl; - let animationEl = timelineEl.querySelectorAll(".animation")[0]; + let animationEl = timelineEl.querySelector(".animation"); checkEndDelayAndName(animationEl); + const state = + panel.animationsTimelineComponent.timeBlocks[0].animation.state; + checkPath(animationEl, state); } }); @@ -42,3 +45,34 @@ function checkEndDelayAndName(animationEl) { ok(endDelayRight >= nameLeft, "The endDelay element does not span over the name element"); } + +function checkPath(animationEl, state) { + // Check existance of enddelay path. + const endDelayPathEl = animationEl.querySelector(".enddelay-path"); + ok(endDelayPathEl, "The endDelay path should exist"); + + // Check enddelay path coordinates. + const pathSegList = endDelayPathEl.pathSegList; + const startingPathSeg = pathSegList.getItem(0); + const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2); + if (state.endDelay < 0) { + ok(endDelayPathEl.classList.contains("negative"), + "The endDelay path should have 'negative' class"); + const endingX = state.delay + state.iterationCount * state.duration; + const startingX = endingX + state.endDelay; + is(startingPathSeg.x, startingX, + `The x of starting point should be ${ startingX }`); + is(endingPathSeg.x, endingX, + `The x of ending point should be ${ endingX }`); + } else { + ok(!endDelayPathEl.classList.contains("negative"), + "The endDelay path should not have 'negative' class"); + const startingX = + state.delay + state.iterationCount * state.duration; + const endingX = startingX + state.endDelay; + is(startingPathSeg.x, startingX, + `The x of starting point should be ${ startingX }`); + is(endingPathSeg.x, endingX, + `The x of ending point should be ${ endingX }`); + } +} diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js index dd6b4e5b3d6f..08e5a2620e4b 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js @@ -17,36 +17,31 @@ add_task(function* () { yield selectNodeAndWaitForAnimations(".delayed", inspector); info("Getting the animation element from the panel"); - let timelineEl = panel.animationsTimelineComponent.rootWrapperEl; + const timelineComponent = panel.animationsTimelineComponent; + const timelineEl = timelineComponent.rootWrapperEl; let animation = timelineEl.querySelector(".time-block"); - let iterations = animation.querySelector(".iterations"); - - // Iterations are rendered with a repeating linear-gradient, so we need to - // calculate how many iterations are represented by looking at the background - // size. - let iterationCount = getIterationCountFromBackground(iterations); + // Get iteration count from summary graph path. + let iterationCount = getIterationCount(animation); is(iterationCount, 10, "The animation timeline contains the right number of iterations"); - ok(!iterations.classList.contains("infinite"), - "The iteration element doesn't have the infinite class"); + ok(!animation.querySelector(".infinity"), + "The summary graph does not have any elements " + + " that have infinity class"); info("Selecting another test node with an infinite animation"); yield selectNodeAndWaitForAnimations(".animated", inspector); info("Getting the animation element from the panel again"); animation = timelineEl.querySelector(".time-block"); - iterations = animation.querySelector(".iterations"); - - iterationCount = getIterationCountFromBackground(iterations); + iterationCount = getIterationCount(animation); is(iterationCount, 1, - "The animation timeline contains just one iteration"); - ok(iterations.classList.contains("infinite"), - "The iteration element has the infinite class"); + "The animation timeline contains one iteration"); + ok(animation.querySelector(".infinity"), + "The summary graph has an element that has infinity class"); }); -function getIterationCountFromBackground(el) { - let backgroundSize = parseFloat(el.style.backgroundSize.split(" ")[0]); - return Math.round(100 / backgroundSize); +function getIterationCount(timeblockEl) { + return timeblockEl.querySelectorAll(".iteration-path").length; } diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js index 80eda566c7f4..f330e880e18a 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js @@ -35,6 +35,15 @@ add_task(function* () { } else { ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations"); } + if (controller.animationPlayers[i].state.easing) { + ok(title.match(/Easing: /), "The tooltip shows the easing"); + } + if (controller.animationPlayers[i].state.fill) { + ok(title.match(/Fill: /), "The tooltip shows the fill"); + } + if (controller.animationPlayers[i].state.direction) { + ok(title.match(/Direction: /), "The tooltip shows the direction"); + } ok(!title.match(/Iteration start:/), "The tooltip doesn't show the iteration start"); }); diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js index 99efea9324c6..42309203abf5 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js @@ -24,13 +24,13 @@ add_task(function* () { info("The first animation has its rate set to 1, let's measure it"); let el = timeBlocks[0]; - let duration = parseInt(el.querySelector(".iterations").style.width, 10); + let duration = getDuration(el.querySelector("path")); let delay = parseInt(el.querySelector(".delay").style.width, 10); info("The second animation has its rate set to 2, so should be shorter"); let el2 = timeBlocks[1]; - let duration2 = parseInt(el2.querySelector(".iterations").style.width, 10); + let duration2 = getDuration(el2.querySelector("path")); let delay2 = parseInt(el2.querySelector(".delay").style.width, 10); // The width are calculated by the animation-inspector dynamically depending @@ -41,3 +41,41 @@ add_task(function* () { let delayDelta = (2 * delay2) - delay; ok(delayDelta <= 1, "The delay width is correct"); }); + +function getDuration(pathEl) { + const pathSegList = pathEl.pathSegList; + // Find the index of starting iterations. + let startingIterationIndex = 0; + const firstPathSeg = pathSegList.getItem(1); + for (let i = 2, n = pathSegList.numberOfItems - 2; i < n; i++) { + // Changing point of the progress acceleration is the time. + const pathSeg = pathSegList.getItem(i); + if (firstPathSeg.y != pathSeg.y) { + startingIterationIndex = i; + break; + } + } + // Find the index of ending iterations. + let endingIterationIndex = 0; + let previousPathSegment = pathSegList.getItem(startingIterationIndex); + for (let i = startingIterationIndex + 1, n = pathSegList.numberOfItems - 2; + i < n; i++) { + // Find forwards fill-mode. + const pathSeg = pathSegList.getItem(i); + if (previousPathSegment.y == pathSeg.y) { + endingIterationIndex = i; + break; + } + previousPathSegment = pathSeg; + } + if (endingIterationIndex) { + // Not forwards fill-mode + endingIterationIndex = pathSegList.numberOfItems - 2; + } + // Return the distance of starting and ending + const startingIterationPathSegment = + pathSegList.getItem(startingIterationIndex); + const endingIterationPathSegment = + pathSegList.getItem(startingIterationIndex); + return endingIterationPathSegment.x - startingIterationPathSegment.x; +} diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js index f88275b3ef60..43c148482b89 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js @@ -37,7 +37,7 @@ add_task(function* () { is(animationEl.querySelector(".name").textContent, animation.state.name, "The name on the timeline is correct"); - ok(animationEl.querySelector(".iterations"), - "The timeline has iterations displayed"); + ok(animationEl.querySelector("svg path"), + "The timeline has svg and path element as summary graph"); } }); diff --git a/devtools/client/animationinspector/test/doc_timing_combination_animation.html b/devtools/client/animationinspector/test/doc_timing_combination_animation.html new file mode 100644 index 000000000000..8b39af015cdb --- /dev/null +++ b/devtools/client/animationinspector/test/doc_timing_combination_animation.html @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/devtools/client/animationinspector/utils.js b/devtools/client/animationinspector/utils.js index 9619fb59036a..45ebab00d2a3 100644 --- a/devtools/client/animationinspector/utils.js +++ b/devtools/client/animationinspector/utils.js @@ -27,6 +27,7 @@ const MILLIS_TIME_FORMAT_MAX_DURATION = 4000; * {attrName1:value1, attrName2: value2, ...} * - parent {DOMNode} Mandatory node to append the newly created node to. * - textContent {String} Optional text for the node. + * - namespace {String} Optional namespace * @return {DOMNode} The newly created node. */ function createNode(options) { @@ -35,7 +36,10 @@ function createNode(options) { } let type = options.nodeType || "div"; - let node = options.parent.ownerDocument.createElement(type); + let node = + options.namespace + ? options.parent.ownerDocument.createElementNS(options.namespace, type) + : options.parent.ownerDocument.createElement(type); for (let name in options.attributes || {}) { let value = options.attributes[name]; @@ -264,31 +268,6 @@ var TimeScale = { return {x, w, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW}; - }, - - /** - * Given an animation, get the background data for .iterations element. - * This background represents iterationCount and iterationStart. - * Returns three properties. - * 1. size: x of background-size (%) - * 2. position: x of background-position (%) - * 3. repeat: background-repeat (string) - */ - getIterationsBackgroundData: function ({state}) { - let iterationCount = state.iterationCount || 1; - let iterationStartW = state.iterationStart % 1 * 100; - let background = {}; - if (iterationCount == 1) { - background.size = 100 - iterationStartW; - background.position = 0; - background.repeat = "no-repeat"; - } else { - background.size = 1 / iterationCount * 100; - background.position = -iterationStartW * background.size / - (100 - background.size); - background.repeat = "repeat-x"; - } - return background; } }; diff --git a/devtools/client/debugger/content/views/sources-view.js b/devtools/client/debugger/content/views/sources-view.js index 81610b560d3e..bb68afcf49d2 100644 --- a/devtools/client/debugger/content/views/sources-view.js +++ b/devtools/client/debugger/content/views/sources-view.js @@ -191,7 +191,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { unBlackBoxButton: () => this._onStopBlackBoxing(), prettyPrintCommand: () => this.togglePrettyPrint(), toggleBreakpointsCommand: () =>this.toggleBreakpoints(), - togglePromiseDebuggerCommand: () => this.togglePromiseDebugger(), nextSourceCommand: () => this.selectNextItem(), prevSourceCommand: () => this.selectPrevItem() }); @@ -614,17 +613,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { } }, - togglePromiseDebugger: function () { - if (Prefs.promiseDebuggerEnabled) { - let promisePane = this.DebuggerView._promisePane; - promisePane.hidden = !promisePane.hidden; - - if (!this.DebuggerView._promiseDebuggerIframe) { - this.DebuggerView._initializePromiseDebugger(); - } - } - }, - hidePrettyPrinting: function () { this._prettyPrintButton.style.display = "none"; diff --git a/devtools/client/debugger/debugger-controller.js b/devtools/client/debugger/debugger-controller.js index 386da446586d..f4e0546b7ef7 100644 --- a/devtools/client/debugger/debugger-controller.js +++ b/devtools/client/debugger/debugger-controller.js @@ -1211,7 +1211,6 @@ var Prefs = new PrefsHelper("devtools", { workersEnabled: ["Bool", "debugger.workers"], editorTabSize: ["Int", "editor.tabsize"], autoBlackBox: ["Bool", "debugger.auto-black-box"], - promiseDebuggerEnabled: ["Bool", "debugger.promise"] }); /** diff --git a/devtools/client/debugger/debugger-view.js b/devtools/client/debugger/debugger-view.js index 964065367777..b6a5850ff8b0 100644 --- a/devtools/client/debugger/debugger-view.js +++ b/devtools/client/debugger/debugger-view.js @@ -23,8 +23,6 @@ const SEARCH_VARIABLE_FLAG = "*"; const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG]; const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft"; const RESIZE_REFRESH_RATE = 50; // ms -const PROMISE_DEBUGGER_URL = - "chrome://devtools/content/promisedebugger/promise-debugger.xhtml"; const EventListenersView = require("./content/views/event-listeners-view"); const SourcesView = require("./content/views/sources-view"); @@ -135,7 +133,6 @@ var DebuggerView = { this.WatchExpressions.destroy(); this.EventListeners.destroy(); this.GlobalSearch.destroy(); - this._destroyPromiseDebugger(); this._destroyPanes(); this.editor.destroy(); @@ -155,7 +152,6 @@ var DebuggerView = { this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane"); this._instrumentsPane = document.getElementById("instruments-pane"); this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); - this._promisePane = document.getElementById("promise-debugger-pane"); this.showEditor = this.showEditor.bind(this); this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this); @@ -191,7 +187,6 @@ var DebuggerView = { this._workersAndSourcesPane = null; this._instrumentsPane = null; this._instrumentsPaneToggleButton = null; - this._promisePane = null; }, /** @@ -238,41 +233,6 @@ var DebuggerView = { }); }, - /** - * Initialie the Promise Debugger instance. - */ - _initializePromiseDebugger: function () { - let iframe = this._promiseDebuggerIframe = document.createElement("iframe"); - iframe.setAttribute("flex", 1); - - let onLoad = (event) => { - iframe.removeEventListener("load", onLoad, true); - - let doc = event.target; - let win = doc.defaultView; - - win.setPanel(DebuggerController._toolbox); - }; - - iframe.addEventListener("load", onLoad, true); - iframe.setAttribute("src", PROMISE_DEBUGGER_URL); - this._promisePane.appendChild(iframe); - }, - - /** - * Destroy the Promise Debugger instance. - */ - _destroyPromiseDebugger: function () { - if (this._promiseDebuggerIframe) { - this._promiseDebuggerIframe.contentWindow.destroy(); - - this._promiseDebuggerIframe.parentNode.removeChild( - this._promiseDebuggerIframe); - - this._promiseDebuggerIframe = null; - } - }, - /** * Initializes the Editor instance. * diff --git a/devtools/client/debugger/debugger.xul b/devtools/client/debugger/debugger.xul index 19c1534f13e5..5a22cf7f8ea4 100644 --- a/devtools/client/debugger/debugger.xul +++ b/devtools/client/debugger/debugger.xul @@ -341,11 +341,6 @@ class="devtools-toolbarbutton" tooltiptext="&debuggerUI.sources.toggleBreakpoints;" command="toggleBreakpointsCommand"/> -