Bug 482659. Give about:blank documents the base URI of the document that did the load. r=dcamp, sr=jst
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 16 Mar 2009 20:59:33 -0400
changeset 23795 780e244b9b8e984f3c3130436e9968a9d355ed94
parent 23794 abd4f2f400f612f6bbfeb5a0c9cbae11a224ced6
child 23796 72845b57dc1f9bef4aef3f38a020938e9d364b9c
push id917
push userbzbarsky@mozilla.com
push dateTue, 17 Mar 2009 13:38:31 +0000
reviewersdcamp, jst
bugs482659
milestone1.9.1b4pre
Bug 482659. Give about:blank documents the base URI of the document that did the load. r=dcamp, sr=jst
content/base/src/nsFrameLoader.cpp
content/html/document/test/Makefile.in
content/html/document/test/test_bug482659.html
layout/reftests/bugs/482659-1-ref.html
layout/reftests/bugs/482659-1a.html
layout/reftests/bugs/482659-1b.html
layout/reftests/bugs/482659-1c.html
layout/reftests/bugs/482659-1d.html
layout/reftests/bugs/reftest.list
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/protocol/about/src/nsAboutProtocolHandler.cpp
netwerk/protocol/about/src/nsAboutProtocolHandler.h
netwerk/test/unit/test_aboutblank.js
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -154,17 +154,17 @@ nsFrameLoader::LoadFrame()
   const char *charset = doc_charset.IsEmpty() ? nsnull : doc_charset.get();
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), src, charset, base_uri);
 
   // If the URI was malformed, try to recover by loading about:blank.
   if (rv == NS_ERROR_MALFORMED_URI) {
     rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("about:blank"),
-                   charset);
+                   charset, base_uri);
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
   return LoadURI(uri);
 }
 
 NS_IMETHODIMP
 nsFrameLoader::LoadURI(nsIURI* aURI)
--- a/content/html/document/test/Makefile.in
+++ b/content/html/document/test/Makefile.in
@@ -81,12 +81,13 @@ include $(topsrcdir)/config/rules.mk
 		bug445004-outer-write.html \
 		bug445004-inner.html \
 		test_bug448564.html \
 		bug448564-iframe-1.html \
 		bug448564-iframe-2.html \
 		bug448564-iframe-3.html \
 		bug448564-echo.sjs \
 		bug448564-submit.js \
