Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-04-26 08:00:50 -04:00
commit a80e3021bb
53 changed files with 3186 additions and 806 deletions

View File

@ -576,12 +576,20 @@ pref("hal.processPriorityManager.gonk.backgroundKillUnderMB", 20);
pref("hal.processPriorityManager.gonk.notifyLowMemUnderMB", 10);
// Niceness values (i.e., CPU priorities) for B2G processes.
//
// Note: The maximum nice value on Linux is 19, but the max value you should
// use here is 18. NSPR adds 1 to some threads' nice values, to mark
// low-priority threads. If the process priority manager were to renice a
// process (and all its threads) to 19, all threads would have the same
// niceness. Then when we reniced the process to (say) 10, all threads would
// /still/ have the same niceness; we'd effectively have erased NSPR's thread
// priorities.
pref("hal.processPriorityManager.gonk.masterNice", 0);
pref("hal.processPriorityManager.gonk.foregroundHighNice", 0);
pref("hal.processPriorityManager.gonk.foregroundNice", 1);
pref("hal.processPriorityManager.gonk.backgroundPerceivableNice", 10);
pref("hal.processPriorityManager.gonk.backgroundHomescreenNice", 20);
pref("hal.processPriorityManager.gonk.backgroundNice", 20);
pref("hal.processPriorityManager.gonk.backgroundHomescreenNice", 18);
pref("hal.processPriorityManager.gonk.backgroundNice", 18);
#ifndef DEBUG
// Enable pre-launching content processes for improved startup time
@ -665,6 +673,9 @@ pref("memory_info_dumper.watch_fifo.directory", "/data/local");
pref("general.useragent.enable_overrides", true);
// Make <audio> and <video> talk to the AudioChannelService.
pref("media.useAudioChannelService", true);
pref("b2g.version", @MOZ_B2G_VERSION@);
// Disable console buffering to save memory.
@ -673,4 +684,4 @@ pref("consoleservice.buffered", false);
#ifdef MOZ_WIDGET_GONK
// Performance testing suggests 2k is a better page size for SQLite.
pref("toolkit.storage.pageSize", 2048);
#endif
#endif

View File

@ -13,6 +13,7 @@ interface nsSubDocumentFrame;
interface nsIMessageSender;
interface nsIVariant;
interface nsIDOMElement;
interface nsITabParent;
typedef unsigned long long nsContentViewId;
@ -110,7 +111,7 @@ interface nsIContentViewManager : nsISupports
readonly attribute nsIContentView rootContentView;
};
[scriptable, uuid(a4db652e-e3b0-4345-8107-cf6a30486759)]
[scriptable, builtinclass, uuid(e4333e51-f2fa-4fdd-becd-75d000703355)]
interface nsIFrameLoader : nsISupports
{
/**
@ -118,6 +119,12 @@ interface nsIFrameLoader : nsISupports
*/
readonly attribute nsIDocShell docShell;
/**
* Get this frame loader's TabParent, if it has a remote frame. Otherwise,
* returns null.
*/
readonly attribute nsITabParent tabParent;
/**
* Start loading the frame. This method figures out what to load
* from the owner content in the frame loader.
@ -249,6 +256,15 @@ interface nsIFrameLoader : nsISupports
* returns the iframe element.
*/
readonly attribute nsIDOMElement ownerElement;
/**
* Get or set this frame loader's visibility.
*
* The notion of "visibility" here is separate from the notion of a
* window/docshell's visibility. This field is mostly here so that we can
* have a notion of visibility in the parent process when frames are OOP.
*/
[infallible] attribute boolean visible;
};
%{C++

View File

@ -285,6 +285,7 @@ nsFrameLoader::nsFrameLoader(Element* aOwner, bool aNetworkCreated)
, mClampScrollPosition(true)
, mRemoteBrowserInitialized(false)
, mObservingOwnerContent(false)
, mVisible(true)
, mCurrentRemoteFrame(nullptr)
, mRemoteBrowser(nullptr)
, mRenderMode(RENDER_MODE_DEFAULT)
@ -2551,3 +2552,29 @@ nsFrameLoader::ResetPermissionManagerStatus()
}
}
/* [infallible] */ NS_IMETHODIMP
nsFrameLoader::SetVisible(bool aVisible)
{
mVisible = aVisible;
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
"frameloader-visible-changed", nullptr);
}
return NS_OK;
}
/* [infallible] */ NS_IMETHODIMP
nsFrameLoader::GetVisible(bool* aVisible)
{
*aVisible = mVisible;
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::GetTabParent(nsITabParent** aTabParent)
{
nsCOMPtr<nsITabParent> tp = mRemoteBrowser;
tp.forget(aTabParent);
return NS_OK;
}

View File

@ -428,6 +428,11 @@ private:
bool mRemoteBrowserInitialized : 1;
bool mObservingOwnerContent : 1;
// Backs nsIFrameLoader::{Get,Set}Visible. Visibility state here relates to
// whether this frameloader's <iframe mozbrowser> is setVisible(true)'ed, and
// doesn't necessarily correlate with docshell/document visibility.
bool mVisible : 1;
// XXX leaking
nsCOMPtr<nsIObserver> mChildHost;
RenderFrameParent* mCurrentRemoteFrame;

View File

@ -45,13 +45,12 @@
#include "mozilla/Services.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ipc/ProcessPriorityManager.h"
#include "mozilla/ProcessPriorityManager.h"
#include "Layers.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using namespace mozilla::gfx;
using namespace mozilla::gl;
using namespace mozilla::layers;
@ -66,7 +65,8 @@ WebGLMemoryPressureObserver::Observe(nsISupports* aSubject,
bool wantToLoseContext = true;
if (!mContext->mCanLoseContextInForeground && CurrentProcessIsForeground())
if (!mContext->mCanLoseContextInForeground &&
ProcessPriorityManager::CurrentProcessIsForeground())
wantToLoseContext = false;
else if (!nsCRT::strcmp(aSomeData,
NS_LITERAL_STRING("heap-minimize").get()))

View File

@ -970,6 +970,11 @@ static bool IsAutoplayEnabled()
return Preferences::GetBool("media.autoplay.enabled");
}
static bool UseAudioChannelService()
{
return Preferences::GetBool("media.useAudioChannelService");
}
void HTMLMediaElement::UpdatePreloadAction()
{
PreloadAction nextAction = PRELOAD_UNDEFINED;
@ -2163,7 +2168,10 @@ bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
bool HTMLMediaElement::CheckAudioChannelPermissions(const nsAString& aString)
{
#ifdef ANDROID
if (!UseAudioChannelService()) {
return true;
}
// Only normal channel doesn't need permission.
if (!aString.EqualsASCII("normal")) {
nsCOMPtr<nsIPermissionManager> permissionManager =
@ -2179,7 +2187,7 @@ bool HTMLMediaElement::CheckAudioChannelPermissions(const nsAString& aString)
return false;
}
}
#endif
return true;
}
@ -3218,17 +3226,17 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendE
void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
{
nsIDocument* ownerDoc = OwnerDoc();
#ifdef ANDROID
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
if (domDoc) {
bool hidden = false;
domDoc->GetHidden(&hidden);
// SetVisibilityState will update mChannelSuspended via the CanPlayChanged callback.
if (mPlayingThroughTheAudioChannel && mAudioChannelAgent) {
mAudioChannelAgent->SetVisibilityState(!hidden);
if (UseAudioChannelService()) {
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
if (domDoc) {
bool hidden = false;
domDoc->GetHidden(&hidden);
// SetVisibilityState will update mChannelSuspended via the CanPlayChanged callback.
if (mPlayingThroughTheAudioChannel && mAudioChannelAgent) {
mAudioChannelAgent->SetVisibilityState(!hidden);
}
}
}
#endif
bool suspendEvents = !ownerDoc->IsActive() || !ownerDoc->IsVisible();
bool pauseElement = suspendEvents || mChannelSuspended;
@ -3656,9 +3664,10 @@ ImageContainer* HTMLMediaElement::GetImageContainer()
nsresult HTMLMediaElement::UpdateChannelMuteState(bool aCanPlay)
{
// Only on B2G we mute the HTMLMediaElement following the rules of
// AudioChannelService.
#ifdef ANDROID
if (!UseAudioChannelService()) {
return NS_OK;
}
// We have to mute this channel:
if (!aCanPlay && !mChannelSuspended) {
mChannelSuspended = true;
@ -3669,15 +3678,15 @@ nsresult HTMLMediaElement::UpdateChannelMuteState(bool aCanPlay)
}
SuspendOrResumeElement(mChannelSuspended, false);
#endif
return NS_OK;
}
void HTMLMediaElement::UpdateAudioChannelPlayingState()
{
// The HTMLMediaElement is registered to the AudioChannelService only on B2G.
#ifdef ANDROID
if (!UseAudioChannelService()) {
return;
}
bool playingThroughTheAudioChannel =
(!mPaused &&
(HasAttr(kNameSpaceID_None, nsGkAtoms::loop) ||
@ -3711,7 +3720,6 @@ void HTMLMediaElement::UpdateAudioChannelPlayingState()
mAudioChannelAgent = nullptr;
}
}
#endif
}
/* void canPlayChanged (in boolean canPlay); */

View File

@ -17,6 +17,7 @@
#include "mozilla/dom/ContentParent.h"
#include "nsThreadUtils.h"
#include "nsHashPropertyBag.h"
#ifdef MOZ_WIDGET_GONK
#include "nsIAudioManager.h"
@ -104,7 +105,7 @@ AudioChannelService::RegisterType(AudioChannelType aType, uint64_t aChildID)
// In order to avoid race conditions, it's safer to notify any existing
// agent any time a new one is registered.
if (XRE_GetProcessType() == GeckoProcessType_Default) {
SendAudioChannelChangedNotification();
SendAudioChannelChangedNotification(aChildID);
Notify();
}
}
@ -142,7 +143,7 @@ AudioChannelService::UnregisterType(AudioChannelType aType,
!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID)) {
mActiveContentChildIDs.RemoveElement(aChildID);
}
SendAudioChannelChangedNotification();
SendAudioChannelChangedNotification(aChildID);
Notify();
}
}
@ -180,7 +181,6 @@ AudioChannelService::GetMuted(AudioChannelAgent* aAgent, bool aElementHidden)
aElementHidden, oldElementHidden);
data->mMuted = muted;
SendAudioChannelChangedNotification();
return muted;
}
@ -207,7 +207,6 @@ AudioChannelService::GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
mActiveContentChildIDs.AppendElement(aChildID);
}
}
else if (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
oldType == AUDIO_CHANNEL_INT_CONTENT &&
!mActiveContentChildIDsFrozen) {
@ -228,6 +227,8 @@ AudioChannelService::GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
Notify();
}
SendAudioChannelChangedNotification(aChildID);
// Let play any visible audio channel.
if (!aElementHidden) {
return false;
@ -258,13 +259,29 @@ AudioChannelService::ContentOrNormalChannelIsActive()
!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty();
}
bool
AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
{
return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) ||
mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID);
}
void
AudioChannelService::SendAudioChannelChangedNotification()
AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
{
if (XRE_GetProcessType() != GeckoProcessType_Default) {
return;
}
nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
props->Init();
props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props),
"audio-channel-process-changed", nullptr);
// Calculating the most important active channel.
AudioChannelType higher = AUDIO_CHANNEL_LAST;
@ -341,7 +358,6 @@ AudioChannelService::SendAudioChannelChangedNotification()
channelName.AssignLiteral("none");
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
}
@ -355,7 +371,6 @@ AudioChannelService::SendAudioChannelChangedNotification()
channelName.AssignLiteral("none");
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get());
}
}
@ -461,7 +476,7 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const PR
// We don't have to remove the agents from the mAgents hashtable because if
// that table contains only agents running on the same process.
SendAudioChannelChangedNotification();
SendAudioChannelChangedNotification(childID);
Notify();
} else {
NS_WARNING("ipc:content-shutdown message without childID property");

View File

@ -60,13 +60,20 @@ public:
*/
virtual bool ContentOrNormalChannelIsActive();
/**
* Return true iff a normal or content channel is active for the given process
* ID.
*/
virtual bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID);
protected:
void Notify();
/**
* Send the audio-channel-changed notification if needed.
* Send the audio-channel-changed notification for the given process ID if
* needed.
*/
void SendAudioChannelChangedNotification();
void SendAudioChannelChangedNotification(uint64_t aChildID);
/* Register/Unregister IPC types: */
void RegisterType(AudioChannelType aType, uint64_t aChildID);

