Bug 422543 - part 1 - make session history support multiple listeners; r=smaug
authorTim Taubert <ttaubert@mozilla.com>
Thu, 20 Sep 2012 16:41:18 +0200
changeset 107702 98a4a0177f22f6b9e0ad0e2d6201fec6567b8283
parent 107600 2208b83cc81d7ea02fa30f27a03c9f3f5b766155
child 107703 196ca3ef5b570e136c7fa2d4641e0d2482032e56
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerssmaug
bugs422543
milestone18.0a1
Bug 422543 - part 1 - make session history support multiple listeners; r=smaug
docshell/base/nsDocShell.cpp
docshell/shistory/public/nsISHistoryInternal.idl
docshell/shistory/src/nsSHistory.cpp
docshell/shistory/src/nsSHistory.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4449,23 +4449,19 @@ nsDocShell::Reload(uint32_t aReloadFlags
 
     uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags);
     NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG);
 
     // Send notifications to the HistoryListener if any, about the impending reload
     nsCOMPtr<nsISHistory> rootSH;
     rv = GetRootSessionHistory(getter_AddRefs(rootSH));
     nsCOMPtr<nsISHistoryInternal> shistInt(do_QueryInterface(rootSH));
-    bool canReload = true; 
+    bool canReload = true;
     if (rootSH) {
-      nsCOMPtr<nsISHistoryListener> listener;
-      shistInt->GetListener(getter_AddRefs(listener));
-      if (listener) {
-        listener->OnHistoryReload(mCurrentURI, aReloadFlags, &canReload);
-      }
+      shistInt->NotifyOnHistoryReload(mCurrentURI, aReloadFlags, &canReload);
     }
 
     if (!canReload)
       return NS_OK;
     
     /* If you change this part of code, make sure bug 45297 does not re-occur */
     if (mOSHE) {
         rv = LoadHistoryEntry(mOSHE, loadType);
--- a/docshell/shistory/public/nsISHistoryInternal.idl
+++ b/docshell/shistory/public/nsISHistoryInternal.idl
@@ -19,17 +19,17 @@ interface nsIDocShell;
 #define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
 
 template<class E, class A> class nsTArray;
 struct nsTArrayDefaultAllocator;
 %}
 
 [ref] native nsDocshellIDArray(nsTArray<uint64_t, nsTArrayDefaultAllocator>);
 
-[scriptable, uuid(e27cf38e-c19f-4294-bd31-d7e0916e7fa2)]
+[scriptable, uuid(f9348014-0239-11e2-b029-3d38e719eb2d)]
 interface nsISHistoryInternal: nsISupports
 {
   /**
    * Add a new Entry to the History List
    * @param aEntry - The entry to add
    * @param aPersist - If true this specifies that the entry should persist
    * in the list.  If false, this means that when new entries are added
    * this element will not appear in the session history list.
@@ -53,20 +53,27 @@ interface nsISHistoryInternal: nsISuppor
 
   /**
    * Replace the nsISHEntry at a particular index
    * @param aIndex - The index at which the entry should be replaced
    * @param aReplaceEntry - The replacement entry for the index.
    */
    void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry);
 
-  /** 
-   * Get handle to the history listener
+  /**
+   * Notifies all registered session history listeners about an impending
+   * reload.
+   *
+   * @param aReloadURI    The URI of the document to be reloaded.
+   * @param aReloadFlags  Flags that indicate how the document is to be
+   *                      refreshed. See constants on the nsIWebNavigation
+   *                      interface.
+   * @return              Whether the operation can proceed.
    */
