Bug 1030889 - [rule view] Add keyframe rules with its associated element r=pbrosset

This commit is contained in:
Gabriel Luong 2014-07-19 20:08:43 -07:00
parent a059c6805b
commit 86f83d1625
13 changed files with 545 additions and 82 deletions

View File

@ -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) {

View File

@ -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;
}

View File

@ -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]

View File

@ -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);

View File

@ -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;
}

View File

@ -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};
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -0,0 +1,13 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<title>test case for keyframes rule in rule-view</title>
<link rel="stylesheet" type="text/css" href="doc_keyframeanimation.css"/>
</head>
<body>
<div id="pacman"></div>
<div id="boxy" class="circle"></div>
<div id="moxy" class="circle"></div>
</body>
</html>

View File

@ -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;
}
/**

View File

@ -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;
},

View File

@ -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);

View File

@ -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