View File

@ -646,13 +646,7 @@ BrowserElementChild.prototype = {
}
this._forcedVisible = data.json.visible;
this._updateDocShellVisibility();
// Fire a notification to the ProcessPriorityManager to reset this
// process's priority now (as opposed to after a brief delay).
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.notifyObservers(/* subject */ null, 'process-priority:reset-now',
/* data */ null);
this._updateVisibility();
},
_recvVisible: function(data) {
@ -669,13 +663,14 @@ BrowserElementChild.prototype = {
_recvOwnerVisibilityChange: function(data) {
debug("Received ownerVisibilityChange: (" + data.json.visible + ")");
this._ownerVisible = data.json.visible;
this._updateDocShellVisibility();
this._updateVisibility();
},
_updateDocShellVisibility: function() {
_updateVisibility: function() {
var visible = this._forcedVisible && this._ownerVisible;
if (docShell.isActive !== visible) {
docShell.isActive = visible;
sendAsyncMsg('visibility-change', {visibility: visible});
}
},

View File

@ -118,7 +118,8 @@ function BrowserElementParent(frameLoader, hasRemoteFrame) {
"fullscreen-origin-change": this._remoteFullscreenOriginChange,
"rollback-fullscreen": this._remoteFrameFullscreenReverted,
"exit-fullscreen": this._exitFullscreen,
"got-visible": this._gotDOMRequestResult
"got-visible": this._gotDOMRequestResult,
"visibility-change": this._childVisibilityChange,
}
this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
@ -447,6 +448,7 @@ BrowserElementParent.prototype = {
_setVisible: function(visible) {
this._sendAsyncMsg('set-visible', {visible: visible});
this._frameLoader.visible = visible;
},
_sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
@ -562,6 +564,19 @@ BrowserElementParent.prototype = {
{visible: !this._window.document.hidden});
},
/*
* Called when the child notices that its visibility has changed.
*
* This is sometimes redundant; for example, the child's visibility may
* change in response to a setVisible request that we made here! But it's
* not always redundant; for example, the child's visibility may change in
* response to its parent docshell being hidden.
*/
_childVisibilityChange: function(data) {
debug("_childVisibilityChange(" + data.json.visible + ")");
this._frameLoader.visible = data.json.visible;
},
_exitFullscreen: function() {
this._windowUtils.exitFullscreen();
},

View File

@ -61,18 +61,42 @@ const browserElementTestHelpers = {
this.tempPermissions.push(location.href)
},
removeAllTempPermissions: function() {
for(var i = 0; i < this.tempPermissions.length; i++) {
SpecialPowers.removePermission("browser", this.tempPermissions[i]);
}
},
'tempPermissions': [],
addPermissionForUrl: function(url) {
SpecialPowers.addPermission("browser", true, url);
this.tempPermissions.push(url);
},
'tempPermissions': [],
_observers: [],
// This function is a wrapper which lets you register an observer to one of
// the process priority manager's test-only topics. observerFn should be a
// function which takes (subject, topic, data).
//
// We'll clean up any observers you add at the end of the test.
addProcessPriorityObserver: function(processPriorityTopic, observerFn) {
var topic = "process-priority-manager:TEST-ONLY:" + processPriorityTopic;
// SpecialPowers appears to require that the observer be an object, not a
// function.
var observer = {
observe: observerFn
};
SpecialPowers.addObserver(observer, topic, /* weak = */ false);
this._observers.push([observer, topic]);
},
cleanUp: function() {
for (var i = 0; i < this.tempPermissions.length; i++) {
SpecialPowers.removePermission("browser", this.tempPermissions[i]);
}
for (var i = 0; i < this._observers.length; i++) {
SpecialPowers.removeObserver(this._observers[i][0],
this._observers[i][1]);
}
},
// Some basically-empty pages from different domains you can load.
'emptyPage1': 'http://example.com' + _getPath() + '/file_empty.html',
@ -81,6 +105,92 @@ const browserElementTestHelpers = {
'focusPage': 'http://example.org' + _getPath() + '/file_focus.html',
};
// Returns a promise which is resolved when a subprocess is created. The
// argument to resolve() is the childID of the subprocess.
function expectProcessCreated() {
var deferred = Promise.defer();
var observed = false;
browserElementTestHelpers.addProcessPriorityObserver(
"process-created",
function(subject, topic, data) {
// Don't run this observer twice, so we don't ok(true) twice. (It's fine
// to resolve a promise twice; the second resolve() call does nothing.)
if (observed) {
return;
}
observed = true;
var childID = parseInt(data);
ok(true, 'Got new process, id=' + childID);
deferred.resolve(childID);
}
);
return deferred.promise;
}
// Just like expectProcessCreated(), except we'll call ok(false) if a second
// process is created.
function expectOnlyOneProcessCreated() {
var p = expectProcessCreated();
p.then(function() {
expectProcessCreated().then(function(childID) {
ok(false, 'Got unexpected process creation, childID=' + childID);
});
});
return p;
}
// Returns a promise which is resolved or rejected the next time the process
// childID changes its priority. We resolve if the priority matches
// expectedPriority, and we reject otherwise.
function expectPriorityChange(childID, expectedPriority) {
var deferred = Promise.defer();
var observed = false;
browserElementTestHelpers.addProcessPriorityObserver(
'process-priority-set',
function(subject, topic, data) {
if (observed) {
return;
}
[id, priority] = data.split(":");
if (id != childID) {
return;
}
// Make sure we run the is() calls in this observer only once, otherwise
// we'll expect /every/ priority change to match expectedPriority.
observed = true;
is(priority, expectedPriority,
'Expected priority of childID ' + childID +
' to change to ' + expectedPriority);
if (priority == expectedPriority) {
deferred.resolve(priority);
} else {
deferred.reject(priority);
}
}
);
return deferred.promise;
}
// Returns a promise which is resolved the first time the given iframe fires
// the mozbrowser##eventName event.
function expectMozbrowserEvent(iframe, eventName) {
var deferred = Promise.defer();
iframe.addEventListener('mozbrowser' + eventName, function handler(e) {
iframe.removeEventListener('mozbrowser' + eventName, handler);
deferred.resolve(e);
});
return deferred.promise;
}
// Set some prefs:
//
// * browser.pageThumbs.enabled: false
@ -129,7 +239,7 @@ const browserElementTestHelpers = {
})();
addEventListener('unload', function() {
browserElementTestHelpers.removeAllTempPermissions();
browserElementTestHelpers.cleanUp();
});
// Wait for the load event before unlocking the test-ready event.
@ -137,3 +247,276 @@ browserElementTestHelpers.lockTestReady();
addEventListener('load', function() {
SimpleTest.executeSoon(browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers));
});
//////////////////////////////////
// promise.js from the addon SDK with some modifications to the module
// boilerplate.
//////////////////////////////////
;(function(id, factory) { // Module boilerplate :(
var globals = this;
factory(function require(id) {
return globals[id];
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
}).call(this, 'Promise', function Promise(require, exports, module) {
'use strict';
module.metadata = {
"stability": "unstable"
};
/**
* Internal utility: Wraps given `value` into simplified promise, successfully
* fulfilled to a given `value`. Note the result is not a complete promise
* implementation, as its method `then` does not returns anything.
*/
function fulfilled(value) {
return { then: function then(fulfill) { fulfill(value); } };
}
/**
* Internal utility: Wraps given input into simplified promise, pre-rejected
* with a given `reason`. Note the result is not a complete promise
* implementation, as its method `then` does not returns anything.
*/
function rejected(reason) {
return { then: function then(fulfill, reject) { reject(reason); } };
}
/**
* Internal utility: Returns `true` if given `value` is a promise. Value is
* assumed to be a promise if it implements method `then`.
*/
function isPromise(value) {
return value && typeof(value.then) === 'function';
}
/**
* Creates deferred object containing fresh promise & methods to either resolve
* or reject it. The result is an object with the following properties:
* - `promise` Eventual value representation implementing CommonJS [Promises/A]
* (http://wiki.commonjs.org/wiki/Promises/A) API.
* - `resolve` Single shot function that resolves enclosed `promise` with a
* given `value`.
* - `reject` Single shot function that rejects enclosed `promise` with a given
* `reason`.
*
* An optional `prototype` argument is used as a prototype of the returned
* `promise` allowing one to implement additional API. If prototype is not
* passed then it falls back to `Object.prototype`.
*
* ## Example
*
* function fetchURI(uri, type) {
* var deferred = defer();
* var request = new XMLHttpRequest();
* request.open("GET", uri, true);
* request.responseType = type;
* request.onload = function onload() {
* deferred.resolve(request.response);
* }
* request.onerror = function(event) {
* deferred.reject(event);
* }
* request.send();
*
* return deferred.promise;
* }
*/
function defer(prototype) {
// Define FIFO queue of observer pairs. Once promise is resolved & all queued
// observers are forwarded to `result` and variable is set to `null`.
var observers = [];
// Promise `result`, which will be assigned a resolution value once promise
// is resolved. Note that result will always be assigned promise (or alike)
// object to take care of propagation through promise chains. If result is
// `null` promise is not resolved yet.
var result = null;
prototype = (prototype || prototype === null) ? prototype : Object.prototype;
// Create an object implementing promise API.
var promise = Object.create(prototype, {
then: { value: function then(onFulfill, onError) {
var deferred = defer(prototype);
function resolve(value) {
// If `onFulfill` handler is provided resolve `deferred.promise` with
// result of invoking it with a resolution value. If handler is not
// provided propagate value through.
try {
deferred.resolve(onFulfill ? onFulfill(value) : value);
}
// `onFulfill` may throw exception in which case resulting promise
// is rejected with thrown exception.
catch(error) {
if (exports._reportErrors && typeof(console) === 'object')
console.error(error);
// Note: Following is equivalent of `deferred.reject(error)`,
// we use this shortcut to reduce a stack.
deferred.resolve(rejected(error));
}
}
function reject(reason) {
try {
if (onError) deferred.resolve(onError(reason));
else deferred.resolve(rejected(reason));
}
catch(error) {
if (exports._reportErrors && typeof(console) === 'object')
console.error(error)
deferred.resolve(rejected(error));
}
}
// If enclosed promise (`this.promise`) observers queue is still alive
// enqueue a new observer pair into it. Note that this does not
// necessary means that promise is pending, it may already be resolved,
// but we still have to queue observers to guarantee an order of
// propagation.
if (observers) {
observers.push({ resolve: resolve, reject: reject });
}
// Otherwise just forward observer pair right to a `result` promise.
else {
result.then(resolve, reject);
}
return deferred.promise;
}}
})
var deferred = {
promise: promise,
/**
* Resolves associated `promise` to a given `value`, unless it's already
* resolved or rejected. Note that resolved promise is not necessary a
* successfully fulfilled. Promise may be resolved with a promise `value`
* in which case `value` promise's fulfillment / rejection will propagate
* up to a promise resolved with `value`.
*/
resolve: function resolve(value) {
if (!result) {
// Store resolution `value` in a `result` as a promise, so that all
// the subsequent handlers can be simply forwarded to it. Since
// `result` will be a promise all the value / error propagation will
// be uniformly taken care of.
result = isPromise(value) ? value : fulfilled(value);
// Forward already registered observers to a `result` promise in the
// order they were registered. Note that we intentionally dequeue
// observer at a time until queue is exhausted. This makes sure that
// handlers registered as side effect of observer forwarding are
// queued instead of being invoked immediately, guaranteeing FIFO
// order.
while (observers.length) {
var observer = observers.shift();
result.then(observer.resolve, observer.reject);
}
// Once `observers` queue is exhausted we `null`-ify it, so that
// new handlers are forwarded straight to the `result`.
observers = null;
}
},
/**
* Rejects associated `promise` with a given `reason`, unless it's already
* resolved / rejected. This is just a (better performing) convenience
* shortcut for `deferred.resolve(reject(reason))`.
*/
reject: function reject(reason) {
// Note that if promise is resolved that does not necessary means that it
// is successfully fulfilled. Resolution value may be a promise in which
// case its result propagates. In other words if promise `a` is resolved
// with promise `b`, `a` is either fulfilled or rejected depending
// on weather `b` is fulfilled or rejected. Here `deferred.promise` is
// resolved with a promise pre-rejected with a given `reason`, there for
// `deferred.promise` is rejected with a given `reason`. This may feel
// little awkward first, but doing it this way greatly simplifies
// propagation through promise chains.
deferred.resolve(rejected(reason));
}
};
return deferred;
}
exports.defer = defer;
/**
* Returns a promise resolved to a given `value`. Optionally a second
* `prototype` argument may be provided to be used as a prototype for the
* returned promise.
*/
function resolve(value, prototype) {
var deferred = defer(prototype);
deferred.resolve(value);
return deferred.promise;
}
exports.resolve = resolve;
/**
* Returns a promise rejected with a given `reason`. Optionally a second
* `prototype` argument may be provided to be used as a prototype for the
* returned promise.
*/
function reject(reason, prototype) {
var deferred = defer(prototype);
deferred.reject(reason);
return deferred.promise;
}
exports.reject = reject;
var promised = (function() {
// Note: Define shortcuts and utility functions here in order to avoid
// slower property accesses and unnecessary closure creations on each
// call of this popular function.
var call = Function.call;
var concat = Array.prototype.concat;
// Utility function that does following:
// execute([ f, self, args...]) => f.apply(self, args)
function execute(args) { return call.apply(call, args) }
// Utility function that takes promise of `a` array and maybe promise `b`
// as arguments and returns promise for `a.concat(b)`.
function promisedConcat(promises, unknown) {
return promises.then(function(values) {
return resolve(unknown).then(function(value) {
return values.concat([ value ])
});
});
}
return function promised(f, prototype) {
/**
Returns a wrapped `f`, which when called returns a promise that resolves to
`f(...)` passing all the given arguments to it, which by the way may be
promises. Optionally second `prototype` argument may be provided to be used
a prototype for a returned promise.
## Example
var promise = promised(Array)(1, promise(2), promise(3))
promise.then(console.log) // => [ 1, 2, 3 ]
**/
return function promised() {
// create array of [ f, this, args... ]
return concat.apply([ f, this ], arguments).
// reduce it via `promisedConcat` to get promised array of fulfillments
reduce(promisedConcat, resolve([], prototype)).
// finally map that to promise of `f.apply(this, args...)`
then(execute);
}
}
})();
exports.promised = promised;
var all = promised(Array);
exports.all = all;
});

View File

@ -4,3 +4,4 @@
# 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/.
DIRS = ['priority']

View File

@ -0,0 +1,46 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
# Note: ../browserElementTestHelpers.js makes all tests in this directory OOP,
# because testing the process-priority manager without OOP frames doesn't make
# much sense.
#
# Good luck running these tests on anything but desktop Linux.
ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
# This test disabled due to bug 865844. In fact, it was never enabled!
#
# test_WebGLContextLost.html \
# file_WebGLContextLost.html \
MOCHITEST_FILES = \
test_Simple.html \
test_Visibility.html \
test_HighPriority.html \
file_HighPriority.html \
test_Background.html \
test_Audio.html \
file_Audio.html \
silence.ogg \
test_MultipleFrames.html \
file_MultipleFrames.html \
test_Preallocated.html \
test_ExpectingSystemMessage.html \
test_ExpectingSystemMessage2.html \
test_NestedFrames.html \
file_NestedFramesOuter.html \
$(NULL)
endif # MOZ_WIDGET_TOOLKIT == gtk2
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,19 @@
<html>
<body>
<script>
addEventListener('load', function() {
setTimeout(function() {
var a = document.getElementById('audio');
a.onplay = function() {
alert('onplay');
};
a.play();
}, 0);
});
</script>
<audio id='audio' loop src='silence.ogg'>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
var lock = navigator.requestWakeLock('high-priority');
alert('step0');
lock.unlock();
alert('step1');
lock = navigator.requestWakeLock('cpu');
alert('step2');
lock.unlock();
alert('step3');
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
<html>
<body>
<p>file_MultipleFrames.html</p>
<script>
addEventListener('load', function() {
setTimeout(function() {
window.open('../file_empty.html');
}, 0);
});
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
<html>
<body>
<p>file_NestedFramesOuter.html</p>
<script>
addEventListener('load', function() {
setTimeout(createIframe, 0);
});
function createIframe()
{
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = location.hash.substr(1);
document.body.appendChild(iframe);
}
</script>
</body>
</html>

View File

@ -0,0 +1,22 @@
<html>
<body>
file_WebGLContextLost.html
<canvas id='canvas'></canvas>
<script>
function runTest()
{
var canvas = document.getElementById('canvas');
canvas.addEventListener('webglcontextlost', function() {
alert('webglcontextlost');
});
var context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
context.viewport(0, 0, 10, 10);
alert('ready');
}
addEventListener('load', function() { setTimeout(runTest, 0) });
</script>
</body>
</html>

Binary file not shown.

View File

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<!--
Test that frames playing audio get BACKGROUND_PERCEIVABLE priority.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = 'file_Audio.html';
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
return Promise.all(
[expectPriorityChange(childID, 'FOREGROUND'),
expectMozbrowserEvent(iframe, 'loadend'),
expectMozbrowserEvent(iframe, 'showmodalprompt').then(function(e) {
is(e.detail.message, 'onplay', 'showmodalprompt message');
})]
);
}).then(function() {
// Send the child process into the background. Because it's playing audio,
// it should get priority BACKGROUND_PERCEIVABLE, not vanilla BACKGROUND.
var p = expectPriorityChange(childID, 'BACKGROUND_PERCEIVABLE');
iframe.setVisible(false);
return p;
}).then(function() {
var p = expectPriorityChange(childID, 'FOREGROUND');
iframe.setVisible(true);
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
// This test relies on <audio> elements interacting with the audio channel
// service. This is controled by the media.useAudioChannelService pref.
addEventListener('testready', function() {
SpecialPowers.pushPrefEnv({set: [['media.useAudioChannelService', true]]},
runTest);
});
</script>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<!--
Test that calling setVisible('false') on an iframe causes its visibility to
change.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = browserElementTestHelpers.emptyPage1;
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
}).then(function() {
return expectPriorityChange(childID, 'FOREGROUND');
}).then(function() {
return expectMozbrowserEvent(iframe, 'loadend');
}).then(function() {
var p = expectPriorityChange(childID, 'BACKGROUND');
// We wait until mozbrowserloadend before calling setVisible, because
// setVisible isn't available until mozbrowser has loaded. In practice, that
// means we can call setVisible once we've gotten /any/ mozbrowser event.
iframe.setVisible(false);
return p;
}).then(function() {
var p = expectPriorityChange(childID, 'FOREGROUND');
iframe.setVisible(true);
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', runTest);
</script>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<!--
Test that a high-priority frame that's expecting a system message initially
gets priority FOREGROUND_HIGH.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
SpecialPowers.addPermission("embed-apps", true, document);
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.setAttribute('expecting-system-message', true);
iframe.setAttribute('mozapptype', 'critical');
iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
iframe.src = browserElementTestHelpers.emptyPage1;
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
return expectPriorityChange(childID, 'FOREGROUND_HIGH');
}).then(function() {
// We go back to foreground when the wake lock taken on behalf of our new
// process times out.
return expectPriorityChange(childID, 'FOREGROUND');
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', function() {
// Cause the CPU wake lock taken on behalf of this new process to time out
// after 1s.
SpecialPowers.pushPrefEnv(
{set: [["dom.ipc.systemMessageCPULockTimeoutSec", 1]]},
runTest);
});
</script>
</body>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<!--
Test that a regular (not mozapptype=critical) frame that's expecting a system
message gets priority BACKGROUND_PERCEIVABLE when it's in the background.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
SpecialPowers.addPermission("embed-apps", true, document);
// Give our origin permission to open browsers, and remove it when the test is complete.
var principal = SpecialPowers.wrap(SpecialPowers.getNodePrincipal(document));
SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec,
appId: principal.appId,
isInBrowserElement: true });
addEventListener('unload', function() {
var principal = SpecialPowers.wrap(SpecialPowers.getNodePrincipal(document));
SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
appId: principal.appId,
isInBrowserElement: true });
});
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.setAttribute('expecting-system-message', true);
iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
iframe.src = browserElementTestHelpers.emptyPage1;
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
return Promise.all(
[expectPriorityChange(childID, 'FOREGROUND'),
expectMozbrowserEvent(iframe, 'loadend')]);
}).then(function() {
var p = expectPriorityChange(childID, 'BACKGROUND_PERCEIVABLE');
iframe.setVisible(false);
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', function() {
// We don't want this wake lock to time out during the test; if it did, then
// we might see BACKGROUND priority instead of BACKGROUND_PERCEIVABLE. So
// set the timeout to a large value.
SpecialPowers.pushPrefEnv(
{set: [["dom.ipc.systemMessageCPULockTimeoutSec", 99999]]},
runTest);
});
</script>
</body>
</html>

