Bug 1012806 - Add @media rules sidebar to style editor; r=jwalker

This commit is contained in:
Heather Arthur 2014-05-26 13:52:24 -04:00
parent c03d4a23f9
commit 24f4d95564
14 changed files with 585 additions and 24 deletions

View File

@ -1340,6 +1340,7 @@ pref("devtools.scratchpad.enableAutocompletion", true);
pref("devtools.styleeditor.enabled", true);
pref("devtools.styleeditor.source-maps-enabled", false);
pref("devtools.styleeditor.autocompletion-enabled", true);
pref("devtools.styleeditor.showMediaSidebar", false);
// Enable the Shader Editor.
pref("devtools.shadereditor.enabled", false);

View File

@ -33,6 +33,7 @@ const console = require("resource://gre/modules/devtools/Console.jsm").console;
const LOAD_ERROR = "error-load";
const STYLE_EDITOR_TEMPLATE = "stylesheet";
const PREF_MEDIA_SIDEBAR = "devtools.styleeditor.showMediaSidebar";
/**
* StyleEditorUI is controls and builds the UI of the Style Editor, including
@ -63,14 +64,17 @@ function StyleEditorUI(debuggee, target, panelDoc) {
this.selectedEditor = null;
this.savedLocations = {};
this._updateSourcesLabel = this._updateSourcesLabel.bind(this);
this._updateContextMenu = this._updateContextMenu.bind(this);
this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
this._onNewDocument = this._onNewDocument.bind(this);
this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this);
this._updateMediaList = this._updateMediaList.bind(this);
this._clear = this._clear.bind(this);
this._onError = this._onError.bind(this);
this._prefObserver = new PrefObserver("devtools.styleeditor.");
this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument);
this._prefObserver.on(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
}
StyleEditorUI.prototype = {
@ -140,24 +144,34 @@ StyleEditorUI.prototype = {
this._contextMenu = this._panelDoc.getElementById("sidebar-context");
this._contextMenu.addEventListener("popupshowing",
this._updateSourcesLabel);
this._updateContextMenu);
this._sourcesItem = this._panelDoc.getElementById("context-origsources");
this._sourcesItem.addEventListener("command",
this._toggleOrigSources);
this._mediaItem = this._panelDoc.getElementById("context-show-media");
this._mediaItem.addEventListener("command",
this._toggleMediaSidebar);
},
/**
* Update text of context menu option to reflect whether we're showing
* original sources (e.g. Sass files) or not.
* Update text of context menu option to reflect current preference
* settings
*/
_updateSourcesLabel: function() {
let string = "showOriginalSources";
_updateContextMenu: function() {
let sourceString = "showOriginalSources";
if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
string = "showCSSSources";
sourceString = "showCSSSources";
}
this._sourcesItem.setAttribute("label", _(string + ".label"));
this._sourcesItem.setAttribute("accesskey", _(string + ".accesskey"));
this._sourcesItem.setAttribute("label", _(sourceString + ".label"));
this._sourcesItem.setAttribute("accesskey", _(sourceString + ".accesskey"));
let mediaString = "showMediaSidebar"
if (Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR)) {
mediaString = "hideMediaSidebar";
}
this._mediaItem.setAttribute("label", _(mediaString + ".label"));
this._mediaItem.setAttribute("accesskey", _(mediaString + ".accesskey"));
},
/**
@ -202,7 +216,7 @@ StyleEditorUI.prototype = {
let {line, ch} = this.selectedEditor.sourceEditor.getCursor();
this._styleSheetToSelect = {
href: href,
stylesheet: href,
line: line,
col: ch
};
@ -274,6 +288,7 @@ StyleEditorUI.prototype = {
new StyleSheetEditor(styleSheet, this._window, file, isNew, this._walker);
editor.on("property-change", this._summaryChange.bind(this, editor));
editor.on("media-rules-changed", this._updateMediaList.bind(this, editor));
editor.on("linked-css-file", this._summaryChange.bind(this, editor));
editor.on("linked-css-file-error", this._summaryChange.bind(this, editor));
editor.on("error", this._onError);
@ -342,13 +357,28 @@ StyleEditorUI.prototype = {
},
/**
* Toggle the original sources pref.
* Toggle the original sources pref.
*/
_toggleOrigSources: function() {
let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
},
/**
* Toggle the pref for showing a @media rules sidebar in each editor.
*/
_toggleMediaSidebar: function() {
let isEnabled = Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR);
Services.prefs.setBoolPref(PREF_MEDIA_SIDEBAR, !isEnabled);
},
/**
* Toggle the @media sidebar in each editor depending on the setting.
*/
_onMediaPrefChanged: function() {
this.editors.forEach(this._updateMediaList);
},
/**
* Remove a particular stylesheet editor from the UI
*
@ -401,6 +431,7 @@ StyleEditorUI.prototype = {
onCreate: function(summary, details, data) {
let editor = data.editor;
editor.summary = summary;
editor.details = details;
wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) {
event.stopPropagation();
@ -442,7 +473,7 @@ StyleEditorUI.prototype = {
}
if (this._styleSheetToSelect
&& this._styleSheetToSelect.href == editor.styleSheet.href) {
&& this._styleSheetToSelect.stylesheet == editor.styleSheet.href) {
yield this.switchToSelectedSheet();
}
@ -492,9 +523,11 @@ StyleEditorUI.prototype = {
*/
switchToSelectedSheet: function() {
let sheet = this._styleSheetToSelect;
let isHref = sheet.stylesheet === null || typeof sheet.stylesheet == "string";
for (let editor of this.editors) {
if (editor.styleSheet.href == sheet.href) {
if ((isHref && editor.styleSheet.href == sheet.stylesheet) ||
sheet.stylesheet == editor.styleSheet) {
// The _styleSheetBoundToSelect will always hold the latest pending
// requested style sheet (with line and column) which is not yet
// selected by the source editor. Only after we select that particular
@ -554,6 +587,24 @@ StyleEditorUI.prototype = {
return deferred.promise;
},
getEditorDetails: function(editor) {
if (editor.details) {
return promise.resolve(editor.details);
}
let deferred = promise.defer();
let self = this;
this.on("editor-added", function onAdd(e, selected) {
if (selected == editor) {
self.off("editor-added", onAdd);
deferred.resolve(editor.details);
}
});
return deferred.promise;
},
/**
* Returns an identifier for the given style sheet.
*
@ -569,19 +620,16 @@ StyleEditorUI.prototype = {
/**
* selects a stylesheet and optionally moves the cursor to a selected line
*
* @param {string} [href]
* Href of stylesheet that should be selected. If a stylesheet is not passed
* and the editor is not initialized we focus the first stylesheet. If
* a stylesheet is not passed and the editor is initialized we ignore
* the call.
* @param {StyleSheetFront} [stylesheet]
* Stylesheet to select or href of stylesheet to select
* @param {Number} [line]
* Line to which the caret should be moved (zero-indexed).
* @param {Number} [col]
* Column to which the caret should be moved (zero-indexed).
*/
selectStyleSheet: function(href, line, col) {
selectStyleSheet: function(stylesheet, line, col) {
this._styleSheetToSelect = {
href: href,
stylesheet: stylesheet,
line: line,
col: col,
};
@ -653,10 +701,66 @@ StyleEditorUI.prototype = {
PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount));
},
/**
* Update the @media rules sidebar for an editor. Hide if there are no rules
* Display a list of the @media rules in the editor's associated style sheet.
* Emits a 'media-list-changed' event after updating the UI.
*
* @param {StyleSheetEditor} editor
* Editor to update @media sidebar of
*/
_updateMediaList: function(editor) {
this.getEditorDetails(editor).then((details) => {
let list = details.querySelector(".stylesheet-media-list");
while (list.firstChild) {
list.removeChild(list.firstChild);
}
let rules = editor.mediaRules;
let showSidebar = Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR);
let sidebar = details.querySelector(".stylesheet-sidebar");
sidebar.hidden = !showSidebar || !rules.length;
for (let rule of rules) {
let div = this._panelDoc.createElement("div");
div.className = "media-rule-label";
div.addEventListener("click", this._jumpToMediaRule.bind(this, rule));
let cond = this._panelDoc.createElement("div");
cond.textContent = rule.conditionText;
cond.className = "media-rule-condition"
if (!rule.matches) {
cond.classList.add("media-condition-unmatched");
}
div.appendChild(cond);
let line = this._panelDoc.createElement("div");
line.className = "media-rule-line theme-link";
line.textContent = ":" + rule.line;
div.appendChild(line);
list.appendChild(div);
}
this.emit("media-list-changed", editor);
});
},
/**
* Jump cursor to the editor for a stylesheet and line number for a rule.
*
* @param {MediaRuleFront} rule
* Rule to jump to.
*/
_jumpToMediaRule: function(rule) {
this.selectStyleSheet(rule.parentStyleSheet, rule.line - 1, rule.column - 1);
},
destroy: function() {
this._clearStyleSheetEditors();
this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument);
this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
this._prefObserver.destroy();
}
}

View File

@ -91,9 +91,17 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker) {
this._onPropertyChange = this._onPropertyChange.bind(this);
this._onError = this._onError.bind(this);
this._onMediaRuleMatchesChange = this._onMediaRuleMatchesChange.bind(this);
this._onMediaRulesChanged = this._onMediaRulesChanged.bind(this)
this.checkLinkedFileForChanges = this.checkLinkedFileForChanges.bind(this);
this.markLinkedFileBroken = this.markLinkedFileBroken.bind(this);
this.mediaRules = [];
if (this.styleSheet.getMediaRules) {
this.styleSheet.getMediaRules().then(this._onMediaRulesChanged);
}
this.styleSheet.on("media-rules-changed", this._onMediaRulesChanged);
this._focusOnSourceEditorReady = false;
let relatedSheet = this.styleSheet.relatedStyleSheet;
@ -275,6 +283,36 @@ StyleSheetEditor.prototype = {
this.emit("property-change", property, value);
},
/**
* Handles changes to the list of @media rules in the stylesheet.
* Emits 'media-rules-changed' if the list has changed.
*
* @param {array} rules
* Array of MediaRuleFronts for new media rules of sheet.
*/
_onMediaRulesChanged: function(rules) {
if (!rules.length && !this.mediaRules.length) {
return;
}
for (let rule of this.mediaRules) {
rule.off("matches-change", this._onMediaRuleMatchesChange);
rule.destroy();
}
this.mediaRules = rules;
for (let rule of rules) {
rule.on("matches-change", this._onMediaRuleMatchesChange);
}
this.emit("media-rules-changed", rules);
},
/**
* Forward media-rules-changed event from stylesheet.
*/
_onMediaRuleMatchesChange: function() {
this.emit("media-rules-changed", this.mediaRules);
},
/**
* Forward error event from stylesheet.
*
@ -590,6 +628,7 @@ StyleSheetEditor.prototype = {
if (this.sourceEditor) {
this.sourceEditor.destroy();
}
this.styleSheet.off("media-rules-changed", this._onMediaRulesChanged);
this.styleSheet.off("property-change", this._onPropertyChange);
this.styleSheet.off("error", this._onError);
}

View File

@ -34,6 +34,25 @@ li.error > .stylesheet-info > .stylesheet-more > .stylesheet-error-message {
display: -moz-box;
}
.stylesheet-details-container {
-moz-box-flex: 1;
}
.stylesheet-media-list {
overflow-x: hidden;
overflow-y: auto;
-moz-box-flex: 1;
}
.media-rule-label {
display: flex;
}
.media-rule-condition {
flex: 1;
overflow: hidden;
}
.splitview-nav > li {
-moz-box-orient: horizontal;
}

View File

@ -61,6 +61,7 @@
</xul:menupopup>
<xul:menupopup id="sidebar-context">
<xul:menuitem id="context-origsources"/>
<xul:menuitem id="context-show-media"/>
</xul:menupopup>
</xul:popupset>
@ -128,8 +129,16 @@
<xul:resizer class="splitview-portrait-resizer"
dir="bottom"
element="splitview-resizer-target"/>
<xul:box class="stylesheet-editor-input textbox"
data-placeholder="&editorTextbox.placeholder;"/>
<xul:hbox class="stylesheet-details-container">
<xul:box class="stylesheet-editor-input textbox"
data-placeholder="&editorTextbox.placeholder;"/>
<xul:vbox class="stylesheet-sidebar theme-sidebar" hidden="true">
<xul:toolbar class="devtools-toolbar">
&mediaRules.label;
</xul:toolbar>
<xul:vbox class="stylesheet-media-list"/>
</xul:vbox>
</xul:hbox>
</xul:box>
</div> <!-- #splitview-templates -->
</xul:box> <!-- .splitview-root -->

View File

@ -14,6 +14,8 @@ support-files =
longload.html
media-small.css
media.html
media-rules.html
media-rules.css
minified.html
nostyle.html
pretty.css
@ -46,6 +48,7 @@ skip-if = os == "linux" || "mac" # bug 949355
[browser_styleeditor_init.js]
[browser_styleeditor_inline_friendly_names.js]
[browser_styleeditor_loading.js]
[browser_styleeditor_media_sidebar.js]
[browser_styleeditor_new.js]
[browser_styleeditor_nostyle.js]
[browser_styleeditor_pretty.js]

View File

@ -0,0 +1,102 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// https rather than chrome to improve coverage
const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
const PREF = "devtools.styleeditor.showMediaSidebar";
const RESIZE = 300;
const LABELS = ["not all", "all", "(max-width: 400px)"];
const LINE_NOS = [2, 8, 20];
waitForExplicitFinish();
let test = asyncTest(function*() {
Services.prefs.setBoolPref(PREF, true);
let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
is(UI.editors.length, 2, "correct number of editors");
// Test first plain css editor
let plainEditor = UI.editors[0];
yield openEditor(plainEditor);
testPlainEditor(plainEditor);
// Test editor with @media rules
let mediaEditor = UI.editors[1];
yield openEditor(mediaEditor);
testMediaEditor(mediaEditor);
// Test resizing and seeing @media matching state change
let originalWidth = window.outerWidth;
let originalHeight = window.outerHeight;
let onMatchesChange = listenForMatchesChange(UI);
window.resizeTo(RESIZE, RESIZE);
yield onMatchesChange;
testMediaMatchChanged(mediaEditor);
window.resizeTo(originalWidth, originalHeight);
Services.prefs.clearUserPref(PREF);
});
function testPlainEditor(editor) {
let sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(sidebar.hidden, true, "sidebar is hidden on editor without @media");
}
function testMediaEditor(editor) {
let sidebar = editor.details.querySelector(".stylesheet-sidebar");
is(sidebar.hidden, false, "sidebar is showing on editor with @media");
let entries = [...sidebar.querySelectorAll(".media-rule-label")];
is(entries.length, 3, "three @media rules displayed in sidebar");
testRule(entries[0], LABELS[0], false, LINE_NOS[0]);
testRule(entries[1], LABELS[1], true, LINE_NOS[1]);
testRule(entries[2], LABELS[2], false, LINE_NOS[2]);
}
function testMediaMatchChanged(editor) {
let sidebar = editor.details.querySelector(".stylesheet-sidebar");
let cond = sidebar.querySelectorAll(".media-rule-condition")[2];
is(cond.textContent, "(max-width: 400px)", "third rule condition text is correct");
ok(!cond.classList.contains("media-condition-unmatched"),
"media rule is now matched after resizing");
}
function testRule(rule, text, matches, lineno) {
let cond = rule.querySelector(".media-rule-condition");
is(cond.textContent, text, "media label is correct for " + text);
let matched = !cond.classList.contains("media-condition-unmatched");
ok(matches ? matched : !matched,
"media rule is " + (matches ? "matched" : "unmatched"));
let line = rule.querySelector(".media-rule-line");
is(line.textContent, ":" + lineno, "correct line number shown");
}
/* Helpers */
function openEditor(editor) {
getLinkFor(editor).click();
return editor.getSourceEditor();
}
function listenForMatchesChange(UI) {
let deferred = promise.defer();
UI.once("media-list-changed", () => {
deferred.resolve();
})
return deferred.promise;
}
function getLinkFor(editor) {
return editor.summary.querySelector(".stylesheet-name");
}

View File

@ -34,7 +34,7 @@ function runTests()
editor.getSourceEditor().then(() => {
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
is(line, LINE_NO, "correct line selected");
is(ch, COL_NO, "correct column selected");
@ -42,6 +42,5 @@ function runTests()
finish();
});
});
gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO);
}

