Bug 1306887 - keep SourceMap response header on CSS style sheets; r?heycam, bz draft
authorTom Tromey <tom@tromey.com>
Wed, 28 Jun 2017 16:51:46 -0700
changeset 604850 f9558d302c889045b97c84012256ddd67f944536
parent 604849 4f6450b876588e3834da2e48057068c84813c241
child 604851 ec21862d7ea0cc954487d9b355828c4090fabf6a
push id67206
push userbmo:ttromey@mozilla.com
push dateThu, 06 Jul 2017 15:05:57 +0000
reviewersheycam, bz
bugs1306887
milestone56.0a1
Bug 1306887 - keep SourceMap response header on CSS style sheets; r?heycam, bz When loading a style sheet, if the SourceMap (or legacy X-SourceMap) response header was seen, record it and make it available to chrome scripts. MozReview-Commit-ID: 3wtUADzgrI3
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/script/ScriptLoader.cpp
dom/webidl/StyleSheet.webidl
layout/style/Loader.cpp
layout/style/StyleSheet.cpp
layout/style/StyleSheet.h
layout/style/StyleSheetInfo.h
layout/style/test/browser.ini
layout/style/test/browser_sourcemap.js
layout/style/test/mapped.css
layout/style/test/mapped.css^headers^
layout/style/test/mapped2.css
layout/style/test/mapped2.css^headers^
layout/style/test/sourcemap_css.html
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10605,16 +10605,26 @@ nsContentUtils::GenerateTabId()
 }
 
 /* static */ bool
 nsContentUtils::GetUserIsInteracting()
 {
   return UserInteractionObserver::sUserActive;
 }
 
