1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/styleeditor/StyleEditorUI.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,652 @@ 1.4 +/* vim:set ts=2 sw=2 sts=2 et: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +"use strict"; 1.10 + 1.11 +this.EXPORTED_SYMBOLS = ["StyleEditorUI"]; 1.12 + 1.13 +const Cc = Components.classes; 1.14 +const Ci = Components.interfaces; 1.15 +const Cu = Components.utils; 1.16 + 1.17 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.18 +Cu.import("resource://gre/modules/Services.jsm"); 1.19 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.20 +Cu.import("resource://gre/modules/osfile.jsm"); 1.21 +Cu.import("resource://gre/modules/Task.jsm"); 1.22 +Cu.import("resource://gre/modules/devtools/event-emitter.js"); 1.23 +Cu.import("resource:///modules/devtools/gDevTools.jsm"); 1.24 +Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm"); 1.25 +Cu.import("resource:///modules/devtools/SplitView.jsm"); 1.26 +Cu.import("resource:///modules/devtools/StyleSheetEditor.jsm"); 1.27 +const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.28 + 1.29 +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", 1.30 + "resource://gre/modules/PluralForm.jsm"); 1.31 + 1.32 +const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; 1.33 +const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils"); 1.34 + 1.35 +const LOAD_ERROR = "error-load"; 1.36 +const STYLE_EDITOR_TEMPLATE = "stylesheet"; 1.37 + 1.38 +/** 1.39 + * StyleEditorUI is controls and builds the UI of the Style Editor, including 1.40 + * maintaining a list of editors for each stylesheet on a debuggee. 1.41 + * 1.42 + * Emits events: 1.43 + * 'editor-added': A new editor was added to the UI 1.44 + * 'editor-selected': An editor was selected 1.45 + * 'error': An error occured 1.46 + * 1.47 + * @param {StyleEditorFront} debuggee 1.48 + * Client-side front for interacting with the page's stylesheets 1.49 + * @param {Target} target 1.50 + * Interface for the page we're debugging 1.51 + * @param {Document} panelDoc 1.52 + * Document of the toolbox panel to populate UI in. 1.53 + */ 1.54 +function StyleEditorUI(debuggee, target, panelDoc) { 1.55 + EventEmitter.decorate(this); 1.56 + 1.57 + this._debuggee = debuggee; 1.58 + this._target = target; 1.59 + this._panelDoc = panelDoc; 1.60 + this._window = this._panelDoc.defaultView; 1.61 + this._root = this._panelDoc.getElementById("style-editor-chrome"); 1.62 + 1.63 + this.editors = []; 1.64 + this.selectedEditor = null; 1.65 + this.savedLocations = {}; 1.66 + 1.67 + this._updateSourcesLabel = this._updateSourcesLabel.bind(this); 1.68 + this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this); 1.69 + this._onNewDocument = this._onNewDocument.bind(this); 1.70 + this._clear = this._clear.bind(this); 1.71 + this._onError = this._onError.bind(this); 1.72 + 1.73 + this._prefObserver = new PrefObserver("devtools.styleeditor."); 1.74 + this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument); 1.75 +} 1.76 + 1.77 +StyleEditorUI.prototype = { 1.78 + /** 1.79 + * Get whether any of the editors have unsaved changes. 1.80 + * 1.81 + * @return boolean 1.82 + */ 1.83 + get isDirty() { 1.84 + if (this._markedDirty === true) { 1.85 + return true; 1.86 + } 1.87 + return this.editors.some((editor) => { 1.88 + return editor.sourceEditor && !editor.sourceEditor.isClean(); 1.89 + }); 1.90 + }, 1.91 + 1.92 + /* 1.93 + * Mark the style editor as having or not having unsaved changes. 1.94 + */ 1.95 + set isDirty(value) { 1.96 + this._markedDirty = value; 1.97 + }, 1.98 + 1.99 + /* 1.100 + * Index of selected stylesheet in document.styleSheets 1.101 + */ 1.102 + get selectedStyleSheetIndex() { 1.103 + return this.selectedEditor ? 1.104 + this.selectedEditor.styleSheet.styleSheetIndex : -1; 1.105 + }, 1.106 + 1.107 + /** 1.108 + * Initiates the style editor ui creation and the inspector front to get 1.109 + * reference to the walker. 1.110 + */ 1.111 + initialize: function() { 1.112 + let toolbox = gDevTools.getToolbox(this._target); 1.113 + return toolbox.initInspector().then(() => { 1.114 + this._walker = toolbox.walker; 1.115 + }).then(() => { 1.116 + this.createUI(); 1.117 + this._debuggee.getStyleSheets().then((styleSheets) => { 1.118 + this._resetStyleSheetList(styleSheets); 1.119 + 1.120 + this._target.on("will-navigate", this._clear); 1.121 + this._target.on("navigate", this._onNewDocument); 1.122 + }); 1.123 + }); 1.124 + }, 1.125 + 1.126 + /** 1.127 + * Build the initial UI and wire buttons with event handlers. 1.128 + */ 1.129 + createUI: function() { 1.130 + let viewRoot = this._root.parentNode.querySelector(".splitview-root"); 1.131 + 1.132 + this._view = new SplitView(viewRoot); 1.133 + 1.134 + wire(this._view.rootElement, ".style-editor-newButton", function onNew() { 1.135 + this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated); 1.136 + }.bind(this)); 1.137 + 1.138 + wire(this._view.rootElement, ".style-editor-importButton", function onImport() { 1.139 + this._importFromFile(this._mockImportFile || null, this._window); 1.140 + }.bind(this)); 1.141 + 1.142 + this._contextMenu = this._panelDoc.getElementById("sidebar-context"); 1.143 + this._contextMenu.addEventListener("popupshowing", 1.144 + this._updateSourcesLabel); 1.145 + 1.146 + this._sourcesItem = this._panelDoc.getElementById("context-origsources"); 1.147 + this._sourcesItem.addEventListener("command", 1.148 + this._toggleOrigSources); 1.149 + }, 1.150 + 1.151 + /** 1.152 + * Update text of context menu option to reflect whether we're showing 1.153 + * original sources (e.g. Sass files) or not. 1.154 + */ 1.155 + _updateSourcesLabel: function() { 1.156 + let string = "showOriginalSources"; 1.157 + if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { 1.158 + string = "showCSSSources"; 1.159 + } 1.160 + this._sourcesItem.setAttribute("label", _(string + ".label")); 1.161 + this._sourcesItem.setAttribute("accesskey", _(string + ".accesskey")); 1.162 + }, 1.163 + 1.164 + /** 1.165 + * Refresh editors to reflect the stylesheets in the document. 1.166 + * 1.167 + * @param {string} event 1.168 + * Event name 1.169 + * @param {StyleSheet} styleSheet 1.170 + * StyleSheet object for new sheet 1.171 + */ 1.172 + _onNewDocument: function() { 1.173 + this._debuggee.getStyleSheets().then((styleSheets) => { 1.174 + this._resetStyleSheetList(styleSheets); 1.175 + }) 1.176 + }, 1.177 + 1.178 + /** 1.179 + * Add editors for all the given stylesheets to the UI. 1.180 + * 1.181 + * @param {array} styleSheets 1.182 + * Array of StyleSheetFront 1.183 + */ 1.184 + _resetStyleSheetList: function(styleSheets) { 1.185 + this._clear(); 1.186 + 1.187 + for (let sheet of styleSheets) { 1.188 + this._addStyleSheet(sheet); 1.189 + } 1.190 + 1.191 + this._root.classList.remove("loading"); 1.192 + 1.193 + this.emit("stylesheets-reset"); 1.194 + }, 1.195 + 1.196 + /** 1.197 + * Remove all editors and add loading indicator. 1.198 + */ 1.199 + _clear: function() { 1.200 + // remember selected sheet and line number for next load 1.201 + if (this.selectedEditor && this.selectedEditor.sourceEditor) { 1.202 + let href = this.selectedEditor.styleSheet.href; 1.203 + let {line, ch} = this.selectedEditor.sourceEditor.getCursor(); 1.204 + 1.205 + this._styleSheetToSelect = { 1.206 + href: href, 1.207 + line: line, 1.208 + col: ch 1.209 + }; 1.210 + } 1.211 + 1.212 + // remember saved file locations 1.213 + for (let editor of this.editors) { 1.214 + if (editor.savedFile) { 1.215 + let identifier = this.getStyleSheetIdentifier(editor.styleSheet); 1.216 + this.savedLocations[identifier] = editor.savedFile; 1.217 + } 1.218 + } 1.219 + 1.220 + this._clearStyleSheetEditors(); 1.221 + this._view.removeAll(); 1.222 + 1.223 + this.selectedEditor = null; 1.224 + 1.225 + this._root.classList.add("loading"); 1.226 + }, 1.227 + 1.228 + /** 1.229 + * Add an editor for this stylesheet. Add editors for its original sources 1.230 + * instead (e.g. Sass sources), if applicable. 1.231 + * 1.232 + * @param {StyleSheetFront} styleSheet 1.233 + * Style sheet to add to style editor 1.234 + */ 1.235 + _addStyleSheet: function(styleSheet) { 1.236 + let editor = this._addStyleSheetEditor(styleSheet); 1.237 + 1.238 + if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { 1.239 + return; 1.240 + } 1.241 + 1.242 + styleSheet.getOriginalSources().then((sources) => { 1.243 + if (sources && sources.length) { 1.244 + this._removeStyleSheetEditor(editor); 1.245 + sources.forEach((source) => { 1.246 + // set so the first sheet will be selected, even if it's a source 1.247 + source.styleSheetIndex = styleSheet.styleSheetIndex; 1.248 + source.relatedStyleSheet = styleSheet; 1.249 + 1.250 + this._addStyleSheetEditor(source); 1.251 + }); 1.252 + } 1.253 + }); 1.254 + }, 1.255 + 1.256 + /** 1.257 + * Add a new editor to the UI for a source. 1.258 + * 1.259 + * @param {StyleSheet} styleSheet 1.260 + * Object representing stylesheet 1.261 + * @param {nsIfile} file 1.262 + * Optional file object that sheet was imported from 1.263 + * @param {Boolean} isNew 1.264 + * Optional if stylesheet is a new sheet created by user 1.265 + */ 1.266 + _addStyleSheetEditor: function(styleSheet, file, isNew) { 1.267 + // recall location of saved file for this sheet after page reload 1.268 + let identifier = this.getStyleSheetIdentifier(styleSheet); 1.269 + let savedFile = this.savedLocations[identifier]; 1.270 + if (savedFile && !file) { 1.271 + file = savedFile; 1.272 + } 1.273 + 1.274 + let editor = 1.275 + new StyleSheetEditor(styleSheet, this._window, file, isNew, this._walker); 1.276 + 1.277 + editor.on("property-change", this._summaryChange.bind(this, editor)); 1.278 + editor.on("linked-css-file", this._summaryChange.bind(this, editor)); 1.279 + editor.on("linked-css-file-error", this._summaryChange.bind(this, editor)); 1.280 + editor.on("error", this._onError); 1.281 + 1.282 + this.editors.push(editor); 1.283 + 1.284 + editor.fetchSource(this._sourceLoaded.bind(this, editor)); 1.285 + return editor; 1.286 + }, 1.287 + 1.288 + /** 1.289 + * Import a style sheet from file and asynchronously create a 1.290 + * new stylesheet on the debuggee for it. 1.291 + * 1.292 + * @param {mixed} file 1.293 + * Optional nsIFile or filename string. 1.294 + * If not set a file picker will be shown. 1.295 + * @param {nsIWindow} parentWindow 1.296 + * Optional parent window for the file picker. 1.297 + */ 1.298 + _importFromFile: function(file, parentWindow) { 1.299 + let onFileSelected = function(file) { 1.300 + if (!file) { 1.301 + // nothing selected 1.302 + return; 1.303 + } 1.304 + NetUtil.asyncFetch(file, (stream, status) => { 1.305 + if (!Components.isSuccessCode(status)) { 1.306 + this.emit("error", LOAD_ERROR); 1.307 + return; 1.308 + } 1.309 + let source = NetUtil.readInputStreamToString(stream, stream.available()); 1.310 + stream.close(); 1.311 + 1.312 + this._debuggee.addStyleSheet(source).then((styleSheet) => { 1.313 + this._onStyleSheetCreated(styleSheet, file); 1.314 + }); 1.315 + }); 1.316 + 1.317 + }.bind(this); 1.318 + 1.319 + showFilePicker(file, false, parentWindow, onFileSelected); 1.320 + }, 1.321 + 1.322 + 1.323 + /** 1.324 + * When a new or imported stylesheet has been added to the document. 1.325 + * Add an editor for it. 1.326 + */ 1.327 + _onStyleSheetCreated: function(styleSheet, file) { 1.328 + this._addStyleSheetEditor(styleSheet, file, true); 1.329 + }, 1.330 + 1.331 + /** 1.332 + * Forward any error from a stylesheet. 1.333 + * 1.334 + * @param {string} event 1.335 + * Event name 1.336 + * @param {string} errorCode 1.337 + * Code represeting type of error 1.338 + * @param {string} message 1.339 + * The full error message 1.340 + */ 1.341 + _onError: function(event, errorCode, message) { 1.342 + this.emit("error", errorCode, message); 1.343 + }, 1.344 + 1.345 + /** 1.346 + * Toggle the original sources pref. 1.347 + */ 1.348 + _toggleOrigSources: function() { 1.349 + let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES); 1.350 + Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled); 1.351 + }, 1.352 + 1.353 + /** 1.354 + * Remove a particular stylesheet editor from the UI 1.355 + * 1.356 + * @param {StyleSheetEditor} editor 1.357 + * The editor to remove. 1.358 + */ 1.359 + _removeStyleSheetEditor: function(editor) { 1.360 + if (editor.summary) { 1.361 + this._view.removeItem(editor.summary); 1.362 + } 1.363 + else { 1.364 + let self = this; 1.365 + this.on("editor-added", function onAdd(event, added) { 1.366 + if (editor == added) { 1.367 + self.off("editor-added", onAdd); 1.368 + self._view.removeItem(editor.summary); 1.369 + } 1.370 + }) 1.371 + } 1.372 + 1.373 + editor.destroy(); 1.374 + this.editors.splice(this.editors.indexOf(editor), 1); 1.375 + }, 1.376 + 1.377 + /** 1.378 + * Clear all the editors from the UI. 1.379 + */ 1.380 + _clearStyleSheetEditors: function() { 1.381 + for (let editor of this.editors) { 1.382 + editor.destroy(); 1.383 + } 1.384 + this.editors = []; 1.385 + }, 1.386 + 1.387 + /** 1.388 + * Called when a StyleSheetEditor's source has been fetched. Create a 1.389 + * summary UI for the editor. 1.390 + * 1.391 + * @param {StyleSheetEditor} editor 1.392 + * Editor to create UI for. 1.393 + */ 1.394 + _sourceLoaded: function(editor) { 1.395 + // add new sidebar item and editor to the UI 1.396 + this._view.appendTemplatedItem(STYLE_EDITOR_TEMPLATE, { 1.397 + data: { 1.398 + editor: editor 1.399 + }, 1.400 + disableAnimations: this._alwaysDisableAnimations, 1.401 + ordinal: editor.styleSheet.styleSheetIndex, 1.402 + onCreate: function(summary, details, data) { 1.403 + let editor = data.editor; 1.404 + editor.summary = summary; 1.405 + 1.406 + wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) { 1.407 + event.stopPropagation(); 1.408 + event.target.blur(); 1.409 + 1.410 + editor.toggleDisabled(); 1.411 + }); 1.412 + 1.413 + wire(summary, ".stylesheet-name", { 1.414 + events: { 1.415 + "keypress": function onStylesheetNameActivate(aEvent) { 1.416 + if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { 1.417 + this._view.activeSummary = summary; 1.418 + } 1.419 + }.bind(this) 1.420 + } 1.421 + }); 1.422 + 1.423 + wire(summary, ".stylesheet-saveButton", function onSaveButton(event) { 1.424 + event.stopPropagation(); 1.425 + event.target.blur(); 1.426 + 1.427 + editor.saveToFile(editor.savedFile); 1.428 + }); 1.429 + 1.430 + this._updateSummaryForEditor(editor, summary); 1.431 + 1.432 + summary.addEventListener("focus", function onSummaryFocus(event) { 1.433 + if (event.target == summary) { 1.434 + // autofocus the stylesheet name 1.435 + summary.querySelector(".stylesheet-name").focus(); 1.436 + } 1.437 + }, false); 1.438 + 1.439 + Task.spawn(function* () { 1.440 + // autofocus if it's a new user-created stylesheet 1.441 + if (editor.isNew) { 1.442 + yield this._selectEditor(editor); 1.443 + } 1.444 + 1.445 + if (this._styleSheetToSelect 1.446 + && this._styleSheetToSelect.href == editor.styleSheet.href) { 1.447 + yield this.switchToSelectedSheet(); 1.448 + } 1.449 + 1.450 + // If this is the first stylesheet and there is no pending request to 1.451 + // select a particular style sheet, select this sheet. 1.452 + if (!this.selectedEditor && !this._styleSheetBoundToSelect 1.453 + && editor.styleSheet.styleSheetIndex == 0) { 1.454 + yield this._selectEditor(editor); 1.455 + } 1.456 + 1.457 + this.emit("editor-added", editor); 1.458 + }.bind(this)).then(null, Cu.reportError); 1.459 + }.bind(this), 1.460 + 1.461 + onShow: function(summary, details, data) { 1.462 + let editor = data.editor; 1.463 + this.selectedEditor = editor; 1.464 + 1.465 + Task.spawn(function* () { 1.466 + if (!editor.sourceEditor) { 1.467 + // only initialize source editor when we switch to this view 1.468 + let inputElement = details.querySelector(".stylesheet-editor-input"); 1.469 + yield editor.load(inputElement); 1.470 + } 1.471 + 1.472 + editor.onShow(); 1.473 + 1.474 + this.emit("editor-selected", editor); 1.475 + }.bind(this)).then(null, Cu.reportError); 1.476 + }.bind(this) 1.477 + }); 1.478 + }, 1.479 + 1.480 + /** 1.481 + * Switch to the editor that has been marked to be selected. 1.482 + * 1.483 + * @return {Promise} 1.484 + * Promise that will resolve when the editor is selected. 1.485 + */ 1.486 + switchToSelectedSheet: function() { 1.487 + let sheet = this._styleSheetToSelect; 1.488 + 1.489 + for (let editor of this.editors) { 1.490 + if (editor.styleSheet.href == sheet.href) { 1.491 + // The _styleSheetBoundToSelect will always hold the latest pending 1.492 + // requested style sheet (with line and column) which is not yet 1.493 + // selected by the source editor. Only after we select that particular 1.494 + // editor and go the required line and column, it will become null. 1.495 + this._styleSheetBoundToSelect = this._styleSheetToSelect; 1.496 + this._styleSheetToSelect = null; 1.497 + return this._selectEditor(editor, sheet.line, sheet.col); 1.498 + } 1.499 + } 1.500 + 1.501 + return promise.resolve(); 1.502 + }, 1.503 + 1.504 + /** 1.505 + * Select an editor in the UI. 1.506 + * 1.507 + * @param {StyleSheetEditor} editor 1.508 + * Editor to switch to. 1.509 + * @param {number} line 1.510 + * Line number to jump to 1.511 + * @param {number} col 1.512 + * Column number to jump to 1.513 + * @return {Promise} 1.514 + * Promise that will resolve when the editor is selected. 1.515 + */ 1.516 + _selectEditor: function(editor, line, col) { 1.517 + line = line || 0; 1.518 + col = col || 0; 1.519 + 1.520 + let editorPromise = editor.getSourceEditor().then(() => { 1.521 + editor.sourceEditor.setCursor({line: line, ch: col}); 1.522 + this._styleSheetBoundToSelect = null; 1.523 + }); 1.524 + 1.525 + let summaryPromise = this.getEditorSummary(editor).then((summary) => { 1.526 + this._view.activeSummary = summary; 1.527 + }); 1.528 + 1.529 + return promise.all([editorPromise, summaryPromise]); 1.530 + }, 1.531 + 1.532 + getEditorSummary: function(editor) { 1.533 + if (editor.summary) { 1.534 + return promise.resolve(editor.summary); 1.535 + } 1.536 + 1.537 + let deferred = promise.defer(); 1.538 + let self = this; 1.539 + 1.540 + this.on("editor-added", function onAdd(e, selected) { 1.541 + if (selected == editor) { 1.542 + self.off("editor-added", onAdd); 1.543 + deferred.resolve(editor.summary); 1.544 + } 1.545 + }); 1.546 + 1.547 + return deferred.promise; 1.548 + }, 1.549 + 1.550 + /** 1.551 + * Returns an identifier for the given style sheet. 1.552 + * 1.553 + * @param {StyleSheet} aStyleSheet 1.554 + * The style sheet to be identified. 1.555 + */ 1.556 + getStyleSheetIdentifier: function (aStyleSheet) { 1.557 + // Identify inline style sheets by their host page URI and index at the page. 1.558 + return aStyleSheet.href ? aStyleSheet.href : 1.559 + "inline-" + aStyleSheet.styleSheetIndex + "-at-" + aStyleSheet.nodeHref; 1.560 + }, 1.561 + 1.562 + /** 1.563 + * selects a stylesheet and optionally moves the cursor to a selected line 1.564 + * 1.565 + * @param {string} [href] 1.566 + * Href of stylesheet that should be selected. If a stylesheet is not passed 1.567 + * and the editor is not initialized we focus the first stylesheet. If 1.568 + * a stylesheet is not passed and the editor is initialized we ignore 1.569 + * the call. 1.570 + * @param {Number} [line] 1.571 + * Line to which the caret should be moved (zero-indexed). 1.572 + * @param {Number} [col] 1.573 + * Column to which the caret should be moved (zero-indexed). 1.574 + */ 1.575 + selectStyleSheet: function(href, line, col) { 1.576 + this._styleSheetToSelect = { 1.577 + href: href, 1.578 + line: line, 1.579 + col: col, 1.580 + }; 1.581 + 1.582 + /* Switch to the editor for this sheet, if it exists yet. 1.583 + Otherwise each editor will be checked when it's created. */ 1.584 + this.switchToSelectedSheet(); 1.585 + }, 1.586 + 1.587 + 1.588 + /** 1.589 + * Handler for an editor's 'property-changed' event. 1.590 + * Update the summary in the UI. 1.591 + * 1.592 + * @param {StyleSheetEditor} editor 1.593 + * Editor for which a property has changed 1.594 + */ 1.595 + _summaryChange: function(editor) { 1.596 + this._updateSummaryForEditor(editor); 1.597 + }, 1.598 + 1.599 + /** 1.600 + * Update split view summary of given StyleEditor instance. 1.601 + * 1.602 + * @param {StyleSheetEditor} editor 1.603 + * @param {DOMElement} summary 1.604 + * Optional item's summary element to update. If none, item corresponding 1.605 + * to passed editor is used. 1.606 + */ 1.607 + _updateSummaryForEditor: function(editor, summary) { 1.608 + summary = summary || editor.summary; 1.609 + if (!summary) { 1.610 + return; 1.611 + } 1.612 + 1.613 + let ruleCount = editor.styleSheet.ruleCount; 1.614 + if (editor.styleSheet.relatedStyleSheet && editor.linkedCSSFile) { 1.615 + ruleCount = editor.styleSheet.relatedStyleSheet.ruleCount; 1.616 + } 1.617 + if (ruleCount === undefined) { 1.618 + ruleCount = "-"; 1.619 + } 1.620 + 1.621 + var flags = []; 1.622 + if (editor.styleSheet.disabled) { 1.623 + flags.push("disabled"); 1.624 + } 1.625 + if (editor.unsaved) { 1.626 + flags.push("unsaved"); 1.627 + } 1.628 + if (editor.linkedCSSFileError) { 1.629 + flags.push("linked-file-error"); 1.630 + } 1.631 + this._view.setItemClassName(summary, flags.join(" ")); 1.632 + 1.633 + let label = summary.querySelector(".stylesheet-name > label"); 1.634 + label.setAttribute("value", editor.friendlyName); 1.635 + if (editor.styleSheet.href) { 1.636 + label.setAttribute("tooltiptext", editor.styleSheet.href); 1.637 + } 1.638 + 1.639 + let linkedCSSFile = ""; 1.640 + if (editor.linkedCSSFile) { 1.641 + linkedCSSFile = OS.Path.basename(editor.linkedCSSFile); 1.642 + } 1.643 + text(summary, ".stylesheet-linked-file", linkedCSSFile); 1.644 + text(summary, ".stylesheet-title", editor.styleSheet.title || ""); 1.645 + text(summary, ".stylesheet-rule-count", 1.646 + PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount)); 1.647 + }, 1.648 + 1.649 + destroy: function() { 1.650 + this._clearStyleSheetEditors(); 1.651 + 1.652 + this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument); 1.653 + this._prefObserver.destroy(); 1.654 + } 1.655 +}