-   readonly attribute nsISHistoryListener listener;
+   boolean notifyOnHistoryReload(in nsIURI aReloadURI, in unsigned long aReloadFlags);
 
    /**
     * Evict content viewers which don't lie in the "safe" range around aIndex.
     * In practice, this should leave us with no more than gHistoryMaxViewers
     * viewers associated with this SHistory object.
     *
     * Also make sure that the total number of content viewers in all windows is
     * not greater than our global max; if it is, evict viewers as appropriate.
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -91,16 +91,58 @@ static PRLogModuleInfo* gLogModule = PR_
   PR_BEGIN_MACRO                                           \
     if (PR_LOG_TEST(gLogModule, PR_LOG_DEBUG)) {           \
       nsCOMPtr<nsIURI> uri;                                \
       shentry->GetURI(getter_AddRefs(uri));                \
       LOG_SPEC(format, uri);                               \
     }                                                      \
   PR_END_MACRO
 
+// Iterates over all registered session history listeners.
+#define ITERATE_LISTENERS(body)                            \
+  PR_BEGIN_MACRO                                           \
+  {                                                        \
+    nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \
+      iter(mListeners);                                    \
+    while (iter.HasMore()) {                               \
+      nsCOMPtr<nsISHistoryListener> listener =             \
+        do_QueryReferent(iter.GetNext());                  \
+      if (listener) {                                      \
+        body;                                              \
+      }                                                    \
+    }                                                      \
+  }                                                        \
+  PR_END_MACRO
+
+// Calls a given method on all registered session history listeners.
+#define NOTIFY_LISTENERS(method, args)                     \
+  ITERATE_LISTENERS(                                       \
+    listener->method args;                                 \
+  );
+
+// Calls a given method on all registered session history listeners.
+// Listeners may return 'false' to cancel an action so make sure that we
+// set the return value to 'false' if one of the listeners wants to cancel.
+#define NOTIFY_LISTENERS_CANCELABLE(method, retval, args)  \
+  PR_BEGIN_MACRO                                           \
+  {                                                        \
+    bool canceled = false;                                 \
+    retval = true;                                         \
+    ITERATE_LISTENERS(                                     \
+      listener->method args;                               \
+      if (!retval) {                                       \
+        canceled = true;                                   \
+      }                                                    \
+    );                                                     \
+    if (canceled) {                                        \
+      retval = false;                                      \
+    }                                                      \
+  }                                                        \
+  PR_END_MACRO
+
 enum HistCmd{
   HIST_CMD_BACK,
   HIST_CMD_FORWARD,
   HIST_CMD_GOTOINDEX,
   HIST_CMD_RELOAD
 } ;
 
 //*****************************************************************************
@@ -365,32 +407,27 @@ nsSHistory::AddEntry(nsISHEntry * aSHEnt
     NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry),NS_ERROR_FAILURE);
     currentTxn->SetPersist(aPersist);
     return NS_OK;
   }
 
   nsCOMPtr<nsISHTransaction> txn(do_CreateInstance(NS_SHTRANSACTION_CONTRACTID));
   NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE);
 
-  // Notify any listener about the new addition
-  if (mListener) {
-    nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
-    if (listener) {
-      nsCOMPtr<nsIURI> uri;
-      nsCOMPtr<nsIHistoryEntry> hEntry(do_QueryInterface(aSHEntry));
-      if (hEntry) {
-        int32_t currentIndex = mIndex;
-        hEntry->GetURI(getter_AddRefs(uri));
-        listener->OnHistoryNewEntry(uri);
+  nsCOMPtr<nsIURI> uri;
+  nsCOMPtr<nsIHistoryEntry> hEntry(do_QueryInterface(aSHEntry));
+  if (hEntry) {
+    int32_t currentIndex = mIndex;
+    hEntry->GetURI(getter_AddRefs(uri));
+    NOTIFY_LISTENERS(OnHistoryNewEntry, (uri));
 
-        // If a listener has changed mIndex, we need to get currentTxn again,
-        // otherwise we'll be left at an inconsistent state (see bug 320742)
-        if (currentIndex != mIndex)
-          GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
-      }
+    // If a listener has changed mIndex, we need to get currentTxn again,
+    // otherwise we'll be left at an inconsistent state (see bug 320742)
+    if (currentIndex != mIndex) {
+      GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
     }
   }
 
   // Set the ShEntry and parent for the transaction. setting the 
   // parent will properly set the parent child relationship
   txn->SetPersist(aPersist);
   NS_ENSURE_SUCCESS(txn->Create(aSHEntry, currentTxn), NS_ERROR_FAILURE);
    
@@ -617,23 +654,18 @@ NS_IMETHODIMP
 nsSHistory::PurgeHistory(int32_t aEntries)
 {
   if (mLength <= 0 || aEntries <= 0)
     return NS_ERROR_FAILURE;
 
   aEntries = NS_MIN(aEntries, mLength);
   
   bool purgeHistory = true;
-  // Notify the listener about the history purge
-  if (mListener) {
-    nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
-    if (listener) {
-      listener->OnHistoryPurge(aEntries, &purgeHistory);
-    } 
-  }
+  NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge, purgeHistory,
+                              (aEntries, &purgeHistory));
 
   if (!purgeHistory) {
     // Listener asked us not to purge
     return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA;
   }
 
   int32_t cnt = 0;
   while (cnt < aEntries) {
@@ -669,32 +701,30 @@ nsSHistory::AddSHistoryListener(nsISHist
 {
   NS_ENSURE_ARG_POINTER(aListener);
 
   // Check if the listener supports Weak Reference. This is a must.
   // This listener functionality is used by embedders and we want to 
   // have the right ownership with who ever listens to SHistory
   nsWeakPtr listener = do_GetWeakReference(aListener);
   if (!listener) return NS_ERROR_FAILURE;
-  mListener = listener;
-  return NS_OK;
+
+  return mListeners.AppendElementUnlessExists(listener) ?
+    NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
 
 NS_IMETHODIMP
 nsSHistory::RemoveSHistoryListener(nsISHistoryListener * aListener)
 {
   // Make sure the listener that wants to be removed is the
   // one we have in store. 
-  nsWeakPtr listener = do_GetWeakReference(aListener);  
-  if (listener == mListener) {
-    mListener = nullptr;
-    return NS_OK;
-  }
-  return NS_ERROR_FAILURE;
+  nsWeakPtr listener = do_GetWeakReference(aListener);
+  mListeners.RemoveElement(listener);
+  return NS_OK;
 }
 
 
 /* Replace an entry in the History list at a particular index.
  * Do not update index or count.
  */
 NS_IMETHODIMP
 nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry * aReplaceEntry)
