michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "DOMStorageObserver.h" michael@0: michael@0: #include "DOMStorageDBThread.h" michael@0: #include "DOMStorageCache.h" michael@0: michael@0: #include "nsIObserverService.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIPermission.h" michael@0: #include "nsIIDNService.h" michael@0: #include "mozIApplicationClearPrivateDataParams.h" michael@0: #include "nsICookiePermission.h" michael@0: michael@0: #include "nsPrintfCString.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsEscape.h" michael@0: #include "nsNetCID.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: static const char kStartupTopic[] = "sessionstore-windows-restored"; michael@0: static const uint32_t kStartupDelay = 0; michael@0: michael@0: NS_IMPL_ISUPPORTS(DOMStorageObserver, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: DOMStorageObserver* DOMStorageObserver::sSelf = nullptr; michael@0: michael@0: extern nsresult michael@0: CreateReversedDomain(const nsACString& aAsciiDomain, nsACString& aKey); michael@0: michael@0: // static michael@0: nsresult michael@0: DOMStorageObserver::Init() michael@0: { michael@0: if (sSelf) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (!obs) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: sSelf = new DOMStorageObserver(); michael@0: NS_ADDREF(sSelf); michael@0: michael@0: // Chrome clear operations. michael@0: obs->AddObserver(sSelf, kStartupTopic, true); michael@0: obs->AddObserver(sSelf, "cookie-changed", true); michael@0: obs->AddObserver(sSelf, "perm-changed", true); michael@0: obs->AddObserver(sSelf, "browser:purge-domain-data", true); michael@0: obs->AddObserver(sSelf, "last-pb-context-exited", true); michael@0: obs->AddObserver(sSelf, "webapps-clear-data", true); michael@0: michael@0: // Shutdown michael@0: obs->AddObserver(sSelf, "profile-after-change", true); michael@0: obs->AddObserver(sSelf, "profile-before-change", true); michael@0: obs->AddObserver(sSelf, "xpcom-shutdown", true); michael@0: michael@0: // Observe low device storage notifications. michael@0: obs->AddObserver(sSelf, "disk-space-watcher", true); michael@0: michael@0: #ifdef DOM_STORAGE_TESTS michael@0: // Testing michael@0: obs->AddObserver(sSelf, "domstorage-test-flush-force", true); michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // Only to forward to child process. michael@0: obs->AddObserver(sSelf, "domstorage-test-flushed", true); michael@0: } michael@0: michael@0: obs->AddObserver(sSelf, "domstorage-test-reload", true); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: DOMStorageObserver::Shutdown() michael@0: { michael@0: if (!sSelf) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: NS_RELEASE(sSelf); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: DOMStorageObserver::AddSink(DOMStorageObserverSink* aObs) michael@0: { michael@0: mSinks.AppendElement(aObs); michael@0: } michael@0: michael@0: void michael@0: DOMStorageObserver::RemoveSink(DOMStorageObserverSink* aObs) michael@0: { michael@0: mSinks.RemoveElement(aObs); michael@0: } michael@0: michael@0: void michael@0: DOMStorageObserver::Notify(const char* aTopic, const nsACString& aData) michael@0: { michael@0: for (uint32_t i = 0; i < mSinks.Length(); ++i) { michael@0: DOMStorageObserverSink* sink = mSinks[i]; michael@0: sink->Observe(aTopic, aData); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorageObserver::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Start the thread that opens the database. michael@0: if (!strcmp(aTopic, kStartupTopic)) { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: obs->RemoveObserver(this, kStartupTopic); michael@0: michael@0: mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: if (!mDBThreadStartDelayTimer) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Timer callback used to start the database a short timer after startup michael@0: if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { michael@0: nsCOMPtr timer = do_QueryInterface(aSubject); michael@0: if (!timer) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (timer == mDBThreadStartDelayTimer) { michael@0: mDBThreadStartDelayTimer = nullptr; michael@0: michael@0: DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); michael@0: NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Clear everything, caches + database michael@0: if (!strcmp(aTopic, "cookie-changed")) { michael@0: if (!NS_LITERAL_STRING("cleared").Equals(aData)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); michael@0: NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); michael@0: michael@0: db->AsyncClearAll(); michael@0: michael@0: Notify("cookie-cleared"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Clear from caches everything that has been stored michael@0: // while in session-only mode michael@0: if (!strcmp(aTopic, "perm-changed")) { michael@0: // Check for cookie permission change michael@0: nsCOMPtr perm(do_QueryInterface(aSubject)); michael@0: if (!perm) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString type; michael@0: perm->GetType(type); michael@0: if (type != NS_LITERAL_CSTRING("cookie")) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t cap = 0; michael@0: perm->GetCapability(&cap); michael@0: if (!(cap & nsICookiePermission::ACCESS_SESSION) || michael@0: !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString host; michael@0: perm->GetHost(host); michael@0: if (host.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString scope; michael@0: rv = CreateReversedDomain(host, scope); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: Notify("session-only-cleared", scope); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Clear everything (including so and pb data) from caches and database michael@0: // for the gived domain and subdomains. michael@0: if (!strcmp(aTopic, "browser:purge-domain-data")) { michael@0: // Convert the domain name to the ACE format michael@0: nsAutoCString aceDomain; michael@0: nsCOMPtr converter = do_GetService(NS_IDNSERVICE_CONTRACTID); michael@0: if (converter) { michael@0: rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: // In case the IDN service is not available, this is the best we can come up with! michael@0: NS_EscapeURL(NS_ConvertUTF16toUTF8(aData), michael@0: esc_OnlyNonASCII | esc_AlwaysCopy, michael@0: aceDomain); michael@0: } michael@0: michael@0: nsAutoCString scopePrefix; michael@0: rv = CreateReversedDomain(aceDomain, scopePrefix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); michael@0: NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); michael@0: michael@0: db->AsyncClearMatchingScope(scopePrefix); michael@0: michael@0: Notify("domain-data-cleared", scopePrefix); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Clear all private-browsing caches michael@0: if (!strcmp(aTopic, "last-pb-context-exited")) { michael@0: Notify("private-browsing-data-cleared"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Clear data beloging to an app. michael@0: if (!strcmp(aTopic, "webapps-clear-data")) { michael@0: nsCOMPtr params = michael@0: do_QueryInterface(aSubject); michael@0: if (!params) { michael@0: NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: uint32_t appId; michael@0: bool browserOnly; michael@0: michael@0: rv = params->GetAppId(&appId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = params->GetBrowserOnly(&browserOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID); michael@0: michael@0: DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); michael@0: NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoCString scope; michael@0: scope.AppendInt(appId); michael@0: scope.Append(NS_LITERAL_CSTRING(":t:")); michael@0: db->AsyncClearMatchingScope(scope); michael@0: Notify("app-data-cleared", scope); michael@0: michael@0: if (!browserOnly) { michael@0: scope.Truncate(); michael@0: scope.AppendInt(appId); michael@0: scope.Append(NS_LITERAL_CSTRING(":f:")); michael@0: db->AsyncClearMatchingScope(scope); michael@0: Notify("app-data-cleared", scope); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "profile-after-change")) { michael@0: Notify("profile-change"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "profile-before-change") || michael@0: !strcmp(aTopic, "xpcom-shutdown")) { michael@0: rv = DOMStorageCache::StopDatabase(); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Error while stopping DOMStorage DB background thread"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "disk-space-watcher")) { michael@0: if (NS_LITERAL_STRING("full").Equals(aData)) { michael@0: Notify("low-disk-space"); michael@0: } else if (NS_LITERAL_STRING("free").Equals(aData)) { michael@0: Notify("no-low-disk-space"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DOM_STORAGE_TESTS michael@0: if (!strcmp(aTopic, "domstorage-test-flush-force")) { michael@0: DOMStorageDBBridge* db = DOMStorageCache::GetDatabase(); michael@0: if (db) { michael@0: db->AsyncFlush(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "domstorage-test-flushed")) { michael@0: // Only used to propagate to IPC children michael@0: Notify("test-flushed"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "domstorage-test-reload")) { michael@0: Notify("test-reload"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: NS_ERROR("Unexpected topic"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: } // ::dom michael@0: } // ::mozilla