/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et tw=78: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Original Author: David W. Hyatt (hyatt@netscape.com) * Gagan Saksena * Benjamin Smedberg * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsChromeRegistry.h" #include "nsChromeRegistryChrome.h" #ifdef MOZ_IPC #include "nsChromeRegistryContent.h" #endif #include #include "prio.h" #include "prprf.h" #include "nsCOMPtr.h" #include "nsDOMError.h" #include "nsEscape.h" #include "nsLayoutCID.h" #include "nsNetUtil.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsCSSStyleSheet.h" #include "nsIConsoleService.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIDocShell.h" #include "nsIDOMElement.h" #include "nsIDOMLocation.h" #include "nsIDOMWindowCollection.h" #include "nsIDOMWindowInternal.h" #include "nsIIOService.h" #include "nsIJARProtocolHandler.h" #include "nsIObserverService.h" #include "nsIPresShell.h" #include "nsIProtocolHandler.h" #include "nsIScriptError.h" #include "nsIWindowMediator.h" nsChromeRegistry* nsChromeRegistry::gChromeRegistry; //////////////////////////////////////////////////////////////////////////////// void nsChromeRegistry::LogMessage(const char* aMsg, ...) { nsCOMPtr console (do_GetService(NS_CONSOLESERVICE_CONTRACTID)); if (!console) return; va_list args; va_start(args, aMsg); char* formatted = PR_vsmprintf(aMsg, args); va_end(args); if (!formatted) return; console->LogStringMessage(NS_ConvertUTF8toUTF16(formatted).get()); PR_smprintf_free(formatted); } void nsChromeRegistry::LogMessageWithContext(nsIURI* aURL, PRUint32 aLineNumber, PRUint32 flags, const char* aMsg, ...) { nsresult rv; nsCOMPtr console (do_GetService(NS_CONSOLESERVICE_CONTRACTID)); nsCOMPtr error (do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); if (!console || !error) return; va_list args; va_start(args, aMsg); char* formatted = PR_vsmprintf(aMsg, args); va_end(args); if (!formatted) return; nsCString spec; if (aURL) aURL->GetSpec(spec); rv = error->Init(NS_ConvertUTF8toUTF16(formatted).get(), NS_ConvertUTF8toUTF16(spec).get(), nsnull, aLineNumber, 0, flags, "chrome registration"); PR_smprintf_free(formatted); if (NS_FAILED(rv)) return; console->LogMessage(error); } nsChromeRegistry::~nsChromeRegistry() { gChromeRegistry = nsnull; } NS_INTERFACE_MAP_BEGIN(nsChromeRegistry) NS_INTERFACE_MAP_ENTRY(nsIChromeRegistry) NS_INTERFACE_MAP_ENTRY(nsIXULChromeRegistry) NS_INTERFACE_MAP_ENTRY(nsIToolkitChromeRegistry) #ifdef MOZ_XUL NS_INTERFACE_MAP_ENTRY(nsIXULOverlayProvider) #endif NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChromeRegistry) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(nsChromeRegistry) NS_IMPL_RELEASE(nsChromeRegistry) //////////////////////////////////////////////////////////////////////////////// // nsIChromeRegistry methods: already_AddRefed nsChromeRegistry::GetService() { if (!gChromeRegistry) { // We don't actually want this ref, we just want the service to // initialize if it hasn't already. nsCOMPtr reg( do_GetService(NS_CHROMEREGISTRY_CONTRACTID)); if (!gChromeRegistry) return NULL; } NS_ADDREF(gChromeRegistry); return gChromeRegistry; } nsresult nsChromeRegistry::Init() { if (!mOverrideTable.Init()) return NS_ERROR_FAILURE; // This initialization process is fairly complicated and may cause reentrant // getservice calls to resolve chrome URIs (especially locale files). We // don't want that, so we inform the protocol handler about our existence // before we are actually fully initialized. gChromeRegistry = this; mInitialized = PR_TRUE; return NS_OK; } nsresult nsChromeRegistry::GetProviderAndPath(nsIURL* aChromeURL, nsACString& aProvider, nsACString& aPath) { nsresult rv; #ifdef DEBUG PRBool isChrome; aChromeURL->SchemeIs("chrome", &isChrome); NS_ASSERTION(isChrome, "Non-chrome URI?"); #endif nsCAutoString path; rv = aChromeURL->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); if (path.Length() < 3) { LogMessage("Invalid chrome URI: %s", path.get()); return NS_ERROR_FAILURE; } path.SetLength(nsUnescapeCount(path.BeginWriting())); NS_ASSERTION(path.First() == '/', "Path should always begin with a slash!"); PRInt32 slash = path.FindChar('/', 1); if (slash == 1) { LogMessage("Invalid chrome URI: %s", path.get()); return NS_ERROR_FAILURE; } if (slash == -1) { aPath.Truncate(); } else { if (slash == (PRInt32) path.Length() - 1) aPath.Truncate(); else aPath.Assign(path.get() + slash + 1, path.Length() - slash - 1); --slash; } aProvider.Assign(path.get() + 1, slash); return NS_OK; } nsresult nsChromeRegistry::Canonify(nsIURL* aChromeURL) { NS_NAMED_LITERAL_CSTRING(kSlash, "/"); nsresult rv; nsCAutoString provider, path; rv = GetProviderAndPath(aChromeURL, provider, path); NS_ENSURE_SUCCESS(rv, rv); if (path.IsEmpty()) { nsCAutoString package; rv = aChromeURL->GetHost(package); NS_ENSURE_SUCCESS(rv, rv); // we re-use the "path" local string to build a new URL path path.Assign(kSlash + provider + kSlash + package); if (provider.EqualsLiteral("content")) { path.AppendLiteral(".xul"); } else if (provider.EqualsLiteral("locale")) { path.AppendLiteral(".dtd"); } else if (provider.EqualsLiteral("skin")) { path.AppendLiteral(".css"); } else { return NS_ERROR_INVALID_ARG; } aChromeURL->SetPath(path); } else { // prevent directory traversals ("..") // path is already unescaped once, but uris can get unescaped twice const char* pos = path.BeginReading(); const char* end = path.EndReading(); while (pos < end) { switch (*pos) { case ':': return NS_ERROR_DOM_BAD_URI; case '.': if (pos[1] == '.') return NS_ERROR_DOM_BAD_URI; break; case '%': // chrome: URIs with double-escapes are trying to trick us. // watch for %2e, and %25 in case someone triple unescapes if (pos[1] == '2' && ( pos[2] == 'e' || pos[2] == 'E' || pos[2] == '5' )) return NS_ERROR_DOM_BAD_URI; break; case '?': case '#': pos = end; continue; } ++pos; } } return NS_OK; } NS_IMETHODIMP nsChromeRegistry::ConvertChromeURL(nsIURI* aChromeURI, nsIURI* *aResult) { nsresult rv; NS_ASSERTION(aChromeURI, "null url!"); if (mOverrideTable.Get(aChromeURI, aResult)) return NS_OK; nsCOMPtr chromeURL (do_QueryInterface(aChromeURI)); NS_ENSURE_TRUE(chromeURL, NS_NOINTERFACE); nsCAutoString package, provider, path; rv = chromeURL->GetHostPort(package); NS_ENSURE_SUCCESS(rv, rv); rv = GetProviderAndPath(chromeURL, provider, path); NS_ENSURE_SUCCESS(rv, rv); nsIURI* baseURI = GetBaseURIFromPackage(package, provider, path); PRUint32 flags; rv = GetFlagsFromPackage(package, &flags); if (NS_FAILED(rv)) return rv; if (flags & PLATFORM_PACKAGE) { #if defined(XP_WIN) || defined(XP_OS2) path.Insert("win/", 0); #elif defined(XP_MACOSX) path.Insert("mac/", 0); #else path.Insert("unix/", 0); #endif } if (!baseURI) { LogMessage("No chrome package registered for chrome://%s/%s/%s", package.get(), provider.get(), path.get()); return NS_ERROR_FAILURE; } return NS_NewURI(aResult, path, nsnull, baseURI); } //////////////////////////////////////////////////////////////////////// // theme stuff static void FlushSkinBindingsForWindow(nsIDOMWindowInternal* aWindow) { // Get the DOM document. nsCOMPtr domDocument; aWindow->GetDocument(getter_AddRefs(domDocument)); if (!domDocument) return; nsCOMPtr document = do_QueryInterface(domDocument); if (!document) return; // Annihilate all XBL bindings. document->FlushSkinBindings(); } // XXXbsmedberg: move this to nsIWindowMediator NS_IMETHODIMP nsChromeRegistry::RefreshSkins() { nsCOMPtr windowMediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); if (!windowMediator) return NS_OK; nsCOMPtr windowEnumerator; windowMediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator)); PRBool more; windowEnumerator->HasMoreElements(&more); while (more) { nsCOMPtr protoWindow; windowEnumerator->GetNext(getter_AddRefs(protoWindow)); if (protoWindow) { nsCOMPtr domWindow = do_QueryInterface(protoWindow); if (domWindow) FlushSkinBindingsForWindow(domWindow); } windowEnumerator->HasMoreElements(&more); } FlushSkinCaches(); windowMediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator)); windowEnumerator->HasMoreElements(&more); while (more) { nsCOMPtr protoWindow; windowEnumerator->GetNext(getter_AddRefs(protoWindow)); if (protoWindow) { nsCOMPtr domWindow = do_QueryInterface(protoWindow); if (domWindow) RefreshWindow(domWindow); } windowEnumerator->HasMoreElements(&more); } return NS_OK; } void nsChromeRegistry::FlushSkinCaches() { nsCOMPtr obsSvc = mozilla::services::GetObserverService(); NS_ASSERTION(obsSvc, "Couldn't get observer service."); obsSvc->NotifyObservers(static_cast(this), NS_CHROME_FLUSH_SKINS_TOPIC, nsnull); } static PRBool IsChromeURI(nsIURI* aURI) { PRBool isChrome=PR_FALSE; if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) return PR_TRUE; return PR_FALSE; } // XXXbsmedberg: move this to windowmediator nsresult nsChromeRegistry::RefreshWindow(nsIDOMWindowInternal* aWindow) { // Deal with our subframes first. nsCOMPtr frames; aWindow->GetFrames(getter_AddRefs(frames)); PRUint32 length; frames->GetLength(&length); PRUint32 j; for (j = 0; j < length; j++) { nsCOMPtr childWin; frames->Item(j, getter_AddRefs(childWin)); nsCOMPtr childInt(do_QueryInterface(childWin)); RefreshWindow(childInt); } nsresult rv; // Get the DOM document. nsCOMPtr domDocument; aWindow->GetDocument(getter_AddRefs(domDocument)); if (!domDocument) return NS_OK; nsCOMPtr document = do_QueryInterface(domDocument); if (!document) return NS_OK; // Deal with the agent sheets first. Have to do all the style sets by hand. nsCOMPtr shell = document->GetShell(); if (shell) { // Reload only the chrome URL agent style sheets. nsCOMArray agentSheets; rv = shell->GetAgentStyleSheets(agentSheets); NS_ENSURE_SUCCESS(rv, rv); nsCOMArray newAgentSheets; for (PRInt32 l = 0; l < agentSheets.Count(); ++l) { nsIStyleSheet *sheet = agentSheets[l]; nsIURI* uri = sheet->GetSheetURI(); if (IsChromeURI(uri)) { // Reload the sheet. nsRefPtr newSheet; rv = document->LoadChromeSheetSync(uri, PR_TRUE, getter_AddRefs(newSheet)); if (NS_FAILED(rv)) return rv; if (newSheet) { rv = newAgentSheets.AppendObject(newSheet) ? NS_OK : NS_ERROR_FAILURE; if (NS_FAILED(rv)) return rv; } } else { // Just use the same sheet. rv = newAgentSheets.AppendObject(sheet) ? NS_OK : NS_ERROR_FAILURE; if (NS_FAILED(rv)) return rv; } } rv = shell->SetAgentStyleSheets(newAgentSheets); NS_ENSURE_SUCCESS(rv, rv); } // Build an array of nsIURIs of style sheets we need to load. nsCOMArray oldSheets; nsCOMArray newSheets; PRInt32 count = document->GetNumberOfStyleSheets(); // Iterate over the style sheets. PRInt32 i; for (i = 0; i < count; i++) { // Get the style sheet nsIStyleSheet *styleSheet = document->GetStyleSheetAt(i); if (!oldSheets.AppendObject(styleSheet)) { return NS_ERROR_OUT_OF_MEMORY; } } // Iterate over our old sheets and kick off a sync load of the new // sheet if and only if it's a chrome URL. for (i = 0; i < count; i++) { nsRefPtr sheet = do_QueryObject(oldSheets[i]); nsIURI* uri = sheet ? sheet->GetOriginalURI() : nsnull; if (uri && IsChromeURI(uri)) { // Reload the sheet. nsRefPtr newSheet; // XXX what about chrome sheets that have a title or are disabled? This // only works by sheer dumb luck. document->LoadChromeSheetSync(uri, PR_FALSE, getter_AddRefs(newSheet)); // Even if it's null, we put in in there. newSheets.AppendObject(newSheet); } else { // Just use the same sheet. newSheets.AppendObject(sheet); } } // Now notify the document that multiple sheets have been added and removed. document->UpdateStyleSheets(oldSheets, newSheets); return NS_OK; } void nsChromeRegistry::FlushAllCaches() { nsCOMPtr obsSvc = mozilla::services::GetObserverService(); NS_ASSERTION(obsSvc, "Couldn't get observer service."); obsSvc->NotifyObservers((nsIChromeRegistry*) this, NS_CHROME_FLUSH_TOPIC, nsnull); } // xxxbsmedberg Move me to nsIWindowMediator NS_IMETHODIMP nsChromeRegistry::ReloadChrome() { UpdateSelectedLocale(); FlushAllCaches(); // Do a reload of all top level windows. nsresult rv = NS_OK; // Get the window mediator nsCOMPtr windowMediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); if (windowMediator) { nsCOMPtr windowEnumerator; rv = windowMediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator)); if (NS_SUCCEEDED(rv)) { // Get each dom window PRBool more; rv = windowEnumerator->HasMoreElements(&more); if (NS_FAILED(rv)) return rv; while (more) { nsCOMPtr protoWindow; rv = windowEnumerator->GetNext(getter_AddRefs(protoWindow)); if (NS_SUCCEEDED(rv)) { nsCOMPtr domWindow = do_QueryInterface(protoWindow); if (domWindow) { nsCOMPtr location; domWindow->GetLocation(getter_AddRefs(location)); if (location) { rv = location->Reload(PR_FALSE); if (NS_FAILED(rv)) return rv; } } } rv = windowEnumerator->HasMoreElements(&more); if (NS_FAILED(rv)) return rv; } } } return rv; } NS_IMETHODIMP nsChromeRegistry::AllowScriptsForPackage(nsIURI* aChromeURI, PRBool *aResult) { nsresult rv; *aResult = PR_FALSE; #ifdef DEBUG PRBool isChrome; aChromeURI->SchemeIs("chrome", &isChrome); NS_ASSERTION(isChrome, "Non-chrome URI passed to AllowScriptsForPackage!"); #endif nsCOMPtr url (do_QueryInterface(aChromeURI)); NS_ENSURE_TRUE(url, NS_NOINTERFACE); nsCAutoString provider, file; rv = GetProviderAndPath(url, provider, file); NS_ENSURE_SUCCESS(rv, rv); if (!provider.EqualsLiteral("skin")) *aResult = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsChromeRegistry::AllowContentToAccess(nsIURI *aURI, PRBool *aResult) { nsresult rv; *aResult = PR_FALSE; #ifdef DEBUG PRBool isChrome; aURI->SchemeIs("chrome", &isChrome); NS_ASSERTION(isChrome, "Non-chrome URI passed to AllowContentToAccess!"); #endif nsCOMPtr url = do_QueryInterface(aURI); if (!url) { NS_ERROR("Chrome URL doesn't implement nsIURL."); return NS_ERROR_UNEXPECTED; } nsCAutoString package; rv = url->GetHostPort(package); NS_ENSURE_SUCCESS(rv, rv); PRUint32 flags; rv = GetFlagsFromPackage(package, &flags); if (NS_SUCCEEDED(rv)) { *aResult = !!(flags & CONTENT_ACCESSIBLE); } return NS_OK; } NS_IMETHODIMP_(PRBool) nsChromeRegistry::WrappersEnabled(nsIURI *aURI) { nsCOMPtr chromeURL (do_QueryInterface(aURI)); if (!chromeURL) return PR_FALSE; PRBool isChrome = PR_FALSE; nsresult rv = chromeURL->SchemeIs("chrome", &isChrome); if (NS_FAILED(rv) || !isChrome) return PR_FALSE; nsCAutoString package; rv = chromeURL->GetHostPort(package); if (NS_FAILED(rv)) return PR_FALSE; PRUint32 flags; rv = GetFlagsFromPackage(package, &flags); return NS_SUCCEEDED(rv) && (flags & XPCNATIVEWRAPPERS); } already_AddRefed nsChromeRegistry::GetSingleton() { if (gChromeRegistry) { NS_ADDREF(gChromeRegistry); return gChromeRegistry; } nsRefPtr cr; #ifdef MOZ_IPC if (GeckoProcessType_Content == XRE_GetProcessType()) cr = new nsChromeRegistryContent(); else #endif cr = new nsChromeRegistryChrome(); if (NS_FAILED(cr->Init())) return NULL; return cr.forget(); }