Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* vim:set ts=2 sw=2 sts=2 et: */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | "use strict"; |
michael@0 | 7 | |
michael@0 | 8 | this.EXPORTED_SYMBOLS = ["StyleEditorUI"]; |
michael@0 | 9 | |
michael@0 | 10 | const Cc = Components.classes; |
michael@0 | 11 | const Ci = Components.interfaces; |
michael@0 | 12 | const Cu = Components.utils; |
michael@0 | 13 | |
michael@0 | 14 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 16 | Cu.import("resource://gre/modules/NetUtil.jsm"); |
michael@0 | 17 | Cu.import("resource://gre/modules/osfile.jsm"); |
michael@0 | 18 | Cu.import("resource://gre/modules/Task.jsm"); |
michael@0 | 19 | Cu.import("resource://gre/modules/devtools/event-emitter.js"); |
michael@0 | 20 | Cu.import("resource:///modules/devtools/gDevTools.jsm"); |
michael@0 | 21 | Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm"); |
michael@0 | 22 | Cu.import("resource:///modules/devtools/SplitView.jsm"); |
michael@0 | 23 | Cu.import("resource:///modules/devtools/StyleSheetEditor.jsm"); |
michael@0 | 24 | const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 25 | |
michael@0 | 26 | XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", |
michael@0 | 27 | "resource://gre/modules/PluralForm.jsm"); |
michael@0 | 28 | |
michael@0 | 29 | const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; |
michael@0 | 30 | const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils"); |
michael@0 | 31 | |
michael@0 | 32 | const LOAD_ERROR = "error-load"; |
michael@0 | 33 | const STYLE_EDITOR_TEMPLATE = "stylesheet"; |
michael@0 | 34 | |
michael@0 | 35 | /** |
michael@0 | 36 | * StyleEditorUI is controls and builds the UI of the Style Editor, including |
michael@0 | 37 | * maintaining a list of editors for each stylesheet on a debuggee. |
michael@0 | 38 | * |
michael@0 | 39 | * Emits events: |
michael@0 | 40 | * 'editor-added': A new editor was added to the UI |
michael@0 | 41 | * 'editor-selected': An editor was selected |
michael@0 | 42 | * 'error': An error occured |
michael@0 | 43 | * |
michael@0 | 44 | * @param {StyleEditorFront} debuggee |
michael@0 | 45 | * Client-side front for interacting with the page's stylesheets |
michael@0 | 46 | * @param {Target} target |
michael@0 | 47 | * Interface for the page we're debugging |
michael@0 | 48 | * @param {Document} panelDoc |
michael@0 | 49 | * Document of the toolbox panel to populate UI in. |
michael@0 | 50 | */ |
michael@0 | 51 | function StyleEditorUI(debuggee, target, panelDoc) { |
michael@0 | 52 | EventEmitter.decorate(this); |
michael@0 | 53 | |
michael@0 | 54 | this._debuggee = debuggee; |
michael@0 | 55 | this._target = target; |
michael@0 | 56 | this._panelDoc = panelDoc; |
michael@0 | 57 | this._window = this._panelDoc.defaultView; |
michael@0 | 58 | this._root = this._panelDoc.getElementById("style-editor-chrome"); |
michael@0 | 59 | |
michael@0 | 60 | this.editors = []; |
michael@0 | 61 | this.selectedEditor = null; |
michael@0 | 62 | this.savedLocations = {}; |
michael@0 | 63 | |
michael@0 | 64 | this._updateSourcesLabel = this._updateSourcesLabel.bind(this); |
michael@0 | 65 | this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this); |
michael@0 | 66 | this._onNewDocument = this._onNewDocument.bind(this); |
michael@0 | 67 | this._clear = this._clear.bind(this); |
michael@0 | 68 | this._onError = this._onError.bind(this); |
michael@0 | 69 | |
michael@0 | 70 | this._prefObserver = new PrefObserver("devtools.styleeditor."); |
michael@0 | 71 | this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument); |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | StyleEditorUI.prototype = { |
michael@0 | 75 | /** |
michael@0 | 76 | * Get whether any of the editors have unsaved changes. |
michael@0 | 77 | * |
michael@0 | 78 | * @return boolean |
michael@0 | 79 | */ |
michael@0 | 80 | get isDirty() { |
michael@0 | 81 | if (this._markedDirty === true) { |
michael@0 | 82 | return true; |
michael@0 | 83 | } |
michael@0 | 84 | return this.editors.some((editor) => { |
michael@0 | 85 | return editor.sourceEditor && !editor.sourceEditor.isClean(); |
michael@0 | 86 | }); |
michael@0 | 87 | }, |
michael@0 | 88 | |
michael@0 | 89 | /* |
michael@0 | 90 | * Mark the style editor as having or not having unsaved changes. |
michael@0 | 91 | */ |
michael@0 | 92 | set isDirty(value) { |
michael@0 | 93 | this._markedDirty = value; |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | /* |
michael@0 | 97 | * Index of selected stylesheet in document.styleSheets |
michael@0 | 98 | */ |
michael@0 | 99 | get selectedStyleSheetIndex() { |
michael@0 | 100 | return this.selectedEditor ? |
michael@0 | 101 | this.selectedEditor.styleSheet.styleSheetIndex : -1; |
michael@0 | 102 | }, |
michael@0 | 103 | |
michael@0 | 104 | /** |
michael@0 | 105 | * Initiates the style editor ui creation and the inspector front to get |
michael@0 | 106 | * reference to the walker. |
michael@0 | 107 | */ |
michael@0 | 108 | initialize: function() { |
michael@0 | 109 | let toolbox = gDevTools.getToolbox(this._target); |
michael@0 | 110 | return toolbox.initInspector().then(() => { |
michael@0 | 111 | this._walker = toolbox.walker; |
michael@0 | 112 | }).then(() => { |
michael@0 | 113 | this.createUI(); |
michael@0 | 114 | this._debuggee.getStyleSheets().then((styleSheets) => { |
michael@0 | 115 | this._resetStyleSheetList(styleSheets); |
michael@0 | 116 | |
michael@0 | 117 | this._target.on("will-navigate", this._clear); |
michael@0 | 118 | this._target.on("navigate", this._onNewDocument); |
michael@0 | 119 | }); |
michael@0 | 120 | }); |
michael@0 | 121 | }, |
michael@0 | 122 | |
michael@0 | 123 | /** |
michael@0 | 124 | * Build the initial UI and wire buttons with event handlers. |
michael@0 | 125 | */ |
michael@0 | 126 | createUI: function() { |
michael@0 | 127 | let viewRoot = this._root.parentNode.querySelector(".splitview-root"); |
michael@0 | 128 | |
michael@0 | 129 | this._view = new SplitView(viewRoot); |
michael@0 | 130 | |
michael@0 | 131 | wire(this._view.rootElement, ".style-editor-newButton", function onNew() { |
michael@0 | 132 | this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated); |
michael@0 | 133 | }.bind(this)); |
michael@0 | 134 | |
michael@0 | 135 | wire(this._view.rootElement, ".style-editor-importButton", function onImport() { |
michael@0 | 136 | this._importFromFile(this._mockImportFile || null, this._window); |
michael@0 | 137 | }.bind(this)); |
michael@0 | 138 | |
michael@0 | 139 | this._contextMenu = this._panelDoc.getElementById("sidebar-context"); |
michael@0 | 140 | this._contextMenu.addEventListener("popupshowing", |
michael@0 | 141 | this._updateSourcesLabel); |
michael@0 | 142 | |
michael@0 | 143 | this._sourcesItem = this._panelDoc.getElementById("context-origsources"); |
michael@0 | 144 | this._sourcesItem.addEventListener("command", |
michael@0 | 145 | this._toggleOrigSources); |
michael@0 | 146 | }, |
michael@0 | 147 | |
michael@0 | 148 | /** |
michael@0 | 149 | * Update text of context menu option to reflect whether we're showing |
michael@0 | 150 | * original sources (e.g. Sass files) or not. |
michael@0 | 151 | */ |
michael@0 | 152 | _updateSourcesLabel: function() { |
michael@0 | 153 | let string = "showOriginalSources"; |
michael@0 | 154 | if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { |
michael@0 | 155 | string = "showCSSSources"; |
michael@0 | 156 | } |
michael@0 | 157 | this._sourcesItem.setAttribute("label", _(string + ".label")); |
michael@0 | 158 | this._sourcesItem.setAttribute("accesskey", _(string + ".accesskey")); |
michael@0 | 159 | }, |
michael@0 | 160 | |
michael@0 | 161 | /** |
michael@0 | 162 | * Refresh editors to reflect the stylesheets in the document. |
michael@0 | 163 | * |
michael@0 | 164 | * @param {string} event |
michael@0 | 165 | * Event name |
michael@0 | 166 | * @param {StyleSheet} styleSheet |
michael@0 | 167 | * StyleSheet object for new sheet |
michael@0 | 168 | */ |
michael@0 | 169 | _onNewDocument: function() { |
michael@0 | 170 | this._debuggee.getStyleSheets().then((styleSheets) => { |
michael@0 | 171 | this._resetStyleSheetList(styleSheets); |
michael@0 | 172 | }) |
michael@0 | 173 | }, |
michael@0 | 174 | |
michael@0 | 175 | /** |
michael@0 | 176 | * Add editors for all the given stylesheets to the UI. |
michael@0 | 177 | * |
michael@0 | 178 | * @param {array} styleSheets |
michael@0 | 179 | * Array of StyleSheetFront |
michael@0 | 180 | */ |
michael@0 | 181 | _resetStyleSheetList: function(styleSheets) { |
michael@0 | 182 | this._clear(); |
michael@0 | 183 | |
michael@0 | 184 | for (let sheet of styleSheets) { |
michael@0 | 185 | this._addStyleSheet(sheet); |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | this._root.classList.remove("loading"); |
michael@0 | 189 | |
michael@0 | 190 | this.emit("stylesheets-reset"); |
michael@0 | 191 | }, |
michael@0 | 192 | |
michael@0 | 193 | /** |
michael@0 | 194 | * Remove all editors and add loading indicator. |
michael@0 | 195 | */ |
michael@0 | 196 | _clear: function() { |
michael@0 | 197 | // remember selected sheet and line number for next load |
michael@0 | 198 | if (this.selectedEditor && this.selectedEditor.sourceEditor) { |
michael@0 | 199 | let href = this.selectedEditor.styleSheet.href; |
michael@0 | 200 | let {line, ch} = this.selectedEditor.sourceEditor.getCursor(); |
michael@0 | 201 | |
michael@0 | 202 | this._styleSheetToSelect = { |
michael@0 | 203 | href: href, |
michael@0 | 204 | line: line, |
michael@0 | 205 | col: ch |
michael@0 | 206 | }; |
michael@0 | 207 | } |
michael@0 | 208 | |
michael@0 | 209 | // remember saved file locations |
michael@0 | 210 | for (let editor of this.editors) { |
michael@0 | 211 | if (editor.savedFile) { |
michael@0 | 212 | let identifier = this.getStyleSheetIdentifier(editor.styleSheet); |
michael@0 | 213 | this.savedLocations[identifier] = editor.savedFile; |
michael@0 | 214 | } |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | this._clearStyleSheetEditors(); |
michael@0 | 218 | this._view.removeAll(); |
michael@0 | 219 | |
michael@0 | 220 | this.selectedEditor = null; |
michael@0 | 221 | |
michael@0 | 222 | this._root.classList.add("loading"); |
michael@0 | 223 | }, |
michael@0 | 224 | |
michael@0 | 225 | /** |
michael@0 | 226 | * Add an editor for this stylesheet. Add editors for its original sources |
michael@0 | 227 | * instead (e.g. Sass sources), if applicable. |
michael@0 | 228 | * |
michael@0 | 229 | * @param {StyleSheetFront} styleSheet |
michael@0 | 230 | * Style sheet to add to style editor |
michael@0 | 231 | */ |
michael@0 | 232 | _addStyleSheet: function(styleSheet) { |
michael@0 | 233 | let editor = this._addStyleSheetEditor(styleSheet); |
michael@0 | 234 | |
michael@0 | 235 | if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) { |
michael@0 | 236 | return; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | styleSheet.getOriginalSources().then((sources) => { |
michael@0 | 240 | if (sources && sources.length) { |
michael@0 | 241 | this._removeStyleSheetEditor(editor); |
michael@0 | 242 | sources.forEach((source) => { |
michael@0 | 243 | // set so the first sheet will be selected, even if it's a source |
michael@0 | 244 | source.styleSheetIndex = styleSheet.styleSheetIndex; |
michael@0 | 245 | source.relatedStyleSheet = styleSheet; |
michael@0 | 246 | |
michael@0 | 247 | this._addStyleSheetEditor(source); |
michael@0 | 248 | }); |
michael@0 | 249 | } |
michael@0 | 250 | }); |
michael@0 | 251 | }, |
michael@0 | 252 | |
michael@0 | 253 | /** |
michael@0 | 254 | * Add a new editor to the UI for a source. |
michael@0 | 255 | * |
michael@0 | 256 | * @param {StyleSheet} styleSheet |
michael@0 | 257 | * Object representing stylesheet |
michael@0 | 258 | * @param {nsIfile} file |
michael@0 | 259 | * Optional file object that sheet was imported from |
michael@0 | 260 | * @param {Boolean} isNew |
michael@0 | 261 | * Optional if stylesheet is a new sheet created by user |
michael@0 | 262 | */ |
michael@0 | 263 | _addStyleSheetEditor: function(styleSheet, file, isNew) { |
michael@0 | 264 | // recall location of saved file for this sheet after page reload |
michael@0 | 265 | let identifier = this.getStyleSheetIdentifier(styleSheet); |
michael@0 | 266 | let savedFile = this.savedLocations[identifier]; |
michael@0 | 267 | if (savedFile && !file) { |
michael@0 | 268 | file = savedFile; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | let editor = |
michael@0 | 272 | new StyleSheetEditor(styleSheet, this._window, file, isNew, this._walker); |
michael@0 | 273 | |
michael@0 | 274 | editor.on("property-change", this._summaryChange.bind(this, editor)); |
michael@0 | 275 | editor.on("linked-css-file", this._summaryChange.bind(this, editor)); |
michael@0 | 276 | editor.on("linked-css-file-error", this._summaryChange.bind(this, editor)); |
michael@0 | 277 | editor.on("error", this._onError); |
michael@0 | 278 | |
michael@0 | 279 | this.editors.push(editor); |
michael@0 | 280 | |
michael@0 | 281 | editor.fetchSource(this._sourceLoaded.bind(this, editor)); |
michael@0 | 282 | return editor; |
michael@0 | 283 | }, |
michael@0 | 284 | |
michael@0 | 285 | /** |
michael@0 | 286 | * Import a style sheet from file and asynchronously create a |
michael@0 | 287 | * new stylesheet on the debuggee for it. |
michael@0 | 288 | * |
michael@0 | 289 | * @param {mixed} file |
michael@0 | 290 | * Optional nsIFile or filename string. |
michael@0 | 291 | * If not set a file picker will be shown. |
michael@0 | 292 | * @param {nsIWindow} parentWindow |
michael@0 | 293 | * Optional parent window for the file picker. |
michael@0 | 294 | */ |
michael@0 | 295 | _importFromFile: function(file, parentWindow) { |
michael@0 | 296 | let onFileSelected = function(file) { |
michael@0 | 297 | if (!file) { |
michael@0 | 298 | // nothing selected |
michael@0 | 299 | return; |
michael@0 | 300 | } |
michael@0 | 301 | NetUtil.asyncFetch(file, (stream, status) => { |
michael@0 | 302 | if (!Components.isSuccessCode(status)) { |
michael@0 | 303 | this.emit("error", LOAD_ERROR); |
michael@0 | 304 | return; |
michael@0 | 305 | } |
michael@0 | 306 | let source = NetUtil.readInputStreamToString(stream, stream.available()); |
michael@0 | 307 | stream.close(); |
michael@0 | 308 | |
michael@0 | 309 | this._debuggee.addStyleSheet(source).then((styleSheet) => { |
michael@0 | 310 | this._onStyleSheetCreated(styleSheet, file); |
michael@0 | 311 | }); |
michael@0 | 312 | }); |
michael@0 | 313 | |
michael@0 | 314 | }.bind(this); |
michael@0 | 315 | |
michael@0 | 316 | showFilePicker(file, false, parentWindow, onFileSelected); |
michael@0 | 317 | }, |
michael@0 | 318 | |
michael@0 | 319 | |
michael@0 | 320 | /** |
michael@0 | 321 | * When a new or imported stylesheet has been added to the document. |
michael@0 | 322 | * Add an editor for it. |
michael@0 | 323 | */ |
michael@0 | 324 | _onStyleSheetCreated: function(styleSheet, file) { |
michael@0 | 325 | this._addStyleSheetEditor(styleSheet, file, true); |
michael@0 | 326 | }, |
michael@0 | 327 | |
michael@0 | 328 | /** |
michael@0 | 329 | * Forward any error from a stylesheet. |
michael@0 | 330 | * |
michael@0 | 331 | * @param {string} event |
michael@0 | 332 | * Event name |
michael@0 | 333 | * @param {string} errorCode |
michael@0 | 334 | * Code represeting type of error |
michael@0 | 335 | * @param {string} message |
michael@0 | 336 | * The full error message |
michael@0 | 337 | */ |
michael@0 | 338 | _onError: function(event, errorCode, message) { |
michael@0 | 339 | this.emit("error", errorCode, message); |
michael@0 | 340 | }, |
michael@0 | 341 | |
michael@0 | 342 | /** |
michael@0 | 343 | * Toggle the original sources pref. |
michael@0 | 344 | */ |
michael@0 | 345 | _toggleOrigSources: function() { |
michael@0 | 346 | let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES); |
michael@0 | 347 | Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled); |
michael@0 | 348 | }, |
michael@0 | 349 | |
michael@0 | 350 | /** |
michael@0 | 351 | * Remove a particular stylesheet editor from the UI |
michael@0 | 352 | * |
michael@0 | 353 | * @param {StyleSheetEditor} editor |
michael@0 | 354 | * The editor to remove. |
michael@0 | 355 | */ |
michael@0 | 356 | _removeStyleSheetEditor: function(editor) { |
michael@0 | 357 | if (editor.summary) { |
michael@0 | 358 | this._view.removeItem(editor.summary); |
michael@0 | 359 | } |
michael@0 | 360 | else { |
michael@0 | 361 | let self = this; |
michael@0 | 362 | this.on("editor-added", function onAdd(event, added) { |
michael@0 | 363 | if (editor == added) { |
michael@0 | 364 | self.off("editor-added", onAdd); |
michael@0 | 365 | self._view.removeItem(editor.summary); |
michael@0 | 366 | } |
michael@0 | 367 | }) |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | editor.destroy(); |
michael@0 | 371 | this.editors.splice(this.editors.indexOf(editor), 1); |
michael@0 | 372 | }, |
michael@0 | 373 | |
michael@0 | 374 | /** |
michael@0 | 375 | * Clear all the editors from the UI. |
michael@0 | 376 | */ |
michael@0 | 377 | _clearStyleSheetEditors: function() { |
michael@0 | 378 | for (let editor of this.editors) { |
michael@0 | 379 | editor.destroy(); |
michael@0 | 380 | } |
michael@0 | 381 | this.editors = []; |
michael@0 | 382 | }, |
michael@0 | 383 | |
michael@0 | 384 | /** |
michael@0 | 385 | * Called when a StyleSheetEditor's source has been fetched. Create a |
michael@0 | 386 | * summary UI for the editor. |
michael@0 | 387 | * |
michael@0 | 388 | * @param {StyleSheetEditor} editor |
michael@0 | 389 | * Editor to create UI for. |
michael@0 | 390 | */ |
michael@0 | 391 | _sourceLoaded: function(editor) { |
michael@0 | 392 | // add new sidebar item and editor to the UI |
michael@0 | 393 | this._view.appendTemplatedItem(STYLE_EDITOR_TEMPLATE, { |
michael@0 | 394 | data: { |
michael@0 | 395 | editor: editor |
michael@0 | 396 | }, |
michael@0 | 397 | disableAnimations: this._alwaysDisableAnimations, |
michael@0 | 398 | ordinal: editor.styleSheet.styleSheetIndex, |
michael@0 | 399 | onCreate: function(summary, details, data) { |
michael@0 | 400 | let editor = data.editor; |
michael@0 | 401 | editor.summary = summary; |
michael@0 | 402 | |
michael@0 | 403 | wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) { |
michael@0 | 404 | event.stopPropagation(); |
michael@0 | 405 | event.target.blur(); |
michael@0 | 406 | |
michael@0 | 407 | editor.toggleDisabled(); |
michael@0 | 408 | }); |
michael@0 | 409 | |
michael@0 | 410 | wire(summary, ".stylesheet-name", { |
michael@0 | 411 | events: { |
michael@0 | 412 | "keypress": function onStylesheetNameActivate(aEvent) { |
michael@0 | 413 | if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { |
michael@0 | 414 | this._view.activeSummary = summary; |
michael@0 | 415 | } |
michael@0 | 416 | }.bind(this) |
michael@0 | 417 | } |
michael@0 | 418 | }); |
michael@0 | 419 | |
michael@0 | 420 | wire(summary, ".stylesheet-saveButton", function onSaveButton(event) { |
michael@0 | 421 | event.stopPropagation(); |
michael@0 | 422 | event.target.blur(); |
michael@0 | 423 | |
michael@0 | 424 | editor.saveToFile(editor.savedFile); |
michael@0 | 425 | }); |
michael@0 | 426 | |
michael@0 | 427 | this._updateSummaryForEditor(editor, summary); |
michael@0 | 428 | |
michael@0 | 429 | summary.addEventListener("focus", function onSummaryFocus(event) { |
michael@0 | 430 | if (event.target == summary) { |
michael@0 | 431 | // autofocus the stylesheet name |
michael@0 | 432 | summary.querySelector(".stylesheet-name").focus(); |
michael@0 | 433 | } |
michael@0 | 434 | }, false); |
michael@0 | 435 | |
michael@0 | 436 | Task.spawn(function* () { |
michael@0 | 437 | // autofocus if it's a new user-created stylesheet |
michael@0 | 438 | if (editor.isNew) { |
michael@0 | 439 | yield this._selectEditor(editor); |
michael@0 | 440 | } |
michael@0 | 441 | |
michael@0 | 442 | if (this._styleSheetToSelect |
michael@0 | 443 | && this._styleSheetToSelect.href == editor.styleSheet.href) { |
michael@0 | 444 | yield this.switchToSelectedSheet(); |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | // If this is the first stylesheet and there is no pending request to |
michael@0 | 448 | // select a particular style sheet, select this sheet. |
michael@0 | 449 | if (!this.selectedEditor && !this._styleSheetBoundToSelect |
michael@0 | 450 | && editor.styleSheet.styleSheetIndex == 0) { |
michael@0 | 451 | yield this._selectEditor(editor); |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | this.emit("editor-added", editor); |
michael@0 | 455 | }.bind(this)).then(null, Cu.reportError); |
michael@0 | 456 | }.bind(this), |
michael@0 | 457 | |
michael@0 | 458 | onShow: function(summary, details, data) { |
michael@0 | 459 | let editor = data.editor; |
michael@0 | 460 | this.selectedEditor = editor; |
michael@0 | 461 | |
michael@0 | 462 | Task.spawn(function* () { |
michael@0 | 463 | if (!editor.sourceEditor) { |
michael@0 | 464 | // only initialize source editor when we switch to this view |
michael@0 | 465 | let inputElement = details.querySelector(".stylesheet-editor-input"); |
michael@0 | 466 | yield editor.load(inputElement); |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | editor.onShow(); |
michael@0 | 470 | |
michael@0 | 471 | this.emit("editor-selected", editor); |
michael@0 | 472 | }.bind(this)).then(null, Cu.reportError); |
michael@0 | 473 | }.bind(this) |
michael@0 | 474 | }); |
michael@0 | 475 | }, |
michael@0 | 476 | |
michael@0 | 477 | /** |
michael@0 | 478 | * Switch to the editor that has been marked to be selected. |
michael@0 | 479 | * |
michael@0 | 480 | * @return {Promise} |
michael@0 | 481 | * Promise that will resolve when the editor is selected. |
michael@0 | 482 | */ |
michael@0 | 483 | switchToSelectedSheet: function() { |
michael@0 | 484 | let sheet = this._styleSheetToSelect; |
michael@0 | 485 | |
michael@0 | 486 | for (let editor of this.editors) { |
michael@0 | 487 | if (editor.styleSheet.href == sheet.href) { |
michael@0 | 488 | // The _styleSheetBoundToSelect will always hold the latest pending |
michael@0 | 489 | // requested style sheet (with line and column) which is not yet |
michael@0 | 490 | // selected by the source editor. Only after we select that particular |
michael@0 | 491 | // editor and go the required line and column, it will become null. |
michael@0 | 492 | this._styleSheetBoundToSelect = this._styleSheetToSelect; |
michael@0 | 493 | this._styleSheetToSelect = null; |
michael@0 | 494 | return this._selectEditor(editor, sheet.line, sheet.col); |
michael@0 | 495 | } |
michael@0 | 496 | } |
michael@0 | 497 | |
michael@0 | 498 | return promise.resolve(); |
michael@0 | 499 | }, |
michael@0 | 500 | |
michael@0 | 501 | /** |
michael@0 | 502 | * Select an editor in the UI. |
michael@0 | 503 | * |
michael@0 | 504 | * @param {StyleSheetEditor} editor |
michael@0 | 505 | * Editor to switch to. |
michael@0 | 506 | * @param {number} line |
michael@0 | 507 | * Line number to jump to |
michael@0 | 508 | * @param {number} col |
michael@0 | 509 | * Column number to jump to |
michael@0 | 510 | * @return {Promise} |
michael@0 | 511 | * Promise that will resolve when the editor is selected. |
michael@0 | 512 | */ |
michael@0 | 513 | _selectEditor: function(editor, line, col) { |
michael@0 | 514 | line = line || 0; |
michael@0 | 515 | col = col || 0; |
michael@0 | 516 | |
michael@0 | 517 | let editorPromise = editor.getSourceEditor().then(() => { |
michael@0 | 518 | editor.sourceEditor.setCursor({line: line, ch: col}); |
michael@0 | 519 | this._styleSheetBoundToSelect = null; |
michael@0 | 520 | }); |
michael@0 | 521 | |
michael@0 | 522 | let summaryPromise = this.getEditorSummary(editor).then((summary) => { |
michael@0 | 523 | this._view.activeSummary = summary; |
michael@0 | 524 | }); |
michael@0 | 525 | |
michael@0 | 526 | return promise.all([editorPromise, summaryPromise]); |
michael@0 | 527 | }, |
michael@0 | 528 | |
michael@0 | 529 | getEditorSummary: function(editor) { |
michael@0 | 530 | if (editor.summary) { |
michael@0 | 531 | return promise.resolve(editor.summary); |
michael@0 | 532 | } |
michael@0 | 533 | |
michael@0 | 534 | let deferred = promise.defer(); |
michael@0 | 535 | let self = this; |
michael@0 | 536 | |
michael@0 | 537 | this.on("editor-added", function onAdd(e, selected) { |
michael@0 | 538 | if (selected == editor) { |
michael@0 | 539 | self.off("editor-added", onAdd); |
michael@0 | 540 | deferred.resolve(editor.summary); |
michael@0 | 541 | } |
michael@0 | 542 | }); |
michael@0 | 543 | |
michael@0 | 544 | return deferred.promise; |
michael@0 | 545 | }, |
michael@0 | 546 | |
michael@0 | 547 | /** |
michael@0 | 548 | * Returns an identifier for the given style sheet. |
michael@0 | 549 | * |
michael@0 | 550 | * @param {StyleSheet} aStyleSheet |
michael@0 | 551 | * The style sheet to be identified. |
michael@0 | 552 | */ |
michael@0 | 553 | getStyleSheetIdentifier: function (aStyleSheet) { |
michael@0 | 554 | // Identify inline style sheets by their host page URI and index at the page. |
michael@0 | 555 | return aStyleSheet.href ? aStyleSheet.href : |
michael@0 | 556 | "inline-" + aStyleSheet.styleSheetIndex + "-at-" + aStyleSheet.nodeHref; |
michael@0 | 557 | }, |
michael@0 | 558 | |
michael@0 | 559 | /** |
michael@0 | 560 | * selects a stylesheet and optionally moves the cursor to a selected line |
michael@0 | 561 | * |
michael@0 | 562 | * @param {string} [href] |
michael@0 | 563 | * Href of stylesheet that should be selected. If a stylesheet is not passed |
michael@0 | 564 | * and the editor is not initialized we focus the first stylesheet. If |
michael@0 | 565 | * a stylesheet is not passed and the editor is initialized we ignore |
michael@0 | 566 | * the call. |
michael@0 | 567 | * @param {Number} [line] |
michael@0 | 568 | * Line to which the caret should be moved (zero-indexed). |
michael@0 | 569 | * @param {Number} [col] |
michael@0 | 570 | * Column to which the caret should be moved (zero-indexed). |
michael@0 | 571 | */ |
michael@0 | 572 | selectStyleSheet: function(href, line, col) { |
michael@0 | 573 | this._styleSheetToSelect = { |
michael@0 | 574 | href: href, |
michael@0 | 575 | line: line, |
michael@0 | 576 | col: col, |
michael@0 | 577 | }; |
michael@0 | 578 | |
michael@0 | 579 | /* Switch to the editor for this sheet, if it exists yet. |
michael@0 | 580 | Otherwise each editor will be checked when it's created. */ |
michael@0 | 581 | this.switchToSelectedSheet(); |
michael@0 | 582 | }, |
michael@0 | 583 | |
michael@0 | 584 | |
michael@0 | 585 | /** |
michael@0 | 586 | * Handler for an editor's 'property-changed' event. |
michael@0 | 587 | * Update the summary in the UI. |
michael@0 | 588 | * |
michael@0 | 589 | * @param {StyleSheetEditor} editor |
michael@0 | 590 | * Editor for which a property has changed |
michael@0 | 591 | */ |
michael@0 | 592 | _summaryChange: function(editor) { |
michael@0 | 593 | this._updateSummaryForEditor(editor); |
michael@0 | 594 | }, |
michael@0 | 595 | |
michael@0 | 596 | /** |
michael@0 | 597 | * Update split view summary of given StyleEditor instance. |
michael@0 | 598 | * |
michael@0 | 599 | * @param {StyleSheetEditor} editor |
michael@0 | 600 | * @param {DOMElement} summary |
michael@0 | 601 | * Optional item's summary element to update. If none, item corresponding |
michael@0 | 602 | * to passed editor is used. |
michael@0 | 603 | */ |
michael@0 | 604 | _updateSummaryForEditor: function(editor, summary) { |
michael@0 | 605 | summary = summary || editor.summary; |
michael@0 | 606 | if (!summary) { |
michael@0 | 607 | return; |
michael@0 | 608 | } |
michael@0 | 609 | |
michael@0 | 610 | let ruleCount = editor.styleSheet.ruleCount; |
michael@0 | 611 | if (editor.styleSheet.relatedStyleSheet && editor.linkedCSSFile) { |
michael@0 | 612 | ruleCount = editor.styleSheet.relatedStyleSheet.ruleCount; |
michael@0 | 613 | } |
michael@0 | 614 | if (ruleCount === undefined) { |
michael@0 | 615 | ruleCount = "-"; |
michael@0 | 616 | } |
michael@0 | 617 | |
michael@0 | 618 | var flags = []; |
michael@0 | 619 | if (editor.styleSheet.disabled) { |
michael@0 | 620 | flags.push("disabled"); |
michael@0 | 621 | } |
michael@0 | 622 | if (editor.unsaved) { |
michael@0 | 623 | flags.push("unsaved"); |
michael@0 | 624 | } |
michael@0 | 625 | if (editor.linkedCSSFileError) { |
michael@0 | 626 | flags.push("linked-file-error"); |
michael@0 | 627 | } |
michael@0 | 628 | this._view.setItemClassName(summary, flags.join(" ")); |
michael@0 | 629 | |
michael@0 | 630 | let label = summary.querySelector(".stylesheet-name > label"); |
michael@0 | 631 | label.setAttribute("value", editor.friendlyName); |
michael@0 | 632 | if (editor.styleSheet.href) { |
michael@0 | 633 | label.setAttribute("tooltiptext", editor.styleSheet.href); |
michael@0 | 634 | } |
michael@0 | 635 | |
michael@0 | 636 | let linkedCSSFile = ""; |
michael@0 | 637 | if (editor.linkedCSSFile) { |
michael@0 | 638 | linkedCSSFile = OS.Path.basename(editor.linkedCSSFile); |
michael@0 | 639 | } |
michael@0 | 640 | text(summary, ".stylesheet-linked-file", linkedCSSFile); |
michael@0 | 641 | text(summary, ".stylesheet-title", editor.styleSheet.title || ""); |
michael@0 | 642 | text(summary, ".stylesheet-rule-count", |
michael@0 | 643 | PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount)); |
michael@0 | 644 | }, |
michael@0 | 645 | |
michael@0 | 646 | destroy: function() { |
michael@0 | 647 | this._clearStyleSheetEditors(); |
michael@0 | 648 | |
michael@0 | 649 | this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument); |
michael@0 | 650 | this._prefObserver.destroy(); |
michael@0 | 651 | } |
michael@0 | 652 | } |