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; }