nsresult
nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
                                      nsIDocument* aBoundDocument,
                                      nsIURI* aBindingURI,
                                      nsIPrincipal* aOriginPrincipal,
                                      bool aForceSyncLoad,
                                      nsXBLDocumentInfo** aResult)
{
  NS_PRECONDITION(aBindingURI, "Must have a binding URI");
  NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
                  "If we're doing a security check, we better have a document!");

  *aResult = nullptr;
  // Allow XBL in unprivileged documents if it's specified in a privileged or
  // chrome: stylesheet. This allows themes to specify XBL bindings.
  if (aOriginPrincipal && !IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
    NS_ENSURE_TRUE(!aBoundDocument || aBoundDocument->AllowXULXBL(),
                   NS_ERROR_XBL_BLOCKED);
  }

  RefPtr<nsXBLDocumentInfo> info;

  nsCOMPtr<nsIURI> documentURI;
  nsresult rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
  NS_ENSURE_SUCCESS(rv, rv);

#ifdef MOZ_XUL
  // We've got a file.  Check our XBL document cache.
  nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
  bool useXULCache = cache && cache->IsEnabled();

  if (useXULCache) {
    // The first line of defense is the chrome cache.
    // This cache crosses the entire product, so that any XBL bindings that are
    // part of chrome will be reused across all XUL documents.
    info = cache->GetXBLDocumentInfo(documentURI);
  }
#endif

  if (!info) {
    // The second line of defense is the binding manager's document table.
    nsBindingManager *bindingManager = nullptr;

    if (aBoundDocument) {
      bindingManager = aBoundDocument->BindingManager();
      info = bindingManager->GetXBLDocumentInfo(documentURI);
      if (aBoundDocument->IsStaticDocument() &&
          IsChromeOrResourceURI(aBindingURI)) {
        aForceSyncLoad = true;
      }
    }

    NodeInfo *ni = nullptr;
    if (aBoundElement)
      ni = aBoundElement->NodeInfo();

    if (!info && bindingManager &&
        (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
                  ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
                  ((ni->Equals(nsGkAtoms::input) ||
                    ni->Equals(nsGkAtoms::select)) &&
                   aBoundElement->IsHTMLElement()))) && !aForceSyncLoad) {
      // The third line of defense is to investigate whether or not the
      // document is currently being loaded asynchronously.  If so, there's no
      // document yet, but we need to glom on our request so that it will be
      // processed whenever the doc does finish loading.
      nsCOMPtr<nsIStreamListener> listener;
      if (bindingManager)
        listener = bindingManager->GetLoadingDocListener(documentURI);
      if (listener) {
        nsXBLStreamListener* xblListener =
          static_cast<nsXBLStreamListener*>(listener.get());
        // Create a new load observer.
        if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
          nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
          xblListener->AddRequest(req);
        }
        return NS_OK;
      }
    }

#ifdef MOZ_XUL
    // Next, look in the startup cache
    bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
    if (!info && useStartupCache) {
      rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
      if (NS_SUCCEEDED(rv)) {
        cache->PutXBLDocumentInfo(info);

        if (bindingManager) {
          // Cache it in our binding manager's document table.
          bindingManager->PutXBLDocumentInfo(info);
        }
      }
    }
#endif

    if (!info) {
      // Finally, if all lines of defense fail, we go and fetch the binding
      // document.

      // Always load chrome synchronously
      bool chrome;
      if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
        aForceSyncLoad = true;

      nsCOMPtr<nsIDocument> document;
      rv = FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
                                aBindingURI, aOriginPrincipal, aForceSyncLoad,
                                getter_AddRefs(document));
      NS_ENSURE_SUCCESS(rv, rv);

      if (document) {
        nsBindingManager *xblDocBindingManager = document->BindingManager();
        info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
        if (!info) {
          NS_ERROR("An XBL file is malformed.  Did you forget the XBL namespace on the bindings tag?");
          return NS_ERROR_FAILURE;
        }
        xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.

        // If the doc is a chrome URI, then we put it into the XUL cache.
#ifdef MOZ_XUL
        if (useStartupCache) {
          cache->PutXBLDocumentInfo(info);

          // now write the bindings into the startup cache
          info->WritePrototypeBindings();
        }
#endif

        if (bindingManager) {
          // Also put it in our binding manager's document table.
          bindingManager->PutXBLDocumentInfo(info);
        }
      }
    }
  }

  info.forget(aResult);

  return NS_OK;
}
nsresult
nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
                                      nsIDocument* aBoundDocument,
                                      nsIURI* aBindingURI,
                                      nsIPrincipal* aOriginPrincipal,
                                      bool aForceSyncLoad,
                                      nsXBLDocumentInfo** aResult)
{
  NS_PRECONDITION(aBindingURI, "Must have a binding URI");
  NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
                  "If we're doing a security check, we better have a document!");

  nsresult rv;
  if (aOriginPrincipal) {
    // Security check - Enforce same-origin policy, except to chrome.
    // We have to be careful to not pass aContent as the context here.
    // Otherwise, if there is a JS-implemented content policy, we will attempt
    // to wrap the content node, which will try to load XBL bindings for it, if
    // any. Since we're not done loading this binding yet, that will reenter
    // this method and we'll end up creating a binding and then immediately
    // clobbering it in our table.  That makes things very confused, leading to
    // misbehavior and crashes.
    rv = nsContentUtils::
      CheckSecurityBeforeLoad(aBindingURI, aOriginPrincipal,
                              nsIScriptSecurityManager::ALLOW_CHROME,
                              gAllowDataURIs,
                              nsIContentPolicy::TYPE_XBL,
                              aBoundDocument);
    NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);

    if (!IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
      // Also make sure that we're same-origin with the bound document
      // except if the stylesheet has the system principal.
      if (!(gAllowDataURIs && SchemeIs(aBindingURI, "data")) &&
          !SchemeIs(aBindingURI, "chrome")) {
        rv = aBoundDocument->NodePrincipal()->CheckMayLoad(aBindingURI,
                                                           true, false);
        NS_ENSURE_SUCCESS(rv, NS_ERROR_XBL_BLOCKED);
      }

      // Finally check if this document is allowed to use XBL at all.
      NS_ENSURE_TRUE(aBoundDocument->AllowXULXBL(),
                     NS_ERROR_XBL_BLOCKED);
    }
  }

  *aResult = nullptr;
  nsRefPtr<nsXBLDocumentInfo> info;

  nsCOMPtr<nsIURI> documentURI;
  rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
  NS_ENSURE_SUCCESS(rv, rv);