+/* static */ bool
+nsContentUtils::GetSourceMapURL(nsIHttpChannel* aChannel, nsACString& aResult)
+{
+  nsresult rv = aChannel->GetResponseHeader(NS_LITERAL_CSTRING("SourceMap"), aResult);
+  if (NS_FAILED(rv)) {
+    rv = aChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), aResult);
+  }
+  return NS_SUCCEEDED(rv);
+}
+
 static const char* kUserInteractionInactive = "user-interaction-inactive";
 static const char* kUserInteractionActive = "user-interaction-active";
 
 void
 nsContentUtils::UserInteractionObserver::Init()
 {
   // Listen for the observer messages from EventStateManager which are telling
   // us whether or not the user is interacting.
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3077,16 +3077,26 @@ public:
 
   /**
    * Checks if the passed-in name should override an existing name on the
    * window. Values which should not override include: "", "_blank", "_top",
    * "_parent" and "_self".
    */
   static bool IsOverridingWindowName(const nsAString& aName);
 
+  /**
+   * If there is a SourceMap (higher precedence) or X-SourceMap (lower
+   * precedence) response header in |aChannel|, set |aResult| to the
+   * header's value and return true.  Otherwise, return false.
+   *
+   * @param aChannel The HTTP channel
+   * @param aResult The string result.
+   */
+  static bool GetSourceMapURL(nsIHttpChannel* aChannel, nsACString& aResult);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2876,21 +2876,17 @@ ScriptLoader::PrepareLoadedRequest(Scrip
   if (httpChannel) {
     bool requestSucceeded;
     rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
     if (NS_SUCCEEDED(rv) && !requestSucceeded) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     nsAutoCString sourceMapURL;
-    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("SourceMap"), sourceMapURL);
-    if (NS_FAILED(rv)) {
-      rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
-    }
-    if (NS_SUCCEEDED(rv)) {
+    if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
       aRequest->mHasSourceMapURL = true;
       aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
     }
 
     if (httpChannel->GetIsTrackingResource()) {
       aRequest->SetIsTracking();
     }
   }
--- a/dom/webidl/StyleSheet.webidl
+++ b/dom/webidl/StyleSheet.webidl
@@ -19,9 +19,16 @@ interface StyleSheet {
   [Pure]
   readonly attribute StyleSheet? parentStyleSheet;
   [Pure]
   readonly attribute DOMString? title;
   [Constant]
   readonly attribute MediaList media;
   [Pure]
   attribute boolean disabled;
+  // If a SourceMap or X-SourceMap response header is seen, this is
+  // the value.  If both are seen, SourceMap is preferred.  If neither
+  // is seen, this will be an empty string.  Because this relies on
+  // the HTTP response, it can change if checked before the response
+  // is available -- which is why it is not [Constant].
+  [ChromeOnly, Pure]
+  readonly attribute DOMString sourceMapURL;
 };
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -886,16 +886,21 @@ SheetLoadData::OnStreamComplete(nsIUnich
   if (httpChannel) {
     bool requestSucceeded;
     result = httpChannel->GetRequestSucceeded(&requestSucceeded);
     if (NS_SUCCEEDED(result) && !requestSucceeded) {
       LOG(("  Load returned an error page"));
       mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
+
+    nsAutoCString sourceMapURL;
+    if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
+      mSheet->SetSourceMapURL(NS_ConvertUTF8toUTF16(sourceMapURL));
+    }
   }
 
   nsAutoCString contentType;
   if (channel) {
     channel->GetContentType(contentType);
   }
 
   // In standards mode, a style sheet must have one of these MIME
--- a/layout/style/StyleSheet.cpp
+++ b/layout/style/StyleSheet.cpp
@@ -254,16 +254,17 @@ StyleSheetInfo::StyleSheetInfo(StyleShee
   , mBaseURI(aCopy.mBaseURI)
   , mPrincipal(aCopy.mPrincipal)
   , mCORSMode(aCopy.mCORSMode)
   , mReferrerPolicy(aCopy.mReferrerPolicy)
   , mIntegrity(aCopy.mIntegrity)
   , mComplete(aCopy.mComplete)
   , mFirstChild()  // We don't rebuild the child because we're making a copy
                    // without children.
+  , mSourceMapURL(aCopy.mSourceMapURL)
 #ifdef DEBUG
   , mPrincipalSet(aCopy.mPrincipalSet)
 #endif
 {
   AddSheet(aPrimarySheet);
 }
 
 StyleSheetInfo::~StyleSheetInfo()
@@ -501,16 +502,28 @@ StyleSheet::GetCssRules(nsIPrincipal& aS
                         ErrorResult& aRv)
 {
   if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
     return nullptr;
   }
   FORWARD_INTERNAL(GetCssRulesInternal, ())
 }
 
+void
+StyleSheet::GetSourceMapURL(nsAString& aSourceMapURL)
+{
+  aSourceMapURL = mInner->mSourceMapURL;
+}
+
+void
+StyleSheet::SetSourceMapURL(const nsAString& aSourceMapURL)
+{
+  mInner->mSourceMapURL = aSourceMapURL;
+}
+
 css::Rule*
 StyleSheet::GetDOMOwnerRule() const
 {
   return mOwnerRule;
 }
 
 uint32_t
 StyleSheet::InsertRule(const nsAString& aRule, uint32_t aIndex,
--- a/layout/style/StyleSheet.h
+++ b/layout/style/StyleSheet.h
@@ -208,16 +208,18 @@ public:
   // The XPCOM GetType is fine for WebIDL.
   // The XPCOM GetHref is fine for WebIDL
   // GetOwnerNode is defined above.
   inline StyleSheet* GetParentStyleSheet() const;
   // The XPCOM GetTitle is fine for WebIDL.
   dom::MediaList* Media();
   bool Disabled() const { return mDisabled; }
   // The XPCOM SetDisabled is fine for WebIDL.
+  void GetSourceMapURL(nsAString& aTitle);
+  void SetSourceMapURL(const nsAString& aSourceMapURL);
 
   // WebIDL CSSStyleSheet API
   // Can't be inline because we can't include ImportRule here.  And can't be
   // called GetOwnerRule because that would be ambiguous with the ImportRule
   // version.
   css::Rule* GetDOMOwnerRule() const;
   dom::CSSRuleList* GetCssRules(nsIPrincipal& aSubjectPrincipal,
                                 ErrorResult& aRv);
--- a/layout/style/StyleSheetInfo.h
+++ b/layout/style/StyleSheetInfo.h
@@ -56,16 +56,21 @@ struct StyleSheetInfo
   // Pointer to start of linked list of child sheets. This is all fundamentally
   // broken, because each of the child sheets has a unique parent... We can
   // only hope (and currently this is the case) that any time page JS can get
   // its hands on a child sheet that means we've already ensured unique infos
   // throughout its parent chain and things are good.
   RefPtr<StyleSheet>     mFirstChild;
   AutoTArray<StyleSheet*, 8> mSheets;
 
+  // If a SourceMap or X-SourceMap response header is seen, this is
+  // the value.  If both are seen, SourceMap is preferred.  If neither
+  // is seen, this will be an empty string.
+  nsString mSourceMapURL;
+
 #ifdef DEBUG
   bool                   mPrincipalSet;
 #endif
 };
 
 } // namespace mozilla
 
 #endif // mozilla_StyleSheetInfo_h
--- a/layout/style/test/browser.ini
+++ b/layout/style/test/browser.ini
@@ -1,8 +1,14 @@
 [DEFAULT]
 support-files =
   bug453896_iframe.html
   media_queries_iframe.html
   newtab_share_rule_processors.html
+  mapped.css
+  mapped.css^headers^
+  mapped2.css
+  mapped2.css^headers^
+  sourcemap_css.html
 
 [browser_bug453896.js]
 [browser_newtab_share_rule_processors.js]
+[browser_sourcemap.js]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/browser_sourcemap.js
@@ -0,0 +1,30 @@
+add_task(async function() {
+  let uri = "http://example.com/browser/layout/style/test/sourcemap_css.html";
+  info(`URI is ${uri}`);
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: uri
+  }, async function(browser) {
+    await ContentTask.spawn(browser, null, function* () {
+      let seenSheets = 0;
+
+      for (let i = 0; i < content.document.styleSheets.length; ++i) {
+        let sheet = content.document.styleSheets[i];
+
+        info(`Checking ${sheet.href}`);
+        if (/mapped\.css/.test(sheet.href)) {
+          is(sheet.sourceMapURL, "mapped.css.map", "X-SourceMap header took effect");
+          seenSheets |= 1;
+        } else if (/mapped2\.css/.test(sheet.href)) {
+          is(sheet.sourceMapURL, "mapped2.css.map", "SourceMap header took effect");
+          seenSheets |= 2;
+        } else {
+          ok(false, "sheet does not have source map URL");
+        }
+      }
+
+        is(seenSheets, 3, "seen all source-mapped sheets");
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/layout/style/test/mapped.css
@@ -0,0 +1,3 @@
+div {
+  color: #f06;
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/test/mapped.css^headers^
@@ -0,0 +1,1 @@
+X-SourceMap: mapped.css.map
new file mode 100644
--- /dev/null
+++ b/layout/style/test/mapped2.css
@@ -0,0 +1,3 @@
+span {
+  color: #f06;
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/test/mapped2.css^headers^
@@ -0,0 +1,2 @@
+SourceMap: mapped2.css.map
+X-SourceMap: ignored.css.map
new file mode 100644
--- /dev/null
+++ b/layout/style/test/sourcemap_css.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Test for bug 1306887</title>
+    <link rel="stylesheet" type="text/css" href="mapped.css"/>
+    <link rel="stylesheet" type="text/css" href="mapped2.css"/>
+  </head>
+  <body>
+    <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1306887">Mozilla Bug 1306887</a>
+  </body>
+</html>