@@ -712,24 +742,22 @@ nsSHistory::ReplaceEntry(int32_t aIndex,
   {
     // Set the replacement entry in the transaction
     rv = currentTxn->SetSHEntry(aReplaceEntry);
     rv = currentTxn->SetPersist(true);
   }
   return rv;
 }
 
-/* Get a handle to the Session history listener */
 NS_IMETHODIMP
-nsSHistory::GetListener(nsISHistoryListener ** aListener)
+nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags,
+                                  bool* aCanReload)
 {
-  NS_ENSURE_ARG_POINTER(aListener);
-  if (mListener) 
-    CallQueryReferent(mListener.get(),  aListener);
-  // Don't addref aListener. It is a weak pointer.
+  NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload,
+                              (aReloadURI, aReloadFlags, aCanReload));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex)
 {
   // Check our per SHistory object limit in the currently navigated SHistory
   EvictOutOfRangeWindowContentViewers(aIndex);
@@ -837,49 +865,40 @@ nsSHistory::Reload(uint32_t aReloadFlags
   {
     loadType = nsIDocShellLoadInfo::loadReloadCharsetChange;
   }
   else
   {
     loadType = nsIDocShellLoadInfo::loadReloadNormal;
   }
   
-  // Notify listeners
+  // We are reloading. Send Reload notifications.
+  // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
+  // is public. So send the reload notifications with the
+  // nsIWebNavigation flags.
   bool canNavigate = true;
-  if (mListener) {
-    nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
-    // We are reloading. Send Reload notifications. 
-    // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
-    // is public. So send the reload notifications with the
-    // nsIWebNavigation flags. 
-    if (listener) {
-      nsCOMPtr<nsIURI> currentURI;
-      rv = GetCurrentURI(getter_AddRefs(currentURI));
-      listener->OnHistoryReload(currentURI, aReloadFlags, &canNavigate);
-    }
-  }
+  nsCOMPtr<nsIURI> currentURI;
+  rv = GetCurrentURI(getter_AddRefs(currentURI));
+  NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate,
+                              (currentURI, aReloadFlags, &canNavigate));
   if (!canNavigate)
     return NS_OK;
 
   return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD);
 }
 
 NS_IMETHODIMP
 nsSHistory::ReloadCurrentEntry()
 {
   // Notify listeners
   bool canNavigate = true;
-  if (mListener) {
-    nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
-    if (listener) {
-      nsCOMPtr<nsIURI> currentURI;
-      GetCurrentURI(getter_AddRefs(currentURI));
-      listener->OnHistoryGotoIndex(mIndex, currentURI, &canNavigate);
-    }
-  }
+  nsCOMPtr<nsIURI> currentURI;
+  GetCurrentURI(getter_AddRefs(currentURI));
+  NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate,
+                              (mIndex, currentURI, &canNavigate));
   if (!canNavigate)
     return NS_OK;
 
   return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD);
 }
 
 void
 nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex)