View File

@ -0,0 +1,23 @@
@media not all {
div {
color: blue;
}
}
@media all {
div {
color: red;
}
}
div {
width: 20px;
height: 20px;
background-color: ghostwhite;
}
@media (max-width: 400px) {
div {
color: green;
}
}

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="simple.css"/>
<link rel="stylesheet" href="media-rules.css"/>
</head>
<body>
<div>
Testing style editor media sidebar
</div>
</body>
</html>

View File

@ -24,6 +24,10 @@
<!ENTITY saveButton.tooltip "Save this style sheet to a file">
<!ENTITY saveButton.accesskey "S">
<!-- LOCALICATION NOTE (mediaRules.label): This is shown above the list of @media rules
in each stylesheet editor sidebar. -->
<!ENTITY mediaRules.label "@media rules">
<!ENTITY editorTextbox.placeholder "Type CSS here.">
<!-- LOCALICATION NOTE (noStyleSheet.label): This is shown when a page has no

View File

@ -80,6 +80,22 @@ showCSSSources.label=Show CSS sources
# menu item to toggle back to showing only CSS sources in the editor.
showCSSSources.accesskey=C
# LOCALIZATION NOTE (showMediaSidebar.label): This is the label on the context
# menu item to toggle showing @media rule shortcuts in a sidebar.
showMediaSidebar.label=Show @media sidebar
# LOCALIZATION NOTE (showMediaSidebar.accesskey): This is the access key for
# the menu item to toggle showing the @media sidebar.
showMediaSidebar.accesskey=M
# LOCALIZATION NOTE (hideMediaSidebar.label): This is the label on the context
# menu item to stop showing @media rule shortcuts in a sidebar.
hideMediaSidebar.label=Hide @media sidebar
# LOCALIZATION NOTE (hideMediaSidebar.accesskey): This is the access key for
# the menu item to stop showing the @media sidebar.
hideMediaSidebar.accesskey=H
# LOCALIZATION NOTE (ToolboxStyleEditor.label):
# This string is displayed in the title of the tab when the style editor is
# displayed inside the developer tools window and in the Developer Tools Menu.

