DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Implementation

Mercurial (4a108e94d3e2)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 *
 * For each import tree there is one master document (the root) and one
 * import manager. The import manager is a map of URI ImportLoader pairs.
 * An ImportLoader is responsible for loading an import document from a
 * given location, and sending out load or error events to all the link
 * nodes that refer to it when it's done. For loading it opens up a
 * channel, using the same CSP as the master document. It then creates a
 * blank document, and starts parsing the data from the channel. When
 * there is no more data on the channel we wait for the DOMContentLoaded
 * event from the parsed document. For the duration of the loading
 * process the scripts on the parent documents are blocked. When an error
 * occurs, or the DOMContentLoaded event is received, the scripts on the
 * parent document are unblocked and we emit the corresponding event on
 * all the referrer link nodes. If a new link node is added to one of the
 * DOM trees in the import tree that refers to an import that was already
 * loaded, the already existing ImportLoader is being used (without
 * loading the referred import document twice) and if necessary the
 * load/error is emitted on it immediately.
 *
 * Ownership model:
 *
 * ImportDocument ----------------------------
 *  ^                                        |
 *  |                                        v
 *  MasterDocument <- ImportManager <-ImportLoader
 *  ^                                        ^
 *  |                                        |
 *  LinkElement <-----------------------------
 *
 */

#ifndef mozilla_dom_ImportManager_h__
#define mozilla_dom_ImportManager_h__

#include "nsTArray.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMEventListener.h"
#include "nsIStreamListener.h"
#include "nsIWeakReferenceUtils.h"
#include "nsRefPtrHashtable.h"
#include "nsScriptLoader.h"
#include "nsURIHashKey.h"

class nsIDocument;
class nsIChannel;
class nsIPrincipal;
class nsINode;
class AutoError;

namespace mozilla {
namespace dom {

class ImportManager;

typedef nsTHashtable<nsPtrHashKey<nsINode>> NodeTable;

class ImportLoader final : public nsIStreamListener
                             , public nsIDOMEventListener
{

  // A helper inner class to decouple the logic of updating the import graph
  // after a new import link has been found by one of the parsers.
  class Updater {

  public:
    explicit Updater(ImportLoader* aLoader) : mLoader(aLoader)
    {}

    // After a new link is added that refers to this import, we
    // have to update the spanning tree, since given this new link the
    // priority of this import might be higher in the scripts
    // execution order than before. It updates mMainReferrer, mImportParent,
    // the corresponding pending ScriptRunners, etc.
    // It also handles updating additional dependant loaders via the
    // UpdateDependants calls.
    // (NOTE: See GetMainReferrer about spanning tree.)
    void UpdateSpanningTree(nsINode* aNode);

  private:
    // Returns an array of links that forms a referring chain from
    // the master document to this import. Each link in the array
    // is marked as main referrer in the list.
    void GetReferrerChain(nsINode* aNode, nsTArray<nsINode*>& aResult);

    // Once we find a new referrer path to our import, we have to see if
    // it changes the load order hence we have to do an update on the graph.
    bool ShouldUpdate(nsTArray<nsINode*>& aNewPath);
    void UpdateMainReferrer(uint32_t newIdx);

    // It's a depth first graph traversal algorithm, for UpdateDependants. The
    // nodes in the graph are the import link elements, and there is a directed
    // edge from link1 to link2 if link2 is a subimport in the import document
    // of link1.
    // If the ImportLoader that aCurrentLink points to didn't need to be updated
    // the algorithm skips its "children" (subimports). Note, that this graph can
    // also contain cycles, aVisistedLinks is used to track the already visited
    // links to avoid an infinite loop.
    // aPath - (in/out) the referrer link chain of aCurrentLink when called, and
    //                  of the next link when the function returns
    // aVisitedLinks - (in/out) list of links that the traversal already visited
    //                          (to handle cycles in the graph)
    // aSkipChildren - when aCurrentLink points to an import that did not need
    //                 to be updated, we can skip its sub-imports ('children')
    nsINode* NextDependant(nsINode* aCurrentLink,
                           nsTArray<nsINode*>& aPath,
                           NodeTable& aVisitedLinks, bool aSkipChildren);

    // When we find a new link that changes the load order of the known imports,
    // we also have to check all the subimports of it, to see if they need an
    // update too. (see test_imports_nested_2.html)
    void UpdateDependants(nsINode* aNode, nsTArray<nsINode*>& aPath);

    ImportLoader* mLoader;
  };

  friend class ::AutoError;
  friend class ImportManager;
  friend class Updater;

public:
  ImportLoader(nsIURI* aURI, nsIDocument* aOriginDocument);

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportLoader, nsIStreamListener)
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSIREQUESTOBSERVER

  // We need to listen to DOMContentLoaded event to know when the document
  // is fully leaded.
  NS_IMETHOD HandleEvent(nsIDOMEvent *aEvent) override;

  // Validation then opening and starting up the channel.
  void Open();
  void AddLinkElement(nsINode* aNode);
  void RemoveLinkElement(nsINode* aNode);
  bool IsReady() { return mReady; }
  bool IsStopped() { return mStopped; }
  bool IsBlocking() { return mBlockingScripts; }

  ImportManager* Manager() {
    MOZ_ASSERT(mDocument || mImportParent, "One of them should be always set");
    return (mDocument ? mDocument : mImportParent)->ImportManager();
  }

  // Simply getter for the import document. Can return a partially parsed
  // document if called too early.
  nsIDocument* GetDocument()
  {
    return mDocument;
  }

  // Getter for the import document that is used in the spec. Returns
  // nullptr if the import is not yet ready.
  nsIDocument* GetImport()
  {
    return mReady ? mDocument : nullptr;
  }

  // There is only one referring link that is marked as primary link per
  // imports. This is the one that has to be taken into account when
  // scrip execution order is determined. Links marked as primary link form
  // a spanning tree in the import graph. (Eliminating the cycles and
  // multiple parents.) This spanning tree is recalculated every time
  // a new import link is added to the manager.
  nsINode* GetMainReferrer()
  {
    return mLinks.IsEmpty() ? nullptr : mLinks[mMainReferrer];
  }

  // An import is not only blocked by its import children, but also
  // by its predecessors. It's enough to find the closest predecessor
  // and wait for that to run its scripts. We keep track of all the
  // ScriptRunners that are waiting for this import. NOTE: updating
  // the main referrer might change this list.
  void AddBlockedScriptLoader(nsScriptLoader* aScriptLoader);
  bool RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader);
  void SetBlockingPredecessor(ImportLoader* aLoader);

private:
  ~ImportLoader() {}

  // If a new referrer LinkElement was added, let's
  // see if we are already finished and if so fire
  // the right event.
  void DispatchEventIfFinished(nsINode* aNode);

  // Dispatch event for a single referrer LinkElement.
  void DispatchErrorEvent(nsINode* aNode);
  void DispatchLoadEvent(nsINode* aNode);

  // Must be called when an error has occured during load.
  void Error(bool aUnblockScripts);

  // Must be called when the import document has been loaded successfully.
  void Done();

  // When the reading from the channel and the parsing
  // of the document is done, we can release the resources
  // that we don't need any longer to hold on.
  void ReleaseResources();

  // While the document is being loaded we must block scripts
  // on the import parent document.
  void BlockScripts();
  void UnblockScripts();

  nsIPrincipal* Principal();

  nsCOMPtr<nsIDocument> mDocument;
  nsCOMPtr<nsIURI> mURI;
  nsCOMPtr<nsIStreamListener> mParserStreamListener;
  nsCOMPtr<nsIDocument> mImportParent;
  ImportLoader* mBlockingPredecessor;

  // List of the LinkElements that are referring to this import
  // we need to keep track of them so we can fire event on them.
  nsTArray<nsCOMPtr<nsINode>> mLinks;

  // List of pending ScriptLoaders that are waiting for this import
  // to finish.
  nsTArray<nsRefPtr<nsScriptLoader>> mBlockedScriptLoaders;

  // There is always exactly one referrer link that is flagged as
  // the main referrer the primary link. This is the one that is
  // used in the script execution order calculation.
  // ("Branch" according to the spec.)
  uint32_t mMainReferrer;
  bool mReady;
  bool mStopped;
  bool mBlockingScripts;
  Updater mUpdater;
};

class ImportManager final : public nsISupports
{
  typedef nsRefPtrHashtable<nsURIHashKey, ImportLoader> ImportMap;

  ~ImportManager() {}

public:
  ImportManager() {}

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS(ImportManager)

  // Finds the ImportLoader that belongs to aImport in the map.
  ImportLoader* Find(nsIDocument* aImport);

  // Find the ImportLoader aLink refers to.
  ImportLoader* Find(nsINode* aLink);

  void AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI);

  // When a new import link is added, this getter either creates
  // a new ImportLoader for it, or returns an existing one if
  // it was already created and in the import map.
  already_AddRefed<ImportLoader> Get(nsIURI* aURI, nsINode* aNode,
                                     nsIDocument* aOriginDocument);

  // It finds the predecessor for an import link node that runs its
  // scripts the latest among its predecessors.
  nsRefPtr<ImportLoader> GetNearestPredecessor(nsINode* aNode);

private:
  ImportMap mImports;
};

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_ImportManager_h__