@@ -1510,32 +1529,28 @@ nsSHistory::LoadEntry(int32_t aIndex, lo
   }
 
   // Send appropriate listener notifications
   bool canNavigate = true;
   // Get the uri for the entry we are about to visit
   nsCOMPtr<nsIURI> nextURI;
   nHEntry->GetURI(getter_AddRefs(nextURI));
 
-  if(mListener) {
-    nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
-    if (listener) {
-      if (aHistCmd == HIST_CMD_BACK) {
-        // We are going back one entry. Send GoBack notifications
-        listener->OnHistoryGoBack(nextURI, &canNavigate);
-      }
-      else if (aHistCmd == HIST_CMD_FORWARD) {
-        // We are going forward. Send GoForward notification
-        listener->OnHistoryGoForward(nextURI, &canNavigate);
-      }
-      else if (aHistCmd == HIST_CMD_GOTOINDEX) {
-        // We are going somewhere else. This is not reload either
-        listener->OnHistoryGotoIndex(aIndex, nextURI, &canNavigate);
-      }
-    }
+  if (aHistCmd == HIST_CMD_BACK) {
+    // We are going back one entry. Send GoBack notifications
+    NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoBack, canNavigate,
+                                (nextURI, &canNavigate));
+  } else if (aHistCmd == HIST_CMD_FORWARD) {
+    // We are going forward. Send GoForward notification
+    NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoForward, canNavigate,
+                                (nextURI, &canNavigate));
+  } else if (aHistCmd == HIST_CMD_GOTOINDEX) {
+    // We are going somewhere else. This is not reload either
+    NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate,
+                                (aIndex, nextURI, &canNavigate));
   }
 
   if (!canNavigate) {
     // If the listener asked us not to proceed with 
     // the operation, simply return.    
     mRequestedIndex = -1;
     return NS_OK;  // XXX Maybe I can return some other error code?
   }
--- a/docshell/shistory/src/nsSHistory.h
+++ b/docshell/shistory/src/nsSHistory.h
@@ -14,17 +14,17 @@
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
 #include "nsISHTransaction.h"
 #include "nsIWebNavigation.h"
 #include "nsIWeakReference.h"
 #include "nsISimpleEnumerator.h"
 #include "nsISHistoryListener.h"
 #include "nsIHistoryEntry.h"
-#include "nsIObserver.h"
+#include "nsTObserverArray.h"
 
 // Needed to maintain global list of all SHistory objects
 #include "prclist.h"
 
 class nsIDocShell;
 class nsSHEnumerator;
 class nsSHistoryObserver;
 class nsSHistory: public PRCList,
@@ -86,18 +86,18 @@ protected:
   // If aKeepNext is true, aIndex is compared to aIndex + 1,
   // otherwise comparison is done to aIndex - 1.
   bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
 
   nsCOMPtr<nsISHTransaction> mListRoot;
   int32_t mIndex;
   int32_t mLength;
   int32_t mRequestedIndex;
-  // Session History listener
-  nsWeakPtr mListener;
+  // Session History listeners
+  nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
   // Weak reference. Do not refcount this.
   nsIDocShell *  mRootDocShell;
 
   // Max viewers allowed total, across all SHistory objects
   static int32_t  sHistoryMaxTotalViewers;
 };
 //*****************************************************************************
 //***    nsSHEnumerator: Object Management