View File

@ -112,6 +112,39 @@
padding: 0 10px;
}
.stylesheet-sidebar {
width: 230px;
}
.theme-light .stylesheet-sidebar {
border-left: 1px solid #A6A6A6;
}
.theme-dark .stylesheet-sidebar {
border-left: 1px solid #606C75;
}
.theme-light .media-rule-label {
border-bottom: 1px solid #CCC;
}
.theme-dark .media-rule-label {
border-bottom: 1px solid #343C45;
}
.media-rule-label {
padding: 4px;
cursor: pointer;
}
.theme-light .media-condition-unmatched {
color: grey;
}
.theme-dark .media-condition-unmatched {
color: #606C75;
}
.stylesheet-enabled {
padding: 8px 0;
margin: 0 8px;
@ -172,6 +205,12 @@ h3 {
padding: 0;
}
@media (max-width: 700px) {
.stylesheet-sidebar {
width: 150px;
}
}
/* portrait mode */
@media (max-width: 550px) {
.splitview-nav {
@ -195,4 +234,8 @@ h3 {
.splitview-nav > li > hgroup.stylesheet-info {
-moz-box-align: baseline;
}
.stylesheet-sidebar {
width: 180px;
}
}

View File

@ -74,6 +74,9 @@ let StyleSheetsActor = protocol.ActorClass({
this.parentActor = tabActor;
// so we can get events when stylesheets and rules are added
this.document.styleSheetChangeEventsEnabled = true;
// keep a map of sheets-to-actors so we don't create two actors for one sheet
this._sheets = new Map();
},
@ -275,6 +278,122 @@ let StyleSheetsFront = protocol.FrontClass(StyleSheetsActor, {
}
});
/**
* A MediaRuleActor lives on the server and provides access to properties
* of a DOM @media rule and emits events when it changes.
*/
let MediaRuleActor = protocol.ActorClass({
typeName: "mediarule",
events: {
"matches-change" : {
type: "matchesChange",
matches: Arg(0, "boolean"),
}
},
get window() this.parentActor.window,
get document() this.window.document,
get matches() {
return this.mql ? this.mql.matches : null;
},
initialize: function(aMediaRule, aParentActor) {
protocol.Actor.prototype.initialize.call(this, null);
this.rawRule = aMediaRule;
this.parentActor = aParentActor;
this.conn = this.parentActor.conn;
this._matchesChange = this._matchesChange.bind(this);
this.line = 0;
this.column = 0;
// We can't get the line of the @media rule itself, so get the line of
// the first rule in the media block. See bug 591303.
let firstRule = this.rawRule.cssRules[0];
if (firstRule && firstRule instanceof Ci.nsIDOMCSSStyleRule) {
this.line = DOMUtils.getRuleLine(firstRule);
this.column = DOMUtils.getRuleColumn(firstRule);
}
try {
this.mql = this.window.matchMedia(aMediaRule.media.mediaText);
} catch(e) {
}
if (this.mql) {
this.mql.addListener(this._matchesChange);
}
},
destroy: function()
{
if (this.mql) {
this.mql.removeListener(this._matchesChange);
}
},
form: function(detail) {
if (detail === "actorid") {
return this.actorID;
}
let form = {
actor: this.actorID, // actorID is set when this is added to a pool
mediaText: this.rawRule.media.mediaText,
conditionText: this.rawRule.conditionText,
matches: this.matches,
line: this.line,
column: this.column,
parentStyleSheet: this.parentActor.actorID
};
return form;
},
_matchesChange: function() {
events.emit(this, "matches-change", this.matches);
}
});
/**
* Cooresponding client-side front for a MediaRuleActor.
*/
let MediaRuleFront = protocol.FrontClass(MediaRuleActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
this._onMatchesChange = this._onMatchesChange.bind(this);
events.on(this, "matches-change", this._onMatchesChange);
},
_onMatchesChange: function(matches) {
this._form.matches = matches;
},
form: function(form, detail) {
if (detail === "actorid") {
this.actorID = form;
return;
}
this.actorID = form.actor;
this._form = form;
},
get mediaText() this._form.mediaText,
get conditionText() this._form.conditionText,
get matches() this._form.matches,
get line() this._form.line || -1,
get column() this._form.column || -1,
get parentStyleSheet() {
return this.conn.getActor(this._form.parentStyleSheet);
}
});
/**
* A StyleSheetActor represents a stylesheet on the server.
*/
@ -289,6 +408,10 @@ let StyleSheetActor = protocol.ActorClass({
},
"style-applied" : {
type: "styleApplied"
},
"media-rules-changed" : {
type: "mediaRulesChanged",
rules: Arg(0, "array:mediarule")
}
},
@ -309,6 +432,16 @@ let StyleSheetActor = protocol.ActorClass({
*/
get document() this.window.document,
/**
* Browser for the target.
*/
get browser() {
if (this.parentActor.parentActor) {
return this.parentActor.parentActor.browser;
}
return null;
},
/**
* URL of underlying stylesheet.
*/
@ -346,6 +479,24 @@ let StyleSheetActor = protocol.ActorClass({
this._styleSheetIndex = -1;
this._transitionRefCount = 0;
this._onRuleAddedOrRemoved = this._onRuleAddedOrRemoved.bind(this);
if (this.browser) {
this.browser.addEventListener("StyleRuleAdded", this._onRuleAddedOrRemoved, true);
this.browser.addEventListener("StyleRuleRemoved", this._onRuleAddedOrRemoved, true);
}
},
_onRuleAddedOrRemoved: function(event) {
if (event.target != this.document || event.stylesheet != this.rawSheet) {
return;
}
if (event.rule && event.rule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
this._getMediaRules().then((rules) => {
events.emit(this, "media-rules-changed", rules);
});
}
},
/**
@ -663,6 +814,41 @@ let StyleSheetActor = protocol.ActorClass({
}))
}),
/**
* Protocol method to get the media rules for the stylesheet.
*/
getMediaRules: method(function() {
return this._getMediaRules();
}, {
request: {},
response: {
mediaRules: RetVal("nullable:array:mediarule")
}
}),
/**
* Get all the @media rules in this stylesheet.
*
* @return {promise}
* A promise that resolves with an array of MediaRuleActors.
*/
_getMediaRules: function() {
return this.getCSSRules().then((rules) => {
let mediaRules = [];
for (let i = 0; i < rules.length; i++) {
let rule = rules[i];
if (rule.type != Ci.nsIDOMCSSRule.MEDIA_RULE) {
continue;
}
let actor = new MediaRuleActor(rule, this);
this.manage(actor);
mediaRules.push(actor);
}
return mediaRules;
});
},
/**
* Get the charset of the stylesheet according to the character set rules
* defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.