diff options
Diffstat (limited to 'devtools/server/actors/stylesheets.js')
-rw-r--r-- | devtools/server/actors/stylesheets.js | 120 |
1 files changed, 89 insertions, 31 deletions
diff --git a/devtools/server/actors/stylesheets.js b/devtools/server/actors/stylesheets.js index f20634e6cb..7fcbca8c48 100644 --- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -13,7 +13,6 @@ const events = require("sdk/event/core"); const protocol = require("devtools/shared/protocol"); const {LongStringActor} = require("devtools/server/actors/string"); const {fetch} = require("devtools/shared/DevToolsUtils"); -const {listenOnce} = require("devtools/shared/async-utils"); const {originalSourceSpec, mediaRuleSpec, styleSheetSpec, styleSheetsSpec} = require("devtools/shared/specs/stylesheets"); const {SourceMapConsumer} = require("source-map"); @@ -251,7 +250,7 @@ var StyleSheetActor = protocol.ActorClassWithSpec(styleSheetSpec, { }, destroy: function () { - if (this._transitionTimeout) { + if (this._transitionTimeout && this.window) { this.window.clearTimeout(this._transitionTimeout); removePseudoClassLock( this.document.documentElement, TRANSITION_PSEUDO_CLASS); @@ -801,6 +800,64 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { protocol.Actor.prototype.initialize.call(this, null); this.parentActor = tabActor; + + this._onNewStyleSheetActor = this._onNewStyleSheetActor.bind(this); + this._onSheetAdded = this._onSheetAdded.bind(this); + this._onWindowReady = this._onWindowReady.bind(this); + + events.on(this.parentActor, "stylesheet-added", this._onNewStyleSheetActor); + events.on(this.parentActor, "window-ready", this._onWindowReady); + + // We listen for StyleSheetApplicableStateChanged rather than + // StyleSheetAdded, because the latter will be sent before the + // rules are ready. Using the former (with a check to ensure that + // the sheet is enabled) ensures that the sheet is ready before we + // try to make an actor for it. + this.parentActor.chromeEventHandler + .addEventListener("StyleSheetApplicableStateChanged", this._onSheetAdded, true); + + // This is used when creating a new style sheet, so that we can + // pass the correct flag when emitting our stylesheet-added event. + // See addStyleSheet and _onNewStyleSheetActor for more details. + this._nextStyleSheetIsNew = false; + }, + + destroy: function () { + for (let win of this.parentActor.windows) { + // This flag only exists for devtools, so we are free to clear + // it when we're done. + win.document.styleSheetChangeEventsEnabled = false; + } + + events.off(this.parentActor, "stylesheet-added", this._onNewStyleSheetActor); + events.off(this.parentActor, "window-ready", this._onWindowReady); + + this.parentActor.chromeEventHandler.removeEventListener("StyleSheetAdded", + this._onSheetAdded, true); + + protocol.Actor.prototype.destroy.call(this); + }, + + /** + * Event handler that is called when a the tab actor emits window-ready. + * + * @param {Event} evt + * The triggering event. + */ + _onWindowReady: function (evt) { + this._addStyleSheets(evt.window); + }, + + /** + * Event handler that is called when a the tab actor emits stylesheet-added. + * + * @param {StyleSheetActor} actor + * The new style sheet actor. + */ + _onNewStyleSheetActor: function (actor) { + // Forward it to the client side. + events.emit(this, "stylesheet-added", actor, this._nextStyleSheetIsNew); + this._nextStyleSheetIsNew = false; }, /** @@ -808,23 +865,11 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { * all the style sheets in this document. */ getStyleSheets: Task.async(function* () { - // Iframe document can change during load (bug 1171919). Track their windows - // instead. - let windows = [this.window]; let actors = []; - for (let win of windows) { + for (let win of this.parentActor.windows) { let sheets = yield this._addStyleSheets(win); actors = actors.concat(sheets); - - // Recursively handle style sheets of the documents in iframes. - for (let iframe of win.document.querySelectorAll("iframe, browser, frame")) { - if (iframe.contentDocument && iframe.contentWindow) { - // Sometimes, iframes don't have any document, like the - // one that are over deeply nested (bug 285395) - windows.push(iframe.contentWindow); - } - } } return actors; }), @@ -832,15 +877,13 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { /** * Check if we should be showing this stylesheet. * - * @param {Document} doc - * Document for which we're checking * @param {DOMCSSStyleSheet} sheet * Stylesheet we're interested in * * @return boolean * Whether the stylesheet should be listed. */ - _shouldListSheet: function (doc, sheet) { + _shouldListSheet: function (sheet) { // Special case about:PreferenceStyleSheet, as it is generated on the // fly and the URI is not registered with the about: handler. // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37 @@ -852,6 +895,22 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { }, /** + * Event handler that is called when a new style sheet is added to + * a document. In particular, StyleSheetApplicableStateChanged is + * listened for, because StyleSheetAdded is sent too early, before + * the rules are ready. + * + * @param {Event} evt + * The triggering event. + */ + _onSheetAdded: function (evt) { + let sheet = evt.stylesheet; + if (this._shouldListSheet(sheet)) { + this.parentActor.createStyleSheetActor(sheet); + } + }, + + /** * Add all the stylesheets for the document in this window to the map and * create an actor for each one if not already created. * @@ -865,24 +924,16 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { { return Task.spawn(function* () { let doc = win.document; - // readyState can be uninitialized if an iframe has just been created but - // it has not started to load yet. - if (doc.readyState === "loading" || doc.readyState === "uninitialized") { - // Wait for the document to load first. - yield listenOnce(win, "DOMContentLoaded", true); - - // Make sure we have the actual document for this window. If the - // readyState was initially uninitialized, the initial dummy document - // was replaced with the actual document (bug 1171919). - doc = win.document; - } + // We have to set this flag in order to get the + // StyleSheetApplicableStateChanged events. See Document.webidl. + doc.styleSheetChangeEventsEnabled = true; let isChrome = Services.scriptSecurityManager.isSystemPrincipal(doc.nodePrincipal); let styleSheets = isChrome ? DOMUtils.getAllStyleSheets(doc) : doc.styleSheets; let actors = []; for (let i = 0; i < styleSheets.length; i++) { let sheet = styleSheets[i]; - if (!this._shouldListSheet(doc, sheet)) { + if (!this._shouldListSheet(sheet)) { continue; } @@ -917,7 +968,7 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) { // Associated styleSheet may be null if it has already been seen due // to duplicate @imports for the same URL. - if (!rule.styleSheet || !this._shouldListSheet(doc, rule.styleSheet)) { + if (!rule.styleSheet || !this._shouldListSheet(rule.styleSheet)) { continue; } let actor = this.parentActor.createStyleSheetActor(rule.styleSheet); @@ -948,6 +999,13 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { * Object with 'styelSheet' property for form on new actor. */ addStyleSheet: function (text) { + // This is a bit convoluted. The style sheet actor may be created + // by a notification from platform. In this case, we can't easily + // pass the "new" flag through to createStyleSheetActor, so we set + // a flag locally and check it before sending an event to the + // client. See |_onNewStyleSheetActor|. + this._nextStyleSheetIsNew = true; + let parent = this.document.documentElement; let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style"); style.setAttribute("type", "text/css"); |