#ifdef MOZ_XUL
  // We've got a file.  Check our XBL document cache.
  nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
  bool useXULCache = cache && cache->IsEnabled();

  if (useXULCache) {
    // The first line of defense is the chrome cache.
    // This cache crosses the entire product, so that any XBL bindings that are
    // part of chrome will be reused across all XUL documents.
    info = cache->GetXBLDocumentInfo(documentURI);
  }
#endif

  if (!info) {
    // The second line of defense is the binding manager's document table.
    nsBindingManager *bindingManager = nullptr;

    if (aBoundDocument) {
      bindingManager = aBoundDocument->BindingManager();
      info = bindingManager->GetXBLDocumentInfo(documentURI);
      if (aBoundDocument->IsStaticDocument() &&
          IsChromeOrResourceURI(aBindingURI)) {
        aForceSyncLoad = true;
      }
    }

    NodeInfo *ni = nullptr;
    if (aBoundElement)
      ni = aBoundElement->NodeInfo();

    if (!info && bindingManager &&
        (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
                  ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
                  ((ni->Equals(nsGkAtoms::input) ||
                    ni->Equals(nsGkAtoms::select)) &&
                   aBoundElement->IsHTML()))) && !aForceSyncLoad) {
      // The third line of defense is to investigate whether or not the
      // document is currently being loaded asynchronously.  If so, there's no
      // document yet, but we need to glom on our request so that it will be
      // processed whenever the doc does finish loading.
      nsCOMPtr<nsIStreamListener> listener;
      if (bindingManager)
        listener = bindingManager->GetLoadingDocListener(documentURI);
      if (listener) {
        nsXBLStreamListener* xblListener =
          static_cast<nsXBLStreamListener*>(listener.get());
        // Create a new load observer.
        if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
          nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
          xblListener->AddRequest(req);
        }
        return NS_OK;
      }
    }

#ifdef MOZ_XUL
    // Next, look in the startup cache
    bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
    if (!info && useStartupCache) {
      rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
      if (NS_SUCCEEDED(rv)) {
        cache->PutXBLDocumentInfo(info);

        if (bindingManager) {
          // Cache it in our binding manager's document table.
          bindingManager->PutXBLDocumentInfo(info);
        }
      }
    }
#endif

    if (!info) {
      // Finally, if all lines of defense fail, we go and fetch the binding
      // document.

      // Always load chrome synchronously
      bool chrome;
      if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
        aForceSyncLoad = true;

      nsCOMPtr<nsIDocument> document;
      FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
                           aBindingURI, aOriginPrincipal, aForceSyncLoad,
                           getter_AddRefs(document));

      if (document) {
        nsBindingManager *xblDocBindingManager = document->BindingManager();
        info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
        if (!info) {
          NS_ERROR("An XBL file is malformed.  Did you forget the XBL namespace on the bindings tag?");
          return NS_ERROR_FAILURE;
        }
        xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.

        // If the doc is a chrome URI, then we put it into the XUL cache.
#ifdef MOZ_XUL
        if (useStartupCache) {
          cache->PutXBLDocumentInfo(info);

          // now write the bindings into the startup cache
          info->WritePrototypeBindings();
        }
#endif

        if (bindingManager) {
          // Also put it in our binding manager's document table.
          bindingManager->PutXBLDocumentInfo(info);
        }
      }
    }
  }

  info.forget(aResult);

  return NS_OK;
}