Bug 308590 patch 3: Add nsIURI::EqualsExceptRef interface & impls. r=bz sr=biesi
authorDaniel Holbert <dholbert@cs.stanford.edu>
Sat, 21 May 2011 18:12:45 -0700
changeset 70097 add8d8d67ba80f21adcc4971be3a26dfbb86c16b
parent 70096 e9c7616c4f722cebd8d2985bb59d35bc419a1d9f
child 70098 835e9789e934e5b9c1cda65ab10547dd5bcc52a4
push idunknown
push userunknown
push dateunknown
reviewersbz, biesi
bugs308590
milestone6.0a1
Bug 308590 patch 3: Add nsIURI::EqualsExceptRef interface & impls. r=bz sr=biesi
caps/src/nsNullPrincipalURI.cpp
content/base/src/nsFileDataProtocolHandler.cpp
modules/libjar/nsIJARURI.idl
modules/libjar/nsJARURI.cpp
modules/libjar/nsJARURI.h
modules/libpr0n/decoders/icon/nsIIconURI.idl
modules/libpr0n/decoders/icon/nsIconURI.cpp
netwerk/base/public/nsIFileURL.idl
netwerk/base/public/nsIURI.idl
netwerk/base/public/nsIURL.idl
netwerk/base/src/nsSimpleNestedURI.cpp
netwerk/base/src/nsSimpleNestedURI.h
netwerk/base/src/nsSimpleURI.cpp
netwerk/base/src/nsSimpleURI.h
netwerk/base/src/nsStandardURL.cpp
netwerk/base/src/nsStandardURL.h
netwerk/test/unit/test_URIs.js
--- a/caps/src/nsNullPrincipalURI.cpp
+++ b/caps/src/nsNullPrincipalURI.cpp
@@ -252,16 +252,24 @@ nsNullPrincipalURI::Equals(nsIURI *aOthe
   if (NS_SUCCEEDED(rv)) {
     *_equals = (mScheme == otherURI->mScheme && mPath == otherURI->mPath);
     NS_RELEASE(otherURI);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsNullPrincipalURI::EqualsExceptRef(nsIURI *aOther, PRBool *_equals)
+{
+  // GetRef/SetRef not supported by nsNullPrincipalURI, so
+  // EqualsExceptRef() is the same as Equals().
+  return Equals(aOther, _equals);
+}
+
+NS_IMETHODIMP
 nsNullPrincipalURI::Resolve(const nsACString &aRelativePath,
                             nsACString &_resolvedURI)
 {
   _resolvedURI = aRelativePath;
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/content/base/src/nsFileDataProtocolHandler.cpp
+++ b/content/base/src/nsFileDataProtocolHandler.cpp
@@ -142,19 +142,21 @@ public:
   // For use only from deserialization
   nsFileDataURI() : nsSimpleURI() {}
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIURIWITHPRINCIPAL
   NS_DECL_NSISERIALIZABLE
   NS_DECL_NSICLASSINFO
 
-  // Override Clone() and Equals()
+  // Override Clone() and EqualsInternal()
   NS_IMETHOD Clone(nsIURI** aClone);
-  NS_IMETHOD Equals(nsIURI* aOther, PRBool *aResult);
+  virtual nsresult EqualsInternal(nsIURI* aOther,
+                                  RefHandlingEnum aRefHandlingMode,
+                                  PRBool* aResult);
 
   // Override StartClone to hand back a nsFileDataURI
   virtual nsSimpleURI* StartClone()
   { return new nsFileDataURI(); }
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
@@ -231,18 +233,20 @@ nsFileDataURI::Clone(nsIURI** aClone)
   nsFileDataURI* fileDataURI = static_cast<nsFileDataURI*>(simpleClone.get());
 
   fileDataURI->mPrincipal = mPrincipal;
 
   simpleClone.forget(aClone);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsFileDataURI::Equals(nsIURI* aOther, PRBool *aResult)
+/* virtual */ nsresult
+nsFileDataURI::EqualsInternal(nsIURI* aOther,
+                              nsSimpleURI::RefHandlingEnum aRefHandlingMode,
+                              PRBool* aResult)
 {
   if (!aOther) {
     *aResult = PR_FALSE;
     return NS_OK;
   }
   
   nsRefPtr<nsFileDataURI> otherFileDataUri;
   aOther->QueryInterface(kFILEDATAURICID, getter_AddRefs(otherFileDataUri));
@@ -253,17 +257,18 @@ nsFileDataURI::Equals(nsIURI* aOther, PR
 
   nsresult rv = mPrincipal->Equals(otherFileDataUri->mPrincipal, aResult);
   NS_ENSURE_SUCCESS(rv, rv);
   
   if (!*aResult) {
     return NS_OK;
   }
 
-  return nsSimpleURI::Equals(otherFileDataUri, aResult);
+  return nsSimpleURI::EqualsInternal(otherFileDataUri, aRefHandlingMode,
+                                     aResult);
 }
 
 // nsIClassInfo methods:
 NS_IMETHODIMP 
 nsFileDataURI::GetInterfaces(PRUint32 *count, nsIID * **array)
 {
   *count = 0;
   *array = nsnull;
--- a/modules/libjar/nsIJARURI.idl
+++ b/modules/libjar/nsIJARURI.idl
@@ -42,17 +42,17 @@
  * JAR URLs have the following syntax
  *
  * jar:<jar-file-uri>!/<jar-entry>
  *
  * EXAMPLE: jar:http://www.big.com/blue.jar!/ocean.html
  *
  * The nsIURL methods operate on the <jar-entry> part of the spec.
  */
-[scriptable, uuid(b0922a89-f87b-4cb5-8612-305a285fcca7)]
+[scriptable, uuid(ba32f809-5adf-4191-866a-1cddf60548f7)]
 interface nsIJARURI : nsIURL {
 
     /**
      * Returns the root URI (the one for the actual JAR file) for this JAR
      * (e.g., http://www.big.com/blue.jar).
      */
     readonly attribute nsIURI JARFile;
 
--- a/modules/libjar/nsJARURI.cpp
+++ b/modules/libjar/nsJARURI.cpp
@@ -453,36 +453,50 @@ nsJARURI::GetOriginCharset(nsACString &a
 {
     aOriginCharset = mCharsetHint;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARURI::Equals(nsIURI *other, PRBool *result)
 {
-    nsresult rv;
+    return EqualsInternal(other, eHonorRef, result);
+}
 
+NS_IMETHODIMP
+nsJARURI::EqualsExceptRef(nsIURI *other, PRBool *result)
+{
+    return EqualsInternal(other, eIgnoreRef, result);
+}
+
+// Helper method:
+/* virtual */ nsresult
+nsJARURI::EqualsInternal(nsIURI *other,
+                         nsJARURI::RefHandlingEnum refHandlingMode,
+                         PRBool *result)
+{
     *result = PR_FALSE;
 
-    if (other == nsnull)
+    if (!other)
         return NS_OK;	// not equal
 
     nsRefPtr<nsJARURI> otherJAR;
     other->QueryInterface(NS_GET_IID(nsJARURI), getter_AddRefs(otherJAR));
     if (!otherJAR)
         return NS_OK;   // not equal
 
     PRBool equal;
-    rv = mJARFile->Equals(otherJAR->mJARFile, &equal);
+    nsresult rv = mJARFile->Equals(otherJAR->mJARFile, &equal);
     if (NS_FAILED(rv) || !equal) {
         return rv;   // not equal
     }
 
-    rv = mJAREntry->Equals(otherJAR->mJAREntry, result);
-    return rv;
+    return refHandlingMode == eHonorRef ?
+        mJAREntry->Equals(otherJAR->mJAREntry, result) :
+        mJAREntry->EqualsExceptRef(otherJAR->mJAREntry, result);
 }
 
 NS_IMETHODIMP
 nsJARURI::SchemeIs(const char *i_Scheme, PRBool *o_Equals)
 {
     NS_ENSURE_ARG_POINTER(o_Equals);
     if (!i_Scheme) return NS_ERROR_INVALID_ARG;
 
--- a/modules/libjar/nsJARURI.h
+++ b/modules/libjar/nsJARURI.h
@@ -89,16 +89,27 @@ public:
     nsresult FormatSpec(const nsACString &entryPath, nsACString &result,
                         PRBool aIncludeScheme = PR_TRUE);
     nsresult CreateEntryURL(const nsACString& entryFilename,
                             const char* charset,
                             nsIURL** url);
     nsresult SetSpecWithBase(const nsACString& aSpec, nsIURI* aBaseURL);
 
 protected:
+    // enum used in a few places to specify how .ref attribute should be handled
+    enum RefHandlingEnum {
+        eIgnoreRef,
+        eHonorRef
+    };
+
+    // Helper to share code between Equals methods.
+    virtual nsresult EqualsInternal(nsIURI* other,
+                                    RefHandlingEnum refHandlingMode,
+                                    PRBool* result);
+
     nsCOMPtr<nsIURI> mJARFile;
     // mJarEntry stored as a URL so that we can easily access things
     // like extensions, refs, etc.
     nsCOMPtr<nsIURL> mJAREntry;
     nsCString        mCharsetHint;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsJARURI, NS_THIS_JARURI_IMPL_CID)
--- a/modules/libpr0n/decoders/icon/nsIIconURI.idl
+++ b/modules/libpr0n/decoders/icon/nsIIconURI.idl
@@ -68,17 +68,17 @@
    *   Values:      [normal | disabled]
    *   Description: The state of the icon.
    *
    *   Parameter:   contentType
    *   Values:      <mime-type>
    *   Description: The mime type we want an icon for. This is ignored by stock images.
    */
 
-[scriptable, uuid(E2D046D2-3729-440D-B0DB-F8D817BC2571)]
+[scriptable, uuid(5d8a4518-bebf-4e7e-bad0-3e6737b3d7f4)]
 interface nsIMozIconURI : nsIURI 
 {
   /**
    * iconFile
    *
    * the file URL contained within this moz-icon url, or null.
    */
   attribute nsIURL iconURL;
--- a/modules/libpr0n/decoders/icon/nsIconURI.cpp
+++ b/modules/libpr0n/decoders/icon/nsIconURI.cpp
@@ -411,16 +411,24 @@ nsMozIconURI::Equals(nsIURI *other, PRBo
   if (!PL_strcasecmp(spec1.get(), spec2.get()))
     *result = PR_TRUE;
   else
     *result = PR_FALSE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsMozIconURI::EqualsExceptRef(nsIURI *other, PRBool *result)
+{
+  // GetRef/SetRef not supported by nsMozIconURI, so
+  // EqualsExceptRef() is the same as Equals().
+  return Equals(other, result);
+}
+
+NS_IMETHODIMP
 nsMozIconURI::SchemeIs(const char *i_Scheme, PRBool *o_Equals)
 {
   NS_ENSURE_ARG_POINTER(o_Equals);
   if (!i_Scheme) return NS_ERROR_INVALID_ARG;
   
   *o_Equals = PL_strcasecmp("moz-icon", i_Scheme) ? PR_FALSE : PR_TRUE;
   return NS_OK;
 }
--- a/netwerk/base/public/nsIFileURL.idl
+++ b/netwerk/base/public/nsIFileURL.idl
@@ -40,17 +40,17 @@
 
 interface nsIFile;
 
 /**
  * nsIFileURL provides access to the underlying nsIFile object corresponding to
  * an URL.  The URL scheme need not be file:, since other local protocols may
  * map URLs to files (e.g., resource:).
  */
-[scriptable, uuid(d26b2e2e-1dd1-11b2-88f3-8545a7ba7949)]
+[scriptable, uuid(e693cb4f-d835-4068-9e14-76de8bd9df1d)]
 interface nsIFileURL : nsIURL
 {
     /**
      * Get/Set nsIFile corresponding to this URL.
      *
      *  - Getter returns a reference to an immutable object.  Callers must clone
      *    before attempting to modify the returned nsIFile object.  NOTE: this
      *    constraint might not be enforced at runtime, so beware!!
--- a/netwerk/base/public/nsIURI.idl
+++ b/netwerk/base/public/nsIURI.idl
@@ -90,17 +90,17 @@
  * 
  * Unescaping URI segments is unadvised unless there is intimate
  * knowledge of the underlying charset or there is no plan to display (or
  * otherwise enforce a charset on) the resulting URI substring.
  *
  * The correct way to create an nsIURI from a string is via
  * nsIIOService.newURI.
  */
-[scriptable, uuid(70d94a92-2b13-4e03-9f84-c1684aeea0f2)]
+[scriptable, uuid(d6d04c36-0fa4-4db3-be05-4a18397103e2)]
 interface nsIURI : nsISupports
 {
     /************************************************************************
      * The URI is broken down into the following principal components:
      */
 
     /**
      * Returns a string representation of the URI. Setting the spec causes
@@ -194,16 +194,25 @@ interface nsIURI : nsISupports
     /**
      * URI equivalence test (not a strict string comparison).
      *
      * eg. http://foo.com:80/ == http://foo.com/
      */
     boolean equals(in nsIURI other);
 
     /**
+     * URI equivalence test (not a strict string comparison), ignoring
+     * the value of the .ref member.
+     *
+     * eg. http://foo.com/# == http://foo.com/
+     *     http://foo.com/#aaa == http://foo.com/#bbb
+     */
+    boolean equalsExceptRef(in nsIURI other);
+
+    /**
      * An optimization to do scheme checks without requiring the users of nsIURI
      * to GetScheme, thereby saving extra allocating and freeing. Returns true if
      * the schemes match (case ignored).
      */
     boolean schemeIs(in string scheme);
 
     /**
      * Clones the current URI.
--- a/netwerk/base/public/nsIURL.idl
+++ b/netwerk/base/public/nsIURL.idl
@@ -49,17 +49,17 @@
  *            \          \                       /
  *             \          -----------------------
  *              \                   |          /
  *               \               fileName     /
  *                ----------------------------
  *                            |
  *                        filePath
  */
-[scriptable, uuid(d55f7915-6ff7-4b17-8795-d3b447bfe1f8)]
+[scriptable, uuid(55e824ca-f1bb-4452-9e14-fcfa1ff091ce)]
 interface nsIURL : nsIURI
 {
     /*************************************************************************
      * The URL path is broken down into the following principal components:
      */
 
     /**
      * Returns a path including the directory and file portions of a
--- a/netwerk/base/src/nsSimpleNestedURI.cpp
+++ b/netwerk/base/src/nsSimpleNestedURI.cpp
@@ -125,37 +125,40 @@ nsSimpleNestedURI::GetInnerURI(nsIURI** 
 }
 
 NS_IMETHODIMP
 nsSimpleNestedURI::GetInnermostURI(nsIURI** uri)
 {
     return NS_ImplGetInnermostURI(this, uri);
 }
 
-// nsIURI overrides
-
-NS_IMETHODIMP
-nsSimpleNestedURI::Equals(nsIURI* other, PRBool *result)
+// nsSimpleURI overrides
+/* virtual */ nsresult
+nsSimpleNestedURI::EqualsInternal(nsIURI* other,
+                                  nsSimpleURI::RefHandlingEnum refHandlingMode,
+                                  PRBool* result)
 {
     *result = PR_FALSE;
     NS_ENSURE_TRUE(mInnerURI, NS_ERROR_NOT_INITIALIZED);
     
     if (other) {
         PRBool correctScheme;
         nsresult rv = other->SchemeIs(mScheme.get(), &correctScheme);
         NS_ENSURE_SUCCESS(rv, rv);
 
         if (correctScheme) {
             nsCOMPtr<nsINestedURI> nest = do_QueryInterface(other);
             if (nest) {
                 nsCOMPtr<nsIURI> otherInner;
                 rv = nest->GetInnerURI(getter_AddRefs(otherInner));
                 NS_ENSURE_SUCCESS(rv, rv);
 
-                return otherInner->Equals(mInnerURI, result);
+                return (refHandlingMode == eHonorRef) ?
+                    otherInner->Equals(mInnerURI, result) :
+                    otherInner->EqualsExceptRef(mInnerURI, result);
             }
         }
     }
 
     return NS_OK;
 }
 
 /* virtual */ nsSimpleURI*
--- a/netwerk/base/src/nsSimpleNestedURI.h
+++ b/netwerk/base/src/nsSimpleNestedURI.h
@@ -68,18 +68,20 @@ public:
     nsSimpleNestedURI(nsIURI* innerURI);
 
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSINESTEDURI
     NS_DECL_NSIIPCSERIALIZABLE
 
     // Overrides for various methods nsSimpleURI implements follow.
   
-    // nsIURI overrides
-    NS_IMETHOD Equals(nsIURI* other, PRBool *result);
+    // nsSimpleURI overrides
+    virtual nsresult EqualsInternal(nsIURI* other,
+                                    RefHandlingEnum refHandlingMode,
+                                    PRBool* result);
     virtual nsSimpleURI* StartClone();
 
     // nsISerializable overrides
     NS_IMETHOD Read(nsIObjectInputStream* aStream);
     NS_IMETHOD Write(nsIObjectOutputStream* aStream);
 
     // Override the nsIClassInfo method GetClassIDNoAlloc to make sure our
     // nsISerializable impl works right.
--- a/netwerk/base/src/nsSimpleURI.cpp
+++ b/netwerk/base/src/nsSimpleURI.cpp
@@ -339,16 +339,32 @@ nsSimpleURI::SetRef(const nsACString &re
 {
     // XXXdholbert Implement this.
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsSimpleURI::Equals(nsIURI* other, PRBool *result)
 {
+    return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::EqualsExceptRef(nsIURI* other, PRBool *result)
+{
+    return EqualsInternal(other, eIgnoreRef, result);
+}
+
+/* virtual */ nsresult
+nsSimpleURI::EqualsInternal(nsIURI* other,
+                            nsSimpleURI::RefHandlingEnum refHandlingMode,
+                            PRBool* result)
+{
+    // XXXdholbert Currently ignoring refHandlingMode.  I'll make use of it
+    // in next patch, when I add support for .ref to this class.
     PRBool eq = PR_FALSE;
     if (other) {
         nsSimpleURI* otherUrl;
         nsresult rv =
             other->QueryInterface(kThisSimpleURIImplementationCID,
                                   (void**)&otherUrl);
         if (NS_SUCCEEDED(rv)) {
             eq = PRBool((0 == strcmp(mScheme.get(), otherUrl->mScheme.get())) && 
--- a/netwerk/base/src/nsSimpleURI.h
+++ b/netwerk/base/src/nsSimpleURI.h
@@ -69,16 +69,28 @@ public:
     NS_DECL_NSIMUTABLE
 
     // nsSimpleURI methods:
 
     nsSimpleURI();
     virtual ~nsSimpleURI();
 
 protected:
+    // enum used in a few places to specify how .ref attribute should be handled
+    enum RefHandlingEnum {
+        eIgnoreRef,
+        eHonorRef
+    };
+
+    // Helper to share code between Equals methods.  Subclasses can override
+    // for custom Equals behavior.
+    virtual nsresult EqualsInternal(nsIURI* other,
+                                    RefHandlingEnum refHandlingMode,
+                                    PRBool* result);
+
     virtual nsSimpleURI* StartClone();
 
     nsCString mScheme;
     nsCString mPath;
     PRBool mMutable;
 };
 
 #endif // nsSimpleURI_h__
--- a/netwerk/base/src/nsStandardURL.cpp
+++ b/netwerk/base/src/nsStandardURL.cpp
@@ -1572,16 +1572,30 @@ nsStandardURL::SetPath(const nsACString 
         mRef.mLen = -1;
     }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsStandardURL::Equals(nsIURI *unknownOther, PRBool *result)
 {
+    return EqualsInternal(unknownOther, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::EqualsExceptRef(nsIURI *unknownOther, PRBool *result)
+{
+    return EqualsInternal(unknownOther, eIgnoreRef, result);
+}
+
+nsresult
+nsStandardURL::EqualsInternal(nsIURI *unknownOther,
+                              nsStandardURL::RefHandlingEnum refHandlingMode,
+                              PRBool *result)
+{
     NS_ENSURE_ARG_POINTER(unknownOther);
     NS_PRECONDITION(result, "null pointer");
 
     nsRefPtr<nsStandardURL> other;
     nsresult rv = unknownOther->QueryInterface(kThisImplCID,
                                                getter_AddRefs(other));
     if (NS_FAILED(rv)) {
         *result = PR_FALSE;
@@ -1597,26 +1611,31 @@ nsStandardURL::Equals(nsIURI *unknownOth
 
     // Next check parts of a URI that, if different, automatically make the
     // URIs different
     if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
         // Check for host manually, since conversion to file will
         // ignore the host!
         !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
         !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
-        !SegmentIs(mRef, other->mSpec.get(), other->mRef) ||
         !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
         !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
         Port() != other->Port() ||
         !SegmentIs(mParam, other->mSpec.get(), other->mParam)) {
         // No need to compare files or other URI parts -- these are different
         // beasties
         *result = PR_FALSE;
         return NS_OK;
     }
+
+    if (refHandlingMode == eHonorRef &&
+        !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
+        *result = PR_FALSE;
+        return NS_OK;
+    }
     
     // Then check for exact identity of URIs.  If we have it, they're equal
     if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
         SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
         SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
         *result = PR_TRUE;
         return NS_OK;
     }
--- a/netwerk/base/src/nsStandardURL.h
+++ b/netwerk/base/src/nsStandardURL.h
@@ -148,16 +148,28 @@ public: /* internal -- HPUX compiler can
         
         const char* mCharset;  // Caller should keep this alive for
                                // the life of the segment encoder
         nsCOMPtr<nsIUnicodeEncoder> mEncoder;
     };
     friend class nsSegmentEncoder;
 
 protected:
+    // enum used in a few places to specify how .ref attribute should be handled
+    enum RefHandlingEnum {
+        eIgnoreRef,
+        eHonorRef
+    };
+
+    // Helper to share code between Equals and EqualsExceptRef
+    // NOTE: *not* virtual, because no one needs to override this so far...
+    nsresult EqualsInternal(nsIURI* unknownOther,
+                            RefHandlingEnum refHandlingMode,
+                            PRBool* result);
+
     virtual nsStandardURL* StartClone();
 
     // Helper for subclass implementation of GetFile().  Subclasses that map
     // URIs to files in a special way should implement this method.  It should
     // ensure that our mFile is initialized, if it's possible.
     // returns NS_ERROR_NO_INTERFACE if the url does not map to a file
     virtual nsresult EnsureFile();
 
--- a/netwerk/test/unit/test_URIs.js
+++ b/netwerk/test/unit/test_URIs.js
@@ -129,16 +129,57 @@ function do_info(text, stack) {
   if (!stack)
     stack = Components.stack.caller;
 
   dump( "\n" +
        "TEST-INFO | " + stack.filename + " | [" + stack.name + " : " +
        stack.lineNumber + "] " + text + "\n");
 }
 
+// Checks that the URIs satisfy equals(), in both possible orderings.
+// Also checks URI.equalsExceptRef(), because equal URIs should also be equal
+// when we ignore the ref.
+// 
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of do_check_true.
+function do_check_uri_eq(aURI1, aURI2, aCheckTrueFunc) {
+  if (!aCheckTrueFunc) {
+    aCheckTrueFunc = do_check_true;
+  }
+
+  do_info("(uri equals check: '" + aURI1.spec + "' == '" + aURI2.spec + "')");
+  aCheckTrueFunc(aURI1.equals(aURI2));
+  do_info("(uri equals check: '" + aURI2.spec + "' == '" + aURI1.spec + "')");
+  aCheckTrueFunc(aURI2.equals(aURI1));
+
+  // (Only take the extra step of testing 'equalsExceptRef' when we expect the
+  // URIs to really be equal.  In 'todo' cases, the URIs may or may not be
+  // equal when refs are ignored - there's no way of knowing in general.)
+  if (aCheckTrueFunc == do_check_true) {
+    do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc);
+  }
+}
+
+// Checks that the URIs satisfy equalsExceptRef(), in both possible orderings.
+//
+// The third argument is optional. If the client passes a third argument
+// (e.g. todo_check_true), we'll use that in lieu of do_check_true.
+function do_check_uri_eqExceptRef(aURI1, aURI2, aCheckTrueFunc) {
+  if (!aCheckTrueFunc) {
+    aCheckTrueFunc = do_check_true;
+  }
+
+  do_info("(uri equalsExceptRef check: '" +
+          aURI1.spec + "' == '" + aURI2.spec + "')");
+  aCheckTrueFunc(aURI1.equalsExceptRef(aURI2));
+  do_info("(uri equalsExceptRef check: '" +
+          aURI2.spec + "' == '" + aURI1.spec + "')");
+  aCheckTrueFunc(aURI2.equalsExceptRef(aURI1));
+}
+
 // Checks that the given property on aURI matches the corresponding property
 // in the test bundle (or matches some function of that corresponding property,
 // if aTestFunctor is passed in).
 function do_check_property(aTest, aURI, aPropertyName, aTestFunctor) {
   var expectedVal = aTestFunctor ?
     aTestFunctor(aTest[aPropertyName]) :
     aTest[aPropertyName];
 
@@ -149,17 +190,17 @@ function do_check_property(aTest, aURI, 
 }
 
 // Test that a given URI parses correctly into its various components.
 function do_test_uri_basic(aTest) {
   var URI = NetUtil.newURI(aTest.spec);
 
   // Sanity-check
   do_info("testing " + aTest.spec + " equals a clone of itself");
-  do_check_true(URI.equals(URI.clone()));
+  do_check_uri_eq(URI, URI.clone());
   do_info("testing " + aTest.spec + " instanceof nsIURL");
   do_check_eq(URI instanceof Ci.nsIURL, aTest.nsIURL);
   do_info("testing " + aTest.spec + " instanceof nsINestedURI");
   do_check_eq(URI instanceof Ci.nsINestedURI,
               aTest.nsINestedURI);
 
   // Check the various components
   do_check_property(aTest, URI, "scheme");
@@ -175,21 +216,20 @@ function do_test_uri_basic(aTest) {
 }
 
 // Test that a given URI parses correctly when we add a given ref to the end
 function do_test_uri_with_hash_suffix(aTest, aSuffix) {
   do_info("making sure caller is using suffix that starts with '#'");
   do_check_eq(aSuffix[0], "#");
 
   var testURI = NetUtil.newURI(aTest.spec + aSuffix);
+  var origURI = NetUtil.newURI(aTest.spec);
 
   do_info("testing " + aTest.spec +
           " doesn't equal self with '" + aSuffix + "' appended");
-
-  var origURI = NetUtil.newURI(aTest.spec);
   if (aTest.spec == "file://") {
     do_info("TODO: bug 656853");
     todo_check_false(origURI.equals(testURI));
     return;  // bail out early since file:// doesn't handle hash refs at all
   }
 
   do_check_false(origURI.equals(testURI));
 
@@ -199,16 +239,20 @@ function do_test_uri_with_hash_suffix(aT
                     function(aStr) { return aStr + aSuffix; });
 
   // XXXdholbert Only URLs (not URIs) support 'ref', until bug 308590's fix
   // lands, so the following only checks 'ref' on URLs.
   if (aTest.nsIURL) {
     var URL = testURI.QueryInterface(Ci.nsIURL);
     do_check_property(aTest, URL, "ref",
                       function(aStr) { return aSuffix.substr(1); });
+
+    do_info("testing " + aTest.spec +
+          " is equalExceptRef to self with '" + aSuffix + "' appended");
+    do_check_uri_eqExceptRef(origURI, testURI);
   }
 }
 
 // Tests various ways of setting & clearing a ref on a URI.
 function do_test_mutate_ref(aTest, aSuffix) {
   do_info("making sure caller is using suffix that starts with '#'");
   do_check_eq(aSuffix[0], "#");
 
@@ -221,66 +265,72 @@ function do_test_mutate_ref(aTest, aSuff
   var refURIWithSuffix    = NetUtil.newURI(aTest.spec + aSuffix).QueryInterface(Ci.nsIURL);
   var refURIWithoutSuffix = NetUtil.newURI(aTest.spec).QueryInterface(Ci.nsIURL);
 
   var testURI             = NetUtil.newURI(aTest.spec).QueryInterface(Ci.nsIURL);
 
   if (aTest.spec == "file://") {
     do_info("TODO: bug 656853");
     testURI.ref = aSuffix;
-    todo_check_true(testURI.equals(refURIWithSuffix));
+    do_check_uri_eq(testURI, refURIWithSuffix, todo_check_true);
     return; // bail out early since file:// doesn't handle hash refs at all
   }
 
   // First: Try setting .ref to our suffix
   do_info("testing that setting .ref on " + aTest.spec +
           " to '" + aSuffix + "' does what we expect");
   testURI.ref = aSuffix;
-  do_check_true(testURI.equals(refURIWithSuffix));
+  do_check_uri_eq(testURI, refURIWithSuffix);
+  do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
 
   // Now try setting .ref but leave off the initial hash (expect same result)
   var suffixLackingHash = aSuffix.substr(1);
   if (suffixLackingHash) { // (skip this our suffix was *just* a #)
     do_info("testing that setting .ref on " + aTest.spec +
             " to '" + suffixLackingHash + "' does what we expect");
     testURI.ref = suffixLackingHash;
-    do_check_true(testURI.equals(refURIWithSuffix));
+    do_check_uri_eq(testURI, refURIWithSuffix);
+    do_check_uri_eqExceptRef(testURI, refURIWithoutSuffix);
   }
 
   // Now, clear .ref (should get us back the original spec)
   do_info("testing that clearing .ref on " + testURI.spec +
           " does what we expect");
   testURI.ref = "";
-  do_check_true(testURI.equals(refURIWithoutSuffix));
+  do_check_uri_eq(testURI, refURIWithoutSuffix);
+  do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
 
   // Now try setting .spec directly (including suffix) and then clearing .ref
   var specWithSuffix = aTest.spec + aSuffix;
   do_info("testing that setting spec to " +
           specWithSuffix + " and then clearing ref does what we expect");
   testURI.spec = specWithSuffix
   testURI.ref = "";
   if (aTest.spec == "http://" && aSuffix == "#") {
     do_info("TODO: bug 657033");
-    todo_check_true(testURI.equals(refURIWithoutSuffix));
+    do_check_uri_eq(testURI, refURIWithoutSuffix, todo_check_true);
+    do_check_uri_eqExceptRef(testURI, refURIWithSuffix, todo_check_true);
   } else {
-    do_check_true(testURI.equals(refURIWithoutSuffix));
+    do_check_uri_eq(testURI, refURIWithoutSuffix);
+    do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
   }
 
   // XXX nsIJARURI throws an exception in SetPath(), so skip it for next part.
   if (!(testURI instanceof Ci.nsIJARURI)) {
     // Now try setting .path directly (including suffix) and then clearing .ref
     // (same as above, but with now with .path instead of .spec)
     testURI = NetUtil.newURI(aTest.spec).QueryInterface(Ci.nsIURL);
 
     var pathWithSuffix = aTest.path + aSuffix;
     do_info("testing that setting path to " +
             pathWithSuffix + " and then clearing ref does what we expect");
     testURI.path = pathWithSuffix;
     testURI.ref = "";
-    do_check_true(testURI.equals(refURIWithoutSuffix));
+    do_check_uri_eq(testURI, refURIWithoutSuffix);
+    do_check_uri_eqExceptRef(testURI, refURIWithSuffix);
 
     // Also: make sure that clearing .path also clears .ref
     testURI.path = pathWithSuffix;
     do_info("testing that clearing path from " + 
             pathWithSuffix + " also clears .ref");
     testURI.path = "";
     do_check_eq(testURI.ref, "");
   }