toolkit/devtools/server/actors/styleeditor.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 let { components, Cc, Ci, Cu } = require("chrome");
michael@0 8 let Services = require("Services");
michael@0 9
michael@0 10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 11 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 12 Cu.import("resource://gre/modules/FileUtils.jsm");
michael@0 13 Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
michael@0 14
michael@0 15 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
michael@0 16 const events = require("sdk/event/core");
michael@0 17 const protocol = require("devtools/server/protocol");
michael@0 18 const {Arg, Option, method, RetVal, types} = protocol;
michael@0 19 const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
michael@0 20
michael@0 21 loader.lazyGetter(this, "CssLogic", () => require("devtools/styleinspector/css-logic").CssLogic);
michael@0 22
michael@0 23 let TRANSITION_CLASS = "moz-styleeditor-transitioning";
michael@0 24 let TRANSITION_DURATION_MS = 500;
michael@0 25 let TRANSITION_RULE = "\
michael@0 26 :root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\
michael@0 27 transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
michael@0 28 transition-delay: 0ms !important;\
michael@0 29 transition-timing-function: ease-out !important;\
michael@0 30 transition-property: all !important;\
michael@0 31 }";
michael@0 32
michael@0 33 let LOAD_ERROR = "error-load";
michael@0 34
michael@0 35 exports.register = function(handle) {
michael@0 36 handle.addTabActor(StyleEditorActor, "styleEditorActor");
michael@0 37 handle.addGlobalActor(StyleEditorActor, "styleEditorActor");
michael@0 38 };
michael@0 39
michael@0 40 exports.unregister = function(handle) {
michael@0 41 handle.removeTabActor(StyleEditorActor);
michael@0 42 handle.removeGlobalActor(StyleEditorActor);
michael@0 43 };
michael@0 44
michael@0 45 types.addActorType("old-stylesheet");
michael@0 46
michael@0 47 /**
michael@0 48 * Creates a StyleEditorActor. StyleEditorActor provides remote access to the
michael@0 49 * stylesheets of a document.
michael@0 50 */
michael@0 51 let StyleEditorActor = protocol.ActorClass({
michael@0 52 typeName: "styleeditor",
michael@0 53
michael@0 54 /**
michael@0 55 * The window we work with, taken from the parent actor.
michael@0 56 */
michael@0 57 get window() this.parentActor.window,
michael@0 58
michael@0 59 /**
michael@0 60 * The current content document of the window we work with.
michael@0 61 */
michael@0 62 get document() this.window.document,
michael@0 63
michael@0 64 events: {
michael@0 65 "document-load" : {
michael@0 66 type: "documentLoad",
michael@0 67 styleSheets: Arg(0, "array:old-stylesheet")
michael@0 68 }
michael@0 69 },
michael@0 70
michael@0 71 form: function()
michael@0 72 {
michael@0 73 return { actor: this.actorID };
michael@0 74 },
michael@0 75
michael@0 76 initialize: function (conn, tabActor) {
michael@0 77 protocol.Actor.prototype.initialize.call(this, null);
michael@0 78
michael@0 79 this.parentActor = tabActor;
michael@0 80
michael@0 81 // keep a map of sheets-to-actors so we don't create two actors for one sheet
michael@0 82 this._sheets = new Map();
michael@0 83 },
michael@0 84
michael@0 85 /**
michael@0 86 * Destroy the current StyleEditorActor instance.
michael@0 87 */
michael@0 88 destroy: function()
michael@0 89 {
michael@0 90 this._sheets.clear();
michael@0 91 },
michael@0 92
michael@0 93 /**
michael@0 94 * Called by client when target navigates to a new document.
michael@0 95 * Adds load listeners to document.
michael@0 96 */
michael@0 97 newDocument: method(function() {
michael@0 98 // delete previous document's actors
michael@0 99 this._clearStyleSheetActors();
michael@0 100
michael@0 101 // Note: listening for load won't be necessary once
michael@0 102 // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
michael@0 103 if (this.document.readyState == "complete") {
michael@0 104 this._onDocumentLoaded();
michael@0 105 }
michael@0 106 else {
michael@0 107 this.window.addEventListener("load", this._onDocumentLoaded, false);
michael@0 108 }
michael@0 109 return {};
michael@0 110 }),
michael@0 111
michael@0 112 /**
michael@0 113 * Event handler for document loaded event. Add actor for each stylesheet
michael@0 114 * and send an event notifying of the load
michael@0 115 */
michael@0 116 _onDocumentLoaded: function(event) {
michael@0 117 if (event) {
michael@0 118 this.window.removeEventListener("load", this._onDocumentLoaded, false);
michael@0 119 }
michael@0 120
michael@0 121 let documents = [this.document];
michael@0 122 var forms = [];
michael@0 123 for (let doc of documents) {
michael@0 124 let sheetForms = this._addStyleSheets(doc.styleSheets);
michael@0 125 forms = forms.concat(sheetForms);
michael@0 126 // Recursively handle style sheets of the documents in iframes.
michael@0 127 for (let iframe of doc.getElementsByTagName("iframe")) {
michael@0 128 documents.push(iframe.contentDocument);
michael@0 129 }
michael@0 130 }
michael@0 131
michael@0 132 events.emit(this, "document-load", forms);
michael@0 133 },
michael@0 134
michael@0 135 /**
michael@0 136 * Add all the stylesheets to the map and create an actor for each one
michael@0 137 * if not already created. Send event that there are new stylesheets.
michael@0 138 *
michael@0 139 * @param {[DOMStyleSheet]} styleSheets
michael@0 140 * Stylesheets to add
michael@0 141 * @return {[object]}
michael@0 142 * Array of actors for each StyleSheetActor created
michael@0 143 */
michael@0 144 _addStyleSheets: function(styleSheets)
michael@0 145 {
michael@0 146 let sheets = [];
michael@0 147 for (let i = 0; i < styleSheets.length; i++) {
michael@0 148 let styleSheet = styleSheets[i];
michael@0 149 sheets.push(styleSheet);
michael@0 150
michael@0 151 // Get all sheets, including imported ones
michael@0 152 let imports = this._getImported(styleSheet);
michael@0 153 sheets = sheets.concat(imports);
michael@0 154 }
michael@0 155 let actors = sheets.map(this._createStyleSheetActor.bind(this));
michael@0 156
michael@0 157 return actors;
michael@0 158 },
michael@0 159
michael@0 160 /**
michael@0 161 * Create a new actor for a style sheet, if it hasn't already been created.
michael@0 162 *
michael@0 163 * @param {DOMStyleSheet} styleSheet
michael@0 164 * The style sheet to create an actor for.
michael@0 165 * @return {StyleSheetActor}
michael@0 166 * The actor for this style sheet
michael@0 167 */
michael@0 168 _createStyleSheetActor: function(styleSheet)
michael@0 169 {
michael@0 170 if (this._sheets.has(styleSheet)) {
michael@0 171 return this._sheets.get(styleSheet);
michael@0 172 }
michael@0 173 let actor = new OldStyleSheetActor(styleSheet, this);
michael@0 174
michael@0 175 this.manage(actor);
michael@0 176 this._sheets.set(styleSheet, actor);
michael@0 177
michael@0 178 return actor;
michael@0 179 },
michael@0 180
michael@0 181 /**
michael@0 182 * Get all the stylesheets @imported from a stylesheet.
michael@0 183 *
michael@0 184 * @param {DOMStyleSheet} styleSheet
michael@0 185 * Style sheet to search
michael@0 186 * @return {array}
michael@0 187 * All the imported stylesheets
michael@0 188 */
michael@0 189 _getImported: function(styleSheet) {
michael@0 190 let imported = [];
michael@0 191
michael@0 192 for (let i = 0; i < styleSheet.cssRules.length; i++) {
michael@0 193 let rule = styleSheet.cssRules[i];
michael@0 194 if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
michael@0 195 // Associated styleSheet may be null if it has already been seen due to
michael@0 196 // duplicate @imports for the same URL.
michael@0 197 if (!rule.styleSheet) {
michael@0 198 continue;
michael@0 199 }
michael@0 200 imported.push(rule.styleSheet);
michael@0 201
michael@0 202 // recurse imports in this stylesheet as well
michael@0 203 imported = imported.concat(this._getImported(rule.styleSheet));
michael@0 204 }
michael@0 205 else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
michael@0 206 // @import rules must precede all others except @charset
michael@0 207 break;
michael@0 208 }
michael@0 209 }
michael@0 210 return imported;
michael@0 211 },
michael@0 212
michael@0 213 /**
michael@0 214 * Clear all the current stylesheet actors in map.
michael@0 215 */
michael@0 216 _clearStyleSheetActors: function() {
michael@0 217 for (let actor in this._sheets) {
michael@0 218 this.unmanage(this._sheets[actor]);
michael@0 219 }
michael@0 220 this._sheets.clear();
michael@0 221 },
michael@0 222
michael@0 223 /**
michael@0 224 * Create a new style sheet in the document with the given text.
michael@0 225 * Return an actor for it.
michael@0 226 *
michael@0 227 * @param {object} request
michael@0 228 * Debugging protocol request object, with 'text property'
michael@0 229 * @return {object}
michael@0 230 * Object with 'styelSheet' property for form on new actor.
michael@0 231 */
michael@0 232 newStyleSheet: method(function(text) {
michael@0 233 let parent = this.document.documentElement;
michael@0 234 let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
michael@0 235 style.setAttribute("type", "text/css");
michael@0 236
michael@0 237 if (text) {
michael@0 238 style.appendChild(this.document.createTextNode(text));
michael@0 239 }
michael@0 240 parent.appendChild(style);
michael@0 241
michael@0 242 let actor = this._createStyleSheetActor(style.sheet);
michael@0 243 return actor;
michael@0 244 }, {
michael@0 245 request: { text: Arg(0, "string") },
michael@0 246 response: { styleSheet: RetVal("old-stylesheet") }
michael@0 247 })
michael@0 248 });
michael@0 249
michael@0 250 /**
michael@0 251 * The corresponding Front object for the StyleEditorActor.
michael@0 252 */
michael@0 253 let StyleEditorFront = protocol.FrontClass(StyleEditorActor, {
michael@0 254 initialize: function(client, tabForm) {
michael@0 255 protocol.Front.prototype.initialize.call(this, client);
michael@0 256 this.actorID = tabForm.styleEditorActor;
michael@0 257
michael@0 258 client.addActorPool(this);
michael@0 259 this.manage(this);
michael@0 260 },
michael@0 261
michael@0 262 getStyleSheets: function() {
michael@0 263 let deferred = promise.defer();
michael@0 264
michael@0 265 events.once(this, "document-load", (styleSheets) => {
michael@0 266 deferred.resolve(styleSheets);
michael@0 267 });
michael@0 268 this.newDocument();
michael@0 269
michael@0 270 return deferred.promise;
michael@0 271 },
michael@0 272
michael@0 273 addStyleSheet: function(text) {
michael@0 274 return this.newStyleSheet(text);
michael@0 275 }
michael@0 276 });
michael@0 277
michael@0 278 /**
michael@0 279 * A StyleSheetActor represents a stylesheet on the server.
michael@0 280 */
michael@0 281 let OldStyleSheetActor = protocol.ActorClass({
michael@0 282 typeName: "old-stylesheet",
michael@0 283
michael@0 284 events: {
michael@0 285 "property-change" : {
michael@0 286 type: "propertyChange",
michael@0 287 property: Arg(0, "string"),
michael@0 288 value: Arg(1, "json")
michael@0 289 },
michael@0 290 "source-load" : {
michael@0 291 type: "sourceLoad",
michael@0 292 source: Arg(0, "string")
michael@0 293 },
michael@0 294 "style-applied" : {
michael@0 295 type: "styleApplied"
michael@0 296 }
michael@0 297 },
michael@0 298
michael@0 299 toString: function() {
michael@0 300 return "[OldStyleSheetActor " + this.actorID + "]";
michael@0 301 },
michael@0 302
michael@0 303 /**
michael@0 304 * Window of target
michael@0 305 */
michael@0 306 get window() this._window || this.parentActor.window,
michael@0 307
michael@0 308 /**
michael@0 309 * Document of target.
michael@0 310 */
michael@0 311 get document() this.window.document,
michael@0 312
michael@0 313 /**
michael@0 314 * URL of underlying stylesheet.
michael@0 315 */
michael@0 316 get href() this.rawSheet.href,
michael@0 317
michael@0 318 /**
michael@0 319 * Retrieve the index (order) of stylesheet in the document.
michael@0 320 *
michael@0 321 * @return number
michael@0 322 */
michael@0 323 get styleSheetIndex()
michael@0 324 {
michael@0 325 if (this._styleSheetIndex == -1) {
michael@0 326 for (let i = 0; i < this.document.styleSheets.length; i++) {
michael@0 327 if (this.document.styleSheets[i] == this.rawSheet) {
michael@0 328 this._styleSheetIndex = i;
michael@0 329 break;
michael@0 330 }
michael@0 331 }
michael@0 332 }
michael@0 333 return this._styleSheetIndex;
michael@0 334 },
michael@0 335
michael@0 336 initialize: function(aStyleSheet, aParentActor, aWindow) {
michael@0 337 protocol.Actor.prototype.initialize.call(this, null);
michael@0 338
michael@0 339 this.rawSheet = aStyleSheet;
michael@0 340 this.parentActor = aParentActor;
michael@0 341 this.conn = this.parentActor.conn;
michael@0 342
michael@0 343 this._window = aWindow;
michael@0 344
michael@0 345 // text and index are unknown until source load
michael@0 346 this.text = null;
michael@0 347 this._styleSheetIndex = -1;
michael@0 348
michael@0 349 this._transitionRefCount = 0;
michael@0 350
michael@0 351 // if this sheet has an @import, then it's rules are loaded async
michael@0 352 let ownerNode = this.rawSheet.ownerNode;
michael@0 353 if (ownerNode) {
michael@0 354 let onSheetLoaded = function(event) {
michael@0 355 ownerNode.removeEventListener("load", onSheetLoaded, false);
michael@0 356 this._notifyPropertyChanged("ruleCount");
michael@0 357 }.bind(this);
michael@0 358
michael@0 359 ownerNode.addEventListener("load", onSheetLoaded, false);
michael@0 360 }
michael@0 361 },
michael@0 362
michael@0 363 /**
michael@0 364 * Get the current state of the actor
michael@0 365 *
michael@0 366 * @return {object}
michael@0 367 * With properties of the underlying stylesheet, plus 'text',
michael@0 368 * 'styleSheetIndex' and 'parentActor' if it's @imported
michael@0 369 */
michael@0 370 form: function(detail) {
michael@0 371 if (detail === "actorid") {
michael@0 372 return this.actorID;
michael@0 373 }
michael@0 374
michael@0 375 let docHref;
michael@0 376 if (this.rawSheet.ownerNode) {
michael@0 377 if (this.rawSheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
michael@0 378 docHref = this.rawSheet.ownerNode.location.href;
michael@0 379 }
michael@0 380 if (this.rawSheet.ownerNode.ownerDocument) {
michael@0 381 docHref = this.rawSheet.ownerNode.ownerDocument.location.href;
michael@0 382 }
michael@0 383 }
michael@0 384
michael@0 385 let form = {
michael@0 386 actor: this.actorID, // actorID is set when this actor is added to a pool
michael@0 387 href: this.href,
michael@0 388 nodeHref: docHref,
michael@0 389 disabled: this.rawSheet.disabled,
michael@0 390 title: this.rawSheet.title,
michael@0 391 system: !CssLogic.isContentStylesheet(this.rawSheet),
michael@0 392 styleSheetIndex: this.styleSheetIndex
michael@0 393 }
michael@0 394
michael@0 395 try {
michael@0 396 form.ruleCount = this.rawSheet.cssRules.length;
michael@0 397 }
michael@0 398 catch(e) {
michael@0 399 // stylesheet had an @import rule that wasn't loaded yet
michael@0 400 }
michael@0 401 return form;
michael@0 402 },
michael@0 403
michael@0 404 /**
michael@0 405 * Toggle the disabled property of the style sheet
michael@0 406 *
michael@0 407 * @return {object}
michael@0 408 * 'disabled' - the disabled state after toggling.
michael@0 409 */
michael@0 410 toggleDisabled: method(function() {
michael@0 411 this.rawSheet.disabled = !this.rawSheet.disabled;
michael@0 412 this._notifyPropertyChanged("disabled");
michael@0 413
michael@0 414 return this.rawSheet.disabled;
michael@0 415 }, {
michael@0 416 response: { disabled: RetVal("boolean")}
michael@0 417 }),
michael@0 418
michael@0 419 /**
michael@0 420 * Send an event notifying that a property of the stylesheet
michael@0 421 * has changed.
michael@0 422 *
michael@0 423 * @param {string} property
michael@0 424 * Name of the changed property
michael@0 425 */
michael@0 426 _notifyPropertyChanged: function(property) {
michael@0 427 events.emit(this, "property-change", property, this.form()[property]);
michael@0 428 },
michael@0 429
michael@0 430 /**
michael@0 431 * Fetch the source of the style sheet from its URL. Send a "sourceLoad"
michael@0 432 * event when it's been fetched.
michael@0 433 */
michael@0 434 fetchSource: method(function() {
michael@0 435 this._getText().then((content) => {
michael@0 436 events.emit(this, "source-load", this.text);
michael@0 437 });
michael@0 438 }),
michael@0 439
michael@0 440 /**
michael@0 441 * Fetch the text for this stylesheet from the cache or network. Return
michael@0 442 * cached text if it's already been fetched.
michael@0 443 *
michael@0 444 * @return {Promise}
michael@0 445 * Promise that resolves with a string text of the stylesheet.
michael@0 446 */
michael@0 447 _getText: function() {
michael@0 448 if (this.text) {
michael@0 449 return promise.resolve(this.text);
michael@0 450 }
michael@0 451
michael@0 452 if (!this.href) {
michael@0 453 // this is an inline <style> sheet
michael@0 454 let content = this.rawSheet.ownerNode.textContent;
michael@0 455 this.text = content;
michael@0 456 return promise.resolve(content);
michael@0 457 }
michael@0 458
michael@0 459 let options = {
michael@0 460 window: this.window,
michael@0 461 charset: this._getCSSCharset()
michael@0 462 };
michael@0 463
michael@0 464 return fetch(this.href, options).then(({ content }) => {
michael@0 465 this.text = content;
michael@0 466 return content;
michael@0 467 });
michael@0 468 },
michael@0 469
michael@0 470 /**
michael@0 471 * Get the charset of the stylesheet according to the character set rules
michael@0 472 * defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.
michael@0 473 *
michael@0 474 * @param string channelCharset
michael@0 475 * Charset of the source string if set by the HTTP channel.
michael@0 476 */
michael@0 477 _getCSSCharset: function(channelCharset)
michael@0 478 {
michael@0 479 // StyleSheet's charset can be specified from multiple sources
michael@0 480 if (channelCharset && channelCharset.length > 0) {
michael@0 481 // step 1 of syndata.html: charset given in HTTP header.
michael@0 482 return channelCharset;
michael@0 483 }
michael@0 484
michael@0 485 let sheet = this.rawSheet;
michael@0 486 if (sheet) {
michael@0 487 // Do we have a @charset rule in the stylesheet?
michael@0 488 // step 2 of syndata.html (without the BOM check).
michael@0 489 if (sheet.cssRules) {
michael@0 490 let rules = sheet.cssRules;
michael@0 491 if (rules.length
michael@0 492 && rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
michael@0 493 return rules.item(0).encoding;
michael@0 494 }
michael@0 495 }
michael@0 496
michael@0 497 // step 3: charset attribute of <link> or <style> element, if it exists
michael@0 498 if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
michael@0 499 let linkCharset = sheet.ownerNode.getAttribute("charset");
michael@0 500 if (linkCharset != null) {
michael@0 501 return linkCharset;
michael@0 502 }
michael@0 503 }
michael@0 504
michael@0 505 // step 4 (1 of 2): charset of referring stylesheet.
michael@0 506 let parentSheet = sheet.parentStyleSheet;
michael@0 507 if (parentSheet && parentSheet.cssRules &&
michael@0 508 parentSheet.cssRules[0].type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
michael@0 509 return parentSheet.cssRules[0].encoding;
michael@0 510 }
michael@0 511
michael@0 512 // step 4 (2 of 2): charset of referring document.
michael@0 513 if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
michael@0 514 return sheet.ownerNode.ownerDocument.characterSet;
michael@0 515 }
michael@0 516 }
michael@0 517
michael@0 518 // step 5: default to utf-8.
michael@0 519 return "UTF-8";
michael@0 520 },
michael@0 521
michael@0 522 /**
michael@0 523 * Update the style sheet in place with new text.
michael@0 524 *
michael@0 525 * @param {object} request
michael@0 526 * 'text' - new text
michael@0 527 * 'transition' - whether to do CSS transition for change.
michael@0 528 */
michael@0 529 update: method(function(text, transition) {
michael@0 530 DOMUtils.parseStyleSheet(this.rawSheet, text);
michael@0 531
michael@0 532 this.text = text;
michael@0 533
michael@0 534 this._notifyPropertyChanged("ruleCount");
michael@0 535
michael@0 536 if (transition) {
michael@0 537 this._insertTransistionRule();
michael@0 538 }
michael@0 539 else {
michael@0 540 this._notifyStyleApplied();
michael@0 541 }
michael@0 542 }, {
michael@0 543 request: {
michael@0 544 text: Arg(0, "string"),
michael@0 545 transition: Arg(1, "boolean")
michael@0 546 }
michael@0 547 }),
michael@0 548
michael@0 549 /**
michael@0 550 * Insert a catch-all transition rule into the document. Set a timeout
michael@0 551 * to remove the rule after a certain time.
michael@0 552 */
michael@0 553 _insertTransistionRule: function() {
michael@0 554 // Insert the global transition rule
michael@0 555 // Use a ref count to make sure we do not add it multiple times.. and remove
michael@0 556 // it only when all pending StyleEditor-generated transitions ended.
michael@0 557 if (this._transitionRefCount == 0) {
michael@0 558 this.rawSheet.insertRule(TRANSITION_RULE, this.rawSheet.cssRules.length);
michael@0 559 this.document.documentElement.classList.add(TRANSITION_CLASS);
michael@0 560 }
michael@0 561
michael@0 562 this._transitionRefCount++;
michael@0 563
michael@0 564 // Set up clean up and commit after transition duration (+10% buffer)
michael@0 565 // @see _onTransitionEnd
michael@0 566 this.window.setTimeout(this._onTransitionEnd.bind(this),
michael@0 567 Math.floor(TRANSITION_DURATION_MS * 1.1));
michael@0 568 },
michael@0 569
michael@0 570 /**
michael@0 571 * This cleans up class and rule added for transition effect and then
michael@0 572 * notifies that the style has been applied.
michael@0 573 */
michael@0 574 _onTransitionEnd: function()
michael@0 575 {
michael@0 576 if (--this._transitionRefCount == 0) {
michael@0 577 this.document.documentElement.classList.remove(TRANSITION_CLASS);
michael@0 578 this.rawSheet.deleteRule(this.rawSheet.cssRules.length - 1);
michael@0 579 }
michael@0 580
michael@0 581 events.emit(this, "style-applied");
michael@0 582 }
michael@0 583 })
michael@0 584
michael@0 585 /**
michael@0 586 * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
michael@0 587 */
michael@0 588 var OldStyleSheetFront = protocol.FrontClass(OldStyleSheetActor, {
michael@0 589 initialize: function(conn, form, ctx, detail) {
michael@0 590 protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
michael@0 591
michael@0 592 this._onPropertyChange = this._onPropertyChange.bind(this);
michael@0 593 events.on(this, "property-change", this._onPropertyChange);
michael@0 594 },
michael@0 595
michael@0 596 destroy: function() {
michael@0 597 events.off(this, "property-change", this._onPropertyChange);
michael@0 598
michael@0 599 protocol.Front.prototype.destroy.call(this);
michael@0 600 },
michael@0 601
michael@0 602 _onPropertyChange: function(property, value) {
michael@0 603 this._form[property] = value;
michael@0 604 },
michael@0 605
michael@0 606 form: function(form, detail) {
michael@0 607 if (detail === "actorid") {
michael@0 608 this.actorID = form;
michael@0 609 return;
michael@0 610 }
michael@0 611 this.actorID = form.actor;
michael@0 612 this._form = form;
michael@0 613 },
michael@0 614
michael@0 615 getText: function() {
michael@0 616 let deferred = promise.defer();
michael@0 617
michael@0 618 events.once(this, "source-load", (source) => {
michael@0 619 let longStr = new ShortLongString(source);
michael@0 620 deferred.resolve(longStr);
michael@0 621 });
michael@0 622 this.fetchSource();
michael@0 623
michael@0 624 return deferred.promise;
michael@0 625 },
michael@0 626
michael@0 627 getOriginalSources: function() {
michael@0 628 return promise.resolve([]);
michael@0 629 },
michael@0 630
michael@0 631 get href() this._form.href,
michael@0 632 get nodeHref() this._form.nodeHref,
michael@0 633 get disabled() !!this._form.disabled,
michael@0 634 get title() this._form.title,
michael@0 635 get isSystem() this._form.system,
michael@0 636 get styleSheetIndex() this._form.styleSheetIndex,
michael@0 637 get ruleCount() this._form.ruleCount
michael@0 638 });
michael@0 639
michael@0 640 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
michael@0 641 return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
michael@0 642 });
michael@0 643
michael@0 644 exports.StyleEditorActor = StyleEditorActor;
michael@0 645 exports.StyleEditorFront = StyleEditorFront;
michael@0 646
michael@0 647 exports.OldStyleSheetActor = OldStyleSheetActor;
michael@0 648 exports.OldStyleSheetFront = OldStyleSheetFront;
michael@0 649
michael@0 650
michael@0 651 /**
michael@0 652 * Performs a request to load the desired URL and returns a promise.
michael@0 653 *
michael@0 654 * @param aURL String
michael@0 655 * The URL we will request.
michael@0 656 * @returns Promise
michael@0 657 * A promise of the document at that URL, as a string.
michael@0 658 */
michael@0 659 function fetch(aURL, aOptions={ loadFromCache: true, window: null,
michael@0 660 charset: null}) {
michael@0 661 let deferred = promise.defer();
michael@0 662 let scheme;
michael@0 663 let url = aURL.split(" -> ").pop();
michael@0 664 let charset;
michael@0 665 let contentType;
michael@0 666
michael@0 667 try {
michael@0 668 scheme = Services.io.extractScheme(url);
michael@0 669 } catch (e) {
michael@0 670 // In the xpcshell tests, the script url is the absolute path of the test
michael@0 671 // file, which will make a malformed URI error be thrown. Add the file
michael@0 672 // scheme prefix ourselves.
michael@0 673 url = "file://" + url;
michael@0 674 scheme = Services.io.extractScheme(url);
michael@0 675 }
michael@0 676
michael@0 677 switch (scheme) {
michael@0 678 case "file":
michael@0 679 case "chrome":
michael@0 680 case "resource":
michael@0 681 try {
michael@0 682 NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
michael@0 683 if (!components.isSuccessCode(aStatus)) {
michael@0 684 deferred.reject(new Error("Request failed with status code = "
michael@0 685 + aStatus
michael@0 686 + " after NetUtil.asyncFetch for url = "
michael@0 687 + url));
michael@0 688 return;
michael@0 689 }
michael@0 690
michael@0 691 let source = NetUtil.readInputStreamToString(aStream, aStream.available());
michael@0 692 contentType = aRequest.contentType;
michael@0 693 deferred.resolve(source);
michael@0 694 aStream.close();
michael@0 695 });
michael@0 696 } catch (ex) {
michael@0 697 deferred.reject(ex);
michael@0 698 }
michael@0 699 break;
michael@0 700
michael@0 701 default:
michael@0 702 let channel;
michael@0 703 try {
michael@0 704 channel = Services.io.newChannel(url, null, null);
michael@0 705 } catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
michael@0 706 // On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
michael@0 707 // newChannel won't be able to handle it.
michael@0 708 url = "file:///" + url;
michael@0 709 channel = Services.io.newChannel(url, null, null);
michael@0 710 }
michael@0 711 let chunks = [];
michael@0 712 let streamListener = {
michael@0 713 onStartRequest: function(aRequest, aContext, aStatusCode) {
michael@0 714 if (!components.isSuccessCode(aStatusCode)) {
michael@0 715 deferred.reject(new Error("Request failed with status code = "
michael@0 716 + aStatusCode
michael@0 717 + " in onStartRequest handler for url = "
michael@0 718 + url));
michael@0 719 }
michael@0 720 },
michael@0 721 onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
michael@0 722 chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
michael@0 723 },
michael@0 724 onStopRequest: function(aRequest, aContext, aStatusCode) {
michael@0 725 if (!components.isSuccessCode(aStatusCode)) {
michael@0 726 deferred.reject(new Error("Request failed with status code = "
michael@0 727 + aStatusCode
michael@0 728 + " in onStopRequest handler for url = "
michael@0 729 + url));
michael@0 730 return;
michael@0 731 }
michael@0 732
michael@0 733 charset = channel.contentCharset || charset;
michael@0 734 contentType = channel.contentType;
michael@0 735 deferred.resolve(chunks.join(""));
michael@0 736 }
michael@0 737 };
michael@0 738
michael@0 739 if (aOptions.window) {
michael@0 740 // respect private browsing
michael@0 741 channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 742 .getInterface(Ci.nsIWebNavigation)
michael@0 743 .QueryInterface(Ci.nsIDocumentLoader)
michael@0 744 .loadGroup;
michael@0 745 }
michael@0 746 channel.loadFlags = aOptions.loadFromCache
michael@0 747 ? channel.LOAD_FROM_CACHE
michael@0 748 : channel.LOAD_BYPASS_CACHE;
michael@0 749 channel.asyncOpen(streamListener, null);
michael@0 750 break;
michael@0 751 }
michael@0 752
michael@0 753 return deferred.promise.then(source => {
michael@0 754 return {
michael@0 755 content: convertToUnicode(source, charset),
michael@0 756 contentType: contentType
michael@0 757 };
michael@0 758 });
michael@0 759 }
michael@0 760
michael@0 761 /**
michael@0 762 * Convert a given string, encoded in a given character set, to unicode.
michael@0 763 *
michael@0 764 * @param string aString
michael@0 765 * A string.
michael@0 766 * @param string aCharset
michael@0 767 * A character set.
michael@0 768 */
michael@0 769 function convertToUnicode(aString, aCharset=null) {
michael@0 770 // Decoding primitives.
michael@0 771 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 772 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 773 try {
michael@0 774 converter.charset = aCharset || "UTF-8";
michael@0 775 return converter.ConvertToUnicode(aString);
michael@0 776 } catch(e) {
michael@0 777 return aString;
michael@0 778 }
michael@0 779 }
michael@0 780
michael@0 781 /**
michael@0 782 * Normalize multiple relative paths towards the base paths on the right.
michael@0 783 */
michael@0 784 function normalize(...aURLs) {
michael@0 785 let base = Services.io.newURI(aURLs.pop(), null, null);
michael@0 786 let url;
michael@0 787 while ((url = aURLs.pop())) {
michael@0 788 base = Services.io.newURI(url, null, base);
michael@0 789 }
michael@0 790 return base.spec;
michael@0 791 }
michael@0 792
michael@0 793 function dirname(aPath) {
michael@0 794 return Services.io.newURI(
michael@0 795 ".", null, Services.io.newURI(aPath, null, null)).spec;
michael@0 796 }

mercurial