View File

@ -0,0 +1,121 @@
<!DOCTYPE HTML>
<html>
<!--
Test that frames with mozapptype=critical which hold the "high-priority" or
"cpu" wake locks get elevated process priority.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.setAttribute('mozapptype', 'critical');
iframe.src = 'file_HighPriority.html';
// We expect the following to happen:
//
// - Process is created.
// - Its priority is set to FOREGROUND (when the process starts).
// - wait_alert('step0', FOREGROUND_HIGH)
// - wait_alert('step1', FOREGROUND)
// - wait_alert('step2', FOREGROUND_HIGH)
//
// Where wait_alert(M, P) means that we expect the subprocess to
// * do alert(M) and
// * be set to priority P
// in some order. If the alert occurs before the priority change, we block
// the alert until we observe the priority change. So the subprocess only
// has to do
//
// // set priority to FOREGROUND_HIGH
// alert('step0');
// // set priority to FOREGROUND
// alert('step1');
//
// etc.
var childID = null;
var alertTimes = [];
// Return a promise that's resolved once the child process calls alert() and
// we get a priority change, in some order.
//
// We check that the text of the alert is |"step" + index|.
//
// If gracePeriod is given, we check that the priority change occurred at
// least gracePeriod ms since the alert from the previous step (with a fudge
// factor to account for inaccurate timers).
function expectAlertAndPriorityChange(index, priority, /* optional */ gracePeriod) {
function checkAlertInfo(e) {
is(e.detail.message, 'step' + index, 'alert() number ' + index);
alertTimes.push(new Date());
// Block the alert; we'll unblock it by calling e.detail.unblock() later.
e.preventDefault();
return Promise.resolve(e.detail.unblock);
}
function checkGracePeriod() {
if (gracePeriod) {
var msSinceLastAlert = (new Date()) - alertTimes[index - 1];
// 50ms fudge factor. This test is set up so that, if nsITimers are
// accurate, we don't need any fudge factor. Unfortunately our timers
// are not accurate! There's little we can do here except fudge.
// Thankfully all we're trying to test is that we get /some/ delay; the
// exact amount of delay isn't so important.
ok(msSinceLastAlert + 50 >= gracePeriod,
msSinceLastAlert + "ms since last alert >= (" + gracePeriod + " - 50ms)");
}
}
return Promise.all(
[expectMozbrowserEvent(iframe, 'showmodalprompt').then(checkAlertInfo),
expectPriorityChange(childID, priority).then(checkGracePeriod)]
).then(function(results) {
// expectMozbrowserEvent returns the function to call to unblock the
// alert. It comes to us as the first element of the results array.
results[0]();
});
}
expectProcessCreated().then(function(chid) {
childID = chid;
return expectPriorityChange(childID, 'FOREGROUND');
}).then(function() {
return expectAlertAndPriorityChange(0, 'FOREGROUND_HIGH');
}).then(function() {
return expectAlertAndPriorityChange(1, 'FOREGROUND', priorityChangeGracePeriod);
}).then(function() {
return expectAlertAndPriorityChange(2, 'FOREGROUND_HIGH');
}).then(function() {
return expectAlertAndPriorityChange(3, 'FOREGROUND', priorityChangeGracePeriod);
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
const priorityChangeGracePeriod = 100;
addEventListener('testready', function() {
SpecialPowers.pushPrefEnv(
{set: [['dom.ipc.processPriorityManager.backgroundGracePeriodMS',
priorityChangeGracePeriod]]},
runTest);
});
</script>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<!--
Test that when we remove one of a process's frames from the DOM, the process's
priority is recomputed.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = 'file_MultipleFrames.html';
var childID = null;
var iframe2;
expectProcessCreated().then(function(chid) {
childID = chid;
return expectPriorityChange(childID, 'FOREGROUND');
}).then(function() {
return expectMozbrowserEvent(iframe, 'openwindow');
}).then(function(e) {
iframe2 = e.detail.frameElement;
document.body.appendChild(iframe2);
return expectMozbrowserEvent(iframe2, 'loadend');
}).then(function() {
// At this point, the child process has been set to FOREGROUND, and the popup
// opened by file_MultipleFrames has finished loading.
//
// Now setVisible(false) the popup frame and remove the popup frame from the
// DOM. This should cause the process to take on BACKGROUND priority.
var p = expectPriorityChange(childID, 'BACKGROUND');
iframe.setVisible(false);
document.body.removeChild(iframe2);
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', runTest);
</script>
</body>
</html>

View File

@ -0,0 +1,73 @@
<!DOCTYPE HTML>
<html>
<!--
Test changing the visibility of an <iframe mozbrowser> changes the visibility
(and thus the priority) of any <iframe mozbrowser>s it contains.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
// Give our origin permission to open browsers, and remove it when the test is complete.
var principal = SpecialPowers.wrap(SpecialPowers.getNodePrincipal(document));
SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec,
appId: principal.appId,
isInBrowserElement: true });
addEventListener('unload', function() {
var principal = SpecialPowers.wrap(SpecialPowers.getNodePrincipal(document));
SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
appId: principal.appId,
isInBrowserElement: true });
});
function runTest() {
// Set up the following hierarchy of frames:
//
// <iframe mozbrowser remote=false src='file_NestedFramesOuter.html'>
// <iframe mozbrowser remote=true src='file_empty.html'>
//
// When we change the visibility of the outer iframe, it should change the
// priority of the inner one.
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.setAttribute('remote', false);
iframe.src = 'file_NestedFramesOuter.html#' + browserElementTestHelpers.emptyPage1;
// Note that this is the process corresponding to the /inner/ iframe. The
// outer iframe runs in-process (because it has remote=false).
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
return Promise.all(
[expectPriorityChange(childID, 'FOREGROUND'),
expectMozbrowserEvent(iframe, 'loadend')]
);
}).then(function() {
// Send the outer iframe into the background. This should change the
// priority of the inner frame's process to BACKGROUND.
var p = expectPriorityChange(childID, 'BACKGROUND');
iframe.setVisible(false);
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', runTest);
</script>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<!--
Test that the preallocated process starts up with priority BACKGROUND.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
var preallocationEnabledPref = null;
try {
preallocationEnabledPref = SpecialPowers.getBoolPref('dom.ipc.processPrelaunch.enabled');
}
catch(e) {
preallocationEnabledPref = null;
}
var childID = null;
var cleanedUp = false;
function cleanUp()
{
if (cleanedUp) {
return;
}
cleanedUp = true;
if (preallocationEnabledPref === null) {
SpecialPowers.clearUserPref('dom.ipc.processPrelaunch.enabled');
} else {
SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled',
preallocationEnabledPref);
}
}
// Even if this test times out, we still want to run cleanUp so as to set the
// pref back.
addEventListener('unload', cleanUp);
function runTest()
{
if (preallocationEnabledPref) {
ok(false, "dom.ipc.processPrelaunch.enabled must be " +
"false for this test to work.");
SimpleTest.finish();
return;
}
// Ensure that the preallocated process initially gets BACKGROUND priority.
// That's it.
expectProcessCreated().then(function(childID) {
return expectPriorityChange(childID, 'BACKGROUND');
}).then(function() {
cleanUp();
SimpleTest.finish();
});
// Setting this pref to true should cause us to prelaunch a process.
SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', true);
}
addEventListener('testready', runTest);
</script>
</body>
</html>

View File

@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html>
<!--
A simple test of the process priority manager.
https://bugzilla.mozilla.org/show_bug.cgi?id=844323
Note: If you run this test alone (i.e. not as part of the larger mochitest
suite), you may see some IPC assertions, e.g. "Can't allocate graphics
resources."
What appears to be happening is that we close the Firefox window before the
frame we create in this tab finishes starting up. Then the frame finishes
loading, and it tries to show itself. But it's too late to show a remote frame
at that point, so we kill the child process.
In other words, I think these errors are nothing to worry about.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = browserElementTestHelpers.emptyPage1;
expectProcessCreated().then(function(childID) {
return expectPriorityChange(childID, 'FOREGROUND');
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', runTest);
</script>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<!--
Test that setVisible() changes a process's priority.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = browserElementTestHelpers.emptyPage1;
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
return Promise.all(
[expectPriorityChange(childID, 'FOREGROUND'),
expectMozbrowserEvent(iframe, 'loadend')]);
}).then(function() {
// Mark the frame as not visible. This should cause its priority to drop
// to BACKGROUND.
var p = expectPriorityChange(childID, 'BACKGROUND');
iframe.setVisible(false);
return p;
}).then(function() {
// Mark the frame as visible again. This should cause its priority change
// back to FOREGROUND.
var p = expectPriorityChange(childID, 'FOREGROUND');
iframe.setVisible(true);
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', runTest);
</script>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<!--
Test that calling setVisible('false') and then sending a low-memory
notification causes a WebGL context loss event.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = 'file_WebGLContextLost.html';
// We use this to ensure that we don't call SimpleTest.finish() twice.
var finished = false;
function finishOnce() {
if (!finished) {
SimpleTest.finish();
finished = true;
}
}
expectMozbrowserEvent(iframe, 'error').then(function(e) {
if (finished) {
// We don't care if the frame dies after the test finishes.
return;
}
todo(false, "child process is crashing; this probably indicates that " +
"something is wrong with WebGL in child processes on your machine.");
is(e.detail.type, 'fatal');
}).then(finishOnce);
var childID = null;
expectOnlyOneProcessCreated().then(function(chid) {
childID = chid;
return Promise.all(
[expectPriorityChange(childID, 'FOREGROUND'),
expectMozbrowserEvent(iframe, 'loadend'),
expectMozbrowserEvent(iframe, 'showmodalprompt').then(function(e) {
is(e.detail.message, 'ready');
})
]);
}).then(function() {
// Fire a low-memory notification once the process goes into the background
// due to the setVisible(false) call below.
expectPriorityChange(childID, 'BACKGROUND').then(function() {
SimpleTest.executeSoon(function() {
var os = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
.getService(SpecialPowers.Ci.nsIObserverService);
os.notifyObservers(null, "memory-pressure", "low-memory");
ok(true, 'Successfully notified observers.');
});
});
// This test isn't the only possible source of a low-memory notification; the
// browser can fire one whenever it likes. So it's fine if we lose the
// WebGL context before we fire the low-memory notification ourself.
var p = expectMozbrowserEvent(iframe, 'showmodalprompt').then(function(e) {
is(e.detail.message, 'webglcontextlost');
});
iframe.setVisible(false);
return p;
}).then(finishOnce);
document.body.appendChild(iframe);
}
addEventListener('testready', function() {
// At the time this test was written, webgl was blocklisted inside child
// processes on desktop Linux. The issue is that we spawn a child process to
// read driver info, but we only did this on the main prrocess. Child
// processes never read the driver info themselves, nor do they get it from
// their parent, so they refuse to start up WebGL.
//
// This isn't a problem on B2G because we force WebGL on there. But it
// obviously makes this test difficult. bjacob says forcing WebGL on here
// shouldn't hurt things, and anyway this setting mirrors what we do on B2G,
// which is what we're trying to test!
SpecialPowers.pushPrefEnv({set: [["webgl.force-enabled", true]]},
runTest);
});
</script>
</body>
</html>

View File

@ -19,7 +19,7 @@ Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
const DB_NAME = "contacts";
const DB_VERSION = 8;
const DB_VERSION = 10;
const STORE_NAME = "contacts";
const SAVED_GETALL_STORE_NAME = "getallcache";
const CHUNK_SIZE = 20;
@ -315,6 +315,50 @@ ContactDB.prototype = {
} else if (currVersion == 7) {
if (DEBUG) debug("Adding object store for cached searches");
db.createObjectStore(SAVED_GETALL_STORE_NAME);
} else if (currVersion == 8) {
if (DEBUG) debug("Make exactTel only contain the value entered by the user");
if (!objectStore) {
objectStore = aTransaction.objectStore(STORE_NAME);
}
objectStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (cursor) {
if (cursor.value.properties.tel) {
cursor.value.search.exactTel = [];
cursor.value.properties.tel.forEach(
function(tel) {
let normalized = PhoneNumberUtils.normalize(tel.value.toString());
cursor.value.search.exactTel.push(normalized);
}
);
cursor.update(cursor.value);
}
cursor.continue();
}
};
} else if (currVersion == 9) {
if (DEBUG) debug("Add a telMatch index with national and international numbers");
objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true});
objectStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (cursor) {
if (cursor.value.properties.tel) {
cursor.value.search.parsedTel = [];
cursor.value.properties.tel.forEach(
function(tel) {
cursor.value.search.parsedTel.push(parsed.nationalNumber);
cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.nationalFormat));
cursor.value.search.parsedTel.push(parsed.internationalNumber);
cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.internationalFormat));
cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(tel.value.toString()));
}
);
cursor.update(cursor.value);
}
cursor.continue();
}
};
}
}
@ -402,7 +446,8 @@ ContactDB.prototype = {
email: [],
category: [],
tel: [],
exactTel: []
exactTel: [],
parsedTel: []
};
for (let field in aContact.properties) {
@ -417,9 +462,11 @@ ContactDB.prototype = {
// Chop off the first characters
let number = aContact.properties[field][i].value;
contact.search.exactTel.push(number);
let search = {};
if (number) {
number = number.toString();
contact.search.exactTel.push(PhoneNumberUtils.normalize(number));
contact.search.parsedTel.push(PhoneNumberUtils.normalize(number));
for (let i = 0; i < number.length; i++) {
search[number.substring(i, number.length)] = 1;
}
@ -440,9 +487,14 @@ ContactDB.prototype = {
debug("NationalNumber: " + parsedNumber.nationalNumber);
debug("NationalFormat: " + parsedNumber.nationalFormat);
}
contact.search.parsedTel.push(parsedNumber.nationalNumber);
contact.search.parsedTel.push(PhoneNumberUtils.normalize(parsedNumber.nationalFormat));
contact.search.parsedTel.push(parsedNumber.internationalNumber);
contact.search.parsedTel.push(PhoneNumberUtils.normalize(parsedNumber.internationalFormat));
if (parsedNumber.internationalNumber &&
number.toString() !== parsedNumber.internationalNumber) {
contact.search.exactTel.push(parsedNumber.internationalNumber);
number !== parsedNumber.internationalNumber) {
let digits = parsedNumber.internationalNumber.match(/\d/g);
if (digits) {
digits = digits.join('');
@ -731,7 +783,7 @@ ContactDB.prototype = {
if (DEBUG) debug("ContactDB:find val:" + aOptions.filterValue + " by: " + aOptions.filterBy + " op: " + aOptions.filterOp);
let self = this;
this.newTxn("readonly", STORE_NAME, function (txn, store) {
if (aOptions && (aOptions.filterOp == "equals" || aOptions.filterOp == "contains")) {
if (aOptions && (["equals", "contains", "match"].indexOf(aOptions.filterOp) >= 0)) {
self._findWithIndex(txn, store, aOptions);
} else {
self._findAll(txn, store, aOptions);
@ -775,12 +827,24 @@ ContactDB.prototype = {
if (DEBUG) debug("Getting index: " + key);
// case sensitive
let index = store.index(key);
request = index.mozGetAll(options.filterValue, limit);
let filterValue = options.filterValue;
if (key == "tel") {
filterValue = PhoneNumberUtils.normalize(filterValue);
}
request = index.mozGetAll(filterValue, limit);
} else if (options.filterOp == "match") {
if (DEBUG) debug("match");
if (key != "tel") {
dump("ContactDB: 'match' filterOp only works on tel\n");
return txn.abort();
}
let index = store.index("telMatch");
let normalized = PhoneNumberUtils.normalize(options.filterValue)
request = index.mozGetAll(normalized, limit);
} else {
// not case sensitive
let tmp = typeof options.filterValue == "string"
? options.filterValue.toLowerCase()
: options.filterValue.toString().toLowerCase();
let tmp = options.filterValue.toString().toLowerCase();
if (key === 'tel') {
let digits = tmp.match(/\d/g);
if (digits) {

View File

@ -87,7 +87,7 @@ var properties1 = {
familyName: ["TestFamilyName","Wagner"],
givenName: ["Test1","Test2"],
nickname: "nicktest",
tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+9-876-5432"}],
tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}, {type: ["home"], value: "+49 451 491934"}],
adr: adr1,
email: [{type: ["work"], value: "x@y.com"}]
};
@ -333,12 +333,12 @@ var steps = [
checkContacts(createResult1, properties1);
dump("findResult: " + JSON.stringify(findResult1) + "\n");
// Some manual testing. Testint the testfunctions
// tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+9-876-5432"}],
// tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}],
is(findResult1.tel[0].carrier, "testCarrier", "Same Carrier");
is(findResult1.tel[0].type, "work", "Same type");
is(findResult1.tel[0].value, "123456", "Same Value");
is(findResult1.tel[1].type[1], "fax", "Same type");
is(findResult1.tel[1].value, "+9-876-5432", "Same Value");
is(findResult1.tel[1].value, "+55 (31) 9876-3456", "Same Value");
is(findResult1.adr[0].countryName, "country 1", "Same country");
@ -503,6 +503,117 @@ var steps = [
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel exact");
var options = {filterBy: ["tel"],
filterOp: "equals",
filterValue: "+55 319 8 7 6 3456"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(createResult1, properties1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel exact with substring");
var options = {filterBy: ["tel"],
filterOp: "equals",
filterValue: "3456"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 0, "Found no contacts.");
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel exact with substring");
var options = {filterBy: ["tel"],
filterOp: "equals",
filterValue: "+55 (31)"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 0, "Found no contacts.");
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel match national number");
var options = {filterBy: ["tel"],
filterOp: "match",
filterValue: "3198763456"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(createResult1, properties1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel match national format");
var options = {filterBy: ["tel"],
filterOp: "match",
filterValue: "0451 491934"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(createResult1, properties1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel match entered number");
var options = {filterBy: ["tel"],
filterOp: "match",
filterValue: "123456"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(createResult1, properties1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by tel match international number");
var options = {filterBy: ["tel"],
filterOp: "match",
filterValue: "+55 31 98763456"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 1, "Found exactly 1 contact.");
findResult1 = req.result[0];
ok(findResult1.id == sample_id1, "Same ID");
checkContacts(createResult1, properties1);
next();
};
req.onerror = onFailure;
},
function () {
ok(true, "Retrieving by match with field other than tel");
var options = {filterBy: ["givenName"],
filterOp: "match",
filterValue: "my friends call me 555-4040"};
req = mozContacts.find(options);
req.onsuccess = onUnwantedSuccess;
req.onerror = function() {
ok(true, "Failed");
next();
}
},
function () {
ok(true, "Retrieving by substring tel2");
var options = {filterBy: ["tel"],
@ -522,7 +633,7 @@ var steps = [
ok(true, "Retrieving by substring tel3");
var options = {filterBy: ["tel"],
filterOp: "contains",
filterValue: "98765432"};
filterValue: "98763456"};
req = mozContacts.find(options);
req.onsuccess = function () {
is(req.result.length, 1, "Found exactly 1 contact.");

View File

@ -48,6 +48,7 @@
#include "nsJSEnvironment.h"
#include "SandboxHal.h"
#include "nsDebugImpl.h"
#include "nsHashPropertyBag.h"
#include "nsLayoutStylesheetCache.h"
#include "IHistory.h"
@ -571,12 +572,6 @@ ContentChild::RecvPBrowserConstructor(PBrowserChild* actor,
{
// This runs after AllocPBrowser() returns and the IPC machinery for this
// PBrowserChild has been set up.
//
// We have to NotifyObservers("tab-child-created") before we
// TemporarilyLockProcessPriority because the NotifyObservers call may cause
// us to initialize the ProcessPriorityManager, and
// TemporarilyLockProcessPriority only works after the
// ProcessPriorityManager has been initialized.
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
@ -592,13 +587,6 @@ ContentChild::RecvPBrowserConstructor(PBrowserChild* actor,
MOZ_ASSERT(!sFirstIdleTask);
sFirstIdleTask = NewRunnableFunction(FirstIdle);
MessageLoop::current()->PostIdleTask(FROM_HERE, sFirstIdleTask);
// We are either a brand-new process loading its first PBrowser, or we
// are the preallocated process transforming into a particular
// app/browser. Either way, our parent has already set our process
// priority, and we want to leave it there for a few seconds while we
// start up.
TemporarilyLockProcessPriority();
}
return true;
@ -1208,5 +1196,57 @@ ContentChild::RecvFileSystemUpdate(const nsString& aFsName,
return true;
}
bool
ContentChild::RecvNotifyProcessPriorityChanged(
const hal::ProcessPriority& aPriority)
{
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
NS_ENSURE_TRUE(os, true);
nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
props->Init();
props->SetPropertyAsInt32(NS_LITERAL_STRING("priority"),
static_cast<int32_t>(aPriority));
os->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
"ipc:process-priority-changed", nullptr);
return true;
}
bool
ContentChild::RecvMinimizeMemoryUsage()
{
nsCOMPtr<nsIMemoryReporterManager> mgr =
do_GetService("@mozilla.org/memory-reporter-manager;1");
NS_ENSURE_TRUE(mgr, true);
nsCOMPtr<nsICancelableRunnable> runnable =
do_QueryReferent(mMemoryMinimizerRunnable);
// Cancel the previous task if it's still pending.
if (runnable) {
runnable->Cancel();
runnable = nullptr;
}
mgr->MinimizeMemoryUsage(/* callback = */ nullptr,
getter_AddRefs(runnable));
mMemoryMinimizerRunnable = do_GetWeakReference(runnable);
return true;
}
bool
ContentChild::RecvCancelMinimizeMemoryUsage()
{
nsCOMPtr<nsICancelableRunnable> runnable =
do_QueryReferent(mMemoryMinimizerRunnable);
if (runnable) {
runnable->Cancel();
mMemoryMinimizerRunnable = nullptr;
}
return true;
}
} // namespace dom
} // namespace mozilla

View File

@ -192,6 +192,10 @@ public:
const int32_t& aState,
const int32_t& aMountGeneration);
virtual bool RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority);
virtual bool RecvMinimizeMemoryUsage();
virtual bool RecvCancelMinimizeMemoryUsage();
#ifdef ANDROID
gfxIntSize GetScreenSize() { return mScreenSize; }
#endif
@ -244,6 +248,7 @@ private:
bool mIsForApp;
bool mIsForBrowser;
nsString mProcessName;
nsWeakPtr mMemoryMinimizerRunnable;
static ContentChild* sSingleton;

View File

@ -59,6 +59,7 @@
#include "nsFrameMessageManager.h"
#include "nsHashPropertyBag.h"
#include "nsIAlertsService.h"
#include "nsIAppsService.h"
#include "nsIClipboard.h"
#include "nsIDOMApplicationRegistry.h"
#include "nsIDOMGeoGeolocation.h"
@ -80,6 +81,8 @@
#include "nsThreadUtils.h"
#include "nsToolkitCompsCID.h"
#include "nsWidgetsCID.h"
#include "PreallocatedProcessManager.h"
#include "ProcessPriorityManager.h"
#include "SandboxHal.h"
#include "StructuredCloneUtils.h"
#include "TabParent.h"
@ -194,9 +197,10 @@ MemoryReportRequestParent::~MemoryReportRequestParent()
MOZ_COUNT_DTOR(MemoryReportRequestParent);
}
nsDataHashtable<nsStringHashKey, ContentParent*>* ContentParent::gAppContentParents;
nsTArray<ContentParent*>* ContentParent::gNonAppContentParents;
nsTArray<ContentParent*>* ContentParent::gPrivateContent;
nsDataHashtable<nsStringHashKey, ContentParent*>* ContentParent::sAppContentParents;
nsTArray<ContentParent*>* ContentParent::sNonAppContentParents;
nsTArray<ContentParent*>* ContentParent::sPrivateContent;
LinkedList<ContentParent> ContentParent::sContentParents;
// This is true when subprocess launching is enabled. This is the
// case between StartUp() and ShutDown() or JoinAllSubprocesses().
@ -205,60 +209,28 @@ static bool sCanLaunchSubprocesses;
// The first content child has ID 1, so the chrome process can have ID 0.
static uint64_t 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<ContentParent> 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
// PreallocateAppProcess is called by the PreallocatedProcessManager.
// ContentParent then takes this process back within
// MaybeTakePreallocatedAppProcess.
/*static*/ already_AddRefed<ContentParent>
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,
/*isBrowserElement=*/false,
nsRefPtr<ContentParent> process =
new ContentParent(/* app = */ nullptr,
/* isForBrowserElement = */ false,
/* isForPreallocated = */ true,
// Final privileges are set when we
// transform into our app.
base::PRIVILEGES_INHERIT,
PROCESS_PRIORITY_BACKGROUND);
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);
process->Init();
return process.forget();
}
/*static*/ already_AddRefed<ContentParent>
@ -266,9 +238,7 @@ ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs,
ProcessPriority aInitialPriority)
{
nsRefPtr<ContentParent> process = sPreallocatedAppProcess.get();
sPreallocatedAppProcess = nullptr;
nsRefPtr<ContentParent> process = PreallocatedProcessManager::Take();
if (!process) {
return nullptr;
}
@ -284,14 +254,6 @@ ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL,
return process.forget();
}
/*static*/ void
ContentParent::FirstIdle(void)
{
// The parent has gone idle for the first time. This would be a good
// time to preallocate an app process.
ScheduleDelayedPreallocateAppProcess();
}
/*static*/ void
ContentParent::StartUp()
{
@ -299,22 +261,10 @@ ContentParent::StartUp()
return;
}
sKeepAppProcessPreallocated =
Preferences::GetBool("dom.ipc.processPrelaunch.enabled", false);
if (sKeepAppProcessPreallocated) {
ClearOnShutdown(&sPreallocatedAppProcess);
sPreallocateDelayMs = Preferences::GetUint(
"dom.ipc.processPrelaunch.delayMs", 1000);
MOZ_ASSERT(!sPreallocateAppProcessTask);
// Let's not slow down the main process initialization. Wait until
// the main process goes idle before we preallocate a process
MessageLoop::current()->PostIdleTask(FROM_HERE, NewRunnableFunction(FirstIdle));
}
sCanLaunchSubprocesses = true;
// Try to preallocate a process that we can transform into an app later.
PreallocatedProcessManager::AllocateAfterDelay();
}
/*static*/ void
@ -376,27 +326,28 @@ ContentParent::JoinAllSubprocesses()
/*static*/ already_AddRefed<ContentParent>
ContentParent::GetNewOrUsed(bool aForBrowserElement)
{
if (!gNonAppContentParents)
gNonAppContentParents = new nsTArray<ContentParent*>();
if (!sNonAppContentParents)
sNonAppContentParents = new nsTArray<ContentParent*>();
int32_t maxContentProcesses = Preferences::GetInt("dom.ipc.processCount", 1);
if (maxContentProcesses < 1)
maxContentProcesses = 1;
if (gNonAppContentParents->Length() >= uint32_t(maxContentProcesses)) {
uint32_t idx = rand() % gNonAppContentParents->Length();
nsRefPtr<ContentParent> p = (*gNonAppContentParents)[idx];
NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in gNonAppContentParents?");
if (sNonAppContentParents->Length() >= uint32_t(maxContentProcesses)) {
uint32_t idx = rand() % sNonAppContentParents->Length();
nsRefPtr<ContentParent> p = (*sNonAppContentParents)[idx];
NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sNonAppContentParents?");
return p.forget();
}
nsRefPtr<ContentParent> p =
new ContentParent(/* appManifestURL = */ EmptyString(),
new ContentParent(/* app = */ nullptr,
aForBrowserElement,
/* isForPreallocated = */ false,
base::PRIVILEGES_DEFAULT,
PROCESS_PRIORITY_FOREGROUND);
p->Init();
gNonAppContentParents->AppendElement(p);
sNonAppContentParents->AppendElement(p);
return p.forget();
}
@ -482,10 +433,10 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext,
// !HasOwnApp() branch above.
nsCOMPtr<mozIApplication> ownApp = aContext.GetOwnApp();
if (!gAppContentParents) {
gAppContentParents =
if (!sAppContentParents) {
sAppContentParents =
new nsDataHashtable<nsStringHashKey, ContentParent*>();
gAppContentParents->Init();
sAppContentParents->Init();
}
// Each app gets its own ContentParent instance.
@ -497,7 +448,7 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext,
ProcessPriority initialPriority = GetInitialProcessPriority(aFrameElement);
nsRefPtr<ContentParent> p = gAppContentParents->Get(manifestURL);
nsRefPtr<ContentParent> p = sAppContentParents->Get(manifestURL);
if (p) {
// Check that the process is still alive and set its priority.
// Hopefully the process won't die after this point, if this call
@ -513,61 +464,36 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext,
initialPriority);
if (!p) {
NS_WARNING("Unable to use pre-allocated app process");
p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
privs, initialPriority);
p = new ContentParent(ownApp,
/* isForBrowserElement = */ false,
/* isForPreallocated = */ false,
privs,
initialPriority);
p->Init();
}
gAppContentParents->Put(manifestURL, p);
sAppContentParents->Put(manifestURL, p);
}
nsRefPtr<TabParent> tp = new TabParent(aContext);
tp->SetOwnerElement(aFrameElement);
PBrowserParent* browser = p->SendPBrowserConstructor(
tp.forget().get(), // DeallocPBrowserParent() releases this ref.
nsRefPtr<TabParent>(tp).forget().get(), // DeallocPBrowserParent() releases this ref.
aContext.AsIPCTabContext(),
/* chromeFlags */ 0);
// Send the frame element's mozapptype down to the child process. This ends
// up in TabChild::GetAppType(). We have to do this /before/ we acquire the
// CPU wake lock for this process, because if the child sees that it has a
// CPU wake lock but its TabChild doesn't have the right mozapptype, it
// might downgrade its process priority.
nsCOMPtr<Element> frameElement = do_QueryInterface(aFrameElement);
if (frameElement) {
nsAutoString appType;
frameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::mozapptype, appType);
unused << browser->SendSetAppType(appType);
}
p->MaybeTakeCPUWakeLock(aFrameElement);
return static_cast<TabParent*>(browser);
}
static PLDHashOperator
AppendToTArray(const nsAString& aKey, ContentParent* aValue, void* aArray)
{
nsTArray<ContentParent*> *array =
static_cast<nsTArray<ContentParent*>*>(aArray);
array->AppendElement(aValue);
return PL_DHASH_NEXT;
}
void
ContentParent::GetAll(nsTArray<ContentParent*>& aArray)
{
aArray.Clear();
if (gNonAppContentParents) {
aArray.AppendElements(*gNonAppContentParents);
}
if (gAppContentParents) {
gAppContentParents->EnumerateRead(&AppendToTArray, &aArray);
}
if (sPreallocatedAppProcess) {
aArray.AppendElement(sPreallocatedAppProcess);
for (ContentParent* cp = sContentParents.getFirst(); cp;
cp = cp->getNext()) {
aArray.AppendElement(cp);
}
}
@ -697,17 +623,6 @@ NS_IMPL_ISUPPORTS1(SystemMessageHandledListener,
} // anonymous namespace
void
ContentParent::SetProcessPriority(ProcessPriority aPriority)
{
if (!Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
return;
}
hal::SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
aPriority);
}
void
ContentParent::MaybeTakeCPUWakeLock(nsIDOMElement* aFrameElement)
{
@ -735,7 +650,7 @@ ContentParent::MaybeTakeCPUWakeLock(nsIDOMElement* aFrameElement)
bool
ContentParent::SetPriorityAndCheckIsAlive(ProcessPriority aPriority)
{
SetProcessPriority(aPriority);
ProcessPriorityManager::SetProcessPriority(this, aPriority);
// Now that we've set this process's priority, check whether the process is
// still alive. Hopefully we've set the priority to FOREGROUND*, so the
@ -754,14 +669,38 @@ ContentParent::SetPriorityAndCheckIsAlive(ProcessPriority aPriority)
return true;
}
// Helper for ContentParent::TransformPreallocatedIntoApp.
static void
TryGetNameFromManifestURL(const nsAString& aManifestURL,
nsAString& aName)
{
aName.Truncate();
if (aManifestURL.IsEmpty() ||
aManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL) {
return;
}
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(appsService);
nsCOMPtr<mozIDOMApplication> domApp;
appsService->GetAppByManifestURL(aManifestURL, getter_AddRefs(domApp));
nsCOMPtr<mozIApplication> app = do_QueryInterface(domApp);
if (!app) {
return;
}
app->GetName(aName);
}
bool
ContentParent::TransformPreallocatedIntoApp(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs)
{
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<nsString&>(mAppManifestURL) = aAppManifestURL;
MOZ_ASSERT(IsPreallocated());
mAppManifestURL = aAppManifestURL;
TryGetNameFromManifestURL(aAppManifestURL, mAppName);
return SendSetProcessPrivileges(aPrivs);
}
@ -790,30 +729,35 @@ void
ContentParent::MarkAsDead()
{
if (!mAppManifestURL.IsEmpty()) {
if (gAppContentParents) {
gAppContentParents->Remove(mAppManifestURL);
if (!gAppContentParents->Count()) {
delete gAppContentParents;
gAppContentParents = NULL;
if (sAppContentParents) {
sAppContentParents->Remove(mAppManifestURL);
if (!sAppContentParents->Count()) {
delete sAppContentParents;
sAppContentParents = NULL;
}
}
} else if (gNonAppContentParents) {
gNonAppContentParents->RemoveElement(this);
if (!gNonAppContentParents->Length()) {
delete gNonAppContentParents;
gNonAppContentParents = NULL;
} else if (sNonAppContentParents) {
sNonAppContentParents->RemoveElement(this);
if (!sNonAppContentParents->Length()) {
delete sNonAppContentParents;
sNonAppContentParents = NULL;
}
}
if (gPrivateContent) {
gPrivateContent->RemoveElement(this);
if (!gPrivateContent->Length()) {
delete gPrivateContent;
gPrivateContent = NULL;
if (sPrivateContent) {
sPrivateContent->RemoveElement(this);
if (!sPrivateContent->Length()) {
delete sPrivateContent;
sPrivateContent = NULL;
}
}
mIsAlive = false;
// Remove from sContentParents.
if (isInList()) {
remove();
}
}
void
@ -922,10 +866,6 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
#endif
}
if (sPreallocatedAppProcess == this) {
sPreallocatedAppProcess = nullptr;
}
mMessageManager->Disconnect();
// clear the child memory reporters
@ -1068,8 +1008,9 @@ ContentParent::GetTestShellSingleton()
return static_cast<TestShellParent*>(ManagedPTestShellParent()[0]);
}
ContentParent::ContentParent(const nsAString& aAppManifestURL,
ContentParent::ContentParent(mozIApplication* aApp,
bool aIsForBrowser,
bool aIsForPreallocated,
ChildPrivileges aOSPrivileges,
ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
: mSubprocess(nullptr)
@ -1078,7 +1019,6 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL,
, mGeolocationWatchID(-1)
, mRunToCompletionDepth(0)
, mShouldCallUnblockChild(false)
, mAppManifestURL(aAppManifestURL)
, mForceKillTask(nullptr)
, mNumDestroyingTabs(0)
, mIsAlive(true)
@ -1086,6 +1026,20 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL,
, mSendPermissionUpdates(false)
, mIsForBrowser(aIsForBrowser)
{
// No more than one of !!aApp, aIsForBrowser, and aIsForPreallocated should
// be true.
MOZ_ASSERT(!!aApp + aIsForBrowser + aIsForPreallocated <= 1);
// Insert ourselves into the global linked list of ContentParent objects.
sContentParents.insertBack(this);
if (aApp) {
aApp->GetManifestURL(mAppManifestURL);
aApp->GetName(mAppName);
} else if (aIsForPreallocated) {
mAppManifestURL = MAGIC_PREALLOCATED_APP_MANIFEST_URL;
}
// From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
// PID along with the warning.
nsDebugImpl::SetMultiprocessMode("Parent");
@ -1096,13 +1050,16 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL,
mSubprocess->LaunchAndWaitForProcessHandle();
// Set the subprocess's priority. We do this first because we're likely
// /lowering/ its CPU and memory priority, which it has inherited from this
// process.
SetProcessPriority(aInitialPriority);
Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
// Set the subprocess's priority. We do this early on because we're likely
// /lowering/ the process's CPU and memory priority, which it has inherited
// from this process.
//
// This call can cause us to send IPC messages to the child process, so it
// must come after the Open() call above.
ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
// 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
@ -1149,17 +1106,17 @@ ContentParent::~ContentParent()
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
// We should be removed from all these lists in ActorDestroy.
MOZ_ASSERT(!gPrivateContent || !gPrivateContent->Contains(this));
MOZ_ASSERT(!sPrivateContent || !sPrivateContent->Contains(this));
if (mAppManifestURL.IsEmpty()) {
MOZ_ASSERT(!gNonAppContentParents ||
!gNonAppContentParents->Contains(this));
MOZ_ASSERT(!sNonAppContentParents ||
!sNonAppContentParents->Contains(this));
} else {
// In general, we expect gAppContentParents->Get(mAppManifestURL) to be
// In general, we expect sAppContentParents->Get(mAppManifestURL) to be
// NULL. But it could be that we created another ContentParent for this
// app after we did this->ActorDestroy(), so the right check is that
// gAppContentParent->Get(mAppManifestURL) != this.
MOZ_ASSERT(!gAppContentParents ||
gAppContentParents->Get(mAppManifestURL) != this);
MOZ_ASSERT(!sAppContentParents ||
sAppContentParents->Get(mAppManifestURL) != this);
}
}
@ -1380,11 +1337,11 @@ ContentParent::RecvGetShowPasswordSetting(bool* showPassword)
bool
ContentParent::RecvFirstIdle()
{
// When the ContentChild goes idle, it sends us a FirstIdle message
// which we use as a good time to prelaunch another process. If we
// prelaunch any sooner than this, then we'll be competing with the
// When the ContentChild goes idle, it sends us a FirstIdle message which we
// use as an indicator that it's a good time to prelaunch another process.
// If we prelaunch any sooner than this, then we'll be competing with the
// child process and slowing it down.
ScheduleDelayedPreallocateAppProcess();
PreallocatedProcessManager::AllocateOnIdle();
return true;
}
@ -1433,7 +1390,7 @@ ContentParent::RecvAudioChannelChangedNotification()
nsRefPtr<AudioChannelService> service =
AudioChannelService::GetAudioChannelService();
if (service) {
service->SendAudioChannelChangedNotification();
service->SendAudioChannelChangedNotification(ChildID());
}
return true;
}
@ -1778,6 +1735,30 @@ ContentParent::KillHard()
OtherProcess(), /*force=*/true));
}
bool
ContentParent::IsPreallocated()
{
return mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL;
}
void
ContentParent::FriendlyName(nsAString& aName)
{
aName.Truncate();
if (IsPreallocated()) {
aName.AssignLiteral("(Preallocated)");
} else if (mIsForBrowser) {
aName.AssignLiteral("Browser");
} else if (!mAppName.IsEmpty()) {
aName = mAppName;
} else if (!mAppManifestURL.IsEmpty()) {
aName.AssignLiteral("Unknown app: ");
aName.Append(mAppManifestURL);
} else {
aName.AssignLiteral("???");
}
}
PCrashReporterParent*
ContentParent::AllocPCrashReporter(const NativeThreadId& tid,
const uint32_t& processType)
@ -2523,17 +2504,17 @@ ContentParent::RecvScriptError(const nsString& aMessage,
bool
ContentParent::RecvPrivateDocShellsExist(const bool& aExist)
{
if (!gPrivateContent)
gPrivateContent = new nsTArray<ContentParent*>();
if (!sPrivateContent)
sPrivateContent = new nsTArray<ContentParent*>();
if (aExist) {
gPrivateContent->AppendElement(this);
sPrivateContent->AppendElement(this);
} else {
gPrivateContent->RemoveElement(this);
if (!gPrivateContent->Length()) {
sPrivateContent->RemoveElement(this);
if (!sPrivateContent->Length()) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
delete gPrivateContent;
gPrivateContent = NULL;
delete sPrivateContent;
sPrivateContent = NULL;
}
}
return true;

View File

@ -16,6 +16,7 @@
#include "mozilla/dom/ipc/Blob.h"
#include "mozilla/Attributes.h"
#include "mozilla/HalTypes.h"
#include "mozilla/LinkedList.h"
#include "nsFrameMessageManager.h"
#include "nsIObserver.h"
@ -58,6 +59,7 @@ class ContentParent : public PContentParent
, public nsIThreadObserver
, public nsIDOMGeoPositionCallback
, public mozilla::dom::ipc::MessageManagerCallback
, public mozilla::LinkedListElement<ContentParent>
{
typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
@ -84,6 +86,11 @@ public:
static already_AddRefed<ContentParent>
GetNewOrUsed(bool aForBrowserElement = false);
/**
* Create a subprocess suitable for use as a preallocated app process.
*/
static already_AddRefed<ContentParent> PreallocateAppProcess();
/**
* Get or create a content process for the given TabContext. aFrameElement
* should be the frame/iframe element with which this process will
@ -131,6 +138,10 @@ public:
return mSubprocess;
}
int32_t Pid() {
return base::GetProcId(mSubprocess->GetChildProcessHandle());
}
bool NeedsPermissionsUpdate() {
return mSendPermissionUpdates;
}
@ -145,23 +156,29 @@ public:
void KillHard();
uint64_t ChildID() { return mChildID; }
bool IsPreallocated();
/**
* Get a user-friendly name for this ContentParent. We make no guarantees
* about this name: It might not be unique, apps can spoof special names,
* etc. So please don't use this name to make any decisions about the
* ContentParent based on the value returned here.
*/
void FriendlyName(nsAString& aName);
protected:
void OnChannelConnected(int32_t pid);
virtual void ActorDestroy(ActorDestroyReason why);
private:
static nsDataHashtable<nsStringHashKey, ContentParent*> *gAppContentParents;
static nsTArray<ContentParent*>* gNonAppContentParents;
static nsTArray<ContentParent*>* gPrivateContent;
static nsDataHashtable<nsStringHashKey, ContentParent*> *sAppContentParents;
static nsTArray<ContentParent*>* sNonAppContentParents;
static nsTArray<ContentParent*>* sPrivateContent;
static LinkedList<ContentParent> sContentParents;
static void JoinProcessesIOThread(const nsTArray<ContentParent*>* aProcesses,
Monitor* aMonitor, bool* aDone);
static void PreallocateAppProcess();
static void DelayedPreallocateAppProcess();
static void ScheduleDelayedPreallocateAppProcess();
// Take the preallocated process and transform it into a "real" app process,
// for the specified manifest URL. If there is no preallocated process (or
// if it's dead), this returns false.
@ -172,24 +189,23 @@ private:
static hal::ProcessPriority GetInitialProcessPriority(nsIDOMElement* aFrameElement);
static void FirstIdle();
// Hide the raw constructor methods since we don't want client code
// using them.
using PContentParent::SendPBrowserConstructor;
using PContentParent::SendPTestShellConstructor;
ContentParent(const nsAString& aAppManifestURL, bool aIsForBrowser,
// No more than one of !!aApp, aIsForBrowser, and aIsForPreallocated may be
// true.
ContentParent(mozIApplication* aApp,
bool aIsForBrowser,
bool aIsForPreallocated,
ChildPrivileges aOSPrivileges = base::PRIVILEGES_DEFAULT,
hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
virtual ~ContentParent();
void Init();
// Set the child process's priority. Once the child starts up, it will
// manage its own priority via the ProcessPriorityManager.
void SetProcessPriority(hal::ProcessPriority aInitialPriority);
// If the frame element indicates that the child process is "critical" and
// has a pending system message, this function acquires the CPU wake lock on
// behalf of the child. We'll release the lock when the system message is
@ -405,7 +421,15 @@ private:
// the nsIObserverService.
nsCOMArray<nsIMemoryReporter> mMemoryReporters;
const nsString mAppManifestURL;
nsString mAppManifestURL;
/**
* We cache mAppName instead of looking it up using mAppManifestURL when we
* need it because it turns out that getting an app from the apps service is
* expensive.
*/
nsString mAppName;
nsRefPtr<nsFrameMessageManager> mMessageManager;
// After we initiate shutdown, we also start a timer to ensure

View File

@ -24,6 +24,7 @@ CPPSRCS = \
CrashReporterParent.cpp \
CrashReporterChild.cpp \
PermissionMessageUtils.cpp \
PreallocatedProcessManager.cpp \
ProcessPriorityManager.cpp \
StructuredCloneUtils.cpp \
TabParent.cpp \

View File

@ -384,20 +384,6 @@ child:
uint32_t renderFlags, bool flushLayout,
nsIntSize renderSize);
/**
* Send the child its app type. The app type identifies the kind of app
* shown in this PBrowser. Currently, the only recognized app type is
* "homescreen".
*
* The value here corresponds to the "mozapptype" attribute on iframes. For
* example, <iframe mozbrowser mozapp="..." mozapptype="homescreen">.
*
* Only app frames (i.e., frames with an app-id) should have a non-empty app
* type. If you try to SetAppType() with a non-empty app type on a non-app
* PBrowserChild, we may assert.
*/
SetAppType(nsString appType);
/**
* Sent by the chrome process when it no longer wants this remote
* <browser>. The child side cleans up in response, then

View File

@ -27,6 +27,7 @@ include URIParams;
include "mozilla/chrome/RegistryMessageUtils.h";
include "mozilla/dom/PermissionMessageUtils.h";
include "mozilla/dom/TabMessageUtils.h";
include "mozilla/HalTypes.h";
include "mozilla/layout/RenderFrameUtils.h";
include "mozilla/net/NeckoMessageUtils.h";
include "nsGeoPositionIPCSerialiser.h";
@ -44,6 +45,7 @@ using mozilla::null_t;
using mozilla::void_t;
using mozilla::dom::AudioChannelType;
using mozilla::dom::NativeThreadId;
using mozilla::hal::ProcessPriority;
using mozilla::layout::ScrollingBehavior;
using gfxIntSize;
@ -349,6 +351,10 @@ child:
FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
int32_t mountGeneration);
NotifyProcessPriorityChanged(ProcessPriority priority);
MinimizeMemoryUsage();
CancelMinimizeMemoryUsage();
parent:
/**
* Tell the content process some attributes of itself. This is

View File

@ -0,0 +1,229 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et ft=cpp : */
/* 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 "mozilla/PreallocatedProcessManager.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/ContentParent.h"
using namespace mozilla;
using namespace mozilla::hal;
using namespace mozilla::dom;
namespace {
/**
* This singleton class implements the static methods on
* PreallocatedProcessManager.
*/
class PreallocatedProcessManagerImpl MOZ_FINAL
: public nsIObserver
{
public:
static PreallocatedProcessManagerImpl* Singleton();
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
// See comments on PreallocatedProcessManager for these methods.
void AllocateAfterDelay();
void AllocateOnIdle();
void AllocateNow();
already_AddRefed<ContentParent> Take();
private:
static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
PreallocatedProcessManagerImpl();
DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
void Init();
void RereadPrefs();
void Enable();
void Disable();
void ObserveProcessShutdown(nsISupports* aSubject);
bool mEnabled;
nsRefPtr<ContentParent> mPreallocatedAppProcess;
};
/* static */ StaticRefPtr<PreallocatedProcessManagerImpl>
PreallocatedProcessManagerImpl::sSingleton;
/* static */ PreallocatedProcessManagerImpl*
PreallocatedProcessManagerImpl::Singleton()
{
if (!sSingleton) {
sSingleton = new PreallocatedProcessManagerImpl();
sSingleton->Init();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
}
NS_IMPL_ISUPPORTS1(PreallocatedProcessManagerImpl, nsIObserver)
PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
: mEnabled(false)
{}
void
PreallocatedProcessManagerImpl::Init()
{
Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->AddObserver(this, "ipc:content-shutdown",
/* weakRef = */ false);
}
RereadPrefs();
}
NS_IMETHODIMP
PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject,
const char* aTopic,
const PRUnichar* aData)
{
if (!strcmp("ipc:content-shutdown", aTopic)) {
ObserveProcessShutdown(aSubject);
} else if (!strcmp("nsPref:changed", aTopic)) {
// The only other observer we registered was for our prefs.
RereadPrefs();
} else {
MOZ_ASSERT(false);
}
return NS_OK;
}
void
PreallocatedProcessManagerImpl::RereadPrefs()
{
if (Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) {
Enable();
} else {
Disable();
}
}
already_AddRefed<ContentParent>
PreallocatedProcessManagerImpl::Take()
{
return mPreallocatedAppProcess.forget();
}
void
PreallocatedProcessManagerImpl::Enable()
{
if (mEnabled) {
return;
}
mEnabled = true;
AllocateAfterDelay();
}
void
PreallocatedProcessManagerImpl::AllocateAfterDelay()
{
if (!mEnabled || mPreallocatedAppProcess) {
return;
}
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateOnIdle),
Preferences::GetUint("dom.ipc.processPrelaunch.delayMs", 1000));
}
void
PreallocatedProcessManagerImpl::AllocateOnIdle()
{
if (!mEnabled || mPreallocatedAppProcess) {
return;
}
MessageLoop::current()->PostIdleTask(
FROM_HERE,
NewRunnableMethod(this, &PreallocatedProcessManagerImpl::AllocateNow));
}
void
PreallocatedProcessManagerImpl::AllocateNow()
{
if (!mEnabled || mPreallocatedAppProcess) {
return;
}
mPreallocatedAppProcess = ContentParent::PreallocateAppProcess();
}
void
PreallocatedProcessManagerImpl::Disable()
{
if (!mEnabled) {
return;
}
mEnabled = false;
if (mPreallocatedAppProcess) {
mPreallocatedAppProcess->ShutDown();
mPreallocatedAppProcess = nullptr;
}
}
void
PreallocatedProcessManagerImpl::ObserveProcessShutdown(nsISupports* aSubject)
{
if (!mPreallocatedAppProcess) {
return;
}
nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
NS_ENSURE_TRUE_VOID(props);
uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN;
props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN);
if (childID == mPreallocatedAppProcess->ChildID()) {
mPreallocatedAppProcess = nullptr;
}
}
} // anonymous namespace
namespace mozilla {
/* static */ void
PreallocatedProcessManager::AllocateAfterDelay()
{
PreallocatedProcessManagerImpl::Singleton()->AllocateAfterDelay();
}
/* static */ void
PreallocatedProcessManager::AllocateOnIdle()
{
PreallocatedProcessManagerImpl::Singleton()->AllocateOnIdle();
}
/* static */ void
PreallocatedProcessManager::AllocateNow()
{
PreallocatedProcessManagerImpl::Singleton()->AllocateNow();
}
/* static */ already_AddRefed<ContentParent>
PreallocatedProcessManager::Take()
{
return PreallocatedProcessManagerImpl::Singleton()->Take();
}
} // namespace mozilla

View File

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et ft=cpp : */
/* 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/. */
#ifndef mozilla_PreallocatedProcessManager_h
#define mozilla_PreallocatedProcessManager_h
#include "base/basictypes.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "nsAutoPtr.h"
namespace mozilla {
namespace dom {
class ContentParent;
}
/**
* This class manages a ContentParent that it starts up ahead of any particular
* need. You can then call Take() to get this process and use it. Since we
* already started it up, it should be ready for use faster than if you'd
* created the process when you needed it.
*
* This class watches the dom.ipc.processPrelaunch.enabled pref. If it changes
* from false to true, it preallocates a process. If it changes from true to
* false, it kills the preallocated process, if any.
*
* We don't expect this pref to flip between true and false in production, but
* flipping the pref is important for tests.
*
* The static methods here are implemented by forwarding calls on to a
* PreallocatedProcessManagerImpl singleton class, so if you add a new static
* method here, you'll need to write a corresponding public method on the
* singleton.
*/
class PreallocatedProcessManager MOZ_FINAL
{
typedef mozilla::dom::ContentParent ContentParent;
public:
/**
* Create a process after a delay. We wait for a period of time (specified
* by the dom.ipc.processPrelaunch.delayMs pref), then wait for this process
* to go idle, then allocate the new process.
*
* If the dom.ipc.processPrelaunch.enabled pref is false, or if we already
* have a preallocated process, this function does nothing.
*/
static void AllocateAfterDelay();
/**
* Create a process once this process goes idle.
*
* If the dom.ipc.processPrelaunch.enabled pref is false, or if we already
* have a preallocated process, this function does nothing.
*/
static void AllocateOnIdle();
/**
* Create a process right now.
*
* If the dom.ipc.processPrelaunch.enabled pref is false, or if we already
* have a preallocated process, this function does nothing.
*/
static void AllocateNow();
/**
* Take the preallocated process, if we have one. If we don't have one, this
* returns null.
*
* If you call Take() twice in a row, the second call is guaranteed to return
* null.
*
* After you Take() the preallocated process, you need to call one of the
* Allocate* functions (or change the dom.ipc.processPrelaunch pref from
* false to true) before we'll create a new process.
*/
static already_AddRefed<ContentParent> Take();
private:
PreallocatedProcessManager();
DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager);
};
} // namespace mozilla
#endif // defined mozilla_PreallocatedProcessManager_h

File diff suppressed because it is too large Load Diff

View File

@ -7,46 +7,75 @@
#ifndef mozilla_ProcessPriorityManager_h_
#define mozilla_ProcessPriorityManager_h_
#include "mozilla/HalTypes.h"
#include "mozilla/StaticPtr.h"
#include "nsIObserver.h"
#include "nsDataHashtable.h"
namespace mozilla {
namespace dom {
namespace ipc {
class ContentParent;
}
/**
* Initialize the ProcessPriorityManager.
* This class sets the priority of subprocesses in response to explicit
* requests and events in the system.
*
* The ProcessPriorityManager informs the hal back-end whether this is the root
* Gecko process, and, if we're not the root, informs hal when this process
* transitions between having no visible top-level windows, and having at least
* one visible top-level window.
* A process's priority changes e.g. when it goes into the background via
* mozbrowser's setVisible(false). Process priority affects CPU scheduling and
* also which processes get killed when we run out of memory.
*
* Hal may adjust this process's operating system priority (e.g. niceness, on
* *nix) according to these notificaitons.
*
* This function call does nothing if the pref for OOP tabs is not set.
* After you call Initialize(), the only thing you probably have to do is call
* SetProcessPriority on processes immediately after creating them in order to
* set their initial priority. The ProcessPriorityManager takes care of the
* rest.
*/
void InitProcessPriorityManager();
class ProcessPriorityManager MOZ_FINAL
{
public:
/**
* Initialize the ProcessPriorityManager machinery, causing the
* ProcessPriorityManager to actively manage the priorities of all
* subprocesses. You should call this before creating any subprocesses.
*
* You should also call this function even if you're in a child process,
* since it will initialize ProcessPriorityManagerChild.
*/
static void Init();
/**
* True iff the current process has foreground or higher priority as
* computed by DOM visibility. The returned answer may not match the
* actual OS process priority, for short intervals.
*/
bool CurrentProcessIsForeground();
/**
* Set the process priority of a given ContentParent's process.
*
* Note that because this method takes a ContentParent*, you can only set the
* priority of your subprocesses. In fact, because we don't support nested
* content processes (bug 761935), you can only call this method from the
* main process.
*
* It probably only makes sense to call this function immediately after a
* process is created. At this point, the process priority manager doesn't
* have enough context about the processs to know what its priority should
* be.
*
* Eventually whatever priority you set here can and probably will be
* overwritten by the process priority manager.
*/
static void SetProcessPriority(dom::ContentParent* aContentParent,
hal::ProcessPriority aPriority);
/**
* Calling this function prevents us from changing this process's priority
* for a few seconds, if that change in priority would not have taken effect
* immediately to begin with.
*
* In practice, this prevents foreground --> background transitions, but not
* background --> foreground transitions. It also does not prevent
* transitions from an unknown priority (as happens immediately after we're
* constructed) to a foreground priority.
*/
void TemporarilyLockProcessPriority();
/**
* Returns true iff this process's priority is FOREGROUND*.
*
* Note that because process priorities are set in the main process, it's
* possible for this method to return a stale value. So be careful about
* what you use this for.
*/
static bool CurrentProcessIsForeground();
private:
ProcessPriorityManager();
DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManager);
};
} // namespace ipc
} // namespace dom
} // namespace mozilla
#endif

View File

@ -2018,14 +2018,6 @@ TabChild::RecvDestroy()
return Send__delete__(this);
}
/* virtual */ bool
TabChild::RecvSetAppType(const nsString& aAppType)
{
MOZ_ASSERT_IF(!aAppType.IsEmpty(), HasOwnApp());
mAppType = aAppType;
return true;
}
PRenderFrameChild*
TabChild::AllocPRenderFrame(ScrollingBehavior* aScrolling,
TextureFactoryIdentifier* aTextureFactoryIdentifier,

View File

@ -315,16 +315,6 @@ public:
void MakeVisible();
void MakeHidden();
virtual bool RecvSetAppType(const nsString& aAppType);
/**
* Get this object's app type.
*
* A TabChild's app type corresponds to the value of its frame element's
* "mozapptype" attribute.
*/
void GetAppType(nsAString& aAppType) const { aAppType = mAppType; }
// Returns true if the file descriptor was found in the cache, false
// otherwise.
bool GetCachedFileDescriptor(const nsAString& aPath,
@ -459,7 +449,6 @@ private:
bool mNotified;
bool mContentDocumentIsDisplayed;
bool mTriedBrowserInit;
nsString mAppType;
ScreenOrientation mOrientation;
DISALLOW_EVIL_CONSTRUCTORS(TabChild);

View File

@ -219,6 +219,31 @@ TabParent::SetOwnerElement(nsIDOMElement* aElement)
TryCacheDPI();
}
void
TabParent::GetAppType(nsAString& aOut)
{
aOut.Truncate();
nsCOMPtr<Element> elem = do_QueryInterface(mFrameElement);
if (!elem) {
return;
}
elem->GetAttr(kNameSpaceID_None, nsGkAtoms::mozapptype, aOut);
}
bool
TabParent::IsVisible()
{
nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
if (!frameLoader) {
return false;
}
bool visible = false;
frameLoader->GetVisible(&visible);
return visible;
}
void
TabParent::Destroy()
{
@ -265,18 +290,20 @@ TabParent::ActorDestroy(ActorDestroyReason why)
mIMETabParent = nullptr;
}
nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (frameLoader) {
ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr, nullptr);
frameLoader->DestroyChild();
if (why == AbnormalShutdown) {
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
"oop-frameloader-crashed", nullptr);
}
if (why == AbnormalShutdown && os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
"oop-frameloader-crashed", nullptr);
}
}
if (os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsITabParent*, this), "ipc:browser-destroyed", nullptr);
}
}
bool

View File

@ -62,6 +62,20 @@ public:
virtual ~TabParent();
nsIDOMElement* GetOwnerElement() { return mFrameElement; }
void SetOwnerElement(nsIDOMElement* aElement);
/**
* Get the mozapptype attribute from this TabParent's owner DOM element.
*/
void GetAppType(nsAString& aOut);
/**
* Returns true iff this TabParent's nsIFrameLoader is visible.
*
* The frameloader's visibility can be independent of e.g. its docshell's
* visibility.
*/
bool IsVisible();
nsIBrowserDOMWindow *GetBrowserDOMWindow() { return mBrowserDOMWindow; }
void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
mBrowserDOMWindow = aBrowserDOMWindow;

View File

@ -16,7 +16,6 @@ EXPORTS += [
EXPORTS.mozilla.dom.ipc += [
'Blob.h',
'ProcessPriorityManager.h',
'nsIRemoteBlob.h',
]
@ -36,5 +35,7 @@ EXPORTS.mozilla.dom += [
EXPORTS.mozilla += [
'AppProcessChecker.h',
'PreallocatedProcessManager.h',
'ProcessPriorityManager.h',
]

View File

@ -1061,6 +1061,83 @@ EnsureKernelLowMemKillerParamsSet()
}
}
static void
SetNiceForPid(int aPid, int aNice)
{
errno = 0;
int origProcPriority = getpriority(PRIO_PROCESS, aPid);
if (errno) {
LOG("Unable to get nice for pid=%d; error %d. SetNiceForPid bailing.",
aPid, errno);
return;
}
int rv = setpriority(PRIO_PROCESS, aPid, aNice);
if (rv) {
LOG("Unable to set nice for pid=%d; error %d. SetNiceForPid bailing.",
aPid, errno);
return;
}
// On Linux, setpriority(aPid) modifies the priority only of the main
// thread of that process. We have to modify the priorities of all of the
// process's threads as well, so iterate over all the threads and increase
// each of their priorites by aNice - origProcPriority (and also ensure that
// none of the tasks has a lower priority than the main thread).
//
// This is horribly racy.
DIR* tasksDir = opendir(nsPrintfCString("/proc/%d/task/", aPid).get());
if (!tasksDir) {
LOG("Unable to open /proc/%d/task. SetNiceForPid bailing.", aPid);
return;
}
// Be careful not to leak tasksDir; after this point, we must call closedir().
while (struct dirent* de = readdir(tasksDir)) {
char* endptr = nullptr;
long tidlong = strtol(de->d_name, &endptr, /* base */ 10);
if (*endptr || tidlong < 0 || tidlong > INT32_MAX || tidlong == aPid) {
// if dp->d_name was not an integer, was negative (?!) or too large, or
// was the same as aPid, we're not interested.
//
// (The |tidlong == aPid| check is very important; without it, we'll
// renice aPid twice, and the second renice will be relative to the
// priority set by the first renice.)
continue;
}
int tid = static_cast<int>(tidlong);
errno = 0;
// Get and set the task's new priority.
int origtaskpriority = getpriority(PRIO_PROCESS, tid);
if (errno) {
LOG("Unable to get nice for tid=%d (pid=%d); error %d. This isn't "
"necessarily a problem; it could be a benign race condition.",
tid, aPid, errno);
continue;
}
int newtaskpriority =
std::max(origtaskpriority + aNice - origProcPriority, origProcPriority);
rv = setpriority(PRIO_PROCESS, tid, newtaskpriority);
if (rv) {
LOG("Unable to set nice for tid=%d (pid=%d); error %d. This isn't "
"necessarily a problem; it could be a benign race condition.",
tid, aPid, errno);
continue;
}
}
LOG("Changed nice for pid %d from %d to %d.",
aPid, origProcPriority, aNice);
closedir(tasksDir);
}
void
SetProcessPriority(int aPid, ProcessPriority aPriority)
{
@ -1141,10 +1218,7 @@ SetProcessPriority(int aPid, ProcessPriority aPriority)
if (NS_SUCCEEDED(rv)) {
HAL_LOG(("Setting nice for pid %d to %d", aPid, nice));
int success = setpriority(PRIO_PROCESS, aPid, nice);
if (success != 0) {
HAL_LOG(("Failed to set nice for pid %d to %d", aPid, nice));
}
SetNiceForPid(aPid, nice);
}
}

View File

@ -110,7 +110,7 @@ using namespace mozilla::system;
#include "nsEditorSpellCheck.h"
#include "nsWindowMemoryReporter.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ipc/ProcessPriorityManager.h"
#include "mozilla/ProcessPriorityManager.h"
#include "nsPermissionManager.h"
#include "nsCookieService.h"
#include "nsApplicationCacheService.h"
@ -265,7 +265,7 @@ nsLayoutStatics::Initialize()
SVGElementFactory::Init();
nsSVGUtils::Init();
InitProcessPriorityManager();
ProcessPriorityManager::Init();
nsPermissionManager::AppClearDataObserverInit();
nsCookieService::AppClearDataObserverInit();

View File

@ -712,3 +712,6 @@ pref("media.webaudio.enabled", true);
// This needs more tests and stability fixes first, as well as UI.
pref("media.navigator.enabled", false);
pref("media.peerconnection.enabled", false);
// Make <audio> and <video> talk to the AudioChannelService.
pref("media.useAudioChannelService", true);

View File

@ -7,6 +7,9 @@
#ifndef mozilla_StaticPtr_h
#define mozilla_StaticPtr_h
#include "mozilla/Assertions.h"
#include "mozilla/NullPtr.h"
namespace mozilla {
/**
@ -216,7 +219,7 @@ REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, U*,
// Let us compare StaticAutoPtr to 0.
REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, StaticPtr_internal::Zero*,
lhs.get() == NULL, class T)
lhs.get() == nullptr, class T)
// StaticRefPtr (in)equality operators
@ -242,7 +245,7 @@ REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, U*,
// Let us compare StaticRefPtr to 0.
REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, StaticPtr_internal::Zero*,
lhs.get() == NULL, class T)
lhs.get() == nullptr, class T)
#undef REFLEXIVE_EQUALITY_OPERATORS