gecko/chrome/src/nsChromeRegistry.cpp

692 lines
19 KiB
C++
Raw Normal View History

/* -*- 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 <gagan@netscape.com>
* Benjamin Smedberg <benjamin@smedbergs.us>
*
* 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 <string.h>
#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<nsIConsoleService> 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<nsIConsoleService> console
(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
nsCOMPtr<nsIScriptError> 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<nsIChromeRegistry>
nsChromeRegistry::GetService()
{
if (!gChromeRegistry)
{
// We don't actually want this ref, we just want the service to
// initialize if it hasn't already.
nsCOMPtr<nsIChromeRegistry> 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<nsIURL> 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<nsIDOMDocument> domDocument;
aWindow->GetDocument(getter_AddRefs(domDocument));
if (!domDocument)
return;
nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument);
if (!document)
return;
// Annihilate all XBL bindings.
document->FlushSkinBindings();
}
// XXXbsmedberg: move this to nsIWindowMediator
NS_IMETHODIMP nsChromeRegistry::RefreshSkins()
{
nsCOMPtr<nsIWindowMediator> windowMediator
(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
if (!windowMediator)
return NS_OK;
nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
windowMediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator));
PRBool more;
windowEnumerator->HasMoreElements(&more);
while (more) {
nsCOMPtr<nsISupports> protoWindow;
windowEnumerator->GetNext(getter_AddRefs(protoWindow));
if (protoWindow) {
nsCOMPtr<nsIDOMWindowInternal> domWindow = do_QueryInterface(protoWindow);
if (domWindow)
FlushSkinBindingsForWindow(domWindow);
}
windowEnumerator->HasMoreElements(&more);
}
FlushSkinCaches();
windowMediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator));
windowEnumerator->HasMoreElements(&more);
while (more) {
nsCOMPtr<nsISupports> protoWindow;
windowEnumerator->GetNext(getter_AddRefs(protoWindow));
if (protoWindow) {
nsCOMPtr<nsIDOMWindowInternal> domWindow = do_QueryInterface(protoWindow);
if (domWindow)
RefreshWindow(domWindow);
}
windowEnumerator->HasMoreElements(&more);
}
return NS_OK;
}
void
nsChromeRegistry::FlushSkinCaches()
{
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
NS_ASSERTION(obsSvc, "Couldn't get observer service.");
obsSvc->NotifyObservers(static_cast<nsIChromeRegistry*>(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<nsIDOMWindowCollection> frames;
aWindow->GetFrames(getter_AddRefs(frames));
PRUint32 length;
frames->GetLength(&length);
PRUint32 j;
for (j = 0; j < length; j++) {
nsCOMPtr<nsIDOMWindow> childWin;
frames->Item(j, getter_AddRefs(childWin));
nsCOMPtr<nsIDOMWindowInternal> childInt(do_QueryInterface(childWin));
RefreshWindow(childInt);
}
nsresult rv;
// Get the DOM document.
nsCOMPtr<nsIDOMDocument> domDocument;
aWindow->GetDocument(getter_AddRefs(domDocument));
if (!domDocument)
return NS_OK;
nsCOMPtr<nsIDocument> 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<nsIPresShell> shell = document->GetShell();
if (shell) {
// Reload only the chrome URL agent style sheets.
nsCOMArray<nsIStyleSheet> agentSheets;
rv = shell->GetAgentStyleSheets(agentSheets);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMArray<nsIStyleSheet> newAgentSheets;
for (PRInt32 l = 0; l < agentSheets.Count(); ++l) {
nsIStyleSheet *sheet = agentSheets[l];
nsIURI* uri = sheet->GetSheetURI();
if (IsChromeURI(uri)) {
// Reload the sheet.
nsRefPtr<nsCSSStyleSheet> 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<nsIStyleSheet> oldSheets;
nsCOMArray<nsIStyleSheet> 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<nsCSSStyleSheet> sheet = do_QueryObject(oldSheets[i]);
nsIURI* uri = sheet ? sheet->GetOriginalURI() : nsnull;
if (uri && IsChromeURI(uri)) {
// Reload the sheet.
nsRefPtr<nsCSSStyleSheet> 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<nsIObserverService> 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<nsIWindowMediator> windowMediator
(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
if (windowMediator) {
nsCOMPtr<nsISimpleEnumerator> 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<nsISupports> protoWindow;
rv = windowEnumerator->GetNext(getter_AddRefs(protoWindow));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIDOMWindowInternal> domWindow =
do_QueryInterface(protoWindow);
if (domWindow) {
nsCOMPtr<nsIDOMLocation> 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<nsIURL> 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<nsIURL> 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<nsIURL> 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>
nsChromeRegistry::GetSingleton()
{
if (gChromeRegistry) {
NS_ADDREF(gChromeRegistry);
return gChromeRegistry;
}
nsRefPtr<nsChromeRegistry> 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();
}