Bug 713564 - Add an API to declare a stylesheet obsolete. r=bz
authorMina Almasry <almasry.mina@gmail.com>
Mon, 15 Jul 2013 17:28:33 -0400
changeset 150871 8d0087957a1d303b26ae59aa7d4659c083cc14fc
parent 150870 75465ea1cfb6c0a421f13a51dc3805c1432ba813
child 150872 0cedd434dddb08547acc61ee6329e49c431f2bfa
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs713564
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 713564 - Add an API to declare a stylesheet obsolete. r=bz
content/base/public/nsIDocument.h
content/base/src/nsDocument.cpp
content/base/test/Makefile.in
content/base/test/test_declare_stylesheet_obsolete.html
content/base/test/variable_style_sheet.sjs
dom/webidl/Document.webidl
layout/style/Loader.cpp
layout/style/Loader.h
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -2111,16 +2111,20 @@ public:
     mStyleSheetChangeEventsEnabled = aValue;
   }
 
   bool StyleSheetChangeEventsEnabled() const
   {
     return mStyleSheetChangeEventsEnabled;
   }
 
+  void ObsoleteSheet(nsIURI *aSheetURI, mozilla::ErrorResult& rv);
+
+  void ObsoleteSheet(const nsAString& aSheetURI, mozilla::ErrorResult& rv);
+
   virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
 
   virtual JSObject* WrapObject(JSContext *aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
 private:
   uint64_t mWarnedAbout;
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -9444,16 +9444,40 @@ nsIDocument::CaretPositionFromPoint(floa
 NS_IMETHODIMP
 nsDocument::CaretPositionFromPoint(float aX, float aY, nsISupports** aCaretPos)
 {
   NS_ENSURE_ARG_POINTER(aCaretPos);
   *aCaretPos = nsIDocument::CaretPositionFromPoint(aX, aY).get();
   return NS_OK;
 }
 
+void
+nsIDocument::ObsoleteSheet(nsIURI *aSheetURI, ErrorResult& rv)
+{
+  nsresult res = CSSLoader()->ObsoleteSheet(aSheetURI);
+  if (NS_FAILED(res)) {
+    rv.Throw(res);
+  }
+}
+
+void
+nsIDocument::ObsoleteSheet(const nsAString& aSheetURI, ErrorResult& rv)
+{
+  nsCOMPtr<nsIURI> uri;
+  nsresult res = NS_NewURI(getter_AddRefs(uri), aSheetURI);
+  if (NS_FAILED(res)) {
+    rv.Throw(res);
+    return;
+  }
+  res = CSSLoader()->ObsoleteSheet(uri);
+  if (NS_FAILED(res)) {
+    rv.Throw(res);
+  }
+}
+
 namespace mozilla {
 
 // Singleton class to manage the list of fullscreen documents which are the
 // root of a branch which contains fullscreen documents. We maintain this list
 // so that we can easily exit all windows from fullscreen when the user
 // presses the escape key.
 class FullscreenRoots {
 public:
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -631,16 +631,18 @@ MOCHITEST_FILES_C= \
 		test_bug869002.html \
 		test_bug876282.html \
 		test_CSP_bug885433.html \
 		file_CSP_bug885433_allows.html \
 		file_CSP_bug885433_allows.html^headers^ \
 		file_CSP_bug885433_blocks.html \
 		file_CSP_bug885433_blocks.html^headers^ \
 		test_bug890580.html \
+		test_declare_stylesheet_obsolete.html \
+		variable_style_sheet.sjs \
 		$(NULL)
 
 # OOP tests don't work on Windows (bug 763081) or native-fennec
 # (see Bug 774939)
 ifneq ($(OS_ARCH),WINNT)
 ifndef MOZ_ANDROID_OMTC
 MOCHITEST_FILES_B += \
 		test_messagemanager_assertpermission.html \
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_declare_stylesheet_obsolete.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=713564
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 713564</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <!-- Load the variable stylesheet, which changes the color of #content. -->
+  <link rel="stylesheet" type="text/css" href="variable_style_sheet.sjs"/>
+
+  <script type="application/javascript">
+
+  function insertLinkToVarSSAndRun(callback) {
+    var ss = document.createElement("link");
+    ss.rel = "stylesheet";
+    ss.type = "text/css";
+    ss.href = "variable_style_sheet.sjs";
+    document.getElementsByTagName("head")[0].appendChild(ss);
+    ss.addEventListener("load", callback);
+  }
+
+  /** Test for Bug 713564 **/
+
+  // Then you link to that sheet, remove the link from the DOM, insert a new link to
+  // the same url and check that there was no new access, then call our new method,
+  // insert _another_ <link> to the same url, and check that this time we hit the
+  // server.
+  SimpleTest.waitForExplicitFinish();
+
+  function do_test() {
+    var var_sheet = document.getElementsByTagName("link")[1];
+    var head = document.getElementsByTagName("head")[0];
+    var content = document.getElementById("content");
+    var var_sheet_url = var_sheet.href;
+
+    var previousBgColor = window.getComputedStyle(content).
+                                 getPropertyValue("background-color");
+    var_sheet.parentNode.removeChild(var_sheet);
+    insertLinkToVarSSAndRun(function() {
+      is(window.getComputedStyle(content).getPropertyValue("background-color"),
+         previousBgColor,
+         "Sheet should still be the same.");
+
+      // Obsolete sheet
+      try {
+        SpecialPowers.wrap(document).obsoleteSheet(var_sheet_url);
+      } catch (e) {
+        ok(false, "obsoleteSheet should not raise an error on valid URL.");
+      }
+      insertLinkToVarSSAndRun(function() {
+        isnot(window.getComputedStyle(content).getPropertyValue("background-color"),
+              previousBgColor,
+              "Sheet should change after obsoleted and reinserted.");
+        SimpleTest.finish();
+      });
+    });
+    // obsoleteSheet should throw with invalid input:
+    try {
+      SpecialPowers.wrap(document).obsoleteSheet("");
+      ok(false, "obsoleteSheet should throw with empty string.");
+    } catch (e) {
+      ok(true, "obsoleteSheet throws with empty string.");
+    }
+    try {
+      SpecialPowers.wrap(document).obsoleteSheet("foo");
+      ok(false, "obsoleteSheet should throw with invalid URL.");
+    } catch (e) {
+      ok(true, "obsoleteSheet throws with invalid URL.");
+    }
+    try {
+      SpecialPowers.wrap(document).obsoleteSheet("http://www.mozilla.org");
+      ok(true, "obsoleteSheet should not throw with valid URL.");
+    } catch (e) {
+      ok(false, "obsoleteSheet throws with valid URL.");
+    }
+  }
+
+  </script>
+</head>
+<body onload="do_test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=713564">Mozilla Bug 713564</a>
+<p id="display"></p>
+<div id="content">
+  <br>
+  <br>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/variable_style_sheet.sjs
@@ -0,0 +1,19 @@
+function handleRequest(request, response)
+{
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/css", false);
+
+  var accessCount;
+  var state = getState("varSSAccessCount");
+  if (!state) {
+    setState("varSSAccessCount", "0");
+    accessCount = 0;
+  } else {
+    setState("varSSAccessCount", (parseInt(state) + 1).toString());
+    accessCount = parseInt(state) + 1;
+  }
+
+  response.write("#content { background-color: rgb(" +
+                 (accessCount % 256) +
+                 ", 0, 0); }");
+}
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -13,16 +13,17 @@
  * http://dev.w3.org/csswg/cssom-view/#extensions-to-the-document-interface
  *
  * http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/core/nsIDOMDocument.idl
  */
 
 interface StyleSheetList;
 interface WindowProxy;
 interface nsISupports;
+interface URI;
 
 enum VisibilityState { "hidden", "visible" };
 
 /* http://dom.spec.whatwg.org/#interface-document */
 [Constructor]
 interface Document : Node {
   [Throws]
   readonly attribute DOMImplementation implementation;
@@ -311,16 +312,21 @@ partial interface Document {
   // createTouchList().
   [Creator, Func="nsGenericHTMLElement::TouchEventsEnabled"]
   TouchList createTouchList();
   [Creator, Func="nsGenericHTMLElement::TouchEventsEnabled"]
   TouchList createTouchList(sequence<Touch> touches);
 
   [ChromeOnly]
   attribute boolean styleSheetChangeEventsEnabled;
+
+  [ChromeOnly, Throws]
+  void obsoleteSheet(URI sheetURI);
+  [ChromeOnly, Throws]
+  void obsoleteSheet(DOMString sheetURI);
 };
 
 // Extension to give chrome JS the ability to determine when a document was
 // created to satisfy an iframe with srcdoc attribute.
 partial interface Document {
   [ChromeOnly] readonly attribute boolean isSrcdocDocument;
 };
 
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -968,16 +968,44 @@ Loader::IsAlternate(const nsAString& aTi
     mDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle, aTitle);
     // We're definitely not an alternate
     return false;
   }
 
   return !aTitle.Equals(mPreferredSheet);
 }
 
+/* static */ PLDHashOperator
+Loader::RemoveEntriesWithURI(URIPrincipalAndCORSModeHashKey* aKey,
+                             nsRefPtr<nsCSSStyleSheet> &aSheet,
+                             void* aUserData)
+{
+  nsIURI* obsoleteURI = static_cast<nsIURI*>(aUserData);
+  nsIURI* sheetURI = aKey->GetURI();
+  bool areEqual;
+  nsresult rv = sheetURI->Equals(obsoleteURI, &areEqual);
+  if (NS_SUCCEEDED(rv) && areEqual) {
+    return PL_DHASH_REMOVE;
+  }
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+Loader::ObsoleteSheet(nsIURI* aURI)
+{
+  if (!mCompleteSheets.IsInitialized()) {
+    return NS_OK;
+  }
+  if (!aURI) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  mCompleteSheets.Enumerate(RemoveEntriesWithURI, aURI);
+  return NS_OK;
+}
+
 /**
  * CheckLoadAllowed will return success if the load is allowed,
  * failure otherwise.
  *
  * @param aSourcePrincipal the principal of the node or document or parent
  *                         sheet loading the sheet
  * @param aTargetURI the uri of the sheet to be loaded
  * @param aContext the node owning the sheet.  This is the element or document
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -95,16 +95,18 @@ public:
   }
 
   static const URIPrincipalAndCORSModeHashKey*
   KeyToPointer(URIPrincipalAndCORSModeHashKey* aKey) { return aKey; }
   static PLDHashNumber HashKey(const URIPrincipalAndCORSModeHashKey* aKey) {
     return nsURIHashKey::HashKey(aKey->mKey);
   }
 
+  nsIURI* GetURI() const { return nsURIHashKey::GetKey(); }
+
   enum { ALLOW_MEMMOVE = true };
 
 protected:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   CORSMode mCORSMode;
 };
 
 
@@ -366,19 +368,28 @@ public:
 
   // Unlink the cached stylesheets we're holding on to.  Again, this
   // should only be called from the document that owns this loader.
   void UnlinkCachedSheets();
 
   // Measure our size.
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
+  // Marks all the sheets at the given URI obsolete, and removes them from the
+  // cache.
+  nsresult ObsoleteSheet(nsIURI* aURI);
+
 private:
   friend class SheetLoadData;
 
+  static PLDHashOperator
+  RemoveEntriesWithURI(URIPrincipalAndCORSModeHashKey* aKey,
+                       nsRefPtr<nsCSSStyleSheet> &aSheet,
+                       void* aUserData);
+
   // Note: null aSourcePrincipal indicates that the content policy and
   // CheckLoadURI checks should be skipped.
   nsresult CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
                             nsIURI* aTargetURI,
                             nsISupports* aContext);
 
 
   // For inline style, the aURI param is null, but the aLinkingContent