Bug 1179820 - Convert style editor to client-side source maps; r?gl
MozReview-Commit-ID: CV53VKKZz4A
--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -133,17 +133,18 @@ SourceMapURLService.prototype._onSourceU
* The new style sheet's actor.
*/
SourceMapURLService.prototype._onNewStyleSheet = function (sheet) {
// Maybe we were shut down while waiting.
if (!this._urls) {
return;
}
- let {href: url, sourceMapURL, actor: id} = sheet._form;
+ let {href, nodeHref, sourceMapURL, actorID: id} = sheet;
+ let url = href || nodeHref;
this._urls.set(url, { id, url, sourceMapURL});
this._idMap.set(id, url);
};
/**
* A callback that is called from the lower-level source map service
* proxy (see toolbox.js) when some tool has installed a new source
* map. This happens when pretty-printing a source.
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -25,16 +25,17 @@ const {
} = require("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
const {SplitView} = require("resource://devtools/client/shared/SplitView.jsm");
const {StyleSheetEditor} = require("resource://devtools/client/styleeditor/StyleSheetEditor.jsm");
const {PluralForm} = require("devtools/shared/plural-form");
const {PrefObserver} = require("devtools/client/shared/prefs");
const csscoverage = require("devtools/shared/fronts/csscoverage");
const {console} = require("resource://gre/modules/Console.jsm");
const {KeyCodes} = require("devtools/client/shared/keycodes");
+const {OriginalSource} = require("devtools/client/styleeditor/original-source");
loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
const LOAD_ERROR = "error-load";
const STYLE_EDITOR_TEMPLATE = "stylesheet";
const SELECTOR_HIGHLIGHTER_TYPE = "SelectorHighlighter";
const PREF_MEDIA_SIDEBAR = "devtools.styleeditor.showMediaSidebar";
const PREF_SIDEBAR_WIDTH = "devtools.styleeditor.mediaSidebarWidth";
@@ -328,34 +329,45 @@ StyleEditorUI.prototype = {
if (this._suppressAdd) {
return null;
}
if (!this._seenSheets.has(styleSheet)) {
let promise = (async () => {
let editor = await this._addStyleSheetEditor(styleSheet, isNew);
- if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
+ let toolbox = gDevTools.getToolbox(this._target);
+ let sourceMapService = toolbox.sourceMapService;
+ if (!sourceMapService) {
return editor;
}
- let sources = await styleSheet.getOriginalSources();
+ let {href, nodeHref, actorID: id, sourceMapURL} = styleSheet;
+ let url = href || nodeHref;
+ let sources = await sourceMapService.getOriginalURLs({
+ id,
+ url,
+ sourceMapURL,
+ });
// A single generated sheet might map to multiple original
// sheets, so make editors for each of them.
if (sources && sources.length) {
let parentEditorName = editor.friendlyName;
this._removeStyleSheetEditor(editor);
editor = null;
for (let source of sources) {
+ let generatedId = sourceMapService.generatedToOriginalId(id, source);
+ let original = new OriginalSource(source, generatedId, sourceMapService);
+
// set so the first sheet will be selected, even if it's a source
- source.styleSheetIndex = styleSheet.styleSheetIndex;
- source.relatedStyleSheet = styleSheet;
- source.relatedEditorName = parentEditorName;
- await this._addStyleSheetEditor(source);
+ original.styleSheetIndex = styleSheet.styleSheetIndex;
+ original.relatedStyleSheet = styleSheet;
+ original.relatedEditorName = parentEditorName;
+ await this._addStyleSheetEditor(original);
}
}
return editor;
})();
this._seenSheets.set(styleSheet, promise);
}
return this._seenSheets.get(styleSheet);
@@ -915,17 +927,19 @@ StyleEditorUI.prototype = {
let location = {
line: line,
column: column,
source: editor.styleSheet.href,
styleSheet: parentStyleSheet
};
if (editor.styleSheet.isOriginalSource) {
- location = yield editor.cssSheet.getOriginalLocation(line, column);
+ let styleSheet = editor.cssSheet;
+ location = yield editor.styleSheet.getOriginalLocation(styleSheet, line,
+ column);
}
// this @media rule is from a different original source
if (location.source != editor.styleSheet.href) {
continue;
}
inSource = true;
--- a/devtools/client/styleeditor/moz.build
+++ b/devtools/client/styleeditor/moz.build
@@ -2,16 +2,17 @@
# vim: set filetype=python:
# 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/.
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
DevToolsModules(
+ 'original-source.js',
'styleeditor-commands.js',
'styleeditor-panel.js',
'StyleEditorUI.jsm',
'StyleEditorUtil.jsm',
'StyleSheetEditor.jsm',
)
with Files('**'):
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/original-source.js
@@ -0,0 +1,99 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+"use strict";
+
+/**
+ * An object of this type represents an original source for the style
+ * editor. An "original" source is one that is mentioned in a source
+ * map.
+ *
+ * @param {String} url
+ * The URL of the original source.
+ * @param {String} sourceID
+ * The source ID of the original source, as used by the source
+ * map service.
+ * @param {SourceMapService} sourceMapService
+ * The source map service; @see Toolbox.sourceMapService
+ */
+function OriginalSource(url, sourceId, sourceMapService) {
+ this.isOriginalSource = true;
+
+ this._url = url;
+ this._sourceId = sourceId;
+ this._sourceMapService = sourceMapService;
+}
+
+OriginalSource.prototype = {
+ /** Get the original source's URL. */
+ get url() {
+ return this._url;
+ },
+
+ /** Get the original source's URL. */
+ get href() {
+ return this._url;
+ },
+
+ /**
+ * Return a promise that will resolve to the original source's full
+ * text. The return result is actually an object with a single
+ * `string` method; this method will return the source text as a
+ * string. This is done because the style editor elsewhere expects
+ * a long string actor.
+ */
+ getText: function () {
+ if (!this._sourcePromise) {
+ this._sourcePromise = this._sourceMapService.getOriginalSourceText({
+ id: this._sourceId,
+ url: this._url,
+ }).then(contents => {
+ // Make it look like a long string actor.
+ return {
+ string: () => contents.text,
+ };
+ });
+ }
+ return this._sourcePromise;
+ },
+
+ /**
+ * Given a source-mapped, generated style sheet, a line, and a
+ * column, return the corresponding original location in this style
+ * sheet.
+ *
+ * @param {StyleSheetFront} relatedSheet
+ * The generated style sheet's actor
+ * @param {Number} line
+ * Line number.
+ * @param {Number} column
+ * Column number.
+ * @return {Location}
+ * The original location, an object with at least
+ * `sourceUrl`, `source`, `styleSheet`, `line`, and `column`
+ * properties.
+ */
+ getOriginalLocation: function (relatedSheet, line, column) {
+ let {href, nodeHref, actorID: sourceId} = relatedSheet;
+ let sourceUrl = href || nodeHref;
+ return this._sourceMapService.getOriginalLocation({
+ sourceId,
+ line,
+ column,
+ sourceUrl,
+ }).then(location => {
+ // Add some properties for the style editor.
+ location.source = location.sourceUrl;
+ location.styleSheet = relatedSheet;
+ return location;
+ });
+ },
+
+ // Dummy implementations, as we never emit an event.
+ on: function () { },
+ off: function () { },
+};
+
+exports.OriginalSource = OriginalSource;
--- a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
@@ -12,19 +12,17 @@ const LABELS = ["screen and (max-width:
"screen and (min-width: 1200px)"];
const LINE_NOS = [5, 8];
waitForExplicitFinish();
add_task(function* () {
Services.prefs.setBoolPref(MAP_PREF, true);
- let { ui, onMediaListChanged } = yield openStyleEditorForURL(TESTCASE_URI);
-
- yield onMediaListChanged;
+ let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
is(ui.editors.length, 1, "correct number of editors");
// Test editor with @media rules
let mediaEditor = ui.editors[0];
yield openEditor(mediaEditor);
testMediaEditor(mediaEditor);
--- a/devtools/client/styleeditor/test/head.js
+++ b/devtools/client/styleeditor/test/head.js
@@ -77,25 +77,21 @@ var openStyleEditor = Task.async(functio
if (!tab) {
tab = gBrowser.selectedTab;
}
let target = TargetFactory.forTab(tab);
let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
let panel = toolbox.getPanel("styleeditor");
let ui = panel.UI;
- // This event is sometimes needed by tests, but may be emitted before the list
- // animation is done. So we listen to it here so tests don't have to and can't miss it.
- let onMediaListChanged = ui.once("media-list-changed");
-
// The stylesheet list appears with an animation. Let this animation finish.
let animations = ui._root.getAnimations({subtree: true});
yield Promise.all(animations.map(a => a.finished));
- return { toolbox, panel, ui, onMediaListChanged };
+ return { toolbox, panel, ui };
});
/**
* Creates a new tab in specified window navigates it to the given URL and
* opens style editor in it.
*/
var openStyleEditorForURL = Task.async(function* (url, win) {
let tab = yield addTab(url, win);
--- a/devtools/shared/fronts/stylesheets.js
+++ b/devtools/shared/fronts/stylesheets.js
@@ -138,16 +138,19 @@ const StyleSheetFront = FrontClassWithSp
return this._form.system;
},
get styleSheetIndex() {
return this._form.styleSheetIndex;
},
get ruleCount() {
return this._form.ruleCount;
},
+ get sourceMapURL() {
+ return this._form.sourceMapURL;
+ },
/**
* Get the indentation to use for edits to this style sheet.
*
* @return {Promise} A promise that will resolve to a string that
* should be used to indent a block in this style sheet.
*/
guessIndentation: function () {