Merge fx-team to central, a=merge CLOSED TREE

This commit is contained in:
Wes Kocher 2016-01-29 13:40:29 -08:00
commit a241fb67ab
33 changed files with 508 additions and 378 deletions

View File

@ -112,7 +112,6 @@ devtools/client/shadereditor/**
devtools/client/shared/**
devtools/client/sourceeditor/**
devtools/client/storage/**
devtools/client/styleeditor/**
devtools/client/tilt/**
devtools/client/webaudioeditor/**
devtools/client/webconsole/**

View File

@ -410,15 +410,20 @@ AnimationsTimeline.prototype = {
drawHeaderAndBackground: function() {
let width = this.timeHeaderEl.offsetWidth;
let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
let intervalLength = findOptimalTimeInterval(minTimeInterval);
let intervalWidth = intervalLength * width / animationDuration;
drawGraphElementBackground(this.win.document, "time-graduations",
width, scale);
width, intervalWidth);
// And the time graduation header.
this.timeHeaderEl.innerHTML = "";
let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING);
for (let i = 0; i < width; i += interval) {
let pos = 100 * i / width;
for (let i = 0; i <= width / intervalWidth; i++) {
let pos = 100 * i * intervalWidth / width;
createNode({
parent: this.timeHeaderEl,
nodeType: "span",

View File

@ -24,11 +24,14 @@ add_task(function*() {
info("Find out how many time graduations should there be");
let width = headerEl.offsetWidth;
let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
// Note that findOptimalTimeInterval is tested separately in xpcshell test
// test_findOptimalTimeInterval.js, so we assume that it works here.
let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING);
let nb = Math.ceil(width / interval);
let interval = findOptimalTimeInterval(minTimeInterval);
let nb = Math.ceil(animationDuration / interval);
is(headerEl.querySelectorAll(".time-tick").length, nb,
"The expected number of time ticks were found");
@ -36,9 +39,9 @@ add_task(function*() {
info("Make sure graduations are evenly distributed and show the right times");
[...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => {
let left = parseFloat(tick.style.left);
let expectedPos = i * interval * 100 / width;
let expectedPos = i * interval * 100 / animationDuration;
is(Math.round(left), Math.round(expectedPos),
"Graduation " + i + " is positioned correctly");
`Graduation ${i} is positioned correctly`);
// Note that the distancetoRelativeTime and formatTime functions are tested
// separately in xpcshell test test_timeScale.js, so we assume that they
@ -46,6 +49,6 @@ add_task(function*() {
let formattedTime = TimeScale.formatTime(
TimeScale.distanceToRelativeTime(expectedPos, width));
is(tick.textContent, formattedTime,
"Graduation " + i + " has the right text content");
`Graduation ${i} has the right text content`);
});
});

View File

@ -14,66 +14,64 @@ const {findOptimalTimeInterval} = require("devtools/client/animationinspector/ut
// findOptimalTimeInterval function. Each object should have the following
// properties:
// - desc: an optional string that will be printed out
// - timeScale: a number that represents how many pixels is 1ms
// - minSpacing: an optional number that represents the minim space between 2
// time graduations
// - minTimeInterval: a number that represents the minimum time in ms
// that should be displayed in one interval
// - expectedInterval: a number that you expect the findOptimalTimeInterval
// function to return as a result.
// Optionally you can pass a string where `interval` is the calculated
// interval, this string will be eval'd and tested to be truthy.
const TEST_DATA = [{
desc: "With 1px being 1ms and no minSpacing, expect the interval to be the " +
"interval multiple",
timeScale: 1,
minSpacing: undefined,
desc: "With no minTimeInterval, expect the interval to be 0",
minTimeInterval: null,
expectedInterval: 0
}, {
desc: "With a minTimeInterval of 0 ms, expect the interval to be 0",
minTimeInterval: 0,
expectedInterval: 0
}, {
desc: "With a minInterval of 1ms, expect the interval to be the 1ms too",
minTimeInterval: 1,
expectedInterval: 1
}, {
desc: "With a very small minTimeInterval, expect the interval to be 1ms",
minTimeInterval: 1e-31,
expectedInterval: 1
}, {
desc: "With a minInterval of 2.5ms, expect the interval to be 2.5ms too",
minTimeInterval: 2.5,
expectedInterval: 2.5
}, {
desc: "With a minInterval of 5ms, expect the interval to be 5ms too",
minTimeInterval: 5,
expectedInterval: 5
}, {
desc: "With a minInterval of 7ms, expect the interval to be the next " +
"multiple of 5",
minTimeInterval: 7,
expectedInterval: 10
}, {
minTimeInterval: 20,
expectedInterval: 25
}, {
desc: "With 1px being 1ms and a custom minSpacing being a multiple of 25 " +
"expect the interval to be the custom min spacing",
timeScale: 1,
minSpacing: 50,
minTimeInterval: 33,
expectedInterval: 50
}, {
desc: "With 1px being 1ms and a custom minSpacing not being multiple of 25 " +
"expect the interval to be the next multiple of 10",
timeScale: 1,
minSpacing: 26,
expectedInterval: 50
minTimeInterval: 987,
expectedInterval: 1000
}, {
desc: "If 1ms corresponds to a distance that is greater than the min " +
"spacing then, expect the interval to be this distance",
timeScale: 20,
minSpacing: undefined,
expectedInterval: 20
minTimeInterval: 1234,
expectedInterval: 2500
}, {
desc: "If 1ms corresponds to a distance that is greater than the min " +
"spacing then, expect the interval to be this distance, even if it " +
"isn't a multiple of 25",
timeScale: 33,
minSpacing: undefined,
expectedInterval: 33
}, {
desc: "If 1ms is a very small distance, then expect this distance to be " +
"multiplied by 25, 50, 100, 200, etc... until it goes over the min " +
"spacing",
timeScale: 0.001,
minSpacing: undefined,
expectedInterval: 12.8
}, {
desc: "If the time scale is such that we need to iterate more than the " +
"maximum allowed number of iterations, then expect an interval lower " +
"than the minimum one",
timeScale: 1e-31,
minSpacing: undefined,
expectedInterval: "interval < 10"
minTimeInterval: 9800,
expectedInterval: 10000
}];
function run_test() {
for (let {timeScale, desc, minSpacing, expectedInterval} of TEST_DATA) {
do_print("Testing timeScale: " + timeScale + " and minSpacing: " +
minSpacing + ". Expecting " + expectedInterval + ".");
for (let {minTimeInterval, desc, expectedInterval} of TEST_DATA) {
do_print(`Testing minTimeInterval: ${minTimeInterval}.
Expecting ${expectedInterval}.`);
let interval = findOptimalTimeInterval(timeScale, minSpacing);
let interval = findOptimalTimeInterval(minTimeInterval);
if (typeof expectedInterval == "string") {
ok(eval(expectedInterval), desc);
} else {

View File

@ -18,17 +18,15 @@ const L10N = new ViewHelpers.L10N(STRINGS_URI);
// How many times, maximum, can we loop before we find the optimal time
// interval in the timeline graph.
const OPTIMAL_TIME_INTERVAL_MAX_ITERS = 100;
// Background time graduations should be multiple of this number of millis.
const TIME_INTERVAL_MULTIPLE = 25;
const TIME_INTERVAL_SCALES = 3;
// The default minimum spacing between time graduations in px.
const TIME_GRADUATION_MIN_SPACING = 10;
// Time graduations should be multiple of one of these number.
const OPTIMAL_TIME_INTERVAL_MULTIPLES = [1, 2.5, 5];
// RGB color for the time interval background.
const TIME_INTERVAL_COLOR = [128, 136, 144];
// byte
const TIME_INTERVAL_OPACITY_MIN = 32;
const TIME_INTERVAL_OPACITY_MIN = 64;
// byte
const TIME_INTERVAL_OPACITY_ADD = 32;
const TIME_INTERVAL_OPACITY_MAX = 96;
const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
@ -72,9 +70,9 @@ exports.createNode = createNode;
* @param {Document} document The document where the image-element should be set.
* @param {String} id The ID for the image-element.
* @param {Number} graphWidth The width of the graph.
* @param {Number} timeScale How many px is 1ms in the graph.
* @param {Number} intervalWidth The width of one interval
*/
function drawGraphElementBackground(document, id, graphWidth, timeScale) {
function drawGraphElementBackground(document, id, graphWidth, intervalWidth) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
@ -93,17 +91,19 @@ function drawGraphElementBackground(document, id, graphWidth, timeScale) {
// Build new millisecond tick lines...
let [r, g, b] = TIME_INTERVAL_COLOR;
let alphaComponent = TIME_INTERVAL_OPACITY_MIN;
let interval = findOptimalTimeInterval(timeScale);
let opacities = [TIME_INTERVAL_OPACITY_MAX, TIME_INTERVAL_OPACITY_MIN];
// Insert one pixel for each division on each scale.
for (let i = 1; i <= TIME_INTERVAL_SCALES; i++) {
let increment = interval * Math.pow(2, i);
for (let x = 0; x < canvas.width; x += increment) {
let position = x | 0;
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
// Insert one tick line on each interval
for (let i = 0; i <= graphWidth / intervalWidth; i++) {
let x = i * intervalWidth;
// Ensure the last line is drawn on canvas
if (x >= graphWidth) {
x = graphWidth - 0.5;
}
alphaComponent += TIME_INTERVAL_OPACITY_ADD;
let position = x | 0;
let alphaComponent = opacities[i % opacities.length];
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
}
// Flush the image data and cache the waterfall background.
@ -116,31 +116,30 @@ exports.drawGraphElementBackground = drawGraphElementBackground;
/**
* Find the optimal interval between time graduations in the animation timeline
* graph based on a time scale and a minimum spacing.
* @param {Number} timeScale How many px is 1ms in the graph.
* @param {Number} minSpacing The minimum spacing between 2 graduations,
* defaults to TIME_GRADUATION_MIN_SPACING.
* @return {Number} The optimal interval, in pixels.
* graph based on a minimum time interval
* @param {Number} minTimeInterval Minimum time in ms in one interval
* @return {Number} The optimal interval time in ms
*/
function findOptimalTimeInterval(timeScale,
minSpacing=TIME_GRADUATION_MIN_SPACING) {
let timingStep = TIME_INTERVAL_MULTIPLE;
function findOptimalTimeInterval(minTimeInterval) {
let numIters = 0;
let multiplier = 1;
if (timeScale > minSpacing) {
return timeScale;
if (!minTimeInterval) {
return 0;
}
let interval;
while (true) {
let scaledStep = timeScale * timingStep;
for (let i = 0; i < OPTIMAL_TIME_INTERVAL_MULTIPLES.length; i++) {
interval = OPTIMAL_TIME_INTERVAL_MULTIPLES[i] * multiplier;
if (minTimeInterval <= interval) {
return interval;
}
}
if (++numIters > OPTIMAL_TIME_INTERVAL_MAX_ITERS) {
return scaledStep;
return interval;
}
if (scaledStep < minSpacing) {
timingStep *= 2;
continue;
}
return scaledStep;
multiplier *= 10;
}
}

View File

@ -7,7 +7,6 @@
this.EXPORTED_SYMBOLS = ["StyleEditorUI"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
@ -18,11 +17,13 @@ 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");
/* import-globals-from StyleEditorUtil.jsm */
Cu.import("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
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 {PrefObserver, PREF_ORIG_SOURCES} =
require("devtools/client/styleeditor/utils");
const csscoverage = require("devtools/server/actors/csscoverage");
const {console} = require("resource://gre/modules/Console.jsm");
const promise = require("promise");
@ -173,7 +174,8 @@ StyleEditorUI.prototype = {
this._contextMenu.addEventListener("popupshowing",
this._updateOpenLinkItem);
this._optionsMenu = this._panelDoc.getElementById("style-editor-options-popup");
this._optionsMenu =
this._panelDoc.getElementById("style-editor-options-popup");
this._optionsMenu.addEventListener("popupshowing",
this._onOptionsPopupShowing);
this._optionsMenu.addEventListener("popuphiding",
@ -187,7 +189,8 @@ StyleEditorUI.prototype = {
this._mediaItem.addEventListener("command",
this._toggleMediaSidebar);
this._openLinkNewTabItem = this._panelDoc.getElementById("context-openlinknewtab");
this._openLinkNewTabItem =
this._panelDoc.getElementById("context-openlinknewtab");
this._openLinkNewTabItem.addEventListener("command",
this._openLinkNewTab);
@ -358,13 +361,13 @@ StyleEditorUI.prototype = {
* Optional parent window for the file picker.
*/
_importFromFile: function(file, parentWindow) {
let onFileSelected = (file) => {
if (!file) {
let onFileSelected = (selectedFile) => {
if (!selectedFile) {
// nothing selected
return;
}
NetUtil.asyncFetch({
uri: NetUtil.newURI(file),
uri: NetUtil.newURI(selectedFile),
loadingNode: this._window.document,
contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
}, (stream, status) => {
@ -372,11 +375,12 @@ StyleEditorUI.prototype = {
this.emit("error", { key: LOAD_ERROR });
return;
}
let source = NetUtil.readInputStreamToString(stream, stream.available());
let source =
NetUtil.readInputStreamToString(stream, stream.available());
stream.close();
this._debuggee.addStyleSheet(source).then((styleSheet) => {
this._onStyleSheetCreated(styleSheet, file);
this._onStyleSheetCreated(styleSheet, selectedFile);
});
});
};
@ -384,7 +388,6 @@ StyleEditorUI.prototype = {
showFilePicker(file, false, parentWindow, onFileSelected);
},
/**
* When a new or imported stylesheet has been added to the document.
* Add an editor for it.
@ -429,16 +432,22 @@ StyleEditorUI.prototype = {
},
/**
* This method handles the following cases related to the context menu item "_openLinkNewTabItem":
* This method handles the following cases related to the context
* menu item "_openLinkNewTabItem":
*
* 1) There was a stylesheet clicked on and it is external: show and enable the context menu item
* 2) There was a stylesheet clicked on and it is inline: show and disable the context menu item
* 3) There was no stylesheet clicked on (the right click happened below the list): hide the context menu
* 1) There was a stylesheet clicked on and it is external: show and
* enable the context menu item
* 2) There was a stylesheet clicked on and it is inline: show and
* disable the context menu item
* 3) There was no stylesheet clicked on (the right click happened
* below the list): hide the context menu
*/
_updateOpenLinkItem: function() {
this._openLinkNewTabItem.setAttribute("hidden", !this._contextMenuStyleSheet);
this._openLinkNewTabItem.setAttribute("hidden",
!this._contextMenuStyleSheet);
if (this._contextMenuStyleSheet) {
this._openLinkNewTabItem.setAttribute("disabled", !this._contextMenuStyleSheet.href);
this._openLinkNewTabItem.setAttribute("disabled",
!this._contextMenuStyleSheet.href);
}
},
@ -460,15 +469,14 @@ StyleEditorUI.prototype = {
_removeStyleSheetEditor: function(editor) {
if (editor.summary) {
this._view.removeItem(editor.summary);
}
else {
} else {
let self = this;
this.on("editor-added", function onAdd(event, added) {
if (editor == added) {
self.off("editor-added", onAdd);
self._view.removeItem(editor.summary);
}
})
});
}
editor.destroy();
@ -503,21 +511,21 @@ StyleEditorUI.prototype = {
disableAnimations: this._alwaysDisableAnimations,
ordinal: ordinal,
onCreate: function(summary, details, data) {
let editor = data.editor;
editor.summary = summary;
editor.details = details;
let createdEditor = data.editor;
createdEditor.summary = summary;
createdEditor.details = details;
wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) {
event.stopPropagation();
event.target.blur();
editor.toggleDisabled();
createdEditor.toggleDisabled();
});
wire(summary, ".stylesheet-name", {
events: {
"keypress": (aEvent) => {
if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
"keypress": (event) => {
if (event.keyCode == event.DOM_VK_RETURN) {
this._view.activeSummary = summary;
}
}
@ -528,13 +536,13 @@ StyleEditorUI.prototype = {
event.stopPropagation();
event.target.blur();
editor.saveToFile(editor.savedFile);
createdEditor.saveToFile(createdEditor.savedFile);
});
this._updateSummaryForEditor(editor, summary);
this._updateSummaryForEditor(createdEditor, summary);
summary.addEventListener("contextmenu", (event) => {
this._contextMenuStyleSheet = editor.styleSheet;
summary.addEventListener("contextmenu", () => {
this._contextMenuStyleSheet = createdEditor.styleSheet;
}, false);
summary.addEventListener("focus", function onSummaryFocus(event) {
@ -554,44 +562,46 @@ StyleEditorUI.prototype = {
Services.prefs.setIntPref(PREF_SIDEBAR_WIDTH, sidebarWidth);
// update all @media sidebars for consistency
let sidebars = [...this._panelDoc.querySelectorAll(".stylesheet-sidebar")];
let sidebars =
[...this._panelDoc.querySelectorAll(".stylesheet-sidebar")];
for (let mediaSidebar of sidebars) {
mediaSidebar.setAttribute("width", sidebarWidth);
}
});
// autofocus if it's a new user-created stylesheet
if (editor.isNew) {
this._selectEditor(editor);
if (createdEditor.isNew) {
this._selectEditor(createdEditor);
}
if (this._isEditorToSelect(editor)) {
if (this._isEditorToSelect(createdEditor)) {
this.switchToSelectedSheet();
}
// If this is the first stylesheet and there is no pending request to
// select a particular style sheet, select this sheet.
if (!this.selectedEditor && !this._styleSheetBoundToSelect
&& editor.styleSheet.styleSheetIndex == 0) {
this._selectEditor(editor);
&& createdEditor.styleSheet.styleSheetIndex == 0) {
this._selectEditor(createdEditor);
}
this.emit("editor-added", editor);
this.emit("editor-added", createdEditor);
}.bind(this),
onShow: function(summary, details, data) {
let editor = data.editor;
this.selectedEditor = editor;
let showEditor = data.editor;
this.selectedEditor = showEditor;
Task.spawn(function* () {
if (!editor.sourceEditor) {
if (!showEditor.sourceEditor) {
// only initialize source editor when we switch to this view
let inputElement = details.querySelector(".stylesheet-editor-input");
yield editor.load(inputElement);
let inputElement =
details.querySelector(".stylesheet-editor-input");
yield showEditor.load(inputElement);
}
editor.onShow();
showEditor.onShow();
this.emit("editor-selected", editor);
this.emit("editor-selected", showEditor);
// Is there any CSS coverage markup to include?
let usage = yield csscoverage.getUsage(this._target);
@ -599,21 +609,20 @@ StyleEditorUI.prototype = {
return;
}
let href = csscoverage.sheetToUrl(editor.styleSheet);
let data = yield usage.createEditorReport(href)
let href = csscoverage.sheetToUrl(showEditor.styleSheet);
let reportData = yield usage.createEditorReport(href);
editor.removeAllUnusedRegions();
showEditor.removeAllUnusedRegions();
if (data.reports.length > 0) {
if (reportData.reports.length > 0) {
// Only apply if this file isn't compressed. We detect a
// compressed file if there are more rules than lines.
let text = editor.sourceEditor.getText();
let text = showEditor.sourceEditor.getText();
let lineCount = text.split("\n").length;
let ruleCount = editor.styleSheet.ruleCount;
let ruleCount = showEditor.styleSheet.ruleCount;
if (lineCount >= ruleCount) {
editor.addUnusedRegions(data.reports);
}
else {
showEditor.addUnusedRegions(reportData.reports);
} else {
this.emit("error", { key: "error-compressed", level: "info" });
}
}
@ -733,13 +742,14 @@ StyleEditorUI.prototype = {
/**
* Returns an identifier for the given style sheet.
*
* @param {StyleSheet} aStyleSheet
* @param {StyleSheet} styleSheet
* The style sheet to be identified.
*/
getStyleSheetIdentifier: function (aStyleSheet) {
// Identify inline style sheets by their host page URI and index at the page.
return aStyleSheet.href ? aStyleSheet.href :
"inline-" + aStyleSheet.styleSheetIndex + "-at-" + aStyleSheet.nodeHref;
getStyleSheetIdentifier: function(styleSheet) {
// Identify inline style sheets by their host page URI and index
// at the page.
return styleSheet.href ? styleSheet.href :
"inline-" + styleSheet.styleSheetIndex + "-at-" + styleSheet.nodeHref;
},
/**
@ -767,7 +777,6 @@ StyleEditorUI.prototype = {
return this.switchToSelectedSheet();
},
/**
* Handler for an editor's 'property-changed' event.
* Update the summary in the UI.
@ -784,8 +793,8 @@ StyleEditorUI.prototype = {
*
* @param {StyleSheetEditor} editor
* @param {DOMElement} summary
* Optional item's summary element to update. If none, item corresponding
* to passed editor is used.
* Optional item's summary element to update. If none, item
* corresponding to passed editor is used.
*/
_updateSummaryForEditor: function(editor, summary) {
summary = summary || editor.summary;
@ -801,7 +810,7 @@ StyleEditorUI.prototype = {
ruleCount = "-";
}
var flags = [];
let flags = [];
if (editor.styleSheet.disabled) {
flags.push("disabled");
}
@ -873,7 +882,8 @@ StyleEditorUI.prototype = {
let div = this._panelDoc.createElement("div");
div.className = "media-rule-label";
div.addEventListener("click", this._jumpToLocation.bind(this, location));
div.addEventListener("click",
this._jumpToLocation.bind(this, location));
let cond = this._panelDoc.createElement("div");
cond.textContent = rule.conditionText;
@ -882,8 +892,13 @@ StyleEditorUI.prototype = {
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, "<a href='#' class='media-responsive-mode-toggle'>$&</a>");
cond.addEventListener("click", this._onMediaConditionClick.bind(this));
const minMaxPattern = /(min\-|max\-)(width|height):\s\d+(px)/ig;
const replacement =
"<a href='#' class='media-responsive-mode-toggle'>$&</a>";
cond.innerHTML = cond.textContent.replace(minMaxPattern, replacement);
cond.addEventListener("click",
this._onMediaConditionClick.bind(this));
}
div.appendChild(cond);
@ -916,7 +931,7 @@ StyleEditorUI.prototype = {
}
let conditionText = e.target.textContent;
let isWidthCond = conditionText.toLowerCase().indexOf("width") > -1;
let mediaVal = parseInt(/\d+/.exec(conditionText));
let mediaVal = parseInt(/\d+/.exec(conditionText), 10);
let options = isWidthCond ? {width: mediaVal} : {height: mediaVal};
this._launchResponsiveMode(options);
@ -936,12 +951,11 @@ StyleEditorUI.prototype = {
ResponsiveUIManager.runIfNeeded(win, tab);
if (options.width && options.height) {
ResponsiveUIManager.getResponsiveUIForTab(tab).setSize(options.width, options.height);
}
else if (options.width) {
ResponsiveUIManager.getResponsiveUIForTab(tab).setSize(options.width,
options.height);
} else if (options.width) {
ResponsiveUIManager.getResponsiveUIForTab(tab).setWidth(options.width);
}
else if (options.height) {
} else if (options.height) {
ResponsiveUIManager.getResponsiveUIForTab(tab).setHeight(options.height);
}
},
@ -978,4 +992,4 @@ StyleEditorUI.prototype = {
this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
this._prefObserver.destroy();
}
}
};

View File

@ -3,6 +3,9 @@
* 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/. */
/* All top-level definitions here are exports. */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
"use strict";
this.EXPORTED_SYMBOLS = [
@ -26,87 +29,82 @@ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const console = require("resource://gre/modules/Console.jsm").console;
const gStringBundle = Services.strings.createBundle(PROPERTIES_URL);
/**
* Returns a localized string with the given key name from the string bundle.
*
* @param aName
* @param name
* @param ...rest
* Optional arguments to format in the string.
* @return string
*/
this._ = function _(aName)
{
function _(name) {
try {
if (arguments.length == 1) {
return gStringBundle.GetStringFromName(aName);
return gStringBundle.GetStringFromName(name);
}
let rest = Array.prototype.slice.call(arguments, 1);
return gStringBundle.formatStringFromName(aName, rest, rest.length);
}
catch (ex) {
return gStringBundle.formatStringFromName(name, rest, rest.length);
} catch (ex) {
console.error(ex);
throw new Error("L10N error. '" + aName + "' is missing from " + PROPERTIES_URL);
throw new Error("L10N error. '" + name + "' is missing from " +
PROPERTIES_URL);
}
}
/**
* Assert an expression is true or throw if false.
*
* @param aExpression
* @param aMessage
* @param expression
* @param message
* Optional message.
* @return aExpression
* @return expression
*/
this.assert = function assert(aExpression, aMessage)
{
if (!!!(aExpression)) {
let msg = aMessage ? "ASSERTION FAILURE:" + aMessage : "ASSERTION FAILURE";
function assert(expression, message) {
if (!expression) {
let msg = message ? "ASSERTION FAILURE:" + message : "ASSERTION FAILURE";
log(msg);
throw new Error(msg);
}
return aExpression;
return expression;
}
/**
* Retrieve or set the text content of an element.
*
* @param DOMElement aRoot
* @param DOMElement root
* The element to use for querySelector.
* @param string aSelector
* @param string selector
* Selector string for the element to get/set the text content.
* @param string aText
* @param string textContent
* Optional text to set.
* @return string
* Text content of matching element or null if there were no element
* matching aSelector.
* matching selector.
*/
this.text = function text(aRoot, aSelector, aText)
{
let element = aRoot.querySelector(aSelector);
function text(root, selector, textContent) {
let element = root.querySelector(selector);
if (!element) {
return null;
}
if (aText === undefined) {
if (textContent === undefined) {
return element.textContent;
}
element.textContent = aText;
return aText;
element.textContent = textContent;
return textContent;
}
/**
* Iterates _own_ properties of an object.
*
* @param aObject
* @param object
* The object to iterate.
* @param function aCallback(aKey, aValue)
* @param function callback(aKey, aValue)
*/
function forEach(aObject, aCallback)
{
for (let key in aObject) {
if (aObject.hasOwnProperty(key)) {
aCallback(key, aObject[key]);
function forEach(object, callback) {
for (let key in object) {
if (object.hasOwnProperty(key)) {
callback(key, object[key]);
}
}
}
@ -116,52 +114,54 @@ function forEach(aObject, aCallback)
*
* @param ...rest
* One or multiple arguments to log.
* If multiple arguments are given, they will be joined by " " in the log.
* If multiple arguments are given, they will be joined by " "
* in the log.
*/
this.log = function log()
{
function log() {
console.logStringMessage(Array.prototype.slice.call(arguments).join(" "));
}
/**
* Wire up element(s) matching selector with attributes, event listeners, etc.
*
* @param DOMElement aRoot
* @param DOMElement root
* The element to use for querySelectorAll.
* Can be null if aSelector is a DOMElement.
* @param string|DOMElement aSelectorOrElement
* Can be null if selector is a DOMElement.
* @param string|DOMElement selectorOrElement
* Selector string or DOMElement for the element(s) to wire up.
* @param object aDescriptor
* An object describing how to wire matching selector, supported properties
* are "events" and "attributes" taking objects themselves.
* @param object descriptor
* An object describing how to wire matching selector,
* supported properties are "events" and "attributes" taking
* objects themselves.
* Each key of properties above represents the name of the event or
* attribute, with the value being a function used as an event handler or
* string to use as attribute value.
* If aDescriptor is a function, the argument is equivalent to :
* {events: {'click': aDescriptor}}
* If descriptor is a function, the argument is equivalent to :
* {events: {'click': descriptor}}
*/
this.wire = function wire(aRoot, aSelectorOrElement, aDescriptor)
{
function wire(root, selectorOrElement, descriptor) {
let matches;
if (typeof(aSelectorOrElement) == "string") { // selector
matches = aRoot.querySelectorAll(aSelectorOrElement);
if (typeof selectorOrElement == "string") {
// selector
matches = root.querySelectorAll(selectorOrElement);
if (!matches.length) {
return;
}
} else {
matches = [aSelectorOrElement]; // element
// element
matches = [selectorOrElement];
}
if (typeof(aDescriptor) == "function") {
aDescriptor = {events: {click: aDescriptor}};
if (typeof descriptor == "function") {
descriptor = {events: {click: descriptor}};
}
for (let i = 0; i < matches.length; i++) {
let element = matches[i];
forEach(aDescriptor.events, function (aName, aHandler) {
element.addEventListener(aName, aHandler, false);
forEach(descriptor.events, function(name, handler) {
element.addEventListener(name, handler, false);
});
forEach(aDescriptor.attributes, element.setAttribute);
forEach(descriptor.attributes, element.setAttribute);
}
}
@ -181,10 +181,9 @@ this.wire = function wire(aRoot, aSelectorOrElement, aDescriptor)
* @param AString suggestedFilename
* The suggested filename when toSave is true.
*/
this.showFilePicker = function showFilePicker(path, toSave, parentWindow,
callback, suggestedFilename)
{
if (typeof(path) == "string") {
function showFilePicker(path, toSave, parentWindow, callback,
suggestedFilename) {
if (typeof path == "string") {
try {
if (Services.io.extractScheme(path) == "file") {
let uri = Services.io.newURI(path, null, null);
@ -197,7 +196,8 @@ this.showFilePicker = function showFilePicker(path, toSave, parentWindow,
return;
}
try {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
let file =
Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(path);
callback(file);
return;
@ -206,7 +206,8 @@ this.showFilePicker = function showFilePicker(path, toSave, parentWindow,
return;
}
}
if (path) { // "path" is an nsIFile
if (path) {
// "path" is an nsIFile
callback(path);
return;
}

View File

@ -12,7 +12,7 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const Editor = require("devtools/client/sourceeditor/editor");
const Editor = require("devtools/client/sourceeditor/editor");
const promise = require("promise");
const {CssLogic} = require("devtools/shared/inspector/css-logic");
const {console} = require("resource://gre/modules/Console.jsm");
@ -20,9 +20,10 @@ const {console} = require("resource://gre/modules/Console.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
const { TextDecoder, OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://devtools/shared/event-emitter.js");
/* import-globals-from StyleEditorUtil.jsm */
Cu.import("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
const LOAD_ERROR = "error-load";
@ -40,10 +41,10 @@ const TRANSITION_PREF = "devtools.styleeditor.transitions";
// How long to wait to update linked CSS file after original source was saved
// to disk. Time in ms.
const CHECK_LINKED_SHEET_DELAY=500;
const CHECK_LINKED_SHEET_DELAY = 500;
// How many times to check for linked file changes
const MAX_CHECK_COUNT=10;
const MAX_CHECK_COUNT = 10;
// The classname used to show a line that is not used
const UNUSED_CLASS = "cm-unused-line";
@ -92,7 +93,8 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
// event from the StyleSheetActor.
this._justSetText = false;
this._state = { // state to use when inputElement attaches
// state to use when inputElement attaches
this._state = {
text: "",
selection: {
start: {line: 0, ch: 0},
@ -123,7 +125,8 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
this.styleSheet.on("error", this._onError);
this.mediaRules = [];
if (this.cssSheet.getMediaRules) {
this.cssSheet.getMediaRules().then(this._onMediaRulesChanged, Cu.reportError);
this.cssSheet.getMediaRules().then(this._onMediaRulesChanged,
Cu.reportError);
}
this.cssSheet.on("media-rules-changed", this._onMediaRulesChanged);
this.cssSheet.on("style-applied", this._onStyleApplied);
@ -195,6 +198,7 @@ StyleSheetEditor.prototype = {
try {
this._friendlyName = decodeURI(this._friendlyName);
} catch (ex) {
// Ignore.
}
}
return this._friendlyName;
@ -226,13 +230,11 @@ StyleSheetEditor.prototype = {
if (uri.scheme == "file") {
let file = uri.QueryInterface(Ci.nsIFileURL).file;
path = file.path;
}
else if (this.savedFile) {
} else if (this.savedFile) {
let origHref = removeQuery(this.styleSheet.href);
let origUri = NetUtil.newURI(origHref);
path = findLinkedFilePath(uri, origUri, this.savedFile);
}
else {
} else {
// we can't determine path to generated file on disk
return;
}
@ -529,7 +531,8 @@ StyleSheetEditor.prototype = {
*/
_updateStyleSheet: function() {
if (this.styleSheet.disabled) {
return; // TODO: do we want to do this?
// TODO: do we want to do this?
return;
}
if (this._justSetText) {
@ -537,10 +540,11 @@ StyleSheetEditor.prototype = {
return;
}
this._updateTask = null; // reset only if we actually perform an update
// (stylesheet is enabled) so that 'missed' updates
// while the stylesheet is disabled can be performed
// when it is enabled back. @see enableStylesheet
// reset only if we actually perform an update
// (stylesheet is enabled) so that 'missed' updates
// while the stylesheet is disabled can be performed
// when it is enabled back. @see enableStylesheet
this._updateTask = null;
if (this.sourceEditor) {
this._state.text = this.sourceEditor.getText();
@ -582,7 +586,8 @@ StyleSheetEditor.prototype = {
return;
}
let node = yield this.walker.getStyleSheetOwnerNode(this.styleSheet.actorID);
let node =
yield this.walker.getStyleSheetOwnerNode(this.styleSheet.actorID);
yield this.highlighter.show(node, {
selector: info.selector,
hideInfoBar: true,
@ -601,7 +606,8 @@ StyleSheetEditor.prototype = {
* Optional nsIFile or string representing the filename to save in the
* background, no UI will be displayed.
* If not specified, the original style sheet URI is used.
* To implement 'Save' instead of 'Save as', you can pass savedFile here.
* To implement 'Save' instead of 'Save as', you can pass
* savedFile here.
* @param function(nsIFile aFile) callback
* Optional callback called when the operation has finished.
* aFile has the nsIFile object for saved file or null if the operation
@ -715,7 +721,7 @@ StyleSheetEditor.prototype = {
this.emit("linked-css-file-error");
error += " querying " + this.linkedCSSFile +
" original source location: " + this.savedFile.path
" original source location: " + this.savedFile.path;
Cu.reportError(error);
},
@ -750,7 +756,7 @@ StyleSheetEditor.prototype = {
this.saveToFile();
};
bindings["Esc"] = false;
bindings.Esc = false;
return bindings;
},

View File

@ -29,40 +29,40 @@ exports.items = [{
description: l10n.lookup("editDesc"),
manual: l10n.lookup("editManual2"),
params: [
{
name: 'resource',
type: {
name: 'resource',
include: 'text/css'
},
description: l10n.lookup("editResourceDesc")
},
{
name: "line",
defaultValue: 1,
type: {
name: "number",
min: 1,
step: 10
},
description: l10n.lookup("editLineToJumpToDesc")
}
],
returnType: "editArgs",
exec: args => {
return { href: args.resource.name, line: args.line };
}
{
name: "resource",
type: {
name: "resource",
include: "text/css"
},
description: l10n.lookup("editResourceDesc")
},
{
name: "line",
defaultValue: 1,
type: {
name: "number",
min: 1,
step: 10
},
description: l10n.lookup("editLineToJumpToDesc")
}
],
returnType: "editArgs",
exec: args => {
return { href: args.resource.name, line: args.line };
}
}, {
item: "converter",
from: "editArgs",
to: "dom",
exec: function(args, context) {
let target = context.environment.target;
let gDevTools = require("resource://devtools/client/framework/gDevTools.jsm").gDevTools;
return gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
let styleEditor = toolbox.getCurrentPanel();
styleEditor.selectStyleSheet(args.href, args.line);
return null;
});
}
exec: function(args, context) {
let target = context.environment.target;
let gDevTools = require("resource://devtools/client/framework/gDevTools.jsm").gDevTools;
return gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
let styleEditor = toolbox.getCurrentPanel();
styleEditor.selectStyleSheet(args.href, args.line);
return null;
});
}
}];

View File

@ -4,7 +4,9 @@
* 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/. */
const {Cc, Ci, Cu, Cr} = require("chrome");
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -13,6 +15,7 @@ var promise = require("promise");
var EventEmitter = require("devtools/shared/event-emitter");
Cu.import("resource://devtools/client/styleeditor/StyleEditorUI.jsm");
/* import-globals-from StyleEditorUtil.jsm */
Cu.import("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
loader.lazyGetter(this, "StyleSheetsFront",
@ -21,7 +24,7 @@ loader.lazyGetter(this, "StyleSheetsFront",
loader.lazyGetter(this, "StyleEditorFront",
() => require("devtools/server/actors/styleeditor").StyleEditorFront);
this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
var StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
EventEmitter.decorate(this);
this._toolbox = toolbox;
@ -31,7 +34,7 @@ this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
this.destroy = this.destroy.bind(this);
this._showError = this._showError.bind(this);
}
};
exports.StyleEditorPanel = StyleEditorPanel;
@ -57,8 +60,7 @@ StyleEditorPanel.prototype = {
if (this.target.form.styleSheetsActor) {
this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
}
else {
} else {
/* We're talking to a pre-Firefox 29 server-side */
this._debuggee = StyleEditorFront(this.target.client, this.target.form);
}
@ -94,7 +96,8 @@ StyleEditorPanel.prototype = {
}
let notificationBox = this._toolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("styleeditor-error");
let notification =
notificationBox.getNotificationWithValue("styleeditor-error");
let level = (data.level === "info") ?
notificationBox.PRIORITY_INFO_LOW :
notificationBox.PRIORITY_CRITICAL_LOW;
@ -120,7 +123,7 @@ StyleEditorPanel.prototype = {
*/
selectStyleSheet: function(href, line, col) {
if (!this._debuggee || !this.UI) {
return;
return null;
}
return this.UI.selectStyleSheet(href, line - 1, col ? col - 1 : 0);
},
@ -146,10 +149,10 @@ StyleEditorPanel.prototype = {
return promise.resolve(null);
},
}
};
XPCOMUtils.defineLazyGetter(StyleEditorPanel.prototype, "strings",
function () {
function() {
return Services.strings.createBundle(
"chrome://devtools/locale/styleeditor.properties");
});

View File

@ -44,6 +44,7 @@ support-files =
sourcemaps-watching.html
test_private.css
test_private.html
doc_frame_script.js
doc_long.css
doc_uncached.css
doc_uncached.html

View File

@ -5,14 +5,16 @@
<head>
<meta charset="utf-8">
<title>Resources</title>
<script type="text/javascript" id="script1">
window.addEventListener('load', function() {
var pid = document.getElementById('pid');
var div = document.createElement('div');
div.id = 'divid';
div.classList.add('divclass');
div.appendChild(document.createTextNode('div'));
div.setAttribute('data-a1', 'div');
<script type="text/javascript;version=1.8" id="script1">
"use strict";
window.addEventListener("load", function() {
let pid = document.getElementById("pid");
let div = document.createElement("div");
div.id = "divid";
div.classList.add("divclass");
div.appendChild(document.createTextNode("div"));
div.setAttribute("data-a1", "div");
pid.parentNode.appendChild(div);
});
</script>
@ -37,12 +39,13 @@
<p class="pclass" id="pid" data-a1="p">paragraph</p>
<script>
var pid = document.getElementById('pid');
var h4 = document.createElement('h4');
h4.id = 'h4id';
h4.classList.add('h4class');
h4.appendChild(document.createTextNode('h4'));
h4.setAttribute('data-a1', 'h4');
"use strict";
let pid = document.getElementById("pid");
let h4 = document.createElement("h4");
h4.id = "h4id";
h4.classList.add("h4class");
h4.appendChild(document.createTextNode("h4"));
h4.setAttribute("data-a1", "h4");
pid.parentNode.appendChild(h4);
</script>

View File

@ -5,6 +5,12 @@
// Tests that the edit command works
// Import the GCLI test helper
/* import-globals-from ../../commandline/test/helpers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js",
this);
const TEST_URI = "http://example.com/browser/devtools/client/styleeditor/" +
"test/browser_styleeditor_cmd_edit.html";
@ -82,9 +88,9 @@ add_task(function* () {
resource: {
arg: " page1",
status: "INCOMPLETE",
message: 'Value required for \'resource\'.'
message: "Value required for \'resource\'."
},
line: { status: 'VALID' },
line: { status: "VALID" },
}
},
},

View File

@ -10,7 +10,8 @@ const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar";
const RESIZE = 300;
const LABELS = ["not all", "all", "(max-width: 400px)",
"(min-height: 200px) and (max-height: 250px)", "(max-width: 600px)"];
"(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; } }";

View File

@ -2,6 +2,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* Tests responsive mode links for
* @media sidebar width and height related conditions */
@ -20,7 +22,7 @@ add_task(function*() {
yield testLinkifiedConditions(mediaEditor, gBrowser.selectedTab, ui);
});
function testLinkifiedConditions(editor, tab, 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";
@ -29,7 +31,7 @@ function testLinkifiedConditions(editor, tab, ui) {
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.")
"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,

View File

@ -13,7 +13,7 @@ add_task(function*() {
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
let editor = yield createNew(ui, panel.panelWindow);
testInitialState(editor);
yield testInitialState(editor);
let originalHref = editor.styleSheet.href;
let waitForPropertyChange = onPropertyChange(editor);
@ -45,14 +45,14 @@ function createNew(ui, panelWindow) {
return deferred.promise;
}
function onPropertyChange(aEditor) {
function onPropertyChange(editor) {
let deferred = promise.defer();
aEditor.styleSheet.on("property-change", function onProp(property) {
editor.styleSheet.on("property-change", function onProp(property) {
// wait for text to be entered fully
let text = aEditor.sourceEditor.getText();
let text = editor.sourceEditor.getText();
if (property == "ruleCount" && text == TESTCASE_CSS_SOURCE + "}") {
aEditor.styleSheet.off("property-change", onProp);
editor.styleSheet.off("property-change", onProp);
deferred.resolve();
}
});
@ -60,33 +60,33 @@ function onPropertyChange(aEditor) {
return deferred.promise;
}
function testInitialState(aEditor) {
function* testInitialState(editor) {
info("Testing the initial state of the new editor");
let summary = aEditor.summary;
let summary = editor.summary;
ok(aEditor.sourceLoaded, "new editor is loaded when attached");
ok(aEditor.isNew, "new editor has isNew flag");
ok(editor.sourceLoaded, "new editor is loaded when attached");
ok(editor.isNew, "new editor has isNew flag");
ok(aEditor.sourceEditor.hasFocus(), "new editor has focus");
ok(editor.sourceEditor.hasFocus(), "new editor has focus");
summary = aEditor.summary;
summary = editor.summary;
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
is(parseInt(ruleCount, 10), 0, "new editor initially shows 0 rules");
let computedStyle = content.getComputedStyle(content.document.body, null);
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
let color = yield getComputedStyleProperty("body", null, "background-color");
is(color, "rgb(255, 255, 255)",
"content's background color is initially white");
}
function typeInEditor(aEditor, panelWindow) {
function typeInEditor(editor, panelWindow) {
let deferred = promise.defer();
waitForFocus(function() {
for (let c of TESTCASE_CSS_SOURCE) {
EventUtils.synthesizeKey(c, {}, panelWindow);
}
ok(aEditor.unsaved, "new editor has unsaved flag");
ok(editor.unsaved, "new editor has unsaved flag");
deferred.resolve();
}, panelWindow);
@ -94,17 +94,17 @@ function typeInEditor(aEditor, panelWindow) {
return deferred.promise;
}
function testUpdated(aEditor, originalHref) {
function testUpdated(editor, originalHref) {
info("Testing the state of the new editor after editing it");
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
is(editor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
"rule bracket has been auto-closed");
let ruleCount = aEditor.summary.querySelector(".stylesheet-rule-count")
let ruleCount = editor.summary.querySelector(".stylesheet-rule-count")
.textContent;
is(parseInt(ruleCount, 10), 1,
"new editor shows 1 rule after modification");
is(aEditor.styleSheet.href, originalHref,
is(editor.styleSheet.href, originalHref,
"style sheet href did not change");
}

View File

@ -53,10 +53,8 @@ add_task(function*() {
yield editor.getSourceEditor();
let element = content.document.querySelector("div");
let style = content.getComputedStyle(element, null);
is(style.color, "rgb(255, 0, 102)", "div is red before saving file");
let color = yield getComputedStyleProperty("div", null, "color");
is(color, "rgb(255, 0, 102)", "div is red before saving file");
// let styleApplied = promise.defer();
let styleApplied = editor.once("style-applied");
@ -75,7 +73,8 @@ add_task(function*() {
yield styleApplied;
is(style.color, "rgb(0, 0, 255)", "div is blue after saving file");
color = yield getComputedStyleProperty("div", null, "color");
is(color, "rgb(0, 0, 255)", "div is blue after saving file");
});
function editSCSS(editor) {
@ -125,7 +124,6 @@ function copy(srcChromeURL, destFilePath) {
function read(srcChromeURL) {
let scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
.getService(Ci.nsIScriptableInputStream);
let principal = Services.scriptSecurityManager.getSystemPrincipal();
let channel = NetUtil.newChannel({
uri: srcChromeURL,
@ -144,7 +142,7 @@ function read(srcChromeURL) {
return data;
}
function write(aData, aFile) {
function write(data, file) {
let deferred = promise.defer();
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
@ -152,15 +150,15 @@ function write(aData, aFile) {
converter.charset = "UTF-8";
let istream = converter.convertToInputStream(aData);
let ostream = FileUtils.openSafeFileOutputStream(aFile);
let istream = converter.convertToInputStream(data);
let ostream = FileUtils.openSafeFileOutputStream(file);
NetUtil.asyncCopy(istream, ostream, function(status) {
if (!Components.isSuccessCode(status)) {
info("Coudln't write to " + aFile.path);
info("Coudln't write to " + file.path);
return;
}
deferred.resolve(aFile);
deferred.resolve(file);
});
return deferred.promise;

View File

@ -47,14 +47,14 @@ const contents = {
"jZjA2O1xuXG4jaGVhZGVyIHtcbiAgY29sb3I6ICRwaW5rO1xufSJdfQ==*/"
].join("\n"),
"test-stylus.styl": [
"paulrougetpink = #f06;",
"",
"div",
" color: paulrougetpink",
"",
"span",
" background-color: #EEE",
""
"paulrougetpink = #f06;",
"",
"div",
" color: paulrougetpink",
"",
"span",
" background-color: #EEE",
""
].join("\n"),
"test-stylus.css": [
"div {",

View File

@ -6,6 +6,7 @@
// Test that changes in the style inspector are synchronized into the
// style editor.
/* import-globals-from ../../inspector/shared/test/head.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/inspector/shared/test/head.js", this);
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";
@ -65,10 +66,6 @@ add_task(function*() {
// For the time being, the actor does not update the style's owning
// node's textContent. See bug 1205380.
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
let style = content.document.querySelector("style");
return style.textContent;
}).then((textContent) => {
isnot(textContent, expectedText, "changes not written back to style node");
});
let textContent = yield executeInContent("Test:GetStyleContent", null, null);
isnot(textContent, expectedText, "changes not written back to style node");
});

View File

@ -5,6 +5,7 @@
// Test that adding a new rule is synced to the style editor.
/* import-globals-from ../../inspector/shared/test/head.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/inspector/shared/test/head.js", this);
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";

View File

@ -6,6 +6,7 @@
// Test that changes in the style inspector are synchronized into the
// style editor.
/* import-globals-from ../../inspector/shared/test/head.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/inspector/shared/test/head.js", this);
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";

View File

@ -6,6 +6,7 @@
// Test that changes in the style inspector are synchronized into the
// style editor.
/* import-globals-from ../../inspector/shared/test/head.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/inspector/shared/test/head.js", this);
const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";

View File

@ -6,6 +6,7 @@
// Test that changes in the style editor are synchronized into the
// style inspector.
/* import-globals-from ../../inspector/shared/test/head.js */
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/inspector/shared/test/head.js", this);
const TEST_URI = `
@ -35,14 +36,14 @@ add_task(function*() {
is(value, "chartreuse", "check that edits were synced to rule view");
});
function typeInEditor(aEditor, panelWindow) {
function typeInEditor(editor, panelWindow) {
let deferred = promise.defer();
waitForFocus(function() {
for (let c of TESTCASE_CSS_SOURCE) {
EventUtils.synthesizeKey(c, {}, panelWindow);
}
ok(aEditor.unsaved, "new editor has unsaved flag");
ok(editor.unsaved, "new editor has unsaved flag");
deferred.resolve();
}, panelWindow);

View File

@ -24,12 +24,13 @@ add_task(function*() {
yield styleChanges;
let sheet = content.document.styleSheets[0];
let rules = yield executeInContent("Test:cssRules", {
num: 0
});
// Test that we removed the transition rule, but kept the rule we added
is(sheet.cssRules.length, 1, "only one rule in stylesheet");
is(sheet.cssRules[0].cssText, NEW_RULE,
"stylesheet only contains rule we added");
is(rules.length, 1, "only one rule in stylesheet");
is(rules[0], NEW_RULE, "stylesheet only contains rule we added");
});
/* Helpers */

View File

@ -4,6 +4,8 @@
// Test that the style-editor initializes correctly for XUL windows.
"use strict";
waitForExplicitFinish();
const TEST_URL = TEST_BASE + "doc_xulpage.xul";
@ -15,5 +17,6 @@ add_task(function*() {
let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
let panel = toolbox.getCurrentPanel();
ok(panel, "The style-editor panel did initialize correctly for the XUL window");
ok(panel,
"The style-editor panel did initialize correctly for the XUL window");
});

View File

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* globals addMessageListener, sendAsyncMessage */
addMessageListener("Test:cssRules", function(msg) {
let {num} = msg.data;
let sheet = content.document.styleSheets[num];
let result = [];
for (let i = 0; i < sheet.cssRules.length; ++i) {
result.push(sheet.cssRules[i].cssText);
}
sendAsyncMessage("Test:cssRules", result);
});
/**
* Get the property value from the computed style for an element.
* @param {Object} data Expects a data object with the following properties
* - {String} selector: The selector used to obtain the element.
* - {String} pseudo: pseudo id to query, or null.
* - {String} name: name of the property
* @return {String} The value, if found, null otherwise
*/
addMessageListener("Test:GetComputedStylePropertyValue", function(msg) {
let {selector, pseudo, name} = msg.data;
let element = content.document.querySelector(selector);
let style = content.document.defaultView.getComputedStyle(element, pseudo);
let value = style.getPropertyValue(name);
sendAsyncMessage("Test:GetComputedStylePropertyValue", value);
});
addMessageListener("Test:GetStyleContent", function() {
sendAsyncMessage("Test:GetStyleContent",
content.document.querySelector("style").textContent);
});

View File

@ -1,23 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js"
const TEST_BASE = "chrome://mochitests/content/browser/devtools/client/styleeditor/test/";
const TEST_BASE_HTTP = "http://example.com/browser/devtools/client/styleeditor/test/";
const TEST_BASE_HTTPS = "https://example.com/browser/devtools/client/styleeditor/test/";
const TEST_HOST = 'mochi.test:8888';
/* All top-level definitions here are exports. */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
"use strict";
const FRAME_SCRIPT_UTILS_URL =
"chrome://devtools/content/shared/frame-script-utils.js";
const TEST_BASE =
"chrome://mochitests/content/browser/devtools/client/styleeditor/test/";
const TEST_BASE_HTTP =
"http://example.com/browser/devtools/client/styleeditor/test/";
const TEST_BASE_HTTPS =
"https://example.com/browser/devtools/client/styleeditor/test/";
const TEST_HOST = "mochi.test:8888";
const EDITOR_FRAME_SCRIPT = getRootDirectory(gTestPath) + "doc_frame_script.js";
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
var {TargetFactory} = require("devtools/client/framework/target");
var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
var promise = require("promise");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
// Import the GCLI test helper
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js",
this);
DevToolsUtils.testing = true;
SimpleTest.registerCleanupFunction(() => {
DevToolsUtils.testing = false;
@ -61,8 +66,7 @@ function navigateTo(url) {
return navigating.promise;
}
function* cleanup()
{
function* cleanup() {
while (gBrowser.tabs.length > 1) {
let target = TargetFactory.forTab(gBrowser.selectedTab);
yield gDevTools.closeToolbox(target);
@ -92,6 +96,10 @@ var openStyleEditor = Task.async(function*(tab) {
*/
var openStyleEditorForURL = Task.async(function* (url, win) {
let tab = yield addTab(url, win);
gBrowser.selectedBrowser.messageManager.loadFrameScript(EDITOR_FRAME_SCRIPT,
false);
let result = yield openStyleEditor(tab);
result.tab = tab;
return result;
@ -128,15 +136,15 @@ function loadCommonFrameScript(tab) {
* Resolves to the response data if a response is expected, immediately
* resolves otherwise
*/
function executeInContent(name, data={}, objects={}, expectResponse=true) {
function executeInContent(name, data = {}, objects = {},
expectResponse = true) {
let mm = gBrowser.selectedBrowser.messageManager;
mm.sendAsyncMessage(name, data, objects);
if (expectResponse) {
return waitForContentMessage(name);
} else {
return promise.resolve();
}
return promise.resolve();
}
/**
@ -152,9 +160,27 @@ function waitForContentMessage(name) {
let def = promise.defer();
mm.addMessageListener(name, function onMessage(msg) {
mm.removeMessageListener(name, onMessage);
def.resolve(msg);
def.resolve(msg.data);
});
return def.promise;
}
registerCleanupFunction(cleanup);
/**
* Send an async message to the frame script and get back the requested
* computed style property.
*
* @param {String} selector
* The selector used to obtain the element.
* @param {String} pseudo
* pseudo id to query, or null.
* @param {String} name
* name of the property.
*/
function* getComputedStyleProperty(selector, pseudo, propName) {
return yield executeInContent("Test:GetComputedStylePropertyValue",
{selector,
pseudo,
name: propName});
}

View File

@ -19,8 +19,9 @@
</head>
<body>
Time passes:
<script>
for (i = 0; i < 5000; i++) {
<script type="application/javascript;version=1.8">
"use strict";
for (let i = 0; i < 5000; i++) {
document.write("<br>...");
}
</script>

View File

@ -4,7 +4,9 @@
* 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/. */
const {Cc, Ci, Cu, Cr} = require("chrome");
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://devtools/shared/event-emitter.js");
@ -34,7 +36,7 @@ PrefObserver.prototype = {
destroy: function() {
if (this.branch) {
this.branch.removeObserver('', this);
this.branch.removeObserver("", this);
}
}
};
};

View File

@ -8,8 +8,10 @@ package org.mozilla.gecko.dlc.catalog;
import android.support.v4.util.AtomicFile;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.io.FileNotFoundException;
@ -57,7 +59,11 @@ public class TestDownloadContentCatalog {
* * Catalog is bootstrapped with items.
*/
@Test
public void testCatalogIsBootstrapedIfFileDoesNotExist() throws Exception {
public void testCatalogIsBootstrappedIfFileDoesNotExist() throws Exception {
// The catalog is only bootstrapped if fonts are excluded from the build. If this is a build
// with fonts included then ignore this test.
Assume.assumeTrue("Fonts are excluded from build", AppConstants.MOZ_ANDROID_EXCLUDE_FONTS);
AtomicFile file = mock(AtomicFile.class);
doThrow(FileNotFoundException.class).when(file).readFully();

View File

@ -29,6 +29,9 @@ if so, checks for possible CPOW usage.
"Cu.importGlobalProperties". Use of this function is undesirable in
some parts of the tree.
``this-top-level-scope`` This rule treats top-level assignments like
``this.mumble = value`` as declaring a global.
Note: These are string matches so we will miss situations where the parent
object is assigned to another variable e.g.::
@ -73,4 +76,5 @@ Example configuration::
no-aArgs
no-cpows-in-tests
reject-importGlobalProperties
this-top-level-scope
var-only-at-top-level

View File

@ -0,0 +1,11 @@
.. _this-top-level-scope:
====================
this-top-level-scope
====================
Rule Details
------------
Treat top-level assignments like ``this.mumble = value`` as declaring
a global.

View File

@ -26,7 +26,6 @@
"is": false,
"isnot": false,
"ok": false,
"promise": false,
"registerCleanupFunction": false,
"requestLongerTimeout": false,
"SimpleTest": false,