EMMA Coverage Report (generated Fri Aug 23 16:39:17 PDT 2013)
[all classes][org.chromium.content.browser]

COVERAGE SUMMARY FOR SOURCE FILE [ChildProcessConnection.java]

nameclass, %method, %block, %line, %
ChildProcessConnection.java100% (5/5)84%  (38/45)75%  (699/931)79%  (146.5/185)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ChildProcessConnection$2100% (1/1)67%  (2/3)17%  (12/71)8%   (0.8/11)
run (): void 0%   (0/1)0%   (0/57)0%   (0/10)
<static initializer> 100% (1/1)75%  (6/8)75%  (0.8/1)
ChildProcessConnection$2 (ChildProcessConnection): void 100% (1/1)100% (6/6)100% (1/1)
     
class ChildProcessConnection100% (1/1)82%  (27/33)76%  (482/633)79%  (97.1/123)
access$1200 (ChildProcessConnection): int 0%   (0/1)0%   (0/3)0%   (0/1)
access$1210 (ChildProcessConnection): int 0%   (0/1)0%   (0/8)0%   (0/1)
access$1300 (ChildProcessConnection): ChildProcessConnection$ChildServiceConn... 0%   (0/1)0%   (0/3)0%   (0/1)
access$600 (ChildProcessConnection): IChildProcessService 0%   (0/1)0%   (0/3)0%   (0/1)
getService (): IChildProcessService 0%   (0/1)0%   (0/15)0%   (0/3)
onBindFailed (): void 0%   (0/1)0%   (0/9)0%   (0/4)
attachAsActive (): void 100% (1/1)57%  (26/46)74%  (6.6/9)
getPid (): int 100% (1/1)67%  (10/15)67%  (2/3)
start (String []): void 100% (1/1)68%  (25/37)79%  (7.2/9)
removeInitialBinding (): void 100% (1/1)70%  (19/27)73%  (4.4/6)
setupConnection (String [], FileDescriptorInfo [], IChildProcessCallback, Chi... 100% (1/1)73%  (30/41)82%  (8.2/10)
<static initializer> 100% (1/1)75%  (6/8)75%  (0.8/1)
doConnectionSetup (): void 100% (1/1)79%  (168/213)76%  (29.6/39)
detachAsActive (): void 100% (1/1)82%  (9/11)90%  (1.8/2)
stop (): void 100% (1/1)88%  (36/41)97%  (11.6/12)
ChildProcessConnection (Context, int, boolean, ChildProcessConnection$DeathCa... 100% (1/1)100% (71/71)100% (19/19)
access$000 (ChildProcessConnection): Intent 100% (1/1)100% (3/3)100% (1/1)
access$100 (ChildProcessConnection): Context 100% (1/1)100% (3/3)100% (1/1)
access$1000 (ChildProcessConnection): ChildProcessConnection$DeathCallback 100% (1/1)100% (3/3)100% (1/1)
access$1100 (ChildProcessConnection): ChildProcessConnection$ChildServiceConn... 100% (1/1)100% (3/3)100% (1/1)
access$200 (ChildProcessConnection): ChildProcessConnection$ConnectionCallbacks 100% (1/1)100% (3/3)100% (1/1)
access$300 (ChildProcessConnection): boolean 100% (1/1)100% (3/3)100% (1/1)
access$302 (ChildProcessConnection, boolean): boolean 100% (1/1)100% (5/5)100% (1/1)
access$400 (ChildProcessConnection): Object 100% (1/1)100% (3/3)100% (1/1)
access$500 (ChildProcessConnection): boolean 100% (1/1)100% (3/3)100% (1/1)
access$502 (ChildProcessConnection, boolean): boolean 100% (1/1)100% (5/5)100% (1/1)
access$602 (ChildProcessConnection, IChildProcessService): IChildProcessService 100% (1/1)100% (5/5)100% (1/1)
access$700 (ChildProcessConnection): ChildProcessConnection$ConnectionParams 100% (1/1)100% (3/3)100% (1/1)
access$800 (ChildProcessConnection): void 100% (1/1)100% (3/3)100% (1/1)
access$900 (ChildProcessConnection): int 100% (1/1)100% (3/3)100% (1/1)
createServiceBindIntent (): Intent 100% (1/1)100% (28/28)100% (4/4)
getServiceNumber (): int 100% (1/1)100% (3/3)100% (1/1)
isInSandbox (): boolean 100% (1/1)100% (3/3)100% (1/1)
     
class ChildProcessConnection$1100% (1/1)100% (2/2)80%  (20/25)94%  (4.7/5)
run (): void 100% (1/1)74%  (14/19)93%  (3.7/4)
ChildProcessConnection$1 (ChildProcessConnection): void 100% (1/1)100% (6/6)100% (1/1)
     
class ChildProcessConnection$ChildServiceConnection100% (1/1)100% (6/6)91%  (173/190)95%  (40.7/43)
onServiceDisconnected (ComponentName): void 100% (1/1)79%  (44/56)86%  (10.3/12)
onServiceConnected (ComponentName, IBinder): void 100% (1/1)88%  (37/42)95%  (10.4/11)
ChildProcessConnection$ChildServiceConnection (ChildProcessConnection, int, b... 100% (1/1)100% (15/15)100% (5/5)
bind (String []): boolean 100% (1/1)100% (44/44)100% (8/8)
isBound (): boolean 100% (1/1)100% (3/3)100% (1/1)
unbind (): void 100% (1/1)100% (30/30)100% (6/6)
     
class ChildProcessConnection$ConnectionParams100% (1/1)100% (1/1)100% (12/12)100% (5/5)
ChildProcessConnection$ConnectionParams (String [], FileDescriptorInfo [], IC... 100% (1/1)100% (12/12)100% (5/5)

1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4 
5package org.chromium.content.browser;
6 
7import android.content.ComponentName;
8import android.content.Context;
9import android.content.Intent;
10import android.content.ServiceConnection;
11import android.os.AsyncTask;
12import android.os.Bundle;
13import android.os.Handler;
14import android.os.IBinder;
15import android.os.Looper;
16import android.os.ParcelFileDescriptor;
17import android.util.Log;
18 
19import java.io.IOException;
20import java.util.concurrent.atomic.AtomicBoolean;
21 
22import org.chromium.base.CalledByNative;
23import org.chromium.base.CpuFeatures;
24import org.chromium.base.SysUtils;
25import org.chromium.base.ThreadUtils;
26import org.chromium.content.app.ChildProcessService;
27import org.chromium.content.common.CommandLine;
28import org.chromium.content.common.IChildProcessCallback;
29import org.chromium.content.common.IChildProcessService;
30import org.chromium.content.common.TraceEvent;
31 
32/**
33 * Manages a connection between the browser activity and a child service. The class is responsible
34 * for estabilishing the connection (start()), closing it (stop()) and increasing the priority of
35 * the service when it is in active use (between calls to attachAsActive() and detachAsActive()).
36 */
37public class ChildProcessConnection {
38    /**
39     * Used to notify the consumer about disconnection of the service. This callback is provided
40     * earlier than ConnectionCallbacks below, as a child process might die before the connection is
41     * fully set up.
42     */
43    interface DeathCallback {
44        void onChildProcessDied(int pid);
45    }
46 
47    /**
48     * Used to notify the consumer about the connection being established and about out-of-memory
49     * bindings being bound for the connection. "Out-of-memory" bindings are bindings that raise the
50     * priority of the service process so that it does not get killed by the OS out-of-memory killer
51     * during normal operation (yet it still may get killed under drastic memory pressure).
52     */
53    interface ConnectionCallbacks {
54        /**
55         * Called when the connection to the service is established. It will be called before any
56         * calls to onOomBindingsAdded(), onOomBindingRemoved().
57         * @param pid Pid of the child process.
58         * @param oomBindingCount Number of the out-of-memory bindings bound before the connection
59         * was established.
60         */
61        void onConnected(int pid, int oomBindingCount);
62 
63        /**
64         * Called when a new out-of-memory binding is bound.
65         */
66        void onOomBindingAdded(int pid);
67 
68        /**
69         * Called when an out-of-memory binding is unbound.
70         */
71        void onOomBindingRemoved(int pid);
72    }
73 
74    // Names of items placed in the bind intent or connection bundle.
75    public static final String EXTRA_COMMAND_LINE =
76            "com.google.android.apps.chrome.extra.command_line";
77    // Note the FDs may only be passed in the connection bundle.
78    public static final String EXTRA_FILES_PREFIX =
79            "com.google.android.apps.chrome.extra.extraFile_";
80    public static final String EXTRA_FILES_ID_SUFFIX = "_id";
81    public static final String EXTRA_FILES_FD_SUFFIX = "_fd";
82 
83    // Used to pass the CPU core count to child processes.
84    public static final String EXTRA_CPU_COUNT =
85            "com.google.android.apps.chrome.extra.cpu_count";
86    // Used to pass the CPU features mask to child processes.
87    public static final String EXTRA_CPU_FEATURES =
88            "com.google.android.apps.chrome.extra.cpu_features";
89 
90    private final Context mContext;
91    private final int mServiceNumber;
92    private final boolean mInSandbox;
93    private final ChildProcessConnection.DeathCallback mDeathCallback;
94    private final Class<? extends ChildProcessService> mServiceClass;
95 
96    // Synchronization: While most internal flow occurs on the UI thread, the public API
97    // (specifically start and stop) may be called from any thread, hence all entry point methods
98    // into the class are synchronized on the lock to protect access to these members. But see also
99    // the TODO where AsyncBoundServiceConnection is created.
100    private final Object mLock = new Object();
101    private IChildProcessService mService = null;
102    // Set to true when the service connect is finished, even if it fails.
103    private boolean mServiceConnectComplete = false;
104    // Set to true when the service disconnects, as opposed to being properly closed. This happens
105    // when the process crashes or gets killed by the system out-of-memory killer.
106    private boolean mServiceDisconnected = false;
107    private int mPID = 0;  // Process ID of the corresponding child process.
108    // Initial binding protects the newly spawned process from being killed before it is put to use,
109    // it is maintained between calls to start() and removeInitialBinding().
110    private ChildServiceConnection mInitialBinding = null;
111    // Strong binding will make the service priority equal to the priority of the activity. We want
112    // the OS to be able to kill background renderers as it kills other background apps, so strong
113    // bindings are maintained only for services that are active at the moment (between
114    // attachAsActive() and detachAsActive()).
115    private ChildServiceConnection mStrongBinding = null;
116    // Low priority binding maintained in the entire lifetime of the connection, i.e. between calls
117    // to start() and stop().
118    private ChildServiceConnection mWaivedBinding = null;
119    // Incremented on attachAsActive(), decremented on detachAsActive().
120    private int mAttachAsActiveCount = 0;
121 
122    private static final String TAG = "ChildProcessConnection";
123 
124    private static class ConnectionParams {
125        final String[] mCommandLine;
126        final FileDescriptorInfo[] mFilesToBeMapped;
127        final IChildProcessCallback mCallback;
128 
129        ConnectionParams(String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
130                IChildProcessCallback callback) {
131            mCommandLine = commandLine;
132            mFilesToBeMapped = filesToBeMapped;
133            mCallback = callback;
134        }
135    }
136 
137    // This is set by the consumer of the class in setupConnection() and is later used in
138    // doSetupConnection(), after which the variable is cleared. Therefore this is only valid while
139    // the connection is being set up.
140    private ConnectionParams mConnectionParams;
141 
142    // Callbacks used to notify the consumer about connection events. This is also provided in
143    // setupConnection(), but remains valid after setup.
144    private ChildProcessConnection.ConnectionCallbacks mConnectionCallbacks;
145 
146    private class ChildServiceConnection implements ServiceConnection {
147        private boolean mBound = false;
148 
149        private final int mBindFlags;
150        private final boolean mProtectsFromOom;
151 
152        public ChildServiceConnection(int bindFlags, boolean protectsFromOom) {
153            mBindFlags = bindFlags;
154            mProtectsFromOom = protectsFromOom;
155        }
156 
157        boolean bind(String[] commandLine) {
158            if (!mBound) {
159                final Intent intent = createServiceBindIntent();
160                if (commandLine != null) {
161                    intent.putExtra(EXTRA_COMMAND_LINE, commandLine);
162                }
163                mBound = mContext.bindService(intent, this, mBindFlags);
164                if (mBound && mProtectsFromOom && mConnectionCallbacks != null) {
165                    mConnectionCallbacks.onOomBindingAdded(getPid());
166                }
167            }
168            return mBound;
169        }
170 
171        void unbind() {
172            if (mBound) {
173                mContext.unbindService(this);
174                mBound = false;
175                // When the process crashes, we stop reporting bindings being unbound (so that their
176                // numbers can be inspected to determine if the process crash could be caused by the
177                // out-of-memory killing), hence the mServiceDisconnected check below.
178                if (mProtectsFromOom && mConnectionCallbacks != null && !mServiceDisconnected) {
179                    mConnectionCallbacks.onOomBindingRemoved(getPid());
180                }
181            }
182        }
183 
184        boolean isBound() {
185            return mBound;
186        }
187 
188        @Override
189        public void onServiceConnected(ComponentName className, IBinder service) {
190            synchronized(mLock) {
191                // A flag from the parent class ensures we run the post-connection logic only once
192                // (instead of once per each ChildServiceConnection).
193                if (mServiceConnectComplete) {
194                    return;
195                }
196                TraceEvent.begin();
197                mServiceConnectComplete = true;
198                mService = IChildProcessService.Stub.asInterface(service);
199                // Make sure that the connection parameters have already been provided. If not,
200                // doConnectionSetup() will be called from setupConnection().
201                if (mConnectionParams != null) {
202                    doConnectionSetup();
203                }
204                TraceEvent.end();
205            }
206        }
207 
208 
209        // Called on the main thread to notify that the child service did not disconnect gracefully.
210        @Override
211        public void onServiceDisconnected(ComponentName className) {
212            // Ensure that the disconnection logic runs only once (instead of once per each
213            // ChildServiceConnection).
214            if (mServiceDisconnected) {
215                return;
216            }
217            mServiceDisconnected = true;
218            int pid = mPID;  // Stash the pid for DeathCallback since stop() will clear it.
219            boolean disconnectedWhileBeingSetUp = mConnectionParams != null;
220            Log.w(TAG, "onServiceDisconnected (crash or killed by oom): pid=" + pid);
221            stop();  // We don't want to auto-restart on crash. Let the browser do that.
222            if (pid != 0) {
223                mDeathCallback.onChildProcessDied(pid);
224            }
225            // TODO(ppi): does anyone know why we need to do that?
226            if (disconnectedWhileBeingSetUp && mConnectionCallbacks != null) {
227                mConnectionCallbacks.onConnected(0, 0);
228            }
229        }
230    }
231 
232    ChildProcessConnection(Context context, int number, boolean inSandbox,
233            ChildProcessConnection.DeathCallback deathCallback,
234            Class<? extends ChildProcessService> serviceClass) {
235        mContext = context;
236        mServiceNumber = number;
237        mInSandbox = inSandbox;
238        mDeathCallback = deathCallback;
239        mServiceClass = serviceClass;
240        mInitialBinding = new ChildServiceConnection(Context.BIND_AUTO_CREATE, true);
241        mStrongBinding = new ChildServiceConnection(
242                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, true);
243        mWaivedBinding = new ChildServiceConnection(
244                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, false);
245    }
246 
247    int getServiceNumber() {
248        return mServiceNumber;
249    }
250 
251    boolean isInSandbox() {
252        return mInSandbox;
253    }
254 
255    IChildProcessService getService() {
256        synchronized(mLock) {
257            return mService;
258        }
259    }
260 
261    private Intent createServiceBindIntent() {
262        Intent intent = new Intent();
263        intent.setClassName(mContext, mServiceClass.getName() + mServiceNumber);
264        intent.setPackage(mContext.getPackageName());
265        return intent;
266    }
267 
268    /**
269     * Starts a connection to an IChildProcessService. This must be followed by a call to
270     * setupConnection() to setup the connection parameters. start() and setupConnection() are
271     * separate to allow the client to pass whatever parameters they have available here, and
272     * complete the remainder later while reducing the connection setup latency.
273     * @param commandLine (Optional) Command line for the child process. If omitted, then
274     *                    the command line parameters must instead be passed to setupConnection().
275     */
276    void start(String[] commandLine) {
277        synchronized(mLock) {
278            TraceEvent.begin();
279            assert !ThreadUtils.runningOnUiThread();
280 
281            if (!mInitialBinding.bind(commandLine)) {
282                onBindFailed();
283            } else {
284                mWaivedBinding.bind(null);
285            }
286            TraceEvent.end();
287        }
288    }
289 
290    /**
291     * Setups the connection after it was started with start(). This method should be called by the
292     * consumer of the class to set up additional connection parameters.
293     * @param commandLine (Optional) will be ignored if the command line was already sent in bind()
294     * @param fileToBeMapped a list of file descriptors that should be registered
295     * @param callback Used for status updates regarding this process connection.
296     * @param connectionCallbacks will notify the consumer about the connection being established
297     * and the status of the out-of-memory bindings being bound for the connection.
298     */
299    void setupConnection(
300            String[] commandLine,
301            FileDescriptorInfo[] filesToBeMapped,
302            IChildProcessCallback processCallback,
303            ConnectionCallbacks connectionCallbacks) {
304        synchronized(mLock) {
305            TraceEvent.begin();
306            assert mConnectionParams == null;
307            mConnectionCallbacks = connectionCallbacks;
308            mConnectionParams = new ConnectionParams(commandLine, filesToBeMapped, processCallback);
309            // Make sure that the service is already connected. If not, doConnectionSetup() will be
310            // called from onServiceConnected().
311            if (mServiceConnectComplete) {
312                doConnectionSetup();
313            }
314            TraceEvent.end();
315        }
316    }
317 
318    /**
319     * Terminates the connection to IChildProcessService, closing all bindings. It is safe to call
320     * this multiple times.
321     */
322    void stop() {
323        synchronized(mLock) {
324            mInitialBinding.unbind();
325            mStrongBinding.unbind();
326            mWaivedBinding.unbind();
327            mAttachAsActiveCount = 0;
328            if (mService != null) {
329                mService = null;
330                mPID = 0;
331            }
332            mConnectionParams = null;
333            mServiceConnectComplete = false;
334        }
335    }
336 
337    // Called on the main thread to notify that the bindService() call failed (returned false).
338    private void onBindFailed() {
339        mServiceConnectComplete = true;
340        if (mConnectionParams != null) {
341            doConnectionSetup();
342        }
343    }
344 
345    /**
346     * Called after the connection parameters have been set (in setupConnection()) *and* a
347     * connection has been established (as signaled by onServiceConnected()) or failed (as signaled
348     * by onBindFailed(), in this case mService will be null). These two events can happen in any
349     * order.
350     */
351    private void doConnectionSetup() {
352        TraceEvent.begin();
353        assert mServiceConnectComplete && mConnectionParams != null;
354 
355        if (mService != null) {
356            Bundle bundle = new Bundle();
357            bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
358 
359            FileDescriptorInfo[] fileInfos = mConnectionParams.mFilesToBeMapped;
360            ParcelFileDescriptor[] parcelFiles = new ParcelFileDescriptor[fileInfos.length];
361            for (int i = 0; i < fileInfos.length; i++) {
362                if (fileInfos[i].mFd == -1) {
363                    // If someone provided an invalid FD, they are doing something wrong.
364                    Log.e(TAG, "Invalid FD (id=" + fileInfos[i].mId + ") for process connection, "
365                          + "aborting connection.");
366                    return;
367                }
368                String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX;
369                String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX;
370                if (fileInfos[i].mAutoClose) {
371                    // Adopt the FD, it will be closed when we close the ParcelFileDescriptor.
372                    parcelFiles[i] = ParcelFileDescriptor.adoptFd(fileInfos[i].mFd);
373                } else {
374                    try {
375                        parcelFiles[i] = ParcelFileDescriptor.fromFd(fileInfos[i].mFd);
376                    } catch(IOException e) {
377                        Log.e(TAG,
378                              "Invalid FD provided for process connection, aborting connection.",
379                              e);
380                        return;
381                    }
382 
383                }
384                bundle.putParcelable(fdName, parcelFiles[i]);
385                bundle.putInt(idName, fileInfos[i].mId);
386            }
387            // Add the CPU properties now.
388            bundle.putInt(EXTRA_CPU_COUNT, CpuFeatures.getCount());
389            bundle.putLong(EXTRA_CPU_FEATURES, CpuFeatures.getMask());
390 
391            try {
392                mPID = mService.setupConnection(bundle, mConnectionParams.mCallback);
393            } catch (android.os.RemoteException re) {
394                Log.e(TAG, "Failed to setup connection.", re);
395            }
396            // We proactively close the FDs rather than wait for GC & finalizer.
397            try {
398                for (ParcelFileDescriptor parcelFile : parcelFiles) {
399                    if (parcelFile != null) parcelFile.close();
400                }
401            } catch (IOException ioe) {
402                Log.w(TAG, "Failed to close FD.", ioe);
403            }
404        }
405        mConnectionParams = null;
406 
407        if (mConnectionCallbacks != null) {
408            // Number of out-of-memory bindings bound before the connection was set up.
409            int oomBindingCount =
410                    (mInitialBinding.isBound() ? 1 : 0) + (mStrongBinding.isBound() ? 1 : 0);
411            mConnectionCallbacks.onConnected(getPid(), oomBindingCount);
412        }
413        TraceEvent.end();
414    }
415 
416    private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000;  // One second.
417 
418    /**
419     * Called to remove the strong binding estabilished when the connection was started. It is safe
420     * to call this multiple times. The binding is removed after a fixed delay period so that the
421     * renderer will not be killed immediately after the call.
422     */
423    void removeInitialBinding() {
424        synchronized(mLock) {
425            if (!mInitialBinding.isBound()) {
426                // While it is safe to post and execute the unbinding multiple times, we prefer to
427                // avoid spamming the message queue.
428                return;
429            }
430        }
431        ThreadUtils.postOnUiThreadDelayed(new Runnable() {
432            @Override
433            public void run() {
434                synchronized(mLock) {
435                    mInitialBinding.unbind();
436                }
437            }
438        }, REMOVE_INITIAL_BINDING_DELAY_MILLIS);
439    }
440 
441    /**
442     * Called when the service becomes active, ie important to the caller. This is handled by
443     * setting up a binding that will make the service as important as the main process. We allow
444     * callers to indicate the same connection as active multiple times. Instead of maintaining
445     * multiple bindings, we count the requests and unbind when the count drops to zero.
446     */
447    void attachAsActive() {
448        synchronized(mLock) {
449            if (mService == null) {
450                Log.w(TAG, "The connection is not bound for " + mPID);
451                return;
452            }
453            if (mAttachAsActiveCount == 0) {
454                mStrongBinding.bind(null);
455            }
456            mAttachAsActiveCount++;
457        }
458    }
459 
460    private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 5 * 1000;  // Five seconds.
461 
462    /**
463     * Called when the service is no longer considered active. For devices that are not considered
464     * low memory the actual binding is removed after a fixed delay period so that the renderer will
465     * not be killed immediately after the call. We don't delay the unbinding for low memory devices
466     * to avoid putting the OS there on strain of having multiple renderers it can't kill.
467     */
468    void detachAsActive() {
469        ThreadUtils.postOnUiThreadDelayed(new Runnable() {
470            @Override
471            public void run() {
472                synchronized(mLock) {
473                    if (mService == null) {
474                        Log.w(TAG, "The connection is not bound for " + mPID);
475                        return;
476                    }
477                    assert mAttachAsActiveCount > 0;
478                    mAttachAsActiveCount--;
479                    if (mAttachAsActiveCount == 0) {
480                        mStrongBinding.unbind();
481                    }
482                }
483            }
484        }, SysUtils.isLowEndDevice() ? 0 : DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS);
485    }
486 
487    /**
488     * @return The connection PID, or 0 if not yet connected.
489     */
490    int getPid() {
491        synchronized(mLock) {
492            return mPID;
493        }
494    }
495}

[all classes][org.chromium.content.browser]
EMMA 2.0.5312 (C) Vladimir Roubtsov