From 0200db2cae8a61a06554efe4eb5a2a76194cbfe0 Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Wed, 18 Feb 2015 03:39:00 +0100 Subject: [PATCH] Bug 1029371 - Make style editor media sidebar interact with media sidebar. r=bgrins r=paul r=mgoodwin --- .../responsivedesign/responsivedesign.jsm | 56 +++++++------ devtools/client/styleeditor/StyleEditorUI.jsm | 79 ++++++++++++++---- devtools/client/styleeditor/test/browser.ini | 1 + .../test/browser_styleeditor_media_sidebar.js | 12 +-- ...browser_styleeditor_media_sidebar_links.js | 82 +++++++++++++++++++ .../client/styleeditor/test/media-rules.css | 6 ++ devtools/client/themes/styleeditor.css | 13 +-- 7 files changed, 198 insertions(+), 51 deletions(-) create mode 100644 devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js diff --git a/devtools/client/responsivedesign/responsivedesign.jsm b/devtools/client/responsivedesign/responsivedesign.jsm index bb030d24d76f..3c334d165d1a 100644 --- a/devtools/client/responsivedesign/responsivedesign.jsm +++ b/devtools/client/responsivedesign/responsivedesign.jsm @@ -52,6 +52,18 @@ this.ResponsiveUIManager = { if (this.isActiveForTab(aTab)) { ActiveTabs.get(aTab).close(); } else { + this.runIfNeeded(aWindow, aTab); + } + }, + + /** + * Launches the responsive mode. + * + * @param aWindow the main window. + * @param aTab the tab targeted. + */ + runIfNeeded: function(aWindow, aTab) { + if (!this.isActiveForTab(aTab)) { new ResponsiveUI(aWindow, aTab); } }, @@ -83,15 +95,11 @@ this.ResponsiveUIManager = { handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) { switch (aCommand) { case "resize to": - if (!this.isActiveForTab(aTab)) { - new ResponsiveUI(aWindow, aTab); - } + this.runIfNeeded(aWindow, aTab); ActiveTabs.get(aTab).setSize(aArgs.width, aArgs.height); break; case "resize on": - if (!this.isActiveForTab(aTab)) { - new ResponsiveUI(aWindow, aTab); - } + this.runIfNeeded(aWindow, aTab); break; case "resize off": if (this.isActiveForTab(aTab)) { @@ -851,36 +859,38 @@ ResponsiveUI.prototype = { * @param aHeight height of the browser. */ setSize: function RUI_setSize(aWidth, aHeight) { + this.setWidth(aWidth); + this.setHeight(aHeight); + }, + + setWidth: function RUI_setWidth(aWidth) { aWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH); - aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_HEIGHT); + this.stack.style.maxWidth = this.stack.style.minWidth = aWidth + "px"; - // We resize the containing stack. - let style = "max-width: %width;" + - "min-width: %width;" + - "max-height: %height;" + - "min-height: %height;"; - - style = style.replace(/%width/g, aWidth + "px"); - style = style.replace(/%height/g, aHeight + "px"); - - this.stack.setAttribute("style", style); - - if (!this.ignoreY) - this.resizeBarV.setAttribute("top", Math.round(aHeight / 2)); if (!this.ignoreX) this.resizeBarH.setAttribute("left", Math.round(aWidth / 2)); let selectedPreset = this.menuitems.get(this.selectedItem); - // We update the custom menuitem if we are using it if (selectedPreset.custom) { selectedPreset.width = aWidth; - selectedPreset.height = aHeight; - this.setMenuLabel(this.selectedItem, selectedPreset); } }, + setHeight: function RUI_setHeight(aHeight) { + aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_HEIGHT); + this.stack.style.maxHeight = this.stack.style.minHeight = aHeight + "px"; + + if (!this.ignoreY) + this.resizeBarV.setAttribute("top", Math.round(aHeight / 2)); + + let selectedPreset = this.menuitems.get(this.selectedItem); + if (selectedPreset.custom) { + selectedPreset.height = aHeight; + this.setMenuLabel(this.selectedItem, selectedPreset); + } + }, /** * Start the process of resizing the browser. * diff --git a/devtools/client/styleeditor/StyleEditorUI.jsm b/devtools/client/styleeditor/StyleEditorUI.jsm index 4c54f01e55ba..b60b81862fa2 100644 --- a/devtools/client/styleeditor/StyleEditorUI.jsm +++ b/devtools/client/styleeditor/StyleEditorUI.jsm @@ -11,25 +11,23 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://devtools/shared/event-emitter.js"); -Cu.import("resource://devtools/client/framework/gDevTools.jsm"); +const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const Services = require("Services"); +const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {}); +const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); +const {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +const EventEmitter = require("devtools/shared/event-emitter"); +const {gDevTools} = require("resource://devtools/client/framework/gDevTools.jsm"); Cu.import("resource://devtools/client/styleeditor/StyleEditorUtil.jsm"); -Cu.import("resource://devtools/client/shared/SplitView.jsm"); -Cu.import("resource://devtools/client/styleeditor/StyleSheetEditor.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", - "resource://gre/modules/PluralForm.jsm"); - -const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); -const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/client/styleeditor/utils"); +const {SplitView} = Cu.import("resource://devtools/client/shared/SplitView.jsm", {}); +const {StyleSheetEditor} = Cu.import("resource://devtools/client/styleeditor/StyleSheetEditor.jsm"); +loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); +const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); const csscoverage = require("devtools/server/actors/csscoverage"); -const console = require("resource://gre/modules/Console.jsm").console; +const {console} = require("resource://gre/modules/Console.jsm"); const promise = require("promise"); +const {ResponsiveUIManager} = + Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {}); const LOAD_ERROR = "error-load"; const STYLE_EDITOR_TEMPLATE = "stylesheet"; @@ -879,10 +877,14 @@ StyleEditorUI.prototype = { let cond = this._panelDoc.createElement("div"); cond.textContent = rule.conditionText; - cond.className = "media-rule-condition" + cond.className = "media-rule-condition"; if (!rule.matches) { cond.classList.add("media-condition-unmatched"); } + if (this._target.tab.tagName == "tab") { + cond.innerHTML = cond.textContent.replace(/(min\-|max\-)(width|height):\s\d+(px)/ig, "$&"); + cond.addEventListener("click", this._onMediaConditionClick.bind(this)); + } div.appendChild(cond); let link = this._panelDoc.createElement("div"); @@ -901,6 +903,49 @@ StyleEditorUI.prototype = { }.bind(this)).then(null, Cu.reportError); }, + /** + * Called when a media condition is clicked + * If a responsive mode link is clicked, it will launch it. + * + * @param {object} e + * Event object + */ + _onMediaConditionClick: function(e) { + if (!e.target.matches(".media-responsive-mode-toggle")) { + return; + } + let conditionText = e.target.textContent; + let isWidthCond = conditionText.toLowerCase().indexOf("width") > -1; + let mediaVal = parseInt(/\d+/.exec(conditionText)); + + let options = isWidthCond ? {width: mediaVal} : {height: mediaVal}; + this._launchResponsiveMode(options); + e.preventDefault(); + e.stopPropagation(); + }, + + /** + * Launches the responsive mode with a specific width or height + * + * @param {object} options + * Object with width or/and height properties. + */ + _launchResponsiveMode: function(options = {}) { + let tab = this._target.tab; + let win = this._target.tab.ownerGlobal; + + ResponsiveUIManager.runIfNeeded(win, tab); + if (options.width && options.height) { + ResponsiveUIManager.getResponsiveUIForTab(tab).setSize(options.width, options.height); + } + else if (options.width) { + ResponsiveUIManager.getResponsiveUIForTab(tab).setWidth(options.width); + } + else if (options.height) { + ResponsiveUIManager.getResponsiveUIForTab(tab).setHeight(options.height); + } + }, + /** * Jump cursor to the editor for a stylesheet and line number for a rule. * diff --git a/devtools/client/styleeditor/test/browser.ini b/devtools/client/styleeditor/test/browser.ini index 4d6de39a2bfb..9cb43b0fb9dd 100644 --- a/devtools/client/styleeditor/test/browser.ini +++ b/devtools/client/styleeditor/test/browser.ini @@ -66,6 +66,7 @@ support-files = [browser_styleeditor_inline_friendly_names.js] [browser_styleeditor_loading.js] [browser_styleeditor_media_sidebar.js] +[browser_styleeditor_media_sidebar_links.js] [browser_styleeditor_media_sidebar_sourcemaps.js] [browser_styleeditor_missing_stylesheet.js] [browser_styleeditor_navigate.js] diff --git a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js index eb89755ed7c7..164304d9db99 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js @@ -9,8 +9,9 @@ const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar"; const RESIZE = 300; -const LABELS = ["not all", "all", "(max-width: 400px)", "(max-width: 600px)"]; -const LINE_NOS = [1, 7, 19, 25]; +const LABELS = ["not all", "all", "(max-width: 400px)", + "(min-height: 200px) and (max-height: 250px)", "(max-width: 600px)"]; +const LINE_NOS = [1, 7, 19, 25, 30]; const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }"; waitForExplicitFinish(); @@ -59,11 +60,12 @@ function testMediaEditor(editor) { 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"); + is(entries.length, 4, "four @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]); + testRule(entries[3], LABELS[3], false, LINE_NOS[3]); } function testMediaMatchChanged(editor) { @@ -102,9 +104,9 @@ function* testMediaRuleAdded(UI, editor) { let sidebar = editor.details.querySelector(".stylesheet-sidebar"); let entries = [...sidebar.querySelectorAll(".media-rule-label")]; - is(entries.length, 4, "four @media rules after changing text"); + is(entries.length, 5, "five @media rules after changing text"); - testRule(entries[3], LABELS[3], false, LINE_NOS[3]); + testRule(entries[4], LABELS[4], false, LINE_NOS[4]); } function testRule(rule, text, matches, lineno) { diff --git a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js new file mode 100644 index 000000000000..4f5573d7ab97 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js @@ -0,0 +1,82 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Tests responsive mode links for + * @media sidebar width and height related conditions */ + +const {ResponsiveUIManager} = + Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {}); +const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; + +waitForExplicitFinish(); + +add_task(function*() { + let {ui} = yield openStyleEditorForURL(TESTCASE_URI); + + let mediaEditor = ui.editors[1]; + yield openEditor(mediaEditor); + + yield testLinkifiedConditions(mediaEditor, gBrowser.selectedTab, ui); +}); + +function testLinkifiedConditions(editor, tab, ui) { + let sidebar = editor.details.querySelector(".stylesheet-sidebar"); + let conditions = sidebar.querySelectorAll(".media-rule-condition"); + let responsiveModeToggleClass = ".media-responsive-mode-toggle"; + + info("Testing if media rules have the appropriate number of links"); + ok(!conditions[0].querySelector(responsiveModeToggleClass), + "There should be no links in the first media rule."); + ok(!conditions[1].querySelector(responsiveModeToggleClass), + "There should be no links in the second media rule.") + ok(conditions[2].querySelector(responsiveModeToggleClass), + "There should be 1 responsive mode link in the media rule"); + is(conditions[3].querySelectorAll(responsiveModeToggleClass).length, 2, + "There should be 2 resposnive mode links in the media rule"); + + info("Launching responsive mode"); + conditions[2].querySelector(responsiveModeToggleClass).click(); + + info("Waiting for the @media list to update"); + let onMediaChange = once("media-list-changed", ui); + yield once("on", ResponsiveUIManager); + yield onMediaChange; + + ok(ResponsiveUIManager.isActiveForTab(tab), + "Responsive mode should be active."); + conditions = sidebar.querySelectorAll(".media-rule-condition"); + ok(!conditions[2].classList.contains("media-condition-unmatched"), + "media rule should now be matched after responsive mode is active"); + + info("Closing responsive mode"); + ResponsiveUIManager.toggle(window, tab); + onMediaChange = once("media-list-changed", ui); + yield once("off", ResponsiveUIManager); + yield onMediaChange; + + ok(!ResponsiveUIManager.isActiveForTab(tab), + "Responsive mode should no longer be active."); + conditions = sidebar.querySelectorAll(".media-rule-condition"); + ok(conditions[2].classList.contains("media-condition-unmatched"), + "media rule should now be unmatched after responsive mode is closed"); +} + +/* Helpers */ +function once(event, target) { + let deferred = promise.defer(); + target.once(event, () => { + deferred.resolve(); + }); + return deferred.promise; +} + +function openEditor(editor) { + getLinkFor(editor).click(); + + return editor.getSourceEditor(); +} + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} diff --git a/devtools/client/styleeditor/test/media-rules.css b/devtools/client/styleeditor/test/media-rules.css index b4db3f216749..f9ae816d0f8a 100644 --- a/devtools/client/styleeditor/test/media-rules.css +++ b/devtools/client/styleeditor/test/media-rules.css @@ -21,3 +21,9 @@ div { color: green; } } + +@media (min-height: 200px) and (max-height: 250px) { + div { + color: orange; + } +} \ No newline at end of file diff --git a/devtools/client/themes/styleeditor.css b/devtools/client/themes/styleeditor.css index b694a06ed20f..76e38631bdeb 100644 --- a/devtools/client/themes/styleeditor.css +++ b/devtools/client/themes/styleeditor.css @@ -84,16 +84,17 @@ border-bottom: 1px solid; } +.media-responsive-mode-toggle { + color: var(--theme-highlight-blue); + text-decoration: underline; +} + .media-rule-line { -moz-padding-start: 4px; } -.theme-light .media-condition-unmatched { - color: grey; -} - -.theme-dark .media-condition-unmatched { - color: #606C75; +.media-condition-unmatched { + opacity: 0.4; } .stylesheet-enabled {