mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1012806 - Add @media rules sidebar to style editor; r=jwalker
This commit is contained in:
parent
c03d4a23f9
commit
24f4d95564
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 -->
|
||||
|
@ -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]
|
||||
|
@ -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");
|
||||
}
|
@ -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);
|
||||
}
|
23
browser/devtools/styleeditor/test/media-rules.css
Normal file
23
browser/devtools/styleeditor/test/media-rules.css
Normal 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;
|
||||
}
|
||||
}
|
13
browser/devtools/styleeditor/test/media-rules.html
Normal file
13
browser/devtools/styleeditor/test/media-rules.html
Normal 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>
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>.
|
||||
|
Loading…
Reference in New Issue
Block a user