Bug 1452143: Reparse doc sheets after enabling error reporting on a docshell. r?jryans draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 12 Apr 2018 23:58:12 +0200
changeset 783199 3af529f5fb4efb1f86d577df04ccff1b88c11fcc
parent 783198 86f5325f547fcba5ca180871863d9253dcff6c62
child 783200 918ad41d9e7b746936a67e46c6fce0360a033466
push id106640
push userbmo:emilio@crisal.io
push dateMon, 16 Apr 2018 19:43:11 +0000
reviewersjryans
bugs1452143
milestone61.0a1
Bug 1452143: Reparse doc sheets after enabling error reporting on a docshell. r?jryans While at it, remove useless charset rule lookups, since charset rules aren't part of the OM, and have no effect at all anymore. MozReview-Commit-ID: EefGrOZvmm7
devtools/server/actors/stylesheets.js
devtools/server/actors/tab.js
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -120,16 +120,147 @@ var MediaRuleActor = protocol.ActorClass
     return form;
   },
 
   _matchesChange: function() {
     this.emit("matches-change", this.matches);
   }
 });
 
+function getSheetText(sheet, consoleActor) {
+  let cssText = modifiedStyleSheets.get(sheet);
+  if (cssText !== undefined) {
+    return Promise.resolve(cssText);
+  }
+
+  if (!sheet.href) {
+    // this is an inline <style> sheet
+    let content = sheet.ownerNode.textContent;
+    return Promise.resolve(content);
+  }
+
+  return fetchStylesheet(sheet, consoleActor).then(({ content }) => content);
+}
+
+exports.getSheetText = getSheetText;
+
+/**
+ * Try to fetch the stylesheet text from the network monitor.  If it was enabled during
+ * the load, it should have a copy of the text saved.
+ *
+ * @param string href
+ *        The URL of the sheet to fetch.
+ */
+function fetchStylesheetFromNetworkMonitor(href, consoleActor) {
+  if (!consoleActor) {
+    return null;
+  }
+  let request = consoleActor.getNetworkEventActorForURL(href);
+  if (!request) {
+    return null;
+  }
+  let content = request._response.content;
+  if (request._discardResponseBody || request._truncated || !content) {
+    return null;
+  }
+  if (content.text.type != "longString") {
+    // For short strings, the text is available directly.
+    return {
+      content: content.text,
+      contentType: content.mimeType,
+    };
+  }
+  // For long strings, look up the actor that holds the full text.
+  let longStringActor = consoleActor.conn._getOrCreateActor(content.text.actor);
+  if (!longStringActor) {
+    return null;
+  }
+  return {
+    content: longStringActor.rawValue(),
+    contentType: content.mimeType,
+  };
+}
+
+/**
+ * Get the charset of the stylesheet.
+ */
+function getCSSCharset(sheet) {
+  if (sheet) {
+    // charset attribute of <link> or <style> element, if it exists
+    if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
+      let linkCharset = sheet.ownerNode.getAttribute("charset");
+      if (linkCharset != null) {
+        return linkCharset;
+      }
+    }
+
+    // charset of referring document.
+    if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
+      return sheet.ownerNode.ownerDocument.characterSet;
+    }
+  }
+
+  return "UTF-8";
+}
+
+/**
+ * Fetch a stylesheet at the provided URL. Returns a promise that will resolve the
+ * result of the fetch command.
+ *
+ * @return {Promise} a promise that resolves with an object with the following members
+ *         on success:
+ *           - content: the document at that URL, as a string,
+ *           - contentType: the content type of the document
+ *         If an error occurs, the promise is rejected with that error.
+ */
+async function fetchStylesheet(sheet, consoleActor) {
+  let href = sheet.href;
+
+  let result = fetchStylesheetFromNetworkMonitor(href, consoleActor);
+  if (result) {
+    return result;
+  }
+
+  let options = {
+    loadFromCache: true,
+    policy: Ci.nsIContentPolicy.TYPE_INTERNAL_STYLESHEET,
+    charset: getCSSCharset(sheet)
+  };
+
+  // Bug 1282660 - We use the system principal to load the default internal
+  // stylesheets instead of the content principal since such stylesheets
+  // require system principal to load. At meanwhile, we strip the loadGroup
+  // for preventing the assertion of the userContextId mismatching.
+
+  // chrome|file|resource|moz-extension protocols rely on the system principal.
+  let excludedProtocolsRe = /^(chrome|file|resource|moz-extension):\/\//;
+  if (!excludedProtocolsRe.test(href)) {
+    // Stylesheets using other protocols should use the content principal.
+    if (sheet.ownerNode) {
+      // eslint-disable-next-line mozilla/use-ownerGlobal
+      options.window = sheet.ownerNode.ownerDocument.defaultView;
+      options.principal = sheet.ownerNode.ownerDocument.nodePrincipal;
+    }
+  }
+
+  try {
+    result = await fetch(href, options);
+  } catch (e) {
+    // The list of excluded protocols can be missing some protocols, try to use the
+    // system principal if the first fetch failed.
+    console.error(`stylesheets actor: fetch failed for ${href},` +
+      ` using system principal instead.`);
+    options.window = undefined;
+    options.principal = undefined;
+    result = await fetch(href, options);
+  }
+
+  return result;
+}
+
 /**
  * A StyleSheetActor represents a stylesheet on the server.
  */
 var StyleSheetActor = protocol.ActorClassWithSpec(styleSheetSpec, {
   toString: function() {
     return "[StyleSheetActor " + this.actorID + "]";
   },
 
@@ -367,138 +498,36 @@ var StyleSheetActor = protocol.ActorClas
    * @return {Promise}
    *         Promise that resolves with a string text of the stylesheet.
    */
   _getText: function() {
     if (typeof this.text === "string") {
       return Promise.resolve(this.text);
     }
 
-    let cssText = modifiedStyleSheets.get(this.rawSheet);
-    if (cssText !== undefined) {
-      this.text = cssText;
-      return Promise.resolve(cssText);
-    }
-
-    if (!this.href) {
-      // this is an inline <style> sheet
-      let content = this.ownerNode.textContent;
-      this.text = content;
-      return Promise.resolve(content);
-    }
-
-    return this.fetchStylesheet(this.href).then(({ content }) => {
-      this.text = content;
-      return content;
+    return getSheetText(this.rawSheet, this._consoleActor).then(text => {
+      this.text = text;
+      return text;
     });
   },
 
   /**
-   * Fetch a stylesheet at the provided URL. Returns a promise that will resolve the
-   * result of the fetch command.
+   * Try to locate the console actor if it exists via our parent actor (the tab).
    *
-   * @param  {String} href
-   *         The href of the stylesheet to retrieve.
-   * @return {Promise} a promise that resolves with an object with the following members
-   *         on success:
-   *           - content: the document at that URL, as a string,
-   *           - contentType: the content type of the document
-   *         If an error occurs, the promise is rejected with that error.
-   */
-  async fetchStylesheet(href) {
-    // Check if network monitor observed this load, and if so, use that.
-    let result = this.fetchStylesheetFromNetworkMonitor(href);
-    if (result) {
-      return result;
-    }
-
-    let options = {
-      loadFromCache: true,
-      policy: Ci.nsIContentPolicy.TYPE_INTERNAL_STYLESHEET,
-      charset: this._getCSSCharset()
-    };
-
-    // Bug 1282660 - We use the system principal to load the default internal
-    // stylesheets instead of the content principal since such stylesheets
-    // require system principal to load. At meanwhile, we strip the loadGroup
-    // for preventing the assertion of the userContextId mismatching.
-
-    // chrome|file|resource|moz-extension protocols rely on the system principal.
-    let excludedProtocolsRe = /^(chrome|file|resource|moz-extension):\/\//;
-    if (!excludedProtocolsRe.test(this.href)) {
-      // Stylesheets using other protocols should use the content principal.
-      options.window = this.ownerWindow;
-      options.principal = this.ownerDocument.nodePrincipal;
-    }
-
-    try {
-      result = await fetch(this.href, options);
-    } catch (e) {
-      // The list of excluded protocols can be missing some protocols, try to use the
-      // system principal if the first fetch failed.
-      console.error(`stylesheets actor: fetch failed for ${this.href},` +
-        ` using system principal instead.`);
-      options.window = undefined;
-      options.principal = undefined;
-      result = await fetch(this.href, options);
-    }
-
-    return result;
-  },
-
-  /**
-   * Try to locate the console actor if it exists via our parent actor (the tab).
+   * Keep this in sync with the TabActor version.
    */
   get _consoleActor() {
     if (this.parentActor.exited) {
       return null;
     }
     let form = this.parentActor.form();
     return this.conn._getOrCreateActor(form.consoleActor);
   },
 
   /**
-   * Try to fetch the stylesheet text from the network monitor.  If it was enabled during
-   * the load, it should have a copy of the text saved.
-   *
-   * @param string href
-   *        The URL of the sheet to fetch.
-   */
-  fetchStylesheetFromNetworkMonitor(href) {
-    let consoleActor = this._consoleActor;
-    if (!consoleActor) {
-      return null;
-    }
-    let request = consoleActor.getNetworkEventActorForURL(href);
-    if (!request) {
-      return null;
-    }
-    let content = request._response.content;
-    if (request._discardResponseBody || request._truncated || !content) {
-      return null;
-    }
-    if (content.text.type != "longString") {
-      // For short strings, the text is available directly.
-      return {
-        content: content.text,
-        contentType: content.mimeType,
-      };
-    }
-    // For long strings, look up the actor that holds the full text.
-    let longStringActor = this.conn._getOrCreateActor(content.text.actor);
-    if (!longStringActor) {
-      return null;
-    }
-    return {
-      content: longStringActor.rawValue(),
-      contentType: content.mimeType,
-    };
-  },
-
-  /**
    * Protocol method to get the media rules for the stylesheet.
    */
   getMediaRules: function() {
     return this._getMediaRules();
   },
 
   /**
    * Get all the @media rules in this stylesheet.
@@ -519,59 +548,16 @@ var StyleSheetActor = protocol.ActorClas
 
         mediaRules.push(actor);
       }
       return mediaRules;
     });
   },
 
   /**
-   * Get the charset of the stylesheet according to the character set rules
-   * defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.
-   * Note that some of the algorithm is implemented in DevToolsUtils.fetch.
-   */
-  _getCSSCharset: function() {
-    let sheet = this.rawSheet;
-    if (sheet) {
-      // Do we have a @charset rule in the stylesheet?
-      // step 2 of syndata.html (without the BOM check).
-      if (sheet.cssRules) {
-        let rules = sheet.cssRules;
-        if (rules.length
-            && rules.item(0).type == CSSRule.CHARSET_RULE) {
-          return rules.item(0).encoding;
-        }
-      }
-
-      // step 3: charset attribute of <link> or <style> element, if it exists
-      if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
-        let linkCharset = sheet.ownerNode.getAttribute("charset");
-        if (linkCharset != null) {
-          return linkCharset;
-        }
-      }
-
-      // step 4 (1 of 2): charset of referring stylesheet.
-      let parentSheet = sheet.parentStyleSheet;
-      if (parentSheet && parentSheet.cssRules &&
-          parentSheet.cssRules[0].type == CSSRule.CHARSET_RULE) {
-        return parentSheet.cssRules[0].encoding;
-      }
-
-      // step 4 (2 of 2): charset of referring document.
-      if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
-        return sheet.ownerNode.ownerDocument.characterSet;
-      }
-    }
-
-    // step 5: default to utf-8.
-    return "UTF-8";
-  },
-
-  /**
    * Update the style sheet in place with new text.
    *
    * @param  {object} request
    *         'text' - new text
    *         'transition' - whether to do CSS transition for change.
    *         'kind' - either UPDATE_PRESERVING_RULES or UPDATE_GENERAL
    */
   update: function(text, transition, kind = UPDATE_GENERAL) {
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -19,25 +19,27 @@ var {
   ActorPool, createExtraActors, appendExtraActors
 } = require("devtools/server/actors/common");
 var { DebuggerServer } = require("devtools/server/main");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { assert } = DevToolsUtils;
 var { TabSources } = require("./utils/TabSources");
 var makeDebugger = require("./utils/make-debugger");
 const EventEmitter = require("devtools/shared/event-emitter");
+const InspectorUtils = require("InspectorUtils");
 
 const EXTENSION_CONTENT_JSM = "resource://gre/modules/ExtensionContent.jsm";
 
 loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/thread", true);
 loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
 loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
 loader.lazyImporter(this, "ExtensionContent", EXTENSION_CONTENT_JSM);
 
 loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
+loader.lazyRequireGetter(this, "getSheetText", "devtools/server/actors/stylesheets", true);
 
 function getWindowID(window) {
   return window.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIDOMWindowUtils)
                .currentInnerWindowID;
 }
 
 function getDocShellChromeEventHandler(docShell) {
@@ -253,16 +255,27 @@ TabActor.prototype = {
   get exited() {
     return this._exited;
   },
 
   get attached() {
     return !!this._attached;
   },
 
+  /**
+   * Try to locate the console actor if it exists.
+   */
+  get _consoleActor() {
+    if (this.exited) {
+      return null;
+    }
+    let form = this.form();
+    return this.conn._getOrCreateActor(form.consoleActor);
+  },
+
   _tabPool: null,
   get tabActorPool() {
     return this._tabPool;
   },
 
   _contextPool: null,
   get contextActorPool() {
     return this._contextPool;
@@ -1006,22 +1019,36 @@ TabActor.prototype = {
 
     return {};
   },
 
   /**
    * Ensure that CSS error reporting is enabled.
    */
   ensureCSSErrorReportingEnabled(request) {
-    if (!this.docShell || this.docShell.cssErrorReportingEnabled) {
-      return {};
+    for (let docShell of this.docShells) {
+      if (docShell.cssErrorReportingEnabled) {
+        continue;
+      }
+      try {
+        docShell.cssErrorReportingEnabled = true;
+      } catch (e) {
+        continue;
+      }
+      // We don't really want to reparse UA sheets and such, but want to do
+      // Shadow DOM / XBL.
+      let sheets =
+        InspectorUtils.getAllStyleSheets(docShell.document, /* documentOnly = */ true);
+      for (let sheet of sheets) {
+        getSheetText(sheet, this._consoleActor).then(text => {
+          InspectorUtils.parseStyleSheet(sheet, text, /* aUpdate = */ false);
+        });
+      }
     }
 
-    this.docShell.cssErrorReportingEnabled = true;
-    // FIXME(emilio): Reparse sheets.
     return {};
   },
 
   /**
    * Handle logic to enable/disable JS/cache/Service Worker testing.
    */
   _toggleDevToolsSettings(options) {
     // Wait a tick so that the response packet can be dispatched before the