/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set sw=4 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "base/basictypes.h" #include "ContentParent.h" #if defined(ANDROID) || defined(LINUX) # include # include #endif #include "chrome/common/process_watcher.h" #include "CrashReporterParent.h" #include "History.h" #include "IDBFactory.h" #include "IndexedDBParent.h" #include "IndexedDatabaseManager.h" #include "mozIApplication.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/ExternalHelperAppParent.h" #include "mozilla/dom/PMemoryReportRequestParent.h" #include "mozilla/dom/StorageParent.h" #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h" #include "mozilla/dom/sms/SmsParent.h" #include "mozilla/hal_sandbox/PHalParent.h" #include "mozilla/ipc/TestShellParent.h" #include "mozilla/layers/CompositorParent.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/Util.h" #include "mozilla/unused.h" #include "nsAppDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nsAppRunner.h" #include "nsAutoPtr.h" #include "nsCExternalHandlerService.h" #include "nsCOMPtr.h" #include "nsChromeRegistryChrome.h" #include "nsConsoleMessage.h" #include "nsDebugImpl.h" #include "nsDirectoryServiceDefs.h" #include "nsDOMFile.h" #include "nsExternalHelperAppService.h" #include "nsFrameMessageManager.h" #include "nsHashPropertyBag.h" #include "nsIAlertsService.h" #include "nsIAppsService.h" #include "nsIClipboard.h" #include "nsIConsoleService.h" #include "nsIDOMApplicationRegistry.h" #include "nsIDOMGeoGeolocation.h" #include "nsIDOMWindow.h" #include "nsIFilePicker.h" #include "nsIMemoryReporter.h" #include "nsIObserverService.h" #include "nsIPresShell.h" #include "nsIRemoteBlob.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsISupportsPrimitives.h" #include "nsIWindowWatcher.h" #include "nsMemoryReporterManager.h" #include "nsServiceManagerUtils.h" #include "nsSystemInfo.h" #include "nsThreadUtils.h" #include "nsToolkitCompsCID.h" #include "nsWidgetsCID.h" #include "SandboxHal.h" #include "StructuredCloneUtils.h" #include "TabParent.h" #ifdef ANDROID # include "gfxAndroidPlatform.h" #endif #ifdef MOZ_CRASHREPORTER # include "nsExceptionHandler.h" # include "nsICrashReporter.h" #endif #ifdef MOZ_PERMISSIONS # include "nsPermissionManager.h" #endif #ifdef MOZ_SYDNEYAUDIO # include "AudioParent.h" #endif #ifdef MOZ_WIDGET_ANDROID # include "AndroidBridge.h" #endif #ifdef MOZ_WIDGET_GONK #include "nsIVolume.h" #include "nsIVolumeService.h" #endif static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID); static const char* sClipboardTextFlavors[] = { kUnicodeMime }; using base::KillProcess; using namespace mozilla::dom::devicestorage; using namespace mozilla::dom::sms; using namespace mozilla::dom::indexedDB; using namespace mozilla::hal_sandbox; using namespace mozilla::ipc; using namespace mozilla::layers; using namespace mozilla::net; using namespace mozilla::places; namespace mozilla { namespace dom { #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline" class MemoryReportRequestParent : public PMemoryReportRequestParent { public: MemoryReportRequestParent(); virtual ~MemoryReportRequestParent(); virtual bool Recv__delete__(const InfallibleTArray& report); private: ContentParent* Owner() { return static_cast(Manager()); } }; MemoryReportRequestParent::MemoryReportRequestParent() { MOZ_COUNT_CTOR(MemoryReportRequestParent); } bool MemoryReportRequestParent::Recv__delete__(const InfallibleTArray& report) { Owner()->SetChildMemoryReporters(report); return true; } MemoryReportRequestParent::~MemoryReportRequestParent() { MOZ_COUNT_DTOR(MemoryReportRequestParent); } nsDataHashtable* ContentParent::gAppContentParents; nsTArray* ContentParent::gNonAppContentParents; nsTArray* ContentParent::gPrivateContent; // The first content child has ID 1, so the chrome process can have ID 0. static PRUint64 gContentChildID = 1; // Try to keep an app process always preallocated, to get // initialization off the critical path of app startup. static bool sKeepAppProcessPreallocated; static StaticRefPtr sPreallocatedAppProcess; static CancelableTask* sPreallocateAppProcessTask; // This number is fairly arbitrary ... the intention is to put off // launching another app process until the last one has finished // loading its content, to reduce CPU/memory/IO contention. static int sPreallocateDelayMs; // We want the prelaunched process to know that it's for apps, but not // actually for any app in particular. Use a magic manifest URL. // Can't be a static constant. #define MAGIC_PREALLOCATED_APP_MANIFEST_URL NS_LITERAL_STRING("{{template}}") /*static*/ void ContentParent::PreallocateAppProcess() { MOZ_ASSERT(!sPreallocatedAppProcess); if (sPreallocateAppProcessTask) { // We were called directly while a delayed task was scheduled. sPreallocateAppProcessTask->Cancel(); sPreallocateAppProcessTask = nullptr; } sPreallocatedAppProcess = new ContentParent(MAGIC_PREALLOCATED_APP_MANIFEST_URL); sPreallocatedAppProcess->Init(); } /*static*/ void ContentParent::DelayedPreallocateAppProcess() { sPreallocateAppProcessTask = nullptr; if (!sPreallocatedAppProcess) { PreallocateAppProcess(); } } /*static*/ void ContentParent::ScheduleDelayedPreallocateAppProcess() { if (!sKeepAppProcessPreallocated || sPreallocateAppProcessTask) { return; } sPreallocateAppProcessTask = NewRunnableFunction(DelayedPreallocateAppProcess); MessageLoop::current()->PostDelayedTask( FROM_HERE, sPreallocateAppProcessTask, sPreallocateDelayMs); } /*static*/ already_AddRefed ContentParent::MaybeTakePreallocatedAppProcess() { nsRefPtr process = sPreallocatedAppProcess.get(); sPreallocatedAppProcess = nullptr; ScheduleDelayedPreallocateAppProcess(); return process.forget(); } /*static*/ void ContentParent::StartUp() { if (XRE_GetProcessType() != GeckoProcessType_Default) { return; } sKeepAppProcessPreallocated = Preferences::GetBool("dom.ipc.processPrelauch.enabled", false); if (sKeepAppProcessPreallocated) { ClearOnShutdown(&sPreallocatedAppProcess); sPreallocateDelayMs = Preferences::GetUint( "dom.ipc.processPrelauch.delayMs", 1000); MOZ_ASSERT(!sPreallocateAppProcessTask); ScheduleDelayedPreallocateAppProcess(); } } /*static*/ void ContentParent::ShutDown() { // No-op for now. We rely on normal process shutdown and // ClearOnShutdown() to clean up our state. } /*static*/ ContentParent* ContentParent::GetNewOrUsed() { if (!gNonAppContentParents) gNonAppContentParents = new nsTArray(); PRInt32 maxContentProcesses = Preferences::GetInt("dom.ipc.processCount", 1); if (maxContentProcesses < 1) maxContentProcesses = 1; if (gNonAppContentParents->Length() >= PRUint32(maxContentProcesses)) { PRUint32 idx = rand() % gNonAppContentParents->Length(); ContentParent* p = (*gNonAppContentParents)[idx]; NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in gNonAppContentParents?"); return p; } nsRefPtr p = new ContentParent(/* appManifestURL = */ EmptyString()); p->Init(); gNonAppContentParents->AppendElement(p); return p; } /*static*/ TabParent* ContentParent::CreateBrowser(mozIApplication* aApp, bool aIsBrowserElement) { if (!aApp) { if (ContentParent* cp = GetNewOrUsed()) { nsRefPtr tp(new TabParent(aApp, aIsBrowserElement)); return static_cast( cp->SendPBrowserConstructor( // DeallocPBrowserParent() releases the ref we take here tp.forget().get(), /*chromeFlags*/0, aIsBrowserElement, nsIScriptSecurityManager::NO_APP_ID)); } return nullptr; } if (!gAppContentParents) { gAppContentParents = new nsDataHashtable(); gAppContentParents->Init(); } // Each app gets its own ContentParent instance. nsAutoString manifestURL; if (NS_FAILED(aApp->GetManifestURL(manifestURL))) { NS_ERROR("Failed to get manifest URL"); return nullptr; } nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); if (!appsService) { NS_ERROR("Failed to get apps service"); return nullptr; } // Send the local app ID to the new TabChild so it knows what app // it is. PRUint32 appId; if (NS_FAILED(appsService->GetAppLocalIdByManifestURL(manifestURL, &appId))) { NS_ERROR("Failed to get local app ID"); return nullptr; } nsRefPtr p = gAppContentParents->Get(manifestURL); if (!p) { p = MaybeTakePreallocatedAppProcess(); if (p) { p->SetManifestFromPreallocated(manifestURL); } else { NS_WARNING("Unable to use pre-allocated app process"); p = new ContentParent(manifestURL); p->Init(); } gAppContentParents->Put(manifestURL, p); } nsRefPtr tp(new TabParent(aApp, aIsBrowserElement)); return static_cast( // DeallocPBrowserParent() releases the ref we take here p->SendPBrowserConstructor(tp.forget().get(), /*chromeFlags*/0, aIsBrowserElement, appId)); } static PLDHashOperator AppendToTArray(const nsAString& aKey, ContentParent* aValue, void* aArray) { nsTArray *array = static_cast*>(aArray); array->AppendElement(aValue); return PL_DHASH_NEXT; } void ContentParent::GetAll(nsTArray& aArray) { aArray.Clear(); if (gNonAppContentParents) { aArray.AppendElements(*gNonAppContentParents); } if (gAppContentParents) { gAppContentParents->EnumerateRead(&AppendToTArray, &aArray); } } void ContentParent::Init() { nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->AddObserver(this, "xpcom-shutdown", false); obs->AddObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, false); obs->AddObserver(this, "child-memory-reporter-request", false); obs->AddObserver(this, "memory-pressure", false); obs->AddObserver(this, "child-gc-request", false); obs->AddObserver(this, "child-cc-request", false); obs->AddObserver(this, "last-pb-context-exited", false); #ifdef MOZ_WIDGET_GONK obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, false); #endif #ifdef ACCESSIBILITY obs->AddObserver(this, "a11y-init-or-shutdown", false); #endif } Preferences::AddStrongObserver(this, ""); nsCOMPtr threadInt(do_QueryInterface(NS_GetCurrentThread())); if (threadInt) { threadInt->AddObserver(this); } if (obs) { obs->NotifyObservers(static_cast(this), "ipc:content-created", nullptr); } #ifdef ACCESSIBILITY // If accessibility is running in chrome process then start it in content // process. if (nsIPresShell::IsAccessibilityActive()) { unused << SendActivateA11y(); } #endif } void ContentParent::SetManifestFromPreallocated(const nsAString& aAppManifestURL) { MOZ_ASSERT(mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL); // Clients should think of mAppManifestURL as const ... we're // bending the rules here just for the preallocation hack. const_cast(mAppManifestURL) = aAppManifestURL; } void ContentParent::ShutDownProcess() { if (mIsAlive) { // Close() can only be called once. It kicks off the // destruction sequence. Close(); } // NB: must MarkAsDead() here so that this isn't accidentally // returned from Get*() while in the midst of shutdown. MarkAsDead(); } void ContentParent::MarkAsDead() { if (!mAppManifestURL.IsEmpty()) { if (gAppContentParents) { gAppContentParents->Remove(mAppManifestURL); if (!gAppContentParents->Count()) { delete gAppContentParents; gAppContentParents = NULL; } } } else if (gNonAppContentParents) { gNonAppContentParents->RemoveElement(this); if (!gNonAppContentParents->Length()) { delete gNonAppContentParents; gNonAppContentParents = NULL; } } if (gPrivateContent) { gPrivateContent->RemoveElement(this); if (!gPrivateContent->Length()) { delete gPrivateContent; gPrivateContent = NULL; } } mIsAlive = false; } void ContentParent::OnChannelConnected(int32 pid) { ProcessHandle handle; if (!base::OpenPrivilegedProcessHandle(pid, &handle)) { NS_WARNING("Can't open handle to child process."); } else { SetOtherProcess(handle); #if defined(ANDROID) || defined(LINUX) // Check nice preference PRInt32 nice = Preferences::GetInt("dom.ipc.content.nice", 0); // Environment variable overrides preference char* relativeNicenessStr = getenv("MOZ_CHILD_PROCESS_RELATIVE_NICENESS"); if (relativeNicenessStr) { nice = atoi(relativeNicenessStr); } /* make the GUI thread have higher priority on single-cpu devices */ nsCOMPtr infoService = do_GetService(NS_SYSTEMINFO_CONTRACTID); if (infoService) { PRInt32 cpus; nsresult rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("cpucount"), &cpus); if (NS_FAILED(rv)) { cpus = 1; } if (nice != 0 && cpus == 1) { setpriority(PRIO_PROCESS, pid, getpriority(PRIO_PROCESS, pid) + nice); } } #endif } } void ContentParent::ProcessingError(Result what) { if (MsgDropped == what) { // Messages sent after crashes etc. are not a big deal. return; } // Other errors are big deals. This ensures the process is // eventually killed, but doesn't immediately KILLITWITHFIRE // because we want to get a minidump if possible. After a timeout // though, the process is forceably killed. if (!KillProcess(OtherProcess(), 1, false)) { NS_WARNING("failed to kill subprocess!"); } XRE_GetIOMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(&ProcessWatcher::EnsureProcessTerminated, OtherProcess(), /*force=*/true)); } namespace { void DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess) { XRE_GetIOMessageLoop() ->PostTask(FROM_HERE, new DeleteTask(aSubprocess)); } // This runnable only exists to delegate ownership of the // ContentParent to this runnable, until it's deleted by the event // system. struct DelayedDeleteContentParentTask : public nsRunnable { DelayedDeleteContentParentTask(ContentParent* aObj) : mObj(aObj) { } // No-op NS_IMETHODIMP Run() { return NS_OK; } nsRefPtr mObj; }; } void ContentParent::ActorDestroy(ActorDestroyReason why) { nsCOMPtr kungFuDeathGrip(static_cast(this)); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(static_cast(this), "xpcom-shutdown"); obs->RemoveObserver(static_cast(this), "memory-pressure"); obs->RemoveObserver(static_cast(this), "child-memory-reporter-request"); obs->RemoveObserver(static_cast(this), NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC); obs->RemoveObserver(static_cast(this), "child-gc-request"); obs->RemoveObserver(static_cast(this), "child-cc-request"); obs->RemoveObserver(static_cast(this), "last-pb-context-exited"); #ifdef MOZ_WIDGET_GONK obs->RemoveObserver(static_cast(this), NS_VOLUME_STATE_CHANGED); #endif #ifdef ACCESSIBILITY obs->RemoveObserver(static_cast(this), "a11y-init-or-shutdown"); #endif } if (sPreallocatedAppProcess == this) { sPreallocatedAppProcess = nullptr; } mMessageManager->Disconnect(); // clear the child memory reporters InfallibleTArray empty; SetChildMemoryReporters(empty); // remove the global remote preferences observers Preferences::RemoveObserver(this, ""); RecvRemoveGeolocationListener(); nsCOMPtr threadInt(do_QueryInterface(NS_GetCurrentThread())); if (threadInt) threadInt->RemoveObserver(this); if (mRunToCompletionDepth) mRunToCompletionDepth = 0; MarkAsDead(); if (obs) { nsRefPtr props = new nsHashPropertyBag(); props->Init(); if (AbnormalShutdown == why) { props->SetPropertyAsBool(NS_LITERAL_STRING("abnormal"), true); #ifdef MOZ_CRASHREPORTER MOZ_ASSERT(ManagedPCrashReporterParent().Length() > 0); CrashReporterParent* crashReporter = static_cast(ManagedPCrashReporterParent()[0]); crashReporter->GenerateCrashReport(this, NULL); nsAutoString dumpID(crashReporter->ChildDumpID()); props->SetPropertyAsAString(NS_LITERAL_STRING("dumpID"), dumpID); #endif obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nullptr); } } MessageLoop::current()-> PostTask(FROM_HERE, NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess)); mSubprocess = NULL; // IPDL rules require actors to live on past ActorDestroy, but it // may be that the kungFuDeathGrip above is the last reference to // |this|. If so, when we go out of scope here, we're deleted and // all hell breaks loose. // // This runnable ensures that a reference to |this| lives on at // least until after the current task finishes running. NS_DispatchToCurrentThread(new DelayedDeleteContentParentTask(this)); } void ContentParent::NotifyTabDestroyed(PBrowserParent* aTab) { // There can be more than one PBrowser for a given app process // because of popup windows. When the last one closes, shut // us down. if (IsForApp() && ManagedPBrowserParent().Length() == 1) { MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(this, &ContentParent::ShutDownProcess)); } } TestShellParent* ContentParent::CreateTestShell() { return static_cast(SendPTestShellConstructor()); } bool ContentParent::DestroyTestShell(TestShellParent* aTestShell) { return PTestShellParent::Send__delete__(aTestShell); } TestShellParent* ContentParent::GetTestShellSingleton() { if (!ManagedPTestShellParent().Length()) return nullptr; return static_cast(ManagedPTestShellParent()[0]); } ContentParent::ContentParent(const nsAString& aAppManifestURL) : mGeolocationWatchID(-1) , mRunToCompletionDepth(0) , mShouldCallUnblockChild(false) , mIsAlive(true) , mSendPermissionUpdates(false) , mAppManifestURL(aAppManifestURL) { // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the // PID along with the warning. nsDebugImpl::SetMultiprocessMode("Parent"); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content); bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop(); if (useOffMainThreadCompositing) { // We need the subprocess's ProcessHandle to create the // PCompositor channel below. Block just until we have that. mSubprocess->LaunchAndWaitForProcessHandle(); } else { mSubprocess->AsyncLaunch(); } Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle()); unused << SendSetID(gContentChildID++); // NB: internally, this will send an IPC message to the child // process to get it to create the CompositorChild. This // message goes through the regular IPC queue for this // channel, so delivery will happen-before any other messages // we send. The CompositorChild must be created before any // PBrowsers are created, because they rely on the Compositor // already being around. (Creation is async, so can't happen // on demand.) if (useOffMainThreadCompositing) { DebugOnly opened = PCompositor::Open(this); MOZ_ASSERT(opened); } nsCOMPtr registrySvc = nsChromeRegistry::GetService(); nsChromeRegistryChrome* chromeRegistry = static_cast(registrySvc.get()); chromeRegistry->SendRegisteredChrome(this); mMessageManager = nsFrameMessageManager::NewProcessMessageManager(this); if (gAppData) { nsCString version(gAppData->version); nsCString buildID(gAppData->buildID); //Sending all information to content process unused << SendAppInfo(version, buildID); } mFileWatchers.Init(); } ContentParent::~ContentParent() { if (OtherProcess()) base::CloseProcessHandle(OtherProcess()); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); // We should be removed from all these lists in ActorDestroy. MOZ_ASSERT(!gPrivateContent || !gPrivateContent->Contains(this)); if (mAppManifestURL.IsEmpty()) { MOZ_ASSERT(!gNonAppContentParents || !gNonAppContentParents->Contains(this)); } else { MOZ_ASSERT(!gAppContentParents || !gAppContentParents->Get(mAppManifestURL)); } } bool ContentParent::IsAlive() { return mIsAlive; } bool ContentParent::IsForApp() { return !mAppManifestURL.IsEmpty(); } bool ContentParent::RecvReadPrefsArray(InfallibleTArray *prefs) { Preferences::MirrorPreferences(prefs); return true; } bool ContentParent::RecvReadFontList(InfallibleTArray* retValue) { #ifdef ANDROID gfxAndroidPlatform::GetPlatform()->GetFontList(retValue); #endif return true; } bool ContentParent::RecvReadPermissions(InfallibleTArray* aPermissions) { #ifdef MOZ_PERMISSIONS nsCOMPtr permissionManagerIface = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); nsPermissionManager* permissionManager = static_cast(permissionManagerIface.get()); NS_ABORT_IF_FALSE(permissionManager, "We have no permissionManager in the Chrome process !"); nsCOMPtr enumerator; DebugOnly rv = permissionManager->GetEnumerator(getter_AddRefs(enumerator)); NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "Could not get enumerator!"); while(1) { bool hasMore; enumerator->HasMoreElements(&hasMore); if (!hasMore) break; nsCOMPtr supp; enumerator->GetNext(getter_AddRefs(supp)); nsCOMPtr perm = do_QueryInterface(supp); nsCString host; perm->GetHost(host); nsCString type; perm->GetType(type); PRUint32 capability; perm->GetCapability(&capability); PRUint32 expireType; perm->GetExpireType(&expireType); PRInt64 expireTime; perm->GetExpireTime(&expireTime); aPermissions->AppendElement(IPC::Permission(host, type, capability, expireType, expireTime)); } // Ask for future changes mSendPermissionUpdates = true; #endif return true; } bool ContentParent::RecvSetClipboardText(const nsString& text, const bool& isPrivateData, const PRInt32& whichClipboard) { nsresult rv; nsCOMPtr clipboard(do_GetService(kCClipboardCID, &rv)); NS_ENSURE_SUCCESS(rv, true); nsCOMPtr dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, true); rv = dataWrapper->SetData(text); NS_ENSURE_SUCCESS(rv, true); nsCOMPtr trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); NS_ENSURE_SUCCESS(rv, true); trans->Init(nullptr); // If our data flavor has already been added, this will fail. But we don't care trans->AddDataFlavor(kUnicodeMime); trans->SetIsPrivateData(isPrivateData); nsCOMPtr nsisupportsDataWrapper = do_QueryInterface(dataWrapper); rv = trans->SetTransferData(kUnicodeMime, nsisupportsDataWrapper, text.Length() * sizeof(PRUnichar)); NS_ENSURE_SUCCESS(rv, true); clipboard->SetData(trans, NULL, whichClipboard); return true; } bool ContentParent::RecvGetClipboardText(const PRInt32& whichClipboard, nsString* text) { nsresult rv; nsCOMPtr clipboard(do_GetService(kCClipboardCID, &rv)); NS_ENSURE_SUCCESS(rv, true); nsCOMPtr trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); NS_ENSURE_SUCCESS(rv, true); trans->Init(nullptr); clipboard->GetData(trans, whichClipboard); nsCOMPtr tmp; PRUint32 len; rv = trans->GetTransferData(kUnicodeMime, getter_AddRefs(tmp), &len); if (NS_FAILED(rv)) return false; nsCOMPtr supportsString = do_QueryInterface(tmp); // No support for non-text data if (!supportsString) return false; supportsString->GetData(*text); return true; } bool ContentParent::RecvEmptyClipboard() { nsresult rv; nsCOMPtr clipboard(do_GetService(kCClipboardCID, &rv)); NS_ENSURE_SUCCESS(rv, true); clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard); return true; } bool ContentParent::RecvClipboardHasText(bool* hasText) { nsresult rv; nsCOMPtr clipboard(do_GetService(kCClipboardCID, &rv)); NS_ENSURE_SUCCESS(rv, true); clipboard->HasDataMatchingFlavors(sClipboardTextFlavors, 1, nsIClipboard::kGlobalClipboard, hasText); return true; } bool ContentParent::RecvGetSystemColors(const PRUint32& colorsCount, InfallibleTArray* colors) { #ifdef MOZ_WIDGET_ANDROID NS_ASSERTION(AndroidBridge::Bridge() != nullptr, "AndroidBridge is not available"); if (AndroidBridge::Bridge() == nullptr) { // Do not fail - the colors won't be right, but it's not critical return true; } colors->AppendElements(colorsCount); // The array elements correspond to the members of AndroidSystemColors structure, // so just pass the pointer to the elements buffer AndroidBridge::Bridge()->GetSystemColors((AndroidSystemColors*)colors->Elements()); #endif return true; } bool ContentParent::RecvGetIconForExtension(const nsCString& aFileExt, const PRUint32& aIconSize, InfallibleTArray* bits) { #ifdef MOZ_WIDGET_ANDROID NS_ASSERTION(AndroidBridge::Bridge() != nullptr, "AndroidBridge is not available"); if (AndroidBridge::Bridge() == nullptr) { // Do not fail - just no icon will be shown return true; } bits->AppendElements(aIconSize * aIconSize * 4); AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, bits->Elements()); #endif return true; } bool ContentParent::RecvGetShowPasswordSetting(bool* showPassword) { // default behavior is to show the last password character *showPassword = true; #ifdef MOZ_WIDGET_ANDROID NS_ASSERTION(AndroidBridge::Bridge() != nullptr, "AndroidBridge is not available"); if (AndroidBridge::Bridge() != nullptr) *showPassword = AndroidBridge::Bridge()->GetShowPasswordSetting(); #endif return true; } NS_IMPL_THREADSAFE_ISUPPORTS3(ContentParent, nsIObserver, nsIThreadObserver, nsIDOMGeoPositionCallback) NS_IMETHODIMP ContentParent::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { if (!strcmp(aTopic, "xpcom-shutdown") && mSubprocess) { mFileWatchers.Clear(); Close(); NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess"); } if (!mIsAlive || !mSubprocess) return NS_OK; // listening for memory pressure event if (!strcmp(aTopic, "memory-pressure")) { unused << SendFlushMemory(nsDependentString(aData)); } // listening for remotePrefs... else if (!strcmp(aTopic, "nsPref:changed")) { // We know prefs are ASCII here. NS_LossyConvertUTF16toASCII strData(aData); PrefTuple pref; bool prefNeedUpdate = Preferences::MirrorPreference(strData.get(), &pref); if (prefNeedUpdate) { if (!SendPreferenceUpdate(pref)) { return NS_ERROR_NOT_AVAILABLE; } } else { // Pref wasn't found. It was probably removed. if (!SendClearUserPreference(strData)) { return NS_ERROR_NOT_AVAILABLE; } } } else if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC)) { NS_ConvertUTF16toUTF8 dataStr(aData); const char *offline = dataStr.get(); if (!SendSetOffline(!strcmp(offline, "true") ? true : false)) return NS_ERROR_NOT_AVAILABLE; } // listening for alert notifications else if (!strcmp(aTopic, "alertfinished") || !strcmp(aTopic, "alertclickcallback") ) { if (!SendNotifyAlertsObserver(nsDependentCString(aTopic), nsDependentString(aData))) return NS_ERROR_NOT_AVAILABLE; } else if (!strcmp(aTopic, "child-memory-reporter-request")) { unused << SendPMemoryReportRequestConstructor(); } else if (!strcmp(aTopic, "child-gc-request")){ unused << SendGarbageCollect(); } else if (!strcmp(aTopic, "child-cc-request")){ unused << SendCycleCollect(); } else if (!strcmp(aTopic, "last-pb-context-exited")) { unused << SendLastPrivateDocShellDestroyed(); } #ifdef MOZ_WIDGET_GONK else if(!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) { nsCOMPtr vol = do_QueryInterface(aSubject); if (!vol) { return NS_ERROR_NOT_AVAILABLE; } nsString volName; nsString mountPoint; PRInt32 state; vol->GetName(volName); vol->GetMountPoint(mountPoint); vol->GetState(&state); unused << SendFileSystemUpdate(volName, mountPoint, state); } #endif #ifdef ACCESSIBILITY // Make sure accessibility is running in content process when accessibility // gets initiated in chrome process. else if (aData && (*aData == '1') && !strcmp(aTopic, "a11y-init-or-shutdown")) { unused << SendActivateA11y(); } #endif return NS_OK; } PCompositorParent* ContentParent::AllocPCompositor(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) { return CompositorParent::Create(aTransport, aOtherProcess); } PBrowserParent* ContentParent::AllocPBrowser(const PRUint32& aChromeFlags, const bool& aIsBrowserElement, const AppId& aApp) { // We only use this Alloc() method when the content processes asks // us to open a window. In that case, we're expecting to see the // opening PBrowser as its app descriptor, and we can trust the data // associated with that PBrowser since it's fully owned by this // process. if (AppId::TPBrowserParent != aApp.type()) { NS_ERROR("Content process attempting to forge app ID"); return nullptr; } TabParent* opener = static_cast(aApp.get_PBrowserParent()); // Popup windows of isBrowser frames are isBrowser if the parent // isBrowser. Allocating a !isBrowser frame with same app ID // would allow the content to access data it's not supposed to. if (opener && opener->IsBrowserElement() && !aIsBrowserElement) { NS_ERROR("Content process attempting to escalate data access privileges"); return nullptr; } TabParent* parent = new TabParent(opener ? opener->GetApp() : nullptr, aIsBrowserElement); // We release this ref in DeallocPBrowser() NS_ADDREF(parent); return parent; } bool ContentParent::DeallocPBrowser(PBrowserParent* frame) { TabParent* parent = static_cast(frame); NS_RELEASE(parent); return true; } PDeviceStorageRequestParent* ContentParent::AllocPDeviceStorageRequest(const DeviceStorageParams& aParams) { DeviceStorageRequestParent* result = new DeviceStorageRequestParent(aParams); NS_ADDREF(result); return result; } bool ContentParent::DeallocPDeviceStorageRequest(PDeviceStorageRequestParent* doomed) { DeviceStorageRequestParent *parent = static_cast(doomed); NS_RELEASE(parent); return true; } PBlobParent* ContentParent::AllocPBlob(const BlobConstructorParams& aParams) { return BlobParent::Create(aParams); } bool ContentParent::DeallocPBlob(PBlobParent* aActor) { delete aActor; return true; } BlobParent* ContentParent::GetOrCreateActorForBlob(nsIDOMBlob* aBlob) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(aBlob, "Null pointer!"); nsCOMPtr remoteBlob = do_QueryInterface(aBlob); if (remoteBlob) { BlobParent* actor = static_cast( static_cast(remoteBlob->GetPBlob())); NS_ASSERTION(actor, "Null actor?!"); return actor; } // XXX This is only safe so long as all blob implementations in our tree // inherit nsDOMFileBase. If that ever changes then this will need to grow // a real interface or something. const nsDOMFileBase* blob = static_cast(aBlob); BlobConstructorParams params; if (blob->IsSizeUnknown()) { // We don't want to call GetSize yet since that may stat a file on the main // thread here. Instead we'll learn the size lazily from the other process. params = MysteryBlobConstructorParams(); } else { nsString contentType; nsresult rv = aBlob->GetType(contentType); NS_ENSURE_SUCCESS(rv, nullptr); PRUint64 length; rv = aBlob->GetSize(&length); NS_ENSURE_SUCCESS(rv, nullptr); nsCOMPtr file = do_QueryInterface(aBlob); if (file) { FileBlobConstructorParams fileParams; rv = file->GetName(fileParams.name()); NS_ENSURE_SUCCESS(rv, nullptr); fileParams.contentType() = contentType; fileParams.length() = length; params = fileParams; } else { NormalBlobConstructorParams blobParams; blobParams.contentType() = contentType; blobParams.length() = length; params = blobParams; } } BlobParent* actor = BlobParent::Create(aBlob); NS_ENSURE_TRUE(actor, nullptr); if (!SendPBlobConstructor(actor, params)) { return nullptr; } return actor; } PCrashReporterParent* ContentParent::AllocPCrashReporter(const NativeThreadId& tid, const PRUint32& processType) { #ifdef MOZ_CRASHREPORTER return new CrashReporterParent(); #else return nullptr; #endif } bool ContentParent::RecvPCrashReporterConstructor(PCrashReporterParent* actor, const NativeThreadId& tid, const PRUint32& processType) { static_cast(actor)->SetChildData(tid, processType); return true; } bool ContentParent::DeallocPCrashReporter(PCrashReporterParent* crashreporter) { delete crashreporter; return true; } PHalParent* ContentParent::AllocPHal() { return CreateHalParent(); } bool ContentParent::DeallocPHal(PHalParent* aHal) { delete aHal; return true; } PIndexedDBParent* ContentParent::AllocPIndexedDB() { return new IndexedDBParent(); } bool ContentParent::DeallocPIndexedDB(PIndexedDBParent* aActor) { delete aActor; return true; } bool ContentParent::RecvPIndexedDBConstructor(PIndexedDBParent* aActor) { nsRefPtr mgr = IndexedDatabaseManager::GetOrCreate(); NS_ENSURE_TRUE(mgr, false); if (!IndexedDatabaseManager::IsMainProcess()) { NS_RUNTIMEABORT("Not supported yet!"); } nsRefPtr factory; nsresult rv = IDBFactory::Create(this, getter_AddRefs(factory)); NS_ENSURE_SUCCESS(rv, false); NS_ASSERTION(factory, "This should never be null!"); IndexedDBParent* actor = static_cast(aActor); actor->mFactory = factory; actor->mASCIIOrigin = factory->GetASCIIOrigin(); return true; } PMemoryReportRequestParent* ContentParent::AllocPMemoryReportRequest() { MemoryReportRequestParent* parent = new MemoryReportRequestParent(); return parent; } bool ContentParent::DeallocPMemoryReportRequest(PMemoryReportRequestParent* actor) { delete actor; return true; } void ContentParent::SetChildMemoryReporters(const InfallibleTArray& report) { nsCOMPtr mgr = do_GetService("@mozilla.org/memory-reporter-manager;1"); for (PRInt32 i = 0; i < mMemoryReporters.Count(); i++) mgr->UnregisterReporter(mMemoryReporters[i]); for (PRUint32 i = 0; i < report.Length(); i++) { nsCString process = report[i].process(); nsCString path = report[i].path(); PRInt32 kind = report[i].kind(); PRInt32 units = report[i].units(); PRInt64 amount = report[i].amount(); nsCString desc = report[i].desc(); nsRefPtr r = new nsMemoryReporter(process, path, kind, units, amount, desc); mMemoryReporters.AppendObject(r); mgr->RegisterReporter(r); } nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1"); if (obs) obs->NotifyObservers(nullptr, "child-memory-reporter-update", nullptr); } PTestShellParent* ContentParent::AllocPTestShell() { return new TestShellParent(); } bool ContentParent::DeallocPTestShell(PTestShellParent* shell) { delete shell; return true; } PAudioParent* ContentParent::AllocPAudio(const PRInt32& numChannels, const PRInt32& rate, const PRInt32& format) { #if defined(MOZ_SYDNEYAUDIO) AudioParent *parent = new AudioParent(numChannels, rate, format); NS_ADDREF(parent); return parent; #else return nullptr; #endif } bool ContentParent::DeallocPAudio(PAudioParent* doomed) { #if defined(MOZ_SYDNEYAUDIO) AudioParent *parent = static_cast(doomed); NS_RELEASE(parent); #endif return true; } PNeckoParent* ContentParent::AllocPNecko() { return new NeckoParent(); } bool ContentParent::DeallocPNecko(PNeckoParent* necko) { delete necko; return true; } PExternalHelperAppParent* ContentParent::AllocPExternalHelperApp(const IPC::URI& uri, const nsCString& aMimeContentType, const nsCString& aContentDisposition, const bool& aForceSave, const PRInt64& aContentLength, const IPC::URI& aReferrer) { ExternalHelperAppParent *parent = new ExternalHelperAppParent(uri, aContentLength); parent->AddRef(); parent->Init(this, aMimeContentType, aContentDisposition, aForceSave, aReferrer); return parent; } bool ContentParent::DeallocPExternalHelperApp(PExternalHelperAppParent* aService) { ExternalHelperAppParent *parent = static_cast(aService); parent->Release(); return true; } PSmsParent* ContentParent::AllocPSms() { return new SmsParent(); } bool ContentParent::DeallocPSms(PSmsParent* aSms) { delete aSms; return true; } PStorageParent* ContentParent::AllocPStorage(const StorageConstructData& aData) { return new StorageParent(aData); } bool ContentParent::DeallocPStorage(PStorageParent* aActor) { delete aActor; return true; } void ContentParent::ReportChildAlreadyBlocked() { if (!mRunToCompletionDepth) { #ifdef DEBUG printf("Running to completion...\n"); #endif mRunToCompletionDepth = 1; mShouldCallUnblockChild = false; } } bool ContentParent::RequestRunToCompletion() { if (!mRunToCompletionDepth && BlockChild()) { #ifdef DEBUG printf("Running to completion...\n"); #endif mRunToCompletionDepth = 1; mShouldCallUnblockChild = true; } return !!mRunToCompletionDepth; } bool ContentParent::RecvStartVisitedQuery(const IPC::URI& aURI) { nsCOMPtr newURI(aURI); nsCOMPtr history = services::GetHistoryService(); NS_ABORT_IF_FALSE(history, "History must exist at this point."); if (history) { history->RegisterVisitedCallback(newURI, nullptr); } return true; } bool ContentParent::RecvVisitURI(const IPC::URI& uri, const IPC::URI& referrer, const PRUint32& flags) { nsCOMPtr ourURI(uri); nsCOMPtr ourReferrer(referrer); nsCOMPtr history = services::GetHistoryService(); NS_ABORT_IF_FALSE(history, "History must exist at this point"); if (history) { history->VisitURI(ourURI, ourReferrer, flags); } return true; } bool ContentParent::RecvSetURITitle(const IPC::URI& uri, const nsString& title) { nsCOMPtr ourURI(uri); nsCOMPtr history = services::GetHistoryService(); NS_ABORT_IF_FALSE(history, "History must exist at this point"); if (history) { history->SetURITitle(ourURI, title); } return true; } bool ContentParent::RecvShowFilePicker(const PRInt16& mode, const PRInt16& selectedType, const bool& addToRecentDocs, const nsString& title, const nsString& defaultFile, const nsString& defaultExtension, const InfallibleTArray& filters, const InfallibleTArray& filterNames, InfallibleTArray* files, PRInt16* retValue, nsresult* result) { nsCOMPtr filePicker = do_CreateInstance("@mozilla.org/filepicker;1"); if (!filePicker) { *result = NS_ERROR_NOT_AVAILABLE; return true; } // as the parent given to the content process would be meaningless in this // process, always use active window as the parent nsCOMPtr ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID); nsCOMPtr window; ww->GetActiveWindow(getter_AddRefs(window)); // initialize the "real" picker with all data given *result = filePicker->Init(window, title, mode); if (NS_FAILED(*result)) return true; filePicker->SetAddToRecentDocs(addToRecentDocs); PRUint32 count = filters.Length(); for (PRUint32 i = 0; i < count; ++i) { filePicker->AppendFilter(filterNames[i], filters[i]); } filePicker->SetDefaultString(defaultFile); filePicker->SetDefaultExtension(defaultExtension); filePicker->SetFilterIndex(selectedType); // and finally open the dialog *result = filePicker->Show(retValue); if (NS_FAILED(*result)) return true; if (mode == nsIFilePicker::modeOpenMultiple) { nsCOMPtr fileIter; *result = filePicker->GetFiles(getter_AddRefs(fileIter)); nsCOMPtr singleFile; bool loop = true; while (NS_SUCCEEDED(fileIter->HasMoreElements(&loop)) && loop) { fileIter->GetNext(getter_AddRefs(singleFile)); if (singleFile) { nsAutoString filePath; singleFile->GetPath(filePath); files->AppendElement(filePath); } } return true; } nsCOMPtr file; filePicker->GetFile(getter_AddRefs(file)); // even with NS_OK file can be null if nothing was selected if (file) { nsAutoString filePath; file->GetPath(filePath); files->AppendElement(filePath); } return true; } bool ContentParent::RecvLoadURIExternal(const IPC::URI& uri) { nsCOMPtr extProtService(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); if (!extProtService) return true; nsCOMPtr ourURI(uri); extProtService->LoadURI(ourURI, nullptr); return true; } /* void onDispatchedEvent (in nsIThreadInternal thread); */ NS_IMETHODIMP ContentParent::OnDispatchedEvent(nsIThreadInternal *thread) { NS_NOTREACHED("OnDispatchedEvent unimplemented"); return NS_ERROR_NOT_IMPLEMENTED; } /* void onProcessNextEvent (in nsIThreadInternal thread, in boolean mayWait, in unsigned long recursionDepth); */ NS_IMETHODIMP ContentParent::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait, PRUint32 recursionDepth) { if (mRunToCompletionDepth) ++mRunToCompletionDepth; return NS_OK; } /* void afterProcessNextEvent (in nsIThreadInternal thread, in unsigned long recursionDepth); */ NS_IMETHODIMP ContentParent::AfterProcessNextEvent(nsIThreadInternal *thread, PRUint32 recursionDepth) { if (mRunToCompletionDepth && !--mRunToCompletionDepth) { #ifdef DEBUG printf("... ran to completion.\n"); #endif if (mShouldCallUnblockChild) { mShouldCallUnblockChild = false; UnblockChild(); } } return NS_OK; } bool ContentParent::RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, const nsString& aText, const bool& aTextClickable, const nsString& aCookie, const nsString& aName) { nsCOMPtr sysAlerts(do_GetService(NS_ALERTSERVICE_CONTRACTID)); if (sysAlerts) { sysAlerts->ShowAlertNotification(aImageUrl, aTitle, aText, aTextClickable, aCookie, this, aName); } return true; } bool ContentParent::RecvSyncMessage(const nsString& aMsg, const ClonedMessageData& aData, InfallibleTArray* aRetvals) { nsRefPtr ppm = mMessageManager; if (ppm) { const SerializedStructuredCloneBuffer& buffer = aData.data(); const InfallibleTArray& blobParents = aData.blobsParent(); StructuredCloneData cloneData; cloneData.mData = buffer.data; cloneData.mDataLength = buffer.dataLength; if (!blobParents.IsEmpty()) { PRUint32 length = blobParents.Length(); cloneData.mClosure.mBlobs.SetCapacity(length); for (PRUint32 index = 0; index < length; index++) { BlobParent* blobParent = static_cast(blobParents[index]); MOZ_ASSERT(blobParent); nsCOMPtr blob = blobParent->GetBlob(); MOZ_ASSERT(blob); cloneData.mClosure.mBlobs.AppendElement(blob); } } ppm->ReceiveMessage(static_cast(ppm.get()), aMsg, true, &cloneData, nullptr, aRetvals); } return true; } bool ContentParent::RecvAsyncMessage(const nsString& aMsg, const ClonedMessageData& aData) { nsRefPtr ppm = mMessageManager; if (ppm) { const SerializedStructuredCloneBuffer& buffer = aData.data(); const InfallibleTArray& blobParents = aData.blobsParent(); StructuredCloneData cloneData; cloneData.mData = buffer.data; cloneData.mDataLength = buffer.dataLength; if (!blobParents.IsEmpty()) { PRUint32 length = blobParents.Length(); cloneData.mClosure.mBlobs.SetCapacity(length); for (PRUint32 index = 0; index < length; index++) { BlobParent* blobParent = static_cast(blobParents[index]); MOZ_ASSERT(blobParent); nsCOMPtr blob = blobParent->GetBlob(); MOZ_ASSERT(blob); cloneData.mClosure.mBlobs.AppendElement(blob); } } ppm->ReceiveMessage(static_cast(ppm.get()), aMsg, false, &cloneData, nullptr, nullptr); } return true; } bool ContentParent::RecvAddGeolocationListener() { if (mGeolocationWatchID == -1) { nsCOMPtr geo = do_GetService("@mozilla.org/geolocation;1"); if (!geo) { return true; } jsval dummy = JSVAL_VOID; geo->WatchPosition(this, nullptr, dummy, nullptr, &mGeolocationWatchID); } return true; } bool ContentParent::RecvRemoveGeolocationListener() { if (mGeolocationWatchID != -1) { nsCOMPtr geo = do_GetService("@mozilla.org/geolocation;1"); if (!geo) { return true; } geo->ClearWatch(mGeolocationWatchID); mGeolocationWatchID = -1; } return true; } NS_IMETHODIMP ContentParent::HandleEvent(nsIDOMGeoPosition* postion) { unused << SendGeolocationUpdate(GeoPosition(postion)); return NS_OK; } bool ContentParent::RecvConsoleMessage(const nsString& aMessage) { nsCOMPtr svc(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); if (!svc) return true; nsRefPtr msg(new nsConsoleMessage(aMessage.get())); svc->LogMessage(msg); return true; } bool ContentParent::RecvScriptError(const nsString& aMessage, const nsString& aSourceName, const nsString& aSourceLine, const PRUint32& aLineNumber, const PRUint32& aColNumber, const PRUint32& aFlags, const nsCString& aCategory) { nsCOMPtr svc(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); if (!svc) return true; nsCOMPtr msg(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); nsresult rv = msg->Init(aMessage.get(), aSourceName.get(), aSourceLine.get(), aLineNumber, aColNumber, aFlags, aCategory.get()); if (NS_FAILED(rv)) return true; svc->LogMessage(msg); return true; } bool ContentParent::RecvPrivateDocShellsExist(const bool& aExist) { if (!gPrivateContent) gPrivateContent = new nsTArray(); if (aExist) { gPrivateContent->AppendElement(this); } else { gPrivateContent->RemoveElement(this); if (!gPrivateContent->Length()) { nsCOMPtr obs = mozilla::services::GetObserverService(); obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); delete gPrivateContent; gPrivateContent = NULL; } } return true; } bool ContentParent::RecvAddFileWatch(const nsString& root) { nsRefPtr f; if (mFileWatchers.Get(root, getter_AddRefs(f))) { f->mUsageCount++; return true; } f = new WatchedFile(this, root); mFileWatchers.Put(root, f); f->Watch(); return true; } bool ContentParent::RecvRemoveFileWatch(const nsString& root) { nsRefPtr f; bool result = mFileWatchers.Get(root, getter_AddRefs(f)); if (!result) { return true; } if (!f) return true; f->mUsageCount--; if (f->mUsageCount > 0) { return true; } f->Unwatch(); mFileWatchers.Remove(root); return true; } NS_IMPL_ISUPPORTS1(ContentParent::WatchedFile, nsIFileUpdateListener) nsresult ContentParent::WatchedFile::Update(const char* aReason, nsIFile* aFile) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsString path; aFile->GetPath(path); unused << mParent->SendFilePathUpdate(path, nsDependentCString(aReason)); #ifdef DEBUG nsCString cpath; aFile->GetNativePath(cpath); printf("ContentParent::WatchedFile::Update: %s -- %s\n", cpath.get(), aReason); #endif return NS_OK; } } // namespace dom } // namespace mozilla