+		test_bug482659.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/document/test/test_bug482659.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482659
+-->
+<head>
+  <title>Test for Bug 482659</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=482659">Mozilla Bug 482659</a>
+<p id="display">
+  <iframe></iframe>
+  <iframe src="about:blank"></iframe>
+  <iframe></iframe>
+  <iframe src="about:blank"></iframe>
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 482659 **/
+SimpleTest.waitForExplicitFinish()
+
+function testFrame(num) {
+  is(window.frames[num].document.baseURI, document.baseURI,
+     "Unexpected base URI in frame " + num);
+  is(window.frames[num].document.documentURI, "about:blank",
+     "Unexpected document URI in frame " + num);
+}
+
+function appendScript(doc) {
+  var s = doc.createElement("script");
+  s.textContent = "document.write('executed'); document.close()";
+  doc.body.appendChild(s);
+}
+
+function verifyScriptRan(num) {
+  is(window.frames[num].document.documentElement.textContent, "executed",
+     "write didn't happen in frame " + num);
+}
+
+addLoadEvent(function() {
+  appendScript(window.frames[2].document);
+  appendScript(window.frames[3].document);
+
+  verifyScriptRan(2);
+  verifyScriptRan(3);
+
+  for (var i = 0; i < 4; ++i) {
+    testFrame(i);
+  }
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482659
+-->
+<head>
+  <title>Test for Bug 482659</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=482659">Mozilla Bug 482659</a>
+<p id="display">
+  <iframe></iframe>
+  <iframe src="about:blank"></iframe>
+  <iframe></iframe>
+  <iframe src="about:blank"></iframe>
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 482659 **/
+SimpleTest.waitForExplicitFinish()
+
+function testFrame(num) {
+  is(window.frames[num].document.baseURI, document.baseURI,
+     "Unexpected base URI in frame " + num);
+  is(window.frames[num].document.documentURI, "about:blank",
+     "Unexpected document URI in frame " + num);
+}
+
+function appendScript(doc) {
+  var s = doc.createElement("script");
+  s.textContent = "document.write('executed'); document.close()";
+  doc.body.appendChild(s);
+}
+
+function verifyScriptRan(num) {
+  is(window.frames[num].document.documentElement.textContent, "executed",
+     "write didn't happen in frame " + num);
+}
+
+addLoadEvent(function() {
+  appendScript(window.frames[2].document);
+  appendScript(window.frames[3].document);
+
+  verifyScriptRan(2);
+  verifyScriptRan(3);
+
+  for (var i = 0; i < 4; ++i) {
+    testFrame(i);
+  }
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/482659-1-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+  <iframe src="subdir/445004-ref-subsubframe.html"></iframe>
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/482659-1a.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+      window.onload = function() {
+        window.frames[0].document.body.innerHTML =
+          "<img src='passouter.png' " +
+          "onload='window.parent.document.documentElement.className = &quot;&quot;'";
+      }
+    </script>
+  </head>
+  <body>
+    <iframe></iframe>
+  </body>
+</html>
+  
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/482659-1b.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+      window.onload = function() {
+        window.frames[0].document.body.innerHTML =
+          "<img src='passouter.png' " +
+          "onload='window.parent.document.documentElement.className = &quot;&quot;'";
+      }
+    </script>
+  </head>
+  <body>
+    <iframe src="about:blank"></iframe>
+  </body>
+</html>
+  
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/482659-1c.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+      window.onload = function() {
+        window.frames[0].location =
+          'javascript:document.write(""); document.close(); ' +
+          'parent.continueTest();'
+      }
+
+      function continueTest() {
+        // Do this part async just in case
+        setTimeout(function() {
+          window.frames[0].document.body.innerHTML =
+            "<img src='passouter.png' " +
+            "onload='window.parent.document.documentElement.className = &quot;&quot;'";
+                                                                                        }, 0);
+      }
+    </script>
+  </head>
+  <body>
+    <iframe></iframe>
+  </body>
+</html>
+  
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/482659-1d.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+      window.onload = function() {
+        window.frames[0].location =
+          'javascript:document.write(""); document.close(); ' +
+          'parent.continueTest();'
+      }
+
+      function continueTest() {
+        // Do this part async just in case
+        setTimeout(function() {
+          window.frames[0].document.body.innerHTML =
+            "<img src='passouter.png' " +
+            "onload='window.parent.document.documentElement.className = &quot;&quot;'";
+                                                                                        }, 0);
+      }
+    </script>
+  </head>
+  <body>
+    <iframe src="about:blank"></iframe>
+  </body>
+</html>
+  
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1006,8 +1006,12 @@ fails == 461512-1.html 461512-1-ref.html
 == 474336-1.xul 474336-1-ref.xul
 == 476357-1.html 476357-1-ref.html
 == 476598-1a.html 476598-1-ref.html
 == 476598-1a.html 476598-1-ref2.html
 == 476598-1b.html 476598-1-ref.html
 == 476598-1b.html 476598-1-ref2.html
 != 476598-1-ref.html about:blank
 == 478377-1.xul 478377-1-ref.xul
+== 482659-1a.html 482659-1-ref.html
+== 482659-1b.html 482659-1-ref.html
+== 482659-1c.html 482659-1-ref.html
+== 482659-1d.html 482659-1-ref.html
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -118,16 +118,26 @@
 #define NS_SIMPLENESTEDURI_CID                           \
 { /* 56388dad-287b-4240-a785-85c394012503 */             \
      0x56388dad,                                         \
      0x287b,                                             \
      0x4240,                                             \
      { 0xa7, 0x85, 0x85, 0xc3, 0x94, 0x01, 0x25, 0x03 }  \
 }
 
+// component inheriting from the nested simple URI component and also
+// carrying along its base URI
+#define NS_NESTEDABOUTURI_CID                            \
+{ /* 2f277c00-0eaf-4ddb-b936-41326ba48aae */             \
+     0x2f277c00,                                         \
+     0x0eaf,                                             \
+     0x4ddb,                                             \
+     { 0xb9, 0x36, 0x41, 0x32, 0x6b, 0xa4, 0x8a, 0xae }  \
+}
+
 // component implementing nsIStandardURL, nsIURI, nsIURL, nsISerializable,
 // and nsIClassInfo.
 #define NS_STANDARDURL_CLASSNAME \
     "nsStandardURL"
 #define NS_STANDARDURL_CONTRACTID \
     "@mozilla.org/network/standard-url;1"
 #define NS_STANDARDURL_CID                           \
 { /* de9472d0-8034-11d3-9399-00104ba0fd40 */         \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -55,16 +55,17 @@
 #include "nsBufferedStreams.h"
 #include "nsMIMEInputStream.h"
 #include "nsSOCKSSocketProvider.h"
 #include "nsCacheService.h"
 #include "nsDiskCacheDeviceSQL.h"
 #include "nsMimeTypes.h"
 #include "nsNetStrings.h"
 #include "nsDNSPrefetch.h"
+#include "nsAboutProtocolHandler.h"
 
 #include "nsNetCID.h"
 
 #if defined(XP_MACOSX)
 #define BUILD_APPLEFILE_DECODER 1
 #else
 #define BUILD_BINHEX_DECODER 1
 #endif
@@ -174,16 +175,17 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 // protocols
 ///////////////////////////////////////////////////////////////////////////////
 
 // about:blank is mandatory
 #include "nsAboutProtocolHandler.h"
 #include "nsAboutBlank.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAboutProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeAboutProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNestedAboutURI)
 
 #ifdef NECKO_PROTOCOL_about
 // about
 #ifdef NS_BUILD_REFCNT_LOGGING
 #include "nsAboutBloat.h"
 #endif
 #include "nsAboutCache.h"
 #include "nsAboutCacheEntry.h"
@@ -1009,16 +1011,20 @@ static const nsModuleComponentInfo gNetM
       NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-safe-about", 
       nsSafeAboutProtocolHandlerConstructor
     },
     { "about:blank", 
       NS_ABOUT_BLANK_MODULE_CID,
       NS_ABOUT_MODULE_CONTRACTID_PREFIX "blank", 
       nsAboutBlank::Create
     },
+    { "Nested about: URI",
+      NS_NESTEDABOUTURI_CID,
+      nsnull,
+      nsNestedAboutURIConstructor },
 #ifdef NECKO_PROTOCOL_about
 #ifdef NS_BUILD_REFCNT_LOGGING
     { "about:bloat", 
       NS_ABOUT_BLOAT_MODULE_CID,
       NS_ABOUT_MODULE_CONTRACTID_PREFIX "bloat", 
       nsAboutBloat::Create
     },
 #endif
--- a/netwerk/protocol/about/src/nsAboutProtocolHandler.cpp
+++ b/netwerk/protocol/about/src/nsAboutProtocolHandler.cpp
@@ -44,19 +44,23 @@
 #include "nsIServiceManager.h"
 #include "nsIAboutModule.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsNetCID.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsNetError.h"
 #include "nsNetUtil.h"
-#include "nsSimpleNestedURI.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsAutoPtr.h"
+#include "nsIWritablePropertyBag2.h"
 
 static NS_DEFINE_CID(kSimpleURICID,     NS_SIMPLEURI_CID);
+static NS_DEFINE_CID(kNestedAboutURICID, NS_NESTEDABOUTURI_CID);
 
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS1(nsAboutProtocolHandler, nsIProtocolHandler)
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIProtocolHandler methods:
 
@@ -126,17 +130,17 @@ nsAboutProtocolHandler::NewURI(const nsA
         NS_ENSURE_SUCCESS(rv, rv);
         
         spec.Insert("moz-safe-about:", 0);
 
         nsCOMPtr<nsIURI> inner;
         rv = NS_NewURI(getter_AddRefs(inner), spec);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        nsSimpleNestedURI* outer = new nsSimpleNestedURI(inner);
+        nsSimpleNestedURI* outer = new nsNestedAboutURI(inner, aBaseURI);
         NS_ENSURE_TRUE(outer, NS_ERROR_OUT_OF_MEMORY);
 
         // Take a ref to it in the COMPtr we plan to return
         url = outer;
 
         rv = outer->SetSpec(aSpec);
         NS_ENSURE_SUCCESS(rv, rv);
     }
@@ -153,17 +157,32 @@ nsAboutProtocolHandler::NewChannel(nsIUR
 {
     NS_ENSURE_ARG_POINTER(uri);
 
     // about:what you ask?
     nsCOMPtr<nsIAboutModule> aboutMod;
     nsresult rv = NS_GetAboutModule(uri, getter_AddRefs(aboutMod));
     if (NS_SUCCEEDED(rv)) {
         // The standard return case:
-        return aboutMod->NewChannel(uri, result);
+        rv = aboutMod->NewChannel(uri, result);
+        if (NS_SUCCEEDED(rv)) {
+            nsRefPtr<nsNestedAboutURI> aboutURI;
+            rv = uri->QueryInterface(kNestedAboutURICID,
+                                     getter_AddRefs(aboutURI));
+            if (NS_SUCCEEDED(rv) && aboutURI->GetBaseURI()) {
+                nsCOMPtr<nsIWritablePropertyBag2> writableBag =
+                    do_QueryInterface(*result);
+                if (writableBag) {
+                    writableBag->
+                        SetPropertyAsInterface(NS_LITERAL_STRING("baseURI"),
+                                               aboutURI->GetBaseURI());
+                }
+            }
+        }
+        return rv;
     }
 
     // mumble...
 
     if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
         // This looks like an about: we don't know about.  Convert
         // this to an invalid URI error.
         rv = NS_ERROR_MALFORMED_URI;
@@ -240,8 +259,81 @@ nsSafeAboutProtocolHandler::NewChannel(n
 
 NS_IMETHODIMP 
 nsSafeAboutProtocolHandler::AllowPort(PRInt32 port, const char *scheme, PRBool *_retval)
 {
     // don't override anything.  
     *_retval = PR_FALSE;
     return NS_OK;
 }
+
+////////////////////////////////////////////////////////////
+// nsNestedAboutURI implementation
+NS_INTERFACE_MAP_BEGIN(nsNestedAboutURI)
+  if (aIID.Equals(kNestedAboutURICID))
+      foundInterface = static_cast<nsIURI*>(this);
+  else
+NS_INTERFACE_MAP_END_INHERITING(nsSimpleNestedURI)
+
+// nsISerializable
+NS_IMETHODIMP
+nsNestedAboutURI::Read(nsIObjectInputStream* aStream)
+{
+    nsresult rv = nsSimpleNestedURI::Read(aStream);
+    if (NS_FAILED(rv)) return rv;
+
+    PRBool haveBase;
+    rv = aStream->ReadBoolean(&haveBase);
+    if (NS_FAILED(rv)) return rv;
+
+    if (haveBase) {
+        rv = aStream->ReadObject(PR_TRUE, getter_AddRefs(mBaseURI));
+        if (NS_FAILED(rv)) return rv;
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNestedAboutURI::Write(nsIObjectOutputStream* aStream)
+{
+    nsresult rv = nsSimpleNestedURI::Write(aStream);
+    if (NS_FAILED(rv)) return rv;
+
+    rv = aStream->WriteBoolean(mBaseURI != nsnull);
+    if (NS_FAILED(rv)) return rv;
+
+    if (mBaseURI) {
+        rv = aStream->WriteObject(mBaseURI, PR_TRUE);
+        if (NS_FAILED(rv)) return rv;
+    }
+
+    return NS_OK;
+}
+
+// nsSimpleURI
+/* virtual */ nsSimpleURI*
+nsNestedAboutURI::StartClone()
+{
+    // Sadly, we can't make use of nsSimpleNestedURI::StartClone here.
+    NS_ENSURE_TRUE(mInnerURI, nsnull);
+
+    nsCOMPtr<nsIURI> innerClone;
+    nsresult rv = mInnerURI->Clone(getter_AddRefs(innerClone));
+    if (NS_FAILED(rv)) {
+        return nsnull;
+    }
+
+    nsNestedAboutURI* url = new nsNestedAboutURI(innerClone, mBaseURI);
+    if (url) {
+        url->SetMutable(PR_FALSE);
+    }
+
+    return url;
+}
+
+// nsIClassInfo
+NS_IMETHODIMP
+nsNestedAboutURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+    *aClassIDNoAlloc = kNestedAboutURICID;
+    return NS_OK;
+}
--- a/netwerk/protocol/about/src/nsAboutProtocolHandler.h
+++ b/netwerk/protocol/about/src/nsAboutProtocolHandler.h
@@ -34,16 +34,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsAboutProtocolHandler_h___
 #define nsAboutProtocolHandler_h___
 
 #include "nsIProtocolHandler.h"
+#include "nsSimpleNestedURI.h"
 
 class nsCString;
 class nsIAboutModule;
 
 class nsAboutProtocolHandler : public nsIProtocolHandler
 {
 public:
     NS_DECL_ISUPPORTS
@@ -67,9 +68,41 @@ public:
     // nsSafeAboutProtocolHandler methods:
     nsSafeAboutProtocolHandler() {}
 
 private:
     ~nsSafeAboutProtocolHandler() {}
 };
 
 
+// Class to allow us to propagate the base URI to about:blank correctly
+class nsNestedAboutURI : public nsSimpleNestedURI {
+public:
+    nsNestedAboutURI(nsIURI* aInnerURI, nsIURI* aBaseURI)
+        : nsSimpleNestedURI(aInnerURI)
+        , mBaseURI(aBaseURI)
+    {}
+
+    // For use only from deserialization
+    nsNestedAboutURI() : nsSimpleNestedURI() {}
+
+    virtual ~nsNestedAboutURI() {}
+
+    // Override QI so we can QI to our CID as needed
+    NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
+
+    // Override StartClone(), the nsISerializable methods, and
+    // GetClassIDNoAlloc; this last is needed to make our nsISerializable impl
+    // work right.
+    virtual nsSimpleURI* StartClone();
+    NS_IMETHOD Read(nsIObjectInputStream* aStream);
+    NS_IMETHOD Write(nsIObjectOutputStream* aStream);
+    NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc);
+
+    nsIURI* GetBaseURI() const {
+        return mBaseURI;
+    }
+
+protected:
+    nsCOMPtr<nsIURI> mBaseURI;
+};
+
 #endif /* nsAboutProtocolHandler_h___ */
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_aboutblank.js
@@ -0,0 +1,29 @@
+function run_test() {
+  var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
+                         .getService(Components.interfaces.nsIIOService);
+
+  var base = ioServ.newURI("http://www.example.com", null, null);
+
+  var about1 = ioServ.newURI("about:blank", null, null);
+  var about2 = ioServ.newURI("about:blank", null, base);
+
+  var chan1 = ioServ.newChannelFromURI(about1)
+                    .QueryInterface(Components.interfaces.nsIPropertyBag2);
+  var chan2 = ioServ.newChannelFromURI(about2)
+                    .QueryInterface(Components.interfaces.nsIPropertyBag2);
+
+  var haveProp = false;
+  var propVal = null;
+  try {
+    propVal = chan1.getPropertyAsInterface("baseURI",
+                                           Components.interfaces.nsIURI);
+    haveProp = true;
+  } catch (e if e.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
+    // Property shouldn't be there.
+  }
+  do_check_eq(propVal, null);
+  do_check_eq(haveProp, false);
+  do_check_eq(chan2.getPropertyAsInterface("baseURI",
+                                           Components.interfaces.nsIURI),
+              base);
+}