diff --git a/browser/devtools/styleinspector/rule-view.js b/browser/devtools/styleinspector/rule-view.js index 141312588ca..260fbf98139 100644 --- a/browser/devtools/styleinspector/rule-view.js +++ b/browser/devtools/styleinspector/rule-view.js @@ -302,10 +302,13 @@ ElementStyle.prototype = { */ markOverridden: function(pseudo="") { // Gather all the text properties applied by these rules, ordered - // from more- to less-specific. + // from more- to less-specific. Text properties from keyframes rule are + // excluded from being marked as overridden since a number of criteria such + // as time, and animation overlay are required to be check in order to + // determine if the property is overridden. let textProps = []; for (let rule of this.rules) { - if (rule.pseudoElement == pseudo) { + if (rule.pseudoElement == pseudo && !rule.keyframes) { textProps = textProps.concat(rule.textProps.slice(0).reverse()); } } @@ -419,6 +422,7 @@ function Rule(aElementStyle, aOptions) { this.isSystem = aOptions.isSystem; this.inherited = aOptions.inherited || null; + this.keyframes = aOptions.keyframes || null; this._modificationDepth = 0; if (this.domRule) { @@ -466,6 +470,18 @@ Rule.prototype = { return this._inheritedSource; }, + get keyframesName() { + if (this._keyframesName) { + return this._keyframesName; + } + this._keyframesName = ""; + if (this.keyframes) { + this._keyframesName = + CssLogic._strings.formatStringFromName("rule.keyframe", [this.keyframes.name], 1); + } + return this._keyframesName; + }, + get selectorText() { return this.domRule.selectors ? this.domRule.selectors.join(", ") : CssLogic.l10n("rule.sourceElement"); }, @@ -1627,25 +1643,6 @@ CssRuleView.prototype = { return this._pseudoElementLabel; }, - togglePseudoElementVisibility: function(value) { - this._showPseudoElements = !!value; - let isOpen = this.showPseudoElements; - - Services.prefs.setBoolPref("devtools.inspector.show_pseudo_elements", - isOpen); - - this.element.classList.toggle("show-pseudo-elements", isOpen); - - if (this.pseudoElementTwisty) { - if (isOpen) { - this.pseudoElementTwisty.setAttribute("open", "true"); - } - else { - this.pseudoElementTwisty.removeAttribute("open"); - } - } - }, - get showPseudoElements() { if (this._showPseudoElements === undefined) { this._showPseudoElements = @@ -1654,6 +1651,68 @@ CssRuleView.prototype = { return this._showPseudoElements; }, + /** + * Creates an expandable container in the rule view + * @param {String} aLabel The label for the container header + * @param {Boolean} isPseudo Whether or not the container will hold + * pseudo element rules + * @return {DOMNode} The container element + */ + createExpandableContainer: function(aLabel, isPseudo = false) { + let header = this.doc.createElementNS(HTML_NS, "div"); + header.className = this._getRuleViewHeaderClassName(true); + header.classList.add("show-expandable-container"); + header.textContent = aLabel; + + let twisty = this.doc.createElementNS(HTML_NS, "span"); + twisty.className = "ruleview-expander theme-twisty"; + twisty.setAttribute("open", "true"); + + header.insertBefore(twisty, header.firstChild); + this.element.appendChild(header); + + let container = this.doc.createElementNS(HTML_NS, "div"); + container.classList.add("ruleview-expandable-container"); + this.element.appendChild(container); + + let toggleContainerVisibility = (isPseudo, showPseudo) => { + let isOpen = twisty.getAttribute("open"); + + if (isPseudo) { + this._showPseudoElements = !!showPseudo; + + Services.prefs.setBoolPref("devtools.inspector.show_pseudo_elements", + this.showPseudoElements); + + header.classList.toggle("show-expandable-container", + this.showPseudoElements); + + isOpen = !this.showPseudoElements; + } else { + header.classList.toggle("show-expandable-container"); + } + + if (isOpen) { + twisty.removeAttribute("open"); + } else { + twisty.setAttribute("open", "true"); + } + }; + + header.addEventListener("dblclick", () => { + toggleContainerVisibility(isPseudo, !this.showPseudoElements); + }, false); + twisty.addEventListener("click", () => { + toggleContainerVisibility(isPseudo, !this.showPseudoElements); + }, false); + + if (isPseudo) { + toggleContainerVisibility(isPseudo, this.showPseudoElements); + } + + return container; + }, + _getRuleViewHeaderClassName: function(isPseudo) { let baseClassName = "theme-gutter ruleview-header"; return isPseudo ? baseClassName + " ruleview-expandable-header" : baseClassName; @@ -1666,8 +1725,10 @@ CssRuleView.prototype = { // Run through the current list of rules, attaching // their editors in order. Create editors if needed. let lastInheritedSource = ""; + let lastKeyframes = null; let seenPseudoElement = false; let seenNormalElement = false; + let container = null; if (!this._elementStyle.rules) { return; @@ -1688,7 +1749,7 @@ CssRuleView.prototype = { } let inheritedSource = rule.inheritedSource; - if (inheritedSource != lastInheritedSource) { + if (inheritedSource && inheritedSource != lastInheritedSource) { let div = this.doc.createElementNS(HTML_NS, "div"); div.className = this._getRuleViewHeaderClassName(); div.textContent = inheritedSource; @@ -1698,33 +1759,25 @@ CssRuleView.prototype = { if (!seenPseudoElement && rule.pseudoElement) { seenPseudoElement = true; + container = this.createExpandableContainer(this.pseudoElementLabel, true); + } - let div = this.doc.createElementNS(HTML_NS, "div"); - div.className = this._getRuleViewHeaderClassName(true); - div.textContent = this.pseudoElementLabel; - div.addEventListener("dblclick", () => { - this.togglePseudoElementVisibility(!this.showPseudoElements); - }, false); - - let twisty = this.pseudoElementTwisty = - this.doc.createElementNS(HTML_NS, "span"); - twisty.className = "ruleview-expander theme-twisty"; - twisty.addEventListener("click", () => { - this.togglePseudoElementVisibility(!this.showPseudoElements); - }, false); - - div.insertBefore(twisty, div.firstChild); - this.element.appendChild(div); + let keyframes = rule.keyframes; + if (keyframes && keyframes != lastKeyframes) { + lastKeyframes = keyframes; + container = this.createExpandableContainer(rule.keyframesName); } if (!rule.editor) { rule.editor = new RuleEditor(this, rule); } - this.element.appendChild(rule.editor.element); + if (container && (rule.pseudoElement || keyframes)) { + container.appendChild(rule.editor.element); + } else { + this.element.appendChild(rule.editor.element); + } } - - this.togglePseudoElementVisibility(this.showPseudoElements); } }; @@ -1756,7 +1809,10 @@ function RuleEditor(aRuleView, aRule) { RuleEditor.prototype = { get isSelectorEditable() { let toolbox = this.ruleView.inspector.toolbox; - return toolbox.target.client.traits.selectorEditable; + return this.isEditable && + toolbox.target.client.traits.selectorEditable && + this.rule.domRule.type !== ELEMENT_STYLE && + this.rule.domRule.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE }, _create: function() { @@ -1764,9 +1820,6 @@ RuleEditor.prototype = { this.element.className = "ruleview-rule theme-separator"; this.element.setAttribute("uneditable", !this.isEditable); this.element._ruleEditor = this; - if (this.rule.pseudoElement) { - this.element.classList.add("ruleview-rule-pseudo-element"); - } // Give a relative position for the inplace editor's measurement // span to be placed absolutely against. @@ -1808,8 +1861,7 @@ RuleEditor.prototype = { class: "ruleview-selector theme-fg-color3" }); - if (this.isEditable && this.rule.domRule.type !== ELEMENT_STYLE && - this.isSelectorEditable) { + if (this.isSelectorEditable) { this.selectorContainer.addEventListener("click", aEvent => { // Clicks within the selector shouldn't propagate any further. aEvent.stopPropagation(); @@ -1925,6 +1977,8 @@ RuleEditor.prototype = { // style, just show the text directly. if (this.rule.domRule.type === ELEMENT_STYLE) { this.selectorText.textContent = this.rule.selectorText; + } else if (this.rule.domRule.type === Ci.nsIDOMCSSRule.KEYFRAME_RULE) { + this.selectorText.textContent = this.rule.domRule.keyText; } else { this.rule.domRule.selectors.forEach((selector, i) => { if (i != 0) { diff --git a/browser/devtools/styleinspector/ruleview.css b/browser/devtools/styleinspector/ruleview.css index a1ef3b148aa..2f86a8839c1 100644 --- a/browser/devtools/styleinspector/ruleview.css +++ b/browser/devtools/styleinspector/ruleview.css @@ -38,11 +38,11 @@ display: none; } -.ruleview-rule-pseudo-element { +.ruleview-expandable-container { display: none; } -.show-pseudo-elements .ruleview-rule-pseudo-element { +.show-expandable-container + .ruleview-expandable-container { display: block; } diff --git a/browser/devtools/styleinspector/test/browser.ini b/browser/devtools/styleinspector/test/browser.ini index 506bb6d73e7..1b92732c3f7 100644 --- a/browser/devtools/styleinspector/test/browser.ini +++ b/browser/devtools/styleinspector/test/browser.ini @@ -9,6 +9,8 @@ support-files = doc_content_stylesheet_linked.css doc_content_stylesheet_script.css doc_content_stylesheet_xul.css + doc_keyframeanimation.html + doc_keyframeanimation.css doc_matched_selectors.html doc_media_queries.html doc_pseudoelement.html @@ -75,6 +77,8 @@ support-files = skip-if = os == "win" && debug # bug 963492 [browser_ruleview_inherit.js] [browser_ruleview_keybindings.js] +[browser_ruleview_keyframes-rule_01.js] +[browser_ruleview_keyframes-rule_02.js] [browser_ruleview_livepreview.js] [browser_ruleview_mathml-element.js] [browser_ruleview_media-queries.js] diff --git a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js index cf5f4735b6b..e5264ee01da 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js @@ -51,7 +51,8 @@ let test = asyncTest(function*() { function* testEditSelector(view, name) { info("Test editing existing selector fields"); - let idRuleEditor = getRuleViewRuleEditor(view, 1); + let idRuleEditor = getRuleViewRuleEditor(view, 1) || + getRuleViewRuleEditor(view, 1, 0); info("Focusing an existing selector name in the rule-view"); let editor = yield focusEditableField(idRuleEditor.selectorText); diff --git a/browser/devtools/styleinspector/test/browser_ruleview_keyframes-rule_01.js b/browser/devtools/styleinspector/test/browser_ruleview_keyframes-rule_01.js new file mode 100644 index 00000000000..d84b50a00fc --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_ruleview_keyframes-rule_01.js @@ -0,0 +1,127 @@ +/* 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"; + +// Test that keyframe rules and gutters are displayed correctly in the rule view + +const TEST_URI = TEST_URL_ROOT + "doc_keyframeanimation.html"; + +let test = asyncTest(function*() { + yield addTab(TEST_URI); + + let {toolbox, inspector, view} = yield openRuleView(); + + yield testPacman(inspector, view); + yield testBoxy(inspector, view); + yield testMoxy(inspector, view); +}); + +function* testPacman(inspector, view) { + info("Test content and gutter in the keyframes rule of #pacman"); + + let { + rules, + element, + elementStyle + } = yield assertKeyframeRules("#pacman", inspector, view, { + elementRulesNb: 2, + keyframeRulesNb: 2, + keyframesRules: ["pacman", "pacman"], + keyframeRules: ["100%", "100%"] + }); + + let gutters = assertGutters(view, { + guttersNbs: 2, + gutterHeading: ["Keyframes pacman", "Keyframes pacman"] + }); +} + +function* testBoxy(inspector, view) { + info("Test content and gutter in the keyframes rule of #boxy"); + + let { + rules, + element, + elementStyle + } = yield assertKeyframeRules("#boxy", inspector, view, { + elementRulesNb: 3, + keyframeRulesNb: 3, + keyframesRules: ["boxy", "boxy", "boxy"], + keyframeRules: ["10%", "20%", "100%"] + }); + + let gutters = assertGutters(view, { + guttersNbs: 1, + gutterHeading: ["Keyframes boxy"] + }); +} + +function testMoxy(inspector, view) { + info("Test content and gutter in the keyframes rule of #moxy"); + + let { + rules, + element, + elementStyle + } = yield assertKeyframeRules("#moxy", inspector, view, { + elementRulesNb: 3, + keyframeRulesNb: 4, + keyframesRules: ["boxy", "boxy", "boxy", "moxy"], + keyframeRules: ["10%", "20%", "100%", "100%"] + }); + + let gutters = assertGutters(view, { + guttersNbs: 2, + gutterHeading: ["Keyframes boxy", "Keyframes moxy"] + }); +} + +function* testNode(selector, inspector, view) { + let element = getNode(selector); + yield selectNode(element, inspector); + let elementStyle = view._elementStyle; + return {element, elementStyle}; +} + +function* assertKeyframeRules(selector, inspector, view, expected) { + let {element, elementStyle} = yield testNode(selector, inspector, view); + + let rules = { + elementRules: elementStyle.rules.filter(rule => !rule.keyframes), + keyframeRules: elementStyle.rules.filter(rule => rule.keyframes) + }; + + is(rules.elementRules.length, expected.elementRulesNb, selector + + " has the correct number of non keyframe element rules"); + is(rules.keyframeRules.length, expected.keyframeRulesNb, selector + + " has the correct number of keyframe rules"); + + let i = 0; + for (let keyframeRule of rules.keyframeRules) { + ok(keyframeRule.keyframes.name == expected.keyframesRules[i], + keyframeRule.keyframes.name + " has the correct keyframes name"); + ok(keyframeRule.domRule.keyText == expected.keyframeRules[i], + keyframeRule.domRule.keyText + " selector heading is correct"); + i++; + } + + return {rules, element, elementStyle}; +} + +function assertGutters(view, expected) { + let gutters = view.element.querySelectorAll(".theme-gutter"); + + is(gutters.length, expected.guttersNbs, + "There are " + gutters.length + " gutter headings"); + + let i = 0; + for (let gutter of gutters) { + is(gutter.textContent, expected.gutterHeading[i], + "Correct " + gutter.textContent + " gutter headings"); + i++; + } + + return gutters; +} diff --git a/browser/devtools/styleinspector/test/browser_ruleview_keyframes-rule_02.js b/browser/devtools/styleinspector/test/browser_ruleview_keyframes-rule_02.js new file mode 100644 index 00000000000..5c3b53f954a --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_ruleview_keyframes-rule_02.js @@ -0,0 +1,111 @@ +/* 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"; + +// Test that verifies the content of the keyframes rule and property changes +// to keyframe rules + +const TEST_URI = TEST_URL_ROOT + "doc_keyframeanimation.html"; + +let test = asyncTest(function*() { + yield addTab(TEST_URI); + + let {toolbox, inspector, view} = yield openRuleView(); + + yield testPacman(inspector, view); + yield testBoxy(inspector, view); +}); + +function* testPacman(inspector, view) { + info("Test content in the keyframes rule of #pacman"); + + let { + rules, + element, + elementStyle + } = yield getKeyframeRules("#pacman", inspector, view); + + info("Test text properties for Keyframes #pacman"); + + is + ( + convertTextPropsToString(rules.keyframeRules[0].textProps), + "left: 750px", + "Keyframe pacman (100%) property is correct" + ); + + info("Test dynamic changes to keyframe rule for #pacman"); + + let defaultView = element.ownerDocument.defaultView; + let ruleEditor = view.element.children[5].childNodes[0]._ruleEditor; + ruleEditor.addProperty("opacity", "0"); + ruleEditor.addProperty("top", "750px"); + + yield ruleEditor._applyingModifications; + yield once(element, "animationend"); + + is + ( + convertTextPropsToString(rules.keyframeRules[1].textProps), + "left: 750px; opacity: 0; top: 750px", + "Keyframe pacman (100%) property is correct" + ); + + is(defaultView.getComputedStyle(element).getPropertyValue("opacity"), "0", + "Added opacity property should have been used."); + is(defaultView.getComputedStyle(element).getPropertyValue("top"), "750px", + "Added top property should have been used."); +} + +function* testBoxy(inspector, view) { + info("Test content in the keyframes rule of #boxy"); + + let { + rules, + element, + elementStyle + } = yield getKeyframeRules("#boxy", inspector, view); + + info("Test text properties for Keyframes #boxy"); + + is + ( + convertTextPropsToString(rules.keyframeRules[0].textProps), + "background-color: blue", + "Keyframe boxy (10%) property is correct" + ); + + is + ( + convertTextPropsToString(rules.keyframeRules[1].textProps), + "background-color: green", + "Keyframe boxy (20%) property is correct" + ); + + is + ( + convertTextPropsToString(rules.keyframeRules[2].textProps), + "opacity: 0", + "Keyframe boxy (100%) property is correct" + ); +} + +function convertTextPropsToString(textProps) { + return textProps.map(t => t.name + ": " + t.value).join("; "); +} + +function* getKeyframeRules(selector, inspector, view) { + let element = getNode(selector); + + yield selectNode(element, inspector); + let elementStyle = view._elementStyle; + + let rules = { + elementRules: elementStyle.rules.filter(rule => !rule.keyframes), + keyframeRules: elementStyle.rules.filter(rule => rule.keyframes) + }; + + return {rules, element, elementStyle}; +} diff --git a/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element.js b/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element.js index a4e86c56312..f2a0853202a 100644 --- a/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element.js @@ -38,24 +38,22 @@ function* testTopLeft(inspector, view) { // Make sure that clicking on the twisty hides pseudo elements let expander = gutters[0].querySelector(".ruleview-expander"); - ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are expanded"); + ok (view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are expanded"); expander.click(); - ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are collapsed by twisty"); + ok (!view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are collapsed by twisty"); expander.click(); - ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are expanded again"); + ok (view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are expanded again"); // Make sure that dblclicking on the header container also toggles the pseudo elements EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2}, inspector.sidebar.getWindowForTab("ruleview")); - ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are collapsed by dblclicking"); + ok (!view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are collapsed by dblclicking"); let defaultView = element.ownerDocument.defaultView; let elementRule = rules.elementRules[0]; - let elementRuleView = [].filter.call(view.element.children, e => { - return e._ruleEditor && e._ruleEditor.rule === elementRule; - })[0]._ruleEditor; + let elementRuleView = getRuleViewRuleEditor(view, 3); let elementAfterRule = rules.afterRules[0]; - let elementAfterRuleView = [].filter.call(view.element.children, (e) => { + let elementAfterRuleView = [].filter.call(view.element.children[1].children, (e) => { return e._ruleEditor && e._ruleEditor.rule === elementAfterRule; })[0]._ruleEditor; @@ -68,7 +66,7 @@ function* testTopLeft(inspector, view) { ); let elementBeforeRule = rules.beforeRules[0]; - let elementBeforeRuleView = [].filter.call(view.element.children, (e) => { + let elementBeforeRuleView = [].filter.call(view.element.children[1].children, (e) => { return e._ruleEditor && e._ruleEditor.rule === elementBeforeRule; })[0]._ruleEditor; @@ -138,10 +136,10 @@ function* testTopRight(inspector, view) { let gutters = assertGutters(view); let expander = gutters[0].querySelector(".ruleview-expander"); - ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements remain collapsed after switching element"); + ok (!view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements remain collapsed after switching element"); expander.scrollIntoView(); expander.click(); - ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are shown again after clicking twisty"); + ok (view.element.firstChild.classList.contains("show-expandable-container"), "Pseudo Elements are shown again after clicking twisty"); } function* testBottomRight(inspector, view) { @@ -183,7 +181,7 @@ function* testParagraph(inspector, view) { let gutters = assertGutters(view); let elementFirstLineRule = rules.firstLineRules[0]; - let elementFirstLineRuleView = [].filter.call(view.element.children, (e) => { + let elementFirstLineRuleView = [].filter.call(view.element.children[1].children, (e) => { return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule; })[0]._ruleEditor; @@ -195,7 +193,7 @@ function* testParagraph(inspector, view) { ); let elementFirstLetterRule = rules.firstLetterRules[0]; - let elementFirstLetterRuleView = [].filter.call(view.element.children, (e) => { + let elementFirstLetterRuleView = [].filter.call(view.element.children[1].children, (e) => { return e._ruleEditor && e._ruleEditor.rule === elementFirstLetterRule; })[0]._ruleEditor; @@ -207,7 +205,7 @@ function* testParagraph(inspector, view) { ); let elementSelectionRule = rules.selectionRules[0]; - let elementSelectionRuleView = [].filter.call(view.element.children, (e) => { + let elementSelectionRuleView = [].filter.call(view.element.children[1].children, (e) => { return e._ruleEditor && e._ruleEditor.rule === elementSelectionRule; })[0]._ruleEditor; diff --git a/browser/devtools/styleinspector/test/doc_keyframeanimation.css b/browser/devtools/styleinspector/test/doc_keyframeanimation.css new file mode 100644 index 00000000000..64582ed358f --- /dev/null +++ b/browser/devtools/styleinspector/test/doc_keyframeanimation.css @@ -0,0 +1,84 @@ +/* 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/. */ + +.box { + height: 50px; + width: 50px; +} + +.circle { + width: 20px; + height: 20px; + border-radius: 10px; + background-color: #FFCB01; +} + +#pacman { + width: 0px; + height: 0px; + border-right: 60px solid transparent; + border-top: 60px solid #FFCB01; + border-left: 60px solid #FFCB01; + border-bottom: 60px solid #FFCB01; + border-top-left-radius: 60px; + border-bottom-left-radius: 60px; + border-top-right-radius: 60px; + border-bottom-right-radius: 60px; + top: 120px; + left: 150px; + position: absolute; + animation-name: pacman; + animation-fill-mode: forwards; + animation-timing-function: linear; + animation-duration: 15s; +} + +#boxy { + top: 170px; + left: 450px; + position: absolute; + animation: 4s linear 0s normal none infinite boxy; +} + + +#moxy { + animation-name: moxy, boxy; + animation-delay: 3.5s; + animation-duration: 2s; + top: 170px; + left: 650px; + position: absolute; +} + +@-moz-keyframes pacman { + 100% { + left: 750px; + } +} + +@keyframes pacman { + 100% { + left: 750px; + } +} + +@keyframes boxy { + 10% { + background-color: blue; + } + + 20% { + background-color: green; + } + + 100% { + opacity: 0; + } +} + +@keyframes moxy { + to { + opacity: 0; + } +} diff --git a/browser/devtools/styleinspector/test/doc_keyframeanimation.html b/browser/devtools/styleinspector/test/doc_keyframeanimation.html new file mode 100644 index 00000000000..4e02c32f057 --- /dev/null +++ b/browser/devtools/styleinspector/test/doc_keyframeanimation.html @@ -0,0 +1,13 @@ + + + + test case for keyframes rule in rule-view + + + +
+
+
+ + diff --git a/browser/devtools/styleinspector/test/head.js b/browser/devtools/styleinspector/test/head.js index 47bc80b38f4..cc0393501bb 100644 --- a/browser/devtools/styleinspector/test/head.js +++ b/browser/devtools/styleinspector/test/head.js @@ -619,11 +619,14 @@ function getRuleViewLinkByIndex(view, index) { /** * Get the rule editor from the rule-view given its index * @param {CssRuleView} view The instance of the rule-view panel - * @param {Number} index The index of the link to get + * @param {Number} childrenIndex The children index of the element to get + * @param {Number} nodeIndex The child node index of the element to get * @return {DOMNode} The rule editor if any at this index */ -function getRuleViewRuleEditor(view, index) { - return view.element.children[index]._ruleEditor; +function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) { + return nodeIndex !== undefined ? + view.element.children[childrenIndex].childNodes[nodeIndex]._ruleEditor : + view.element.children[childrenIndex]._ruleEditor; } /** diff --git a/toolkit/devtools/server/actors/styles.js b/toolkit/devtools/server/actors/styles.js index 63256b91c8a..7cc1aec9fe8 100644 --- a/toolkit/devtools/server/actors/styles.js +++ b/toolkit/devtools/server/actors/styles.js @@ -45,7 +45,8 @@ types.addLifetime("walker", "walker"); */ types.addDictType("appliedstyle", { rule: "domstylerule#actorid", - inherited: "nullable:domnode#actorid" + inherited: "nullable:domnode#actorid", + keyframes: "nullable:domstylerule#actorid" }); types.addDictType("matchedselector", { @@ -154,7 +155,7 @@ var PageStyleActor = protocol.ActorClass({ this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA; this.cssLogic.highlight(node.rawNode); - let computed = this.cssLogic._computedStyle || []; + let computed = this.cssLogic.computedStyle || []; Array.prototype.forEach.call(computed, name => { let matched = undefined; @@ -324,8 +325,7 @@ var PageStyleActor = protocol.ActorClass({ * Helper function for getApplied, adds all the rules from a given * element. */ - addElementRules: function(element, inherited, options, rules) - { + addElementRules: function(element, inherited, options, rules) { if (!element.style) { return; } @@ -379,7 +379,6 @@ var PageStyleActor = protocol.ActorClass({ isSystem: isSystem }); } - } }, @@ -431,6 +430,28 @@ var PageStyleActor = protocol.ActorClass({ } } + // Add all the keyframes rule associated with the element + let computedStyle = this.cssLogic.computedStyle; + if (computedStyle) { + let animationNames = computedStyle.animationName.split(","); + animationNames = animationNames.map(name => name.trim()); + + if (animationNames) { + // Traverse through all the available keyframes rule and add + // the keyframes rule that matches the computed animation name + for (let keyframesRule of this.cssLogic.keyframesRules) { + if (animationNames.indexOf(keyframesRule.name) > -1) { + for (let rule of keyframesRule.cssRules) { + entries.push({ + rule: this._styleRef(rule), + keyframes: this._styleRef(keyframesRule) + }); + } + } + } + } + } + let rules = new Set; let sheets = new Set; entries.forEach(entry => rules.add(entry.rule)); @@ -661,7 +682,9 @@ var StyleRuleActor = protocol.ActorClass({ if (item instanceof (Ci.nsIDOMCSSRule)) { this.type = item.type; this.rawRule = item; - if (this.rawRule instanceof Ci.nsIDOMCSSStyleRule && this.rawRule.parentStyleSheet) { + if ((this.rawRule instanceof Ci.nsIDOMCSSStyleRule || + this.rawRule instanceof Ci.nsIDOMMozCSSKeyframeRule) && + this.rawRule.parentStyleSheet) { this.line = DOMUtils.getRuleLine(this.rawRule); this.column = DOMUtils.getRuleColumn(this.rawRule); } @@ -739,6 +762,14 @@ var StyleRuleActor = protocol.ActorClass({ form.media.push(this.rawRule.media.item(i)); } break; + case Ci.nsIDOMCSSRule.KEYFRAMES_RULE: + form.cssText = this.rawRule.cssText; + form.name = this.rawRule.name; + break; + case Ci.nsIDOMCSSRule.KEYFRAME_RULE: + form.cssText = this.rawStyle.cssText || ""; + form.keyText = this.rawRule.keyText || ""; + break; } return form; @@ -884,7 +915,7 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, { * Return a new RuleModificationList for this node. */ startModifyingProperties: function() { - return new RuleModificationList(this); + return new RuleModificationList(this); }, get type() this._form.type, @@ -893,6 +924,12 @@ var StyleRuleFront = protocol.FrontClass(StyleRuleActor, { get cssText() { return this._form.cssText; }, + get keyText() { + return this._form.keyText; + }, + get name() { + return this._form.name; + }, get selectors() { return this._form.selectors; }, diff --git a/toolkit/devtools/styleinspector/css-logic.js b/toolkit/devtools/styleinspector/css-logic.js index ccecae5544d..cf2087d9d84 100644 --- a/toolkit/devtools/styleinspector/css-logic.js +++ b/toolkit/devtools/styleinspector/css-logic.js @@ -129,6 +129,9 @@ CssLogic.prototype = { _matchedRules: null, _matchedSelectors: null, + // Cached keyframes rules in all stylesheets + _keyframesRules: null, + /** * Reset various properties */ @@ -141,6 +144,7 @@ CssLogic.prototype = { this._sheetsCached = false; this._matchedRules = null; this._matchedSelectors = null; + this._keyframesRules = []; }, /** @@ -179,6 +183,15 @@ CssLogic.prototype = { this._computedStyle = win.getComputedStyle(this.viewedElement, ""); }, + /** + * Get the values of all the computed CSS properties for the highlighted + * element. + * @returns {object} The computed CSS properties for a selected element + */ + get computedStyle() { + return this._computedStyle; + }, + /** * Get the source filter. * @returns {string} The source filter being used. @@ -270,7 +283,7 @@ CssLogic.prototype = { * Cache a stylesheet if it falls within the requirements: if it's enabled, * and if the @media is allowed. This method also walks through the stylesheet * cssRules to find @imported rules, to cache the stylesheets of those rules - * as well. + * as well. In addition, the @keyframes rules in the stylesheet are cached. * * @private * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object to cache. @@ -291,13 +304,15 @@ CssLogic.prototype = { if (cssSheet._passId != this._passId) { cssSheet._passId = this._passId; - // Find import rules. - Array.prototype.forEach.call(aDomSheet.cssRules, function(aDomRule) { + // Find import and keyframes rules. + for (let aDomRule of aDomSheet.cssRules) { if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && aDomRule.styleSheet && this.mediaMatches(aDomRule)) { this._cacheSheet(aDomRule.styleSheet); + } else if (aDomRule.type == Ci.nsIDOMCSSRule.KEYFRAMES_RULE) { + this._keyframesRules.push(aDomRule); } - }, this); + } } }, @@ -322,6 +337,19 @@ CssLogic.prototype = { return sheets; }, + /** + * Retrieve the list of keyframes rules in the document. + * + * @ return {array} the list of keyframes rules in the document. + */ + get keyframesRules() + { + if (!this._sheetsCached) { + this._cacheSheets(); + } + return this._keyframesRules; + }, + /** * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the * stylesheet is already cached, you get the existing CssSheet object, @@ -620,7 +648,6 @@ CssLogic.prototype = { this._matchedRules.push([rule, status]); } - // Add element.style information. if (element.style && element.style.length > 0) { let rule = new CssRule(null, { style: element.style }, element); @@ -644,7 +671,7 @@ CssLogic.prototype = { let mediaText = aDomObject.media.mediaText; return !mediaText || this.viewedDocument.defaultView. matchMedia(mediaText).matches; - }, + }, }; /** @@ -1535,9 +1562,9 @@ CssPropertyInfo.prototype = { */ get value() { - if (!this._value && this._cssLogic._computedStyle) { + if (!this._value && this._cssLogic.computedStyle) { try { - this._value = this._cssLogic._computedStyle.getPropertyValue(this.property); + this._value = this._cssLogic.computedStyle.getPropertyValue(this.property); } catch (ex) { Services.console.logStringMessage('Error reading computed style for ' + this.property); diff --git a/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties b/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties index 3dce179519c..ba22d54a8fa 100644 --- a/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties +++ b/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties @@ -35,6 +35,10 @@ rule.sourceElement=element # e.g "Inherited from body#bodyID" rule.inheritedFrom=Inherited from %S +# LOCALIZATION NOTE (rule.keyframe): Shown for CSS Rules keyframe header. +# Will be passed an identifier of the keyframe animation name. +rule.keyframe=Keyframes %S + # LOCALIZATION NOTE (rule.userAgentStyles): Shown next to the style sheet # link for CSS rules that were loaded from a user agent style sheet. # These styles will not be editable, and will only be visible if the