/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set sw=4 ts=8 et tw=80 : */ /* ***** 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 Content App. * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Frederic Plourde * * 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 "ContentParent.h" #include "TabParent.h" #include "CrashReporterParent.h" #include "History.h" #include "mozilla/ipc/TestShellParent.h" #include "mozilla/net/NeckoParent.h" #include "nsHashPropertyBag.h" #include "nsIFilePicker.h" #include "nsIWindowWatcher.h" #include "nsIDOMWindow.h" #include "nsIPrefBranch.h" #include "nsIPrefBranch2.h" #include "nsIPrefService.h" #include "nsIPrefLocalizedString.h" #include "nsIObserverService.h" #include "nsContentUtils.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsChromeRegistryChrome.h" #include "nsExternalHelperAppService.h" #include "nsCExternalHandlerService.h" #include "nsFrameMessageManager.h" #include "nsIAlertsService.h" #include "nsToolkitCompsCID.h" #include "nsIDOMGeoGeolocation.h" #include "nsIConsoleService.h" #include "nsIScriptError.h" #include "nsConsoleMessage.h" #include "AudioParent.h" #if defined(ANDROID) || defined(LINUX) #include #include #endif #ifdef MOZ_PERMISSIONS #include "nsPermissionManager.h" #endif #ifdef MOZ_CRASHREPORTER #include "nsICrashReporter.h" #include "nsExceptionHandler.h" #endif #include "mozilla/dom/ExternalHelperAppParent.h" #include "mozilla/dom/StorageParent.h" #include "nsAccelerometer.h" using namespace mozilla::ipc; using namespace mozilla::net; using namespace mozilla::places; using mozilla::MonitorAutoEnter; using base::KillProcess; namespace mozilla { namespace dom { #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline" ContentParent* ContentParent::gSingleton; ContentParent* ContentParent::GetSingleton(PRBool aForceNew) { if (gSingleton && !gSingleton->IsAlive()) gSingleton = nsnull; if (!gSingleton && aForceNew) { nsRefPtr parent = new ContentParent(); if (parent) { nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1"); if (obs) { if (NS_SUCCEEDED(obs->AddObserver(parent, "xpcom-shutdown", PR_FALSE))) { gSingleton = parent; nsCOMPtr prefs (do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefs) { prefs->AddObserver("", parent, PR_FALSE); } } obs->AddObserver( parent, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, PR_FALSE); obs->AddObserver(parent, "memory-pressure", PR_FALSE); } nsCOMPtr threadInt(do_QueryInterface(NS_GetCurrentThread())); if (threadInt) { threadInt->GetObserver(getter_AddRefs(parent->mOldObserver)); threadInt->SetObserver(parent); } if (obs) { obs->NotifyObservers(nsnull, "ipc:content-created", nsnull); } } } return gSingleton; } 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) EnsurePrefService(); nsCOMPtr branch; branch = do_QueryInterface(mPrefService); // Check nice preference PRInt32 nice = 0; branch->GetIntPref("dom.ipc.content.nice", &nice); // Environment variable overrides preference char* relativeNicenessStr = getenv("MOZ_CHILD_PROCESS_RELATIVE_NICENESS"); if (relativeNicenessStr) { nice = atoi(relativeNicenessStr); } if (nice != 0) { setpriority(PRIO_PROCESS, pid, getpriority(PRIO_PROCESS, pid) + nice); } #endif } } namespace { void DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess) { XRE_GetIOMessageLoop() ->PostTask(FROM_HERE, new DeleteTask(aSubprocess)); } } void ContentParent::ActorDestroy(ActorDestroyReason why) { nsCOMPtr kungFuDeathGrip(static_cast(this)); nsCOMPtr obs(do_GetService("@mozilla.org/observer-service;1")); if (obs) { obs->RemoveObserver(static_cast(this), "xpcom-shutdown"); obs->RemoveObserver(static_cast(this), "memory-pressure"); obs->RemoveObserver(static_cast(this), NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC); } // remove the global remote preferences observers nsCOMPtr prefs (do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefs) { prefs->RemoveObserver("", this); } RecvRemoveGeolocationListener(); RecvRemoveAccelerometerListener(); nsCOMPtr threadInt(do_QueryInterface(NS_GetCurrentThread())); if (threadInt) threadInt->SetObserver(mOldObserver); if (mRunToCompletionDepth) mRunToCompletionDepth = 0; mIsAlive = false; if (obs) { nsRefPtr props = new nsHashPropertyBag(); props->Init(); if (AbnormalShutdown == why) { props->SetPropertyAsBool(NS_LITERAL_STRING("abnormal"), PR_TRUE); #ifdef MOZ_CRASHREPORTER nsAutoString dumpID; nsCOMPtr crashDump; TakeMinidump(getter_AddRefs(crashDump)) && CrashReporter::GetIDFromMinidump(crashDump, dumpID); if (!dumpID.IsEmpty()) { props->SetPropertyAsAString(NS_LITERAL_STRING("dumpID"), dumpID); CrashReporter::AnnotationTable notes; notes.Init(); notes.Put(NS_LITERAL_CSTRING("ProcessType"), NS_LITERAL_CSTRING("content")); char startTime[32]; sprintf(startTime, "%lld", static_cast(mProcessStartTime)); notes.Put(NS_LITERAL_CSTRING("StartupTime"), nsDependentCString(startTime)); // TODO: Additional per-process annotations. CrashReporter::AppendExtraData(dumpID, notes); } #endif obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nsnull); } } MessageLoop::current()-> PostTask(FROM_HERE, NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess)); mSubprocess = NULL; } TabParent* ContentParent::CreateTab(PRUint32 aChromeFlags) { return static_cast(SendPBrowserConstructor(aChromeFlags)); } TestShellParent* ContentParent::CreateTestShell() { return static_cast(SendPTestShellConstructor()); } bool ContentParent::DestroyTestShell(TestShellParent* aTestShell) { return PTestShellParent::Send__delete__(aTestShell); } ContentParent::ContentParent() : mMonitor("ContentParent::mMonitor") , mGeolocationWatchID(-1) , mRunToCompletionDepth(0) , mShouldCallUnblockChild(false) , mIsAlive(true) , mProcessStartTime(time(NULL)) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content); mSubprocess->AsyncLaunch(); Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle()); nsCOMPtr registrySvc = nsChromeRegistry::GetService(); nsChromeRegistryChrome* chromeRegistry = static_cast(registrySvc.get()); chromeRegistry->SendRegisteredChrome(this); } ContentParent::~ContentParent() { if (OtherProcess()) base::CloseProcessHandle(OtherProcess()); NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); //If the previous content process has died, a new one could have //been started since. if (gSingleton == this) gSingleton = nsnull; } bool ContentParent::IsAlive() { return mIsAlive; } bool ContentParent::RecvReadPrefsArray(InfallibleTArray *prefs) { EnsurePrefService(); mPrefService->MirrorPreferences(prefs); return true; } void ContentParent::EnsurePrefService() { nsresult rv; if (!mPrefService) { mPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "We lost prefService in the Chrome process !"); } } bool ContentParent::RecvReadPermissions(InfallibleTArray* aPermissions) { #ifdef MOZ_PERMISSIONS nsRefPtr permissionManager = nsPermissionManager::GetSingleton(); NS_ABORT_IF_FALSE(permissionManager, "We have no permissionManager in the Chrome process !"); nsCOMPtr enumerator; nsresult rv = permissionManager->GetEnumerator(getter_AddRefs(enumerator)); NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "Could not get enumerator!"); while(1) { PRBool 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 permissionManager->ChildRequestPermissions(); #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) { 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")) { SendFlushMemory(nsDependentString(aData)); } // listening for remotePrefs... else if (!strcmp(aTopic, "nsPref:changed")) { // We know prefs are ASCII here. NS_LossyConvertUTF16toASCII strData(aData); nsCOMPtr prefService = do_GetService("@mozilla.org/preferences-service;1"); PRBool prefNeedUpdate; prefService->PrefHasUserValue(strData, &prefNeedUpdate); // If the pref does not have a user value, check if it exist on the // default branch or not if (!prefNeedUpdate) { nsCOMPtr defaultBranch; nsCOMPtr prefsService = do_QueryInterface(prefService); prefsService->GetDefaultBranch(nsnull, getter_AddRefs(defaultBranch)); PRInt32 prefType = nsIPrefBranch::PREF_INVALID; defaultBranch->GetPrefType(strData.get(), &prefType); prefNeedUpdate = (prefType != nsIPrefBranch::PREF_INVALID); } if (prefNeedUpdate) { // Pref was created, or previously existed and its value // changed. PrefTuple pref; nsresult rv = prefService->MirrorPreference(strData, &pref); NS_ASSERTION(NS_SUCCEEDED(rv), "Pref has value but can't mirror?"); 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; } return NS_OK; } PBrowserParent* ContentParent::AllocPBrowser(const PRUint32& aChromeFlags) { TabParent* parent = new TabParent(); if (parent){ NS_ADDREF(parent); } return parent; } bool ContentParent::DeallocPBrowser(PBrowserParent* frame) { TabParent* parent = static_cast(frame); NS_RELEASE(parent); return true; } PCrashReporterParent* ContentParent::AllocPCrashReporter() { return new CrashReporterParent(); } bool ContentParent::DeallocPCrashReporter(PCrashReporterParent* crashreporter) { delete crashreporter; return true; } 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) { AudioParent *parent = new AudioParent(numChannels, rate, format); NS_ADDREF(parent); return parent; } bool ContentParent::DeallocPAudio(PAudioParent* doomed) { AudioParent *parent = static_cast(doomed); NS_RELEASE(parent); 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; } 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, nsnull); } 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 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; 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; PRBool loop = PR_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, nsnull); return true; } /* void onDispatchedEvent (in nsIThreadInternal thread); */ NS_IMETHODIMP ContentParent::OnDispatchedEvent(nsIThreadInternal *thread) { if (mOldObserver) return mOldObserver->OnDispatchedEvent(thread); return NS_OK; } /* void onProcessNextEvent (in nsIThreadInternal thread, in boolean mayWait, in unsigned long recursionDepth); */ NS_IMETHODIMP ContentParent::OnProcessNextEvent(nsIThreadInternal *thread, PRBool mayWait, PRUint32 recursionDepth) { if (mRunToCompletionDepth) ++mRunToCompletionDepth; if (mOldObserver) return mOldObserver->OnProcessNextEvent(thread, mayWait, recursionDepth); 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(); } } if (mOldObserver) return mOldObserver->AfterProcessNextEvent(thread, recursionDepth); return NS_OK; } bool ContentParent::RecvShowAlertNotification(const nsString& aImageUrl, const nsString& aTitle, const nsString& aText, const PRBool& 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 nsString& aJSON, InfallibleTArray* aRetvals) { nsRefPtr ppm = nsFrameMessageManager::sParentProcessManager; if (ppm) { ppm->ReceiveMessage(static_cast(ppm.get()), aMsg,PR_TRUE, aJSON, nsnull, aRetvals); } return true; } bool ContentParent::RecvAsyncMessage(const nsString& aMsg, const nsString& aJSON) { nsRefPtr ppm = nsFrameMessageManager::sParentProcessManager; if (ppm) { ppm->ReceiveMessage(static_cast(ppm.get()), aMsg, PR_FALSE, aJSON, nsnull, nsnull); } return true; } bool ContentParent::RecvAddGeolocationListener() { if (mGeolocationWatchID == -1) { nsCOMPtr geo = do_GetService("@mozilla.org/geolocation;1"); if (!geo) { return true; } geo->WatchPosition(this, nsnull, nsnull, &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; } bool ContentParent::RecvAddAccelerometerListener() { nsCOMPtr ac = do_GetService(NS_ACCELEROMETER_CONTRACTID); if (ac) ac->AddListener(this); return true; } bool ContentParent::RecvRemoveAccelerometerListener() { nsCOMPtr ac = do_GetService(NS_ACCELEROMETER_CONTRACTID); if (ac) ac->RemoveListener(this); return true; } NS_IMETHODIMP ContentParent::HandleEvent(nsIDOMGeoPosition* postion) { 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; } NS_IMETHODIMP ContentParent::OnAccelerationChange(nsIAcceleration *aAcceleration) { double x, y, z; aAcceleration->GetX(&x); aAcceleration->GetY(&y); aAcceleration->GetZ(&z); mozilla::dom::ContentParent::GetSingleton()-> SendAccelerationChanged(x, y, z); return NS_OK; } } // namespace dom } // namespace mozilla