Merge last PGO-green changeset of mozilla-inbound to mozilla-central

This commit is contained in:
Ed Morley 2012-08-22 10:28:02 +01:00
commit a227f14256
76 changed files with 3194 additions and 477 deletions

View File

@ -396,6 +396,9 @@ pref("dom.mozSettings.enabled", true);
pref("device.camera.enabled", true);
pref("media.realtime_decoder.enabled", true);
// TCPSocket
pref("dom.mozTCPSocket.enabled", true);
// "Preview" landing of bug 710563, which is bogged down in analysis
// of talos regression. This is a needed change for higher-framerate
// CSS animations, and incidentally works around an apparent bug in

View File

@ -98,3 +98,8 @@ SettingsListener.observe('devtools.debugger.remote-port', 6000, function(value)
SettingsListener.observe('devtools.debugger.force-local', true, function(value) {
Services.prefs.setBoolPref('devtools.debugger.force-local', value);
});
SettingsListener.observe('debug.log-animations.enabled', false, function(value) {
Services.prefs.setBoolPref('layers.offmainthreadcomposition.log-animations', value);
});

View File

@ -64,8 +64,11 @@ var shell = {
},
reportCrash: function shell_reportCrash() {
let crashID = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULRuntime).lastRunCrashID;
let crashID;
try {
crashID = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULRuntime).lastRunCrashID;
} catch(e) { }
if (Services.prefs.getBoolPref('app.reportCrashes') &&
crashID) {
this.CrashSubmit().submit(crashID)
@ -615,7 +618,11 @@ window.addEventListener('ContentStart', function ss_onContentStart() {
context.drawWindow(window, 0, 0, width, height,
'rgb(255,255,255)', flags);
shell.sendChromeEvent({
// I can't use sendChromeEvent() here because it doesn't wrap
// the blob in the detail object correctly. So I use __exposedProps__
// instead to safely send the chrome detail object to content.
shell.sendEvent(getContentWindow(), 'mozChromeEvent', {
__exposedProps__: { type: 'r', file: 'r' },
type: 'take-screenshot-success',
file: canvas.mozGetAsFile('screenshot', 'image/png')
});

View File

@ -484,6 +484,9 @@
@BINPATH@/components/ActivityRequestHandler.js
@BINPATH@/components/ActivityWrapper.js
@BINPATH@/components/TCPSocket.js
@BINPATH@/components/TCPSocket.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest

View File

@ -31,7 +31,7 @@ let gSyncAddDevice = {
// Kick off a sync. That way the server will have the most recent data from
// this computer and it will show up immediately on the new device.
Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
Weave.SyncScheduler.scheduleNextSync(0);
},
onPageShow: function onPageShow() {

View File

@ -484,6 +484,9 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/AlarmsManager.js
@BINPATH@/components/AlarmsManager.manifest
@BINPATH@/components/TCPSocket.js
@BINPATH@/components/TCPSocket.manifest
#ifdef ENABLE_MARIONETTE
@BINPATH@/chrome/marionette@JAREXT@
@BINPATH@/chrome/marionette.manifest

View File

@ -207,10 +207,13 @@ class PythonJob(Job):
self.pycommandpath = pycommandpath or []
def run(self):
oldenv = os.environ
# os.environ is a magic dictionary. Setting it to something else
# doesn't affect the environment of subprocesses, so use clear/update
oldenv = dict(os.environ)
try:
os.chdir(self.cwd)
os.environ = self.env
os.environ.clear()
os.environ.update(self.env)
if self.module not in sys.modules:
load_module_recursive(self.module,
sys.path + self.pycommandpath)
@ -240,7 +243,8 @@ class PythonJob(Job):
print >>sys.stderr, traceback.print_exc()
return (e.code if isinstance(e.code, int) else 1)
finally:
os.environ = oldenv
os.environ.clear()
os.environ.update(oldenv)
return 0
def job_runner(job):

View File

@ -1,10 +1,11 @@
#T gmake skip
export EXPECTED := some data
CMD = %pycmd writeenvtofile
PYCOMMANDPATH = $(TESTPATH)
all:
$(CMD) results EXPECTED
%pycmd writeenvtofile results EXPECTED
test "$$(cat results)" = "$(EXPECTED)"
%pycmd writesubprocessenvtofile results EXPECTED
test "$$(cat results)" = "$(EXPECTED)"
@echo TEST-PASS

View File

@ -1,4 +1,4 @@
import os, sys
import os, sys, subprocess
def writetofile(args):
with open(args[0], 'w') as f:
@ -8,6 +8,15 @@ def writeenvtofile(args):
with open(args[0], 'w') as f:
f.write(os.environ[args[1]])
def writesubprocessenvtofile(args):
with open(args[0], 'w') as f:
p = subprocess.Popen([sys.executable, "-c",
"import os; print os.environ['%s']" % args[1]],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
assert p.returncode == 0
f.write(stdout)
def convertasplode(arg):
try:
return int(arg)

View File

@ -54,7 +54,7 @@ for a in args:
if os.path.isfile(a):
makefiles.append(a)
elif os.path.isdir(a):
makefiles.extend(glob.glob(os.path.join(a, '*.mk')))
makefiles.extend(sorted(glob.glob(os.path.join(a, '*.mk'))))
def runTest(makefile, make, logfile, options):
"""

View File

@ -42,6 +42,7 @@ nsReferencedElement.h \
nsTreeSanitizer.h \
nsXMLNameSpaceMap.h \
nsIXFormsUtilityService.h \
nsBlobProtocolHandler.h \
$(NULL)
EXPORTS_NAMESPACES = mozilla/dom mozilla

View File

@ -0,0 +1,51 @@
/* 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 nsBlobProtocolHandler_h
#define nsBlobProtocolHandler_h
#include "nsIProtocolHandler.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#define BLOBURI_SCHEME "blob"
class nsIDOMBlob;
class nsIPrincipal;
class nsIInputStream;
inline bool IsBlobURI(nsIURI* aUri)
{
bool isBlob;
return NS_SUCCEEDED(aUri->SchemeIs(BLOBURI_SCHEME, &isBlob)) && isBlob;
}
extern nsresult
NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
class nsBlobProtocolHandler : public nsIProtocolHandler
{
public:
NS_DECL_ISUPPORTS
// nsIProtocolHandler methods:
NS_DECL_NSIPROTOCOLHANDLER
// nsBlobProtocolHandler methods:
nsBlobProtocolHandler() {}
virtual ~nsBlobProtocolHandler() {}
// Methods for managing uri->file mapping
static void AddFileDataEntry(nsACString& aUri,
nsIDOMBlob* aFile,
nsIPrincipal* aPrincipal);
static void RemoveFileDataEntry(nsACString& aUri);
static nsIPrincipal* GetFileDataEntryPrincipal(nsACString& aUri);
};
#define NS_BLOBPROTOCOLHANDLER_CID \
{ 0xb43964aa, 0xa078, 0x44b2, \
{ 0xb0, 0x6b, 0xfd, 0x4d, 0x1b, 0x17, 0x2e, 0x66 } }
#endif /* nsBlobProtocolHandler_h */

View File

@ -163,8 +163,8 @@ nsBlobProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
nsCOMPtr<nsIChannel> channel;
rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
uri,
stream);
uri,
stream);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupports> owner = do_QueryInterface(info->mPrincipal);
@ -189,3 +189,23 @@ nsBlobProtocolHandler::AllowPort(PRInt32 port, const char *scheme,
*_retval = false;
return NS_OK;
}
nsresult
NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream)
{
NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs");
*aStream = nullptr;
nsCString spec;
aURI->GetSpec(spec);
FileDataInfo* info =
GetFileDataInfo(spec);
if (!info) {
return NS_ERROR_DOM_BAD_URI;
}
return info->mFile->GetInternalStream(aStream);
}

View File

@ -6,11 +6,23 @@
#define nsBlobProtocolHandler_h
#include "nsIProtocolHandler.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#define BLOBURI_SCHEME "blob"
class nsIDOMBlob;
class nsIPrincipal;
class nsIInputStream;
inline bool IsBlobURI(nsIURI* aUri)
{
bool isBlob;
return NS_SUCCEEDED(aUri->SchemeIs(BLOBURI_SCHEME, &isBlob)) && isBlob;
}
extern nsresult
NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
class nsBlobProtocolHandler : public nsIProtocolHandler
{
@ -26,11 +38,10 @@ public:
// Methods for managing uri->file mapping
static void AddFileDataEntry(nsACString& aUri,
nsIDOMBlob* aFile,
nsIDOMBlob* aFile,
nsIPrincipal* aPrincipal);
static void RemoveFileDataEntry(nsACString& aUri);
static nsIPrincipal* GetFileDataEntryPrincipal(nsACString& aUri);
};
#define NS_BLOBPROTOCOLHANDLER_CID \

View File

@ -683,9 +683,9 @@ nsObjectLoadingContent::~nsObjectLoadingContent()
nsresult
nsObjectLoadingContent::InstantiatePluginInstance()
{
if (mType != eType_Plugin) {
LOG(("OBJLC [%p]: Refusing to instantiate non-plugin, "
"type %u", this, mType));
if (mType != eType_Plugin || mIsLoading) {
LOG(("OBJLC [%p]: Not instantiating loading or non-plugin object, type %u",
this, mType));
return NS_OK;
}
@ -1560,6 +1560,9 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
// original load call needs to abort when unwinding
// NOTE this is located *after* the state change check, a subseqent load
// with no subsequently changed state will be a no-op.
if (mIsLoading) {
LOG(("OBJLC [%p]: Re-entering into LoadObject", this));
}
mIsLoading = true;
AutoSetLoadingToFalse reentryCheck(this);
@ -1569,6 +1572,7 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
if (!mIsLoading) {
// The event loop must've spun and re-entered into LoadObject, which
// finished the load
LOG(("OBJLC [%p]: Re-entered into LoadObject, aborting outer load", this));
return NS_OK;
}
@ -1619,6 +1623,13 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
allowLoad = CheckProcessPolicy(&contentPolicy);
}
// Content policy implementations can mutate the DOM, check for re-entry
if (!mIsLoading) {
LOG(("OBJLC [%p]: We re-entered in content policy, leaving original load",
this));
return NS_OK;
}
// Load denied, switch to fallback and set disabled/suppressed if applicable
if (!allowLoad) {
LOG(("OBJLC [%p]: Load denied by policy", this));
@ -1664,6 +1675,10 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
///
/// Attempt to load new type
///
// Remove blocker on entering into instantiate
mIsLoading = false;
switch (mType) {
case eType_Image:
if (!mChannel) {
@ -2335,6 +2350,7 @@ nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner,
}
aInstanceOwner->Destroy();
mIsStopping = false;
}
NS_IMETHODIMP

View File

@ -610,7 +610,7 @@ WebGLContext::Render(gfxContext *ctx, gfxPattern::GraphicsFilter f, PRUint32 aFl
if (surf->CairoStatus() != 0)
return NS_ERROR_FAILURE;
gl->ReadPixelsIntoImageSurface(0, 0, mWidth, mHeight, surf);
gl->ReadPixelsIntoImageSurface(surf);
bool srcPremultAlpha = mOptions.premultipliedAlpha;
bool dstPremultAlpha = aFlags & RenderFlagPremultAlpha;

View File

@ -28,6 +28,7 @@
#include "nsIAsyncVerifyRedirectCallback.h"
#include "mozilla/Util.h" // for DebugOnly
#include "nsContentUtils.h"
#include "nsBlobProtocolHandler.h"
static const PRUint32 HTTP_OK_CODE = 200;
static const PRUint32 HTTP_PARTIAL_RESPONSE_CODE = 206;
@ -1038,14 +1039,15 @@ nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
// implements nsISeekableStream, so we have to find the underlying
// file and reopen it
nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
if (!fc)
return NS_ERROR_UNEXPECTED;
if (fc) {
nsCOMPtr<nsIFile> file;
rv = fc->GetFile(getter_AddRefs(file));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> file;
rv = fc->GetFile(getter_AddRefs(file));
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
} else if (IsBlobURI(mURI)) {
rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
}
} else {
// Ensure that we never load a local file from some page on a
// web server.
@ -1201,7 +1203,7 @@ MediaResource*
MediaResource::Create(nsMediaDecoder* aDecoder, nsIChannel* aChannel)
{
NS_ASSERTION(NS_IsMainThread(),
"MediaResource::Open called on non-main thread");
"MediaResource::Open called on non-main thread");
// If the channel was redirected, we want the post-redirect URI;
// but if the URI scheme was expanded, say from chrome: to jar:file:,
@ -1211,7 +1213,7 @@ MediaResource::Create(nsMediaDecoder* aDecoder, nsIChannel* aChannel)
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
if (fc) {
if (fc || IsBlobURI(uri)) {
return new FileMediaResource(aDecoder, aChannel, uri);
}
return new ChannelMediaResource(aDecoder, aChannel, uri);

View File

@ -245,7 +245,7 @@ void* nsBuiltinDecoderReader::VideoQueueMemoryFunctor::operator()(void* anObject
if (!v->mImage) {
return nullptr;
}
NS_ASSERTION(v->mImage->GetFormat() == mozilla::ImageFormat::PLANAR_YCBCR,
NS_ASSERTION(v->mImage->GetFormat() == PLANAR_YCBCR,
"Wrong format?");
mozilla::layers::PlanarYCbCrImage* vi = static_cast<mozilla::layers::PlanarYCbCrImage*>(v->mImage.get());

View File

@ -19,6 +19,7 @@ XPIDLSRCS = \
nsIDOMMobileConnection.idl \
nsIMobileConnectionProvider.idl \
nsIDOMUSSDReceivedEvent.idl \
nsIDOMTCPSocket.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,219 @@
/* 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/. */
/**
* MozTCPSocket exposes a TCP client socket (no server sockets yet)
* to highly privileged apps. It provides a buffered, non-blocking
* interface for sending. For receiving, it uses an asynchronous,
* event handler based interface.
*/
#include "domstubs.idl"
#include "nsIDOMEvent.idl"
// Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
// nsITCPSocket should be an nsIEventTarget but js objects
// cannot be an nsIEventTarget yet
// #include "nsIEventTarget.idl"
// Bug 723206 - Constructors implemented in JS from IDL should be
// allowed to have arguments
//
// Once bug 723206 will be fixed, this method could be replaced by
// arguments when instantiating a TCPSocket object. For example it will
// be possible to do (similarly to the WebSocket API):
// var s = new MozTCPSocket(host, port);
[scriptable, uuid(b82e17da-6476-11e1-8813-57a2ffe9e42c)]
interface nsIDOMTCPSocket : nsISupports
{
/**
* Create and return a socket object which will attempt to connect to
* the given host and port.
*
* @param host The hostname of the server to connect to.
* @param port The port to connect to.
* @param options An object specifying one or more parameters which
* determine the details of the socket.
*
* useSSL: true to create an SSL socket. Defaults to false.
*
* binaryType: "arraybuffer" to use UInt8 array
* instances in the ondata callback and as the argument
* to send. Defaults to "string", to use JavaScript strings.
*
* @return The new TCPSocket instance.
*/
nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
/**
* The host of this socket object.
*/
readonly attribute DOMString host;
/**
* The port of this socket object.
*/
readonly attribute unsigned short port;
/**
* True if this socket object is an SSL socket.
*/
readonly attribute boolean ssl;
/**
* The number of bytes which have previously been buffered by calls to
* send on this socket.
*/
readonly attribute unsigned long bufferedAmount;
/**
* Pause reading incoming data and invocations of the ondata handler until
* resume is called.
*/
void suspend();
/**
* Resume reading incoming data and invoking ondata as usual.
*/
void resume();
/**
* Close the socket.
*/
void close();
/**
* Write data to the socket.
*
* @param data The data to write to the socket. If
* binaryType: "arraybuffer" was passed in the options
* object, then this object should be an Uint8Array instance.
* If binaryType: "string" was passed, or if no binaryType
* option was specified, then this object should be an
* ordinary JavaScript string.
*
* @return Send returns true or false as a hint to the caller that
* they may either continue sending more data immediately, or
* may want to wait until the other side has read some of the
* data which has already been written to the socket before
* buffering more. If send returns true, then less than 64k
* has been buffered and it's safe to immediately write more.
* If send returns false, then more than 64k has been buffered,
* and the caller may wish to wait until the ondrain event
* handler has been called before buffering more data by more
* calls to send.
*/
boolean send(in jsval data);
/**
* The readyState attribute indicates which state the socket is currently
* in. The state will be either CONNECTING, OPEN, CLOSING, or CLOSED.
*/
readonly attribute DOMString readyState;
readonly attribute DOMString CONNECTING;
readonly attribute DOMString OPEN;
readonly attribute DOMString CLOSING;
readonly attribute DOMString CLOSED;
/**
* The binaryType attribute indicates which mode this socket uses for
* sending and receiving data. If the binaryType: "arraybuffer" option
* was passed to the open method that created this socket, binaryType
* will be "arraybuffer". Otherwise, it will be "string".
*/
readonly attribute DOMString binaryType;
/**
* The onopen event handler is called when the connection to the server
* has been established. If the connection is refused, onerror will be
* called, instead.
*/
attribute jsval onopen;
/**
* After send has buffered more than 64k of data, it returns false to
* indicate that the client should pause before sending more data, to
* avoid accumulating large buffers. This is only advisory, and the client
* is free to ignore it and buffer as much data as desired, but if reducing
* the size of buffers is important (especially for a streaming application)
* ondrain will be called once the previously-buffered data has been written
* to the network, at which point the client can resume calling send again.
*/
attribute jsval ondrain;
/**
* The ondata handler will be called repeatedly and asynchronously after
* onopen has been called, every time some data was available from the server
* and was read. If binaryType: "arraybuffer" was passed to open, the data
* attribute of the event object will be an Uint8Array. If not, it will be a
* normal JavaScript string.
*
* At any time, the client may choose to pause reading and receiving ondata
* callbacks, by calling the socket's suspend() method. Further invocations
* of ondata will be paused until resume() is called.
*/
attribute jsval ondata;
/**
* The onerror handler will be called when there is an error. The data
* attribute of the event passed to the onerror handler will have a
* description of the kind of error.
*
* If onerror is called before onopen, the error was connection refused,
* and onclose will not be called. If onerror is called after onopen,
* the connection was lost, and onclose will be called after onerror.
*/
attribute jsval onerror;
/**
* The onclose handler is called once the underlying network socket
* has been closed, either by the server, or by the client calling
* close.
*
* If onerror was not called before onclose, then either side cleanly
* closed the connection.
*/
attribute jsval onclose;
};
/**
* nsITCPSocketEvent is the event object which is passed as the
* first argument to all the event handler callbacks. It contains
* the socket that was associated with the event, the type of event,
* and the data associated with the event (if any).
*/
[scriptable, uuid(0f2abcca-b483-4539-a3e8-345707f75c44)]
interface nsITCPSocketEvent : nsISupports {
/**
* The socket object which produced this event.
*/
readonly attribute nsIDOMTCPSocket socket;
/**
* The type of this event. One of:
*
* onopen
* onerror
* ondata
* ondrain
* onclose
*/
readonly attribute DOMString type;
/**
* The data related to this event, if any. In the ondata callback,
* data will be the bytes read from the network; if the binaryType
* of the socket was "arraybuffer", this value will be of type Uint8Array;
* otherwise, it will be a normal JavaScript string.
*
* In the onerror callback, data will be a string with a description
* of the error.
*
* In the other callbacks, data will be an empty string.
*/
readonly attribute jsval data;
};

View File

@ -13,6 +13,11 @@ LIBRARY_NAME = dom_network_s
LIBXUL_LIBRARY = 1
FORCE_STATIC_LIB = 1
EXTRA_COMPONENTS = \
TCPSocket.js \
TCPSocket.manifest \
$(NULL)
include $(topsrcdir)/dom/dom-config.mk
EXPORTS_NAMESPACES = mozilla/dom/network

View File

@ -0,0 +1,556 @@
/* 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/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const CC = Components.Constructor;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const InputStreamPump = CC(
"@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"),
AsyncStreamCopier = CC(
"@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"),
ScriptableInputStream = CC(
"@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
BinaryInputStream = CC(
"@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"),
StringInputStream = CC(
'@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'),
MultiplexInputStream = CC(
'@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
const kCONNECTING = 'connecting';
const kOPEN = 'open';
const kCLOSING = 'closing';
const kCLOSED = 'closed';
const BUFFER_SIZE = 65536;
/*
* Debug logging function
*/
let debug = true;
function LOG(msg) {
if (debug)
dump("TCPSocket: " + msg + "\n");
}
/*
* nsITCPSocketEvent object
*/
function TCPSocketEvent(type, sock, data) {
this._type = type;
this._socket = sock;
this._data = data;
}
TCPSocketEvent.prototype = {
__exposedProps__: {
type: 'r',
socket: 'r',
data: 'r'
},
get type() {
return this._type;
},
get socket() {
return this._socket;
},
get data() {
return this._data;
}
}
/*
* nsIDOMTCPSocket object
*/
function TCPSocket() {
this._readyState = kCLOSED;
this._onopen = null;
this._ondrain = null;
this._ondata = null;
this._onerror = null;
this._onclose = null;
this._binaryType = "string";
this._host = "";
this._port = 0;
this._ssl = false;
}
TCPSocket.prototype = {
__exposedProps__: {
open: 'r',
host: 'r',
port: 'r',
ssl: 'r',
bufferedAmount: 'r',
suspend: 'r',
resume: 'r',
close: 'r',
send: 'r',
readyState: 'r',
CONNECTING: 'r',
OPEN: 'r',
CLOSING: 'r',
CLOSED: 'r',
binaryType: 'r',
onopen: 'rw',
ondrain: 'rw',
ondata: 'rw',
onerror: 'rw',
onclose: 'rw'
},
// Constants
CONNECTING: kCONNECTING,
OPEN: kOPEN,
CLOSING: kCLOSING,
CLOSED: kCLOSED,
// The binary type, "string" or "arraybuffer"
_binaryType: null,
// Internal
_hasPrivileges: null,
// Raw socket streams
_transport: null,
_socketInputStream: null,
_socketOutputStream: null,
// Input stream machinery
_inputStreamPump: null,
_inputStreamScriptable: null,
_inputStreamBinary: null,
// Output stream machinery
_multiplexStream: null,
_multiplexStreamCopier: null,
_asyncCopierActive: false,
_waitingForDrain: false,
_suspendCount: 0,
// Public accessors.
get readyState() {
return this._readyState;
},
get binaryType() {
return this._binaryType;
},
get host() {
return this._host;
},
get port() {
return this._port;
},
get ssl() {
return this._ssl;
},
get bufferedAmount() {
return this._multiplexStream.available();
},
get onopen() {
return this._onopen;
},
set onopen(f) {
this._onopen = f;
},
get ondrain() {
return this._ondrain;
},
set ondrain(f) {
this._ondrain = f;
},
get ondata() {
return this._ondata;
},
set ondata(f) {
this._ondata = f;
},
get onerror() {
return this._onerror;
},
set onerror(f) {
this._onerror = f;
},
get onclose() {
return this._onclose;
},
set onclose(f) {
this._onclose = f;
},
// Helper methods.
_createTransport: function ts_createTransport(host, port, sslMode) {
let options, optlen;
if (sslMode) {
options = [sslMode];
optlen = 1;
} else {
options = null;
optlen = 0;
}
return Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService)
.createTransport(options, optlen, host, port, null);
},
_ensureCopying: function ts_ensureCopying() {
let self = this;
if (this._asyncCopierActive) {
return;
}
this._asyncCopierActive = true;
this._multiplexStreamCopier.asyncCopy({
onStartRequest: function ts_output_onStartRequest() {
},
onStopRequest: function ts_output_onStopRequest(request, context, status) {
self._asyncCopierActive = false;
self._multiplexStream.removeStream(0);
if (status) {
this._readyState = kCLOSED;
let err = new Error("Connection closed while writing: " + status);
err.status = status;
this.callListener("onerror", err);
this.callListener("onclose");
return;
}
if (self._multiplexStream.count) {
self._ensureCopying();
} else {
if (self._waitingForDrain) {
self._waitingForDrain = false;
self.callListener("ondrain");
}
if (self._readyState === kCLOSING) {
self._socketOutputStream.close();
self._readyState = kCLOSED;
self.callListener("onclose");
}
}
}
}, null);
},
callListener: function ts_callListener(type, data) {
if (!this[type])
return;
this[type].call(null, new TCPSocketEvent(type, this, data || ""));
},
init: function ts_init(aWindow) {
if (!Services.prefs.getBoolPref("dom.mozTCPSocket.enabled"))
return null;
let principal = aWindow.document.nodePrincipal;
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
let perm = principal == secMan.getSystemPrincipal()
? Ci.nsIPermissionManager.ALLOW_ACTION
: Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket");
this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
let util = aWindow.QueryInterface(
Ci.nsIInterfaceRequestor
).getInterface(Ci.nsIDOMWindowUtils);
this.innerWindowID = util.currentInnerWindowID;
LOG("window init: " + this.innerWindowID);
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "inner-window-destroyed") {
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId == this.innerWindowID) {
LOG("inner-window-destroyed: " + this.innerWindowID);
// This window is now dead, so we want to clear the callbacks
// so that we don't get a "can't access dead object" when the
// underlying stream goes to tell us that we are closed
this.onopen = null;
this.ondrain = null;
this.ondata = null;
this.onerror = null;
this.onclose = null;
// Clean up our socket
this.close();
}
}
},
// nsIDOMTCPSocket
open: function ts_open(host, port, options) {
// in the testing case, init won't be called and
// hasPrivileges will be null. We want to proceed to test.
if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
throw new Error("TCPSocket does not have permission in this context.\n");
}
let that = new TCPSocket();
that.innerWindowID = this.innerWindowID;
LOG("window init: " + that.innerWindowID);
Services.obs.addObserver(that, "inner-window-destroyed", true);
LOG("startup called\n");
LOG("Host info: " + host + ":" + port + "\n");
that._readyState = kCONNECTING;
that._host = host;
that._port = port;
if (options !== undefined) {
if (options.useSSL) {
that._ssl = 'ssl';
} else {
that._ssl = false;
}
that._binaryType = options.binaryType || that._binaryType;
}
LOG("SSL: " + that.ssl + "\n");
let transport = that._transport = this._createTransport(host, port, that._ssl);
transport.setEventSink(that, Services.tm.currentThread);
transport.securityCallbacks = new SecurityCallbacks(that);
that._socketInputStream = transport.openInputStream(0, 0, 0);
that._socketOutputStream = transport.openOutputStream(
Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
// If the other side is not listening, we will
// get an onInputStreamReady callback where available
// raises to indicate the connection was refused.
that._socketInputStream.asyncWait(
that, that._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
if (that._binaryType === "arraybuffer") {
that._inputStreamBinary = new BinaryInputStream(that._socketInputStream);
} else {
that._inputStreamScriptable = new ScriptableInputStream(that._socketInputStream);
}
that._multiplexStream = new MultiplexInputStream();
that._multiplexStreamCopier = new AsyncStreamCopier(
that._multiplexStream,
that._socketOutputStream,
// (nsSocketTransport uses gSocketTransportService)
Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsIEventTarget),
/* source buffered */ true, /* sink buffered */ false,
BUFFER_SIZE, /* close source*/ false, /* close sink */ false);
return that;
},
close: function ts_close() {
if (this._readyState === kCLOSED || this._readyState === kCLOSING)
return;
LOG("close called\n");
this._readyState = kCLOSING;
if (!this._multiplexStream.count) {
this._socketOutputStream.close();
}
this._socketInputStream.close();
},
send: function ts_send(data) {
if (this._readyState !== kOPEN) {
throw new Error("Socket not open.");
}
let new_stream = new StringInputStream();
if (this._binaryType === "arraybuffer") {
// It would be really nice if there were an interface
// that took an ArrayBuffer like StringInputStream takes
// a string. There is one, but only in C++ and not exposed
// to js as far as I can tell
var dataLen = data.length;
var offset = 0;
var result = "";
while (dataLen) {
var fragmentLen = dataLen;
if (fragmentLen > 32768)
fragmentLen = 32768;
dataLen -= fragmentLen;
var fragment = data.subarray(offset, offset + fragmentLen);
offset += fragmentLen;
result += String.fromCharCode.apply(null, fragment);
}
data = result;
}
var newBufferedAmount = this.bufferedAmount + data.length;
new_stream.setData(data, data.length);
this._multiplexStream.appendStream(new_stream);
if (newBufferedAmount >= BUFFER_SIZE) {
// If we buffered more than some arbitrary amount of data,
// (65535 right now) we should tell the caller so they can
// wait until ondrain is called if they so desire. Once all the
//buffered data has been written to the socket, ondrain is
// called.
this._waitingForDrain = true;
}
this._ensureCopying();
return newBufferedAmount < BUFFER_SIZE;
},
suspend: function ts_suspend() {
if (this._inputStreamPump) {
this._inputStreamPump.suspend();
} else {
++this._suspendCount;
}
},
resume: function ts_resume() {
if (this._inputStreamPump) {
this._inputStreamPump.resume();
} else {
--this._suspendCount;
}
},
// nsITransportEventSink (Triggered by transport.setEventSink)
onTransportStatus: function ts_onTransportStatus(
transport, status, progress, max) {
if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
this._readyState = kOPEN;
this.callListener("onopen");
this._inputStreamPump = new InputStreamPump(
this._socketInputStream, -1, -1, 0, 0, false
);
while (this._suspendCount--) {
this._inputStreamPump.suspend();
}
this._inputStreamPump.asyncRead(this, null);
}
},
// nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait)
// Only used for detecting connection refused
onInputStreamReady: function ts_onInputStreamReady(input) {
try {
input.available();
} catch (e) {
this.callListener("onerror", new Error("Connection refused"));
}
},
// nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
onStartRequest: function ts_onStartRequest(request, context) {
},
// nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
onStopRequest: function ts_onStopRequest(request, context, status) {
let buffered_output = this._multiplexStream.count !== 0;
this._inputStreamPump = null;
if (buffered_output && !status) {
// If we have some buffered output still, and status is not an
// error, the other side has done a half-close, but we don't
// want to be in the close state until we are done sending
// everything that was buffered. We also don't want to call onclose
// yet.
return;
}
this._readyState = kCLOSED;
if (status) {
let err = new Error("Connection closed: " + status);
err.status = status;
this.callListener("onerror", err);
}
this.callListener("onclose");
},
// nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
if (this._binaryType === "arraybuffer") {
let ua = new Uint8Array(count);
ua.set(this._inputStreamBinary.readByteArray(count));
this.callListener("ondata", ua);
} else {
this.callListener("ondata", this._inputStreamScriptable.read(count));
}
},
classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
classInfo: XPCOMUtils.generateCI({
classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
contractID: "@mozilla.org/tcp-socket;1",
classDescription: "Client TCP Socket",
interfaces: [
Ci.nsIDOMTCPSocket,
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsIObserver,
Ci.nsISupportsWeakReference
],
flags: Ci.nsIClassInfo.DOM_OBJECT,
}),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIDOMTCPSocket,
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsIObserver,
Ci.nsISupportsWeakReference
])
}
function SecurityCallbacks(socket) {
this._socket = socket;
}
SecurityCallbacks.prototype = {
notifyCertProblem: function sc_notifyCertProblem(socketInfo, status,
targetSite) {
this._socket.callListener("onerror", status);
this._socket.close();
return true;
},
getInterface: function sc_getInterface(iid) {
return this.QueryInterface(iid);
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIBadCertListener2,
Ci.nsIInterfaceRequestor,
Ci.nsISupports
])
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);

View File

@ -0,0 +1,4 @@
# TCPSocket.js
component {cda91b22-6472-11e1-aa11-834fec09cd0a} TCPSocket.js
contract @mozilla.org/tcp-socket;1 {cda91b22-6472-11e1-aa11-834fec09cd0a}
category JavaScript-navigator-property mozTCPSocket @mozilla.org/tcp-socket;1

View File

@ -16,6 +16,13 @@ DIRS = \
MOCHITEST_FILES = \
test_network_basics.html \
test_tcpsocket_default_permissions.html \
test_tcpsocket_enabled_no_perm.html \
test_tcpsocket_enabled_with_perm.html \
$(NULL)
MODULE = test_dom_socket
XPCSHELL_TESTS = unit
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure TCPSocket permission is disabled by default</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test to ensure TCPSocket permission is disabled by default **/
try {
navigator.mozTCPSocket;
throw new Error("Error: navigator.mozTCPSocket should not exist by default");
} catch (e) {
ok(true, "navigator.mozTCPSocket should not exist by default");
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure TCPSocket permission enabled and no tcp-socket perm does not allow open</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test to ensure TCPSocket permission being turned on enables
navigator.mozTCPSocket, but mozTCPSocket.open does not work
in content.
**/
SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", true);
ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
try {
navigator.mozTCPSocket.open('localhost', 80);
throw new Error("Error: navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
} catch (e) {
ok(true, "navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
}
SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure TCPSocket permission enabled and open works with tcp-socket perm</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test to ensure TCPSocket permission being turned on enables
navigator.mozTCPSocket, and mozTCPSocket.open works when
the tcp-socket permission has been granted.
**/
SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", true);
SpecialPowers.addPermission("tcp-socket", true, document);
ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,479 @@
/**
* Test TCPSocket.js by creating an XPCOM-style server socket, then sending
* data in both directions and making sure each side receives their data
* correctly and with the proper events.
*
* This test is derived from netwerk/test/unit/test_socks.js, except we don't
* involve a subprocess.
*
* Future work:
* - SSL. see https://bugzilla.mozilla.org/show_bug.cgi?id=466524
* https://bugzilla.mozilla.org/show_bug.cgi?id=662180
* Alternatively, mochitests could be used.
* - Testing overflow logic.
*
**/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const CC = Components.Constructor;
/**
*
* Constants
*
*/
// Some binary data to send.
const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY),
HELLO_WORLD = "hlo wrld. ",
BIG_ARRAY = new Array(65539),
BIG_ARRAY_2 = new Array(65539);
for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
}
const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY),
BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_2);
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"init"),
InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
"nsIInputStreamPump",
"init"),
BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream"),
BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream"),
TCPSocket = new (CC("@mozilla.org/tcp-socket;1",
"nsIDOMTCPSocket"))();
/**
*
* Helper functions
*
*/
/**
* Spin up a listening socket and associate at most one live, accepted socket
* with ourselves.
*/
function TestServer() {
this.listener = ServerSocket(-1, true, -1);
do_print('server: listening on', this.listener.port);
this.listener.asyncListen(this);
this.binaryInput = null;
this.input = null;
this.binaryOutput = null;
this.output = null;
this.onaccept = null;
this.ondata = null;
this.onclose = null;
}
TestServer.prototype = {
onSocketAccepted: function(socket, trans) {
if (this.input)
do_throw("More than one live connection!?");
do_print('server: got client connection');
this.input = trans.openInputStream(0, 0, 0);
this.binaryInput = new BinaryInputStream(this.input);
this.output = trans.openOutputStream(0, 0, 0);
this.binaryOutput = new BinaryOutputStream(this.output);
new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null);
if (this.onaccept)
this.onaccept();
else
do_throw("Received unexpected connection!");
},
onStopListening: function(socket) {
},
onDataAvailable: function(request, context, inputStream, offset, count) {
var readData = this.binaryInput.readByteArray(count);
if (this.ondata) {
try {
this.ondata(readData);
} catch(ex) {
// re-throw if this is from do_throw
if (ex === Cr.NS_ERROR_ABORT)
throw ex;
// log if there was a test problem
do_print('Caught exception: ' + ex + '\n' + ex.stack);
do_throw('test is broken; bad ondata handler; see above');
}
} else {
do_throw('Received ' + count + ' bytes of unexpected data!');
}
},
onStartRequest: function(request, context) {
},
onStopRequest: function(request, context, status) {
if (this.onclose)
this.onclose();
else
do_throw("Received unexpected close!");
},
close: function() {
this.binaryInput.close();
this.binaryOutput.close();
},
/**
* Forget about the socket we knew about before.
*/
reset: function() {
this.binaryInput = null;
this.input = null;
this.binaryOutput = null;
this.output = null;
},
};
function makeSuccessCase(name) {
return function() {
do_print('got expected: ' + name);
run_next_test();
};
}
function makeJointSuccess(names) {
let funcs = {}, successCount = 0;
names.forEach(function(name) {
funcs[name] = function() {
do_print('got expected: ' + name);
if (++successCount === names.length)
run_next_test();
};
});
return funcs;
}
function makeFailureCase(name) {
return function() {
let argstr;
if (arguments.length) {
argstr = '(args: ' +
Array.map(arguments, function(x) { return x + ""; }).join(" ") + ')';
}
else {
argstr = '(no arguments)';
}
do_throw('got unexpected: ' + name + ' ' + argstr);
};
}
function makeExpectData(name, expectedData, fromEvent, callback) {
let dataBuffer = fromEvent ? null : [], done = false;
return function(receivedData) {
if (fromEvent) {
receivedData = receivedData.data;
if (dataBuffer) {
let newBuffer = new Uint8Array(dataBuffer.length + receivedData.length);
newBuffer.set(dataBuffer, 0);
newBuffer.set(receivedData, dataBuffer.length);
dataBuffer = newBuffer;
}
else {
dataBuffer = receivedData;
}
}
else {
dataBuffer = dataBuffer.concat(receivedData);
}
do_print(name + ' received ' + receivedData.length + ' bytes');
if (done)
do_throw(name + ' Received data event when already done!');
if (dataBuffer.length >= expectedData.length) {
// check the bytes are equivalent
for (let i = 0; i < expectedData.length; i++) {
if (dataBuffer[i] !== expectedData[i]) {
do_throw(name + ' Received mismatched character at position ' + i);
}
}
if (dataBuffer.length > expectedData.length)
do_throw(name + ' Received ' + dataBuffer.length + ' bytes but only expected ' +
expectedData.length + ' bytes.');
done = true;
if (callback) {
callback();
} else {
run_next_test();
}
}
};
}
var server = null, sock = null, failure_drain = null;
/**
*
* Test functions
*
*/
/**
* Connect the socket to the server. This test is added as the first
* test, and is also added after every test which results in the socket
* being closed.
*/
function connectSock() {
server.reset();
var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']);
sock = TCPSocket.open(
'127.0.0.1', server.listener.port,
{ binaryType: 'arraybuffer' });
sock.onopen = yayFuncs.clientopen;
sock.ondrain = null;
sock.ondata = makeFailureCase('data');
sock.onerror = makeFailureCase('error');
sock.onclose = makeFailureCase('close');
server.onaccept = yayFuncs.serveropen;
server.ondata = makeFailureCase('serverdata');
server.onclose = makeFailureCase('serverclose');
}
/**
* Test that sending a small amount of data works, and that buffering
* does not take place for this small amount of data.
*/
function sendData() {
server.ondata = makeExpectData('serverdata', DATA_ARRAY);
if (!sock.send(TYPED_DATA_ARRAY)) {
do_throw("send should not have buffered such a small amount of data");
}
}
/**
* Test that sending a large amount of data works, that buffering
* takes place (send returns true), and that ondrain is called once
* the data has been sent.
*/
function sendBig() {
var yays = makeJointSuccess(['serverdata', 'clientdrain']),
amount = 0;
server.ondata = function (data) {
amount += data.length;
if (amount === BIG_TYPED_ARRAY.length) {
yays.serverdata();
}
};
sock.ondrain = function(evt) {
if (sock.bufferedAmount) {
do_throw("sock.bufferedAmount was > 0 in ondrain");
}
yays.clientdrain(evt);
}
if (sock.send(BIG_TYPED_ARRAY)) {
do_throw("expected sock.send to return false on large buffer send");
}
}
/**
* Test that data sent from the server correctly fires the ondata
* callback on the client side.
*/
function receiveData() {
server.ondata = makeFailureCase('serverdata');
sock.ondata = makeExpectData('data', DATA_ARRAY, true);
server.binaryOutput.writeByteArray(DATA_ARRAY, DATA_ARRAY.length);
}
/**
* Test that when the server closes the connection, the onclose callback
* is fired on the client side.
*/
function serverCloses() {
// we don't really care about the server's close event, but we do want to
// make sure it happened for sequencing purposes.
var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
sock.ondata = makeFailureCase('data');
sock.onclose = yayFuncs.clientclose;
server.onclose = yayFuncs.serverclose;
server.close();
}
/**
* Test that when the client closes the connection, the onclose callback
* is fired on the server side.
*/
function clientCloses() {
// we want to make sure the server heard the close and also that the client's
// onclose event fired for consistency.
var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
server.onclose = yayFuncs.serverclose;
sock.onclose = yayFuncs.clientclose;
sock.close();
}
/**
* Send a large amount of data and immediately call close
*/
function bufferedClose() {
var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
server.ondata = makeExpectData(
"ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
server.onclose = yays.serverclose;
sock.onclose = yays.clientclose;
sock.send(BIG_TYPED_ARRAY);
sock.close();
}
/**
* Connect to a port we know is not listening so an error is assured,
* and make sure that onerror and onclose are fired on the client side.
*/
function badConnect() {
// There's probably nothing listening on tcp port 2.
sock = TCPSocket.open('127.0.0.1', 2);
sock.onopen = makeFailureCase('open');
sock.ondata = makeFailureCase('data');
sock.onclose = makeFailureCase('close');
sock.onerror = makeSuccessCase('error');
}
/**
* Test that calling send with enough data to buffer causes ondrain to
* be invoked once the data has been sent, and then test that calling send
* and buffering again causes ondrain to be fired again.
*/
function drainTwice() {
let yays = makeJointSuccess(
['ondrain', 'ondrain2',
'ondata', 'ondata2',
'serverclose', 'clientclose']);
function serverSideCallback() {
yays.ondata();
server.ondata = makeExpectData(
"ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
sock.ondrain = yays.ondrain2;
if (sock.send(BIG_TYPED_ARRAY_2)) {
do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
}
sock.close();
}
server.onclose = yays.serverclose;
server.ondata = makeExpectData(
"ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
sock.onclose = yays.clientclose;
sock.ondrain = yays.ondrain;
if (sock.send(BIG_TYPED_ARRAY)) {
throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
}
}
function cleanup() {
do_print("Cleaning up");
sock.close();
run_next_test();
}
/**
* Test that calling send with enough data to buffer twice in a row without
* waiting for ondrain still results in ondrain being invoked at least once.
*/
function bufferTwice() {
let yays = makeJointSuccess(
['ondata', 'ondrain', 'serverclose', 'clientclose']);
let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2));
server.ondata = makeExpectData(
"ondata", double_array, false, yays.ondata);
server.onclose = yays.serverclose;
sock.onclose = yays.clientclose;
sock.ondrain = function () {
sock.close();
yays.ondrain();
}
if (sock.send(BIG_TYPED_ARRAY)) {
throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
}
if (sock.send(BIG_TYPED_ARRAY_2)) {
throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
}
}
// - connect, data and events work both ways
add_test(connectSock);
add_test(sendData);
add_test(sendBig);
add_test(receiveData);
// - server closes on us
add_test(serverCloses);
// - connect, we close on the server
add_test(connectSock);
add_test(clientCloses);
// - connect, buffer, close
add_test(connectSock);
add_test(bufferedClose);
// - get an error on an attempt to connect to a non-listening port
add_test(badConnect);
// send a buffer, get a drain, send a buffer, get a drain
add_test(connectSock);
add_test(drainTwice);
// send a buffer, get a drain, send a buffer, get a drain
add_test(connectSock);
add_test(bufferTwice);
// clean up
add_test(cleanup);
function run_test() {
server = new TestServer();
run_next_test();
}

View File

@ -0,0 +1,5 @@
[DEFAULT]
head =
tail =
[test_tcpsocket.js]

View File

@ -533,7 +533,8 @@ var interfaceNamesInGlobalScope =
"CameraCapabilities",
"CameraManager",
"CSSSupportsRule",
"MozMobileCellInfo"
"MozMobileCellInfo",
"TCPSocket"
]
for (var i in Components.interfaces) {

View File

@ -154,6 +154,7 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl)
{ (PRFuncPtr*) &mSymbols.fGetUniformLocation, { "GetUniformLocation", "GetUniformLocationARB", NULL } },
{ (PRFuncPtr*) &mSymbols.fGetVertexAttribfv, { "GetVertexAttribfv", "GetVertexAttribfvARB", NULL } },
{ (PRFuncPtr*) &mSymbols.fGetVertexAttribiv, { "GetVertexAttribiv", "GetVertexAttribivARB", NULL } },
{ (PRFuncPtr*) &mSymbols.fGetVertexAttribPointerv, { "GetVertexAttribPointerv", NULL } },
{ (PRFuncPtr*) &mSymbols.fHint, { "Hint", NULL } },
{ (PRFuncPtr*) &mSymbols.fIsBuffer, { "IsBuffer", "IsBufferARB", NULL } },
{ (PRFuncPtr*) &mSymbols.fIsEnabled, { "IsEnabled", NULL } },
@ -1757,6 +1758,7 @@ GLContext::MarkDestroyed()
if (MakeCurrent()) {
DeleteOffscreenFBOs();
DeleteTexBlitProgram();
fDeleteProgram(mBlitProgram);
mBlitProgram = 0;
@ -1769,16 +1771,15 @@ GLContext::MarkDestroyed()
mSymbols.Zero();
}
static void SwapRAndBComponents(gfxImageSurface* aSurf)
static void SwapRAndBComponents(gfxImageSurface* surf)
{
gfxIntSize size = aSurf->GetSize();
for (int j = 0; j < size.height; ++j) {
PRUint32 *row = (PRUint32*) (aSurf->Data() + aSurf->Stride() * j);
for (int i = 0; i < size.width; ++i) {
*row = (*row & 0xff00ff00) | ((*row & 0xff) << 16) | ((*row & 0xff0000) >> 16);
row++;
for (int j = 0; j < surf->Height(); ++j) {
uint32_t* row = (uint32_t*)(surf->Data() + surf->Stride() * j);
for (int i = 0; i < surf->Width(); ++i) {
*row = (*row & 0xff00ff00) | ((*row & 0xff) << 16) | ((*row & 0xff0000) >> 16);
row++;
}
}
}
}
static already_AddRefed<gfxImageSurface> YInvertImageSurface(gfxImageSurface* aSurf)
@ -2005,33 +2006,25 @@ GLContext::ReadScreenIntoImageSurface(gfxImageSurface* dest)
fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, (GLint*)&boundFB);
fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
ReadPixelsIntoImageSurface(0, 0, dest->Width(), dest->Height(), dest);
ReadPixelsIntoImageSurface(dest);
fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, boundFB);
}
void
GLContext::ReadPixelsIntoImageSurface(GLint aX, GLint aY,
GLsizei aWidth, GLsizei aHeight,
gfxImageSurface *aDest)
GLContext::ReadPixelsIntoImageSurface(gfxImageSurface* dest)
{
MOZ_ASSERT(dest->Format() == gfxASurface::ImageFormatARGB32 ||
dest->Format() == gfxASurface::ImageFormatRGB24);
MOZ_ASSERT(dest->Stride() == dest->Width() * 4);
MOZ_ASSERT(dest->Format() == gfxASurface::ImageFormatARGB32 ||
dest->Format() == gfxASurface::ImageFormatRGB24);
MOZ_ASSERT(dest->Stride() == dest->Width() * 4);
MakeCurrent();
if (aDest->Format() != gfxASurface::ImageFormatARGB32 &&
aDest->Format() != gfxASurface::ImageFormatRGB24)
{
NS_WARNING("ReadPixelsIntoImageSurface called with invalid image format");
return;
}
if (aDest->Width() != aWidth ||
aDest->Height() != aHeight ||
aDest->Stride() != aWidth * 4)
{
NS_WARNING("ReadPixelsIntoImageSurface called with wrong size or stride surface");
return;
}
GLint currentPackAlignment = 0;
fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, &currentPackAlignment);
@ -2043,20 +2036,14 @@ GLContext::ReadPixelsIntoImageSurface(GLint aX, GLint aY,
GetOptimalReadFormats(this, format, datatype);
fReadPixels(0, 0, aWidth, aHeight,
fReadPixels(0, 0,
dest->Width(), dest->Height(),
format, datatype,
aDest->Data());
dest->Data());
// Output should be in BGRA, so swap if RGBA
// Output should be in BGRA, so swap if RGBA.
if (format == LOCAL_GL_RGBA) {
// swap B and R bytes
for (int j = 0; j < aHeight; ++j) {
PRUint32 *row = (PRUint32*) (aDest->Data() + aDest->Stride() * j);
for (int i = 0; i < aWidth; ++i) {
*row = (*row & 0xff00ff00) | ((*row & 0xff) << 16) | ((*row & 0xff0000) >> 16);
row++;
}
}
SwapRAndBComponents(dest);
}
if (currentPackAlignment != 4)

View File

@ -38,6 +38,7 @@ typedef char realGLboolean;
#include "GLContextSymbols.h"
#include "mozilla/mozalloc.h"
#include "mozilla/Preferences.h"
namespace android {
class GraphicBuffer;
@ -510,7 +511,12 @@ public:
GLContext(const ContextFormat& aFormat,
bool aIsOffscreen = false,
GLContext *aSharedContext = nullptr)
: mUserBoundDrawFBO(0),
: mTexBlit_Buffer(0),
mTexBlit_VertShader(0),
mTexBlit_FragShader(0),
mTexBlit_Program(0),
mTexBlit_UseDrawNotCopy(false),
mUserBoundDrawFBO(0),
mUserBoundReadFBO(0),
mInternalBoundDrawFBO(0),
mInternalBoundReadFBO(0),
@ -549,6 +555,8 @@ public:
{
mUserData.Init();
mOwningThread = NS_GetCurrentThread();
mTexBlit_UseDrawNotCopy = Preferences::GetBool("gl.blit-draw-not-copy", false);
}
virtual ~GLContext() {
@ -609,6 +617,8 @@ public:
return MakeCurrentImpl(aForce);
}
virtual bool IsCurrent() = 0;
bool IsContextLost() { return mContextLost; }
virtual bool SetupLookupFunction() = 0;
@ -822,6 +832,31 @@ public:
fFinish();
}
protected:
GLuint mTexBlit_Buffer;
GLuint mTexBlit_VertShader;
GLuint mTexBlit_FragShader;
GLuint mTexBlit_Program;
bool mTexBlit_UseDrawNotCopy;
bool UseTexQuadProgram();
void DeleteTexBlitProgram();
public:
void BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize);
void BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize);
void BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
const gfxIntSize& srcSize,
const gfxIntSize& destSize);
void BlitTextureToTexture(GLuint srcTex, GLuint destTex,
const gfxIntSize& srcSize,
const gfxIntSize& destSize);
/*
* Resize the current offscreen buffer. Returns true on success.
* If it returns false, the context should be treated as unusable
@ -1102,6 +1137,15 @@ public:
#endif
}
GLuint GetUserBoundFBO() {
MOZ_ASSERT(GetUserBoundDrawFBO() == GetUserBoundReadFBO());
return GetUserBoundReadFBO();
}
void BindUserFBO(GLuint name) {
fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, name);
}
// BindInternalDraw/ReadFBO() switch us over into 'internal binding mode'
// for the corresponding Draw or Read binding.
// To exit internal binding mode, use BindUserDraw/ReadFBO().
@ -1109,7 +1153,7 @@ public:
// GetBoundUserDraw/ReadFBO() is undefined, and will trigger ABORT in DEBUG builds.
void BindInternalDrawFBO(GLuint name) {
#ifdef DEBUG
mInInternalBindingMode_DrawFBO = true;
mInInternalBindingMode_DrawFBO = true;
#endif
if (SupportsOffscreenSplit())
raw_fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER_EXT, name);
@ -1121,7 +1165,7 @@ public:
void BindInternalReadFBO(GLuint name) {
#ifdef DEBUG
mInInternalBindingMode_ReadFBO = true;
mInInternalBindingMode_ReadFBO = true;
#endif
if (SupportsOffscreenSplit())
raw_fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER_EXT, name);
@ -1385,15 +1429,16 @@ public:
already_AddRefed<gfxImageSurface> GetTexImage(GLuint aTexture, bool aYInvert, ShaderProgramType aShader);
/**
* Call ReadPixels into an existing gfxImageSurface for the given bounds.
* The image surface must be using image format RGBA32 or RGB24.
* Call ReadPixels into an existing gfxImageSurface.
* The image surface must be using image format RGBA32 or RGB24,
* and must have stride == width*4.
* Note that neither ReadPixelsIntoImageSurface nor
* ReadScreenIntoImageSurface call dest->Flush/MarkDirty.
*/
void THEBES_API ReadPixelsIntoImageSurface(GLint aX, GLint aY,
GLsizei aWidth, GLsizei aHeight,
gfxImageSurface *aDest);
void THEBES_API ReadPixelsIntoImageSurface(gfxImageSurface* dest);
// Similar to ReadPixelsIntoImageSurface, but pulls from the screen
// instead of the currenly bound framebuffer.
// instead of the currently bound framebuffer.
void ReadScreenIntoImageSurface(gfxImageSurface* dest);
/**
@ -2368,6 +2413,10 @@ public:
}
}
void GetUIntegerv(GLenum pname, GLuint *params) {
fGetIntegerv(pname, reinterpret_cast<GLint*>(params));
}
void fGetFloatv(GLenum pname, GLfloat *params) {
BEFORE_GL_CALL;
mSymbols.fGetFloatv(pname, params);
@ -2486,6 +2535,12 @@ public:
AFTER_GL_CALL;
}
void fGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid** retval) {
BEFORE_GL_CALL;
mSymbols.fGetVertexAttribPointerv(index, pname, retval);
AFTER_GL_CALL;
}
void fHint(GLenum target, GLenum mode) {
BEFORE_GL_CALL;
mSymbols.fHint(target, mode);
@ -3230,6 +3285,174 @@ DoesStringMatch(const char* aString, const char *aWantedString)
return true;
}
//RAII via CRTP!
template <class Derived>
struct ScopedGLWrapper
{
private:
bool mIsUnwrapped;
protected:
GLContext* const mGL;
ScopedGLWrapper(GLContext* gl)
: mIsUnwrapped(false)
, mGL(gl)
{
MOZ_ASSERT(&ScopedGLWrapper<Derived>::Unwrap == &Derived::Unwrap);
MOZ_ASSERT(&Derived::UnwrapImpl);
MOZ_ASSERT(mGL->IsCurrent());
}
virtual ~ScopedGLWrapper() {
if (!mIsUnwrapped)
Unwrap();
}
public:
void Unwrap() {
MOZ_ASSERT(!mIsUnwrapped);
Derived* derived = static_cast<Derived*>(this);
derived->UnwrapImpl();
mIsUnwrapped = true;
}
};
struct ScopedFramebufferTexture
: public ScopedGLWrapper<ScopedFramebufferTexture>
{
friend class ScopedGLWrapper<ScopedFramebufferTexture>;
protected:
bool mComplete; // True if the framebuffer we create is complete.
GLuint mFB;
public:
ScopedFramebufferTexture(GLContext* gl, GLuint texture)
: ScopedGLWrapper<ScopedFramebufferTexture>(gl)
, mComplete(false)
, mFB(0)
{
MOZ_ASSERT(mGL->IsCurrent());
GLuint boundFB = mGL->GetUserBoundFBO();
mGL->fGenFramebuffers(1, &mFB);
mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mFB);
mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
LOCAL_GL_COLOR_ATTACHMENT0,
LOCAL_GL_TEXTURE_2D,
texture,
0);
GLenum status = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
if (status == LOCAL_GL_FRAMEBUFFER_COMPLETE) {
mComplete = true;
} else {
mGL->fDeleteFramebuffers(1, &mFB);
mFB = 0;
}
mGL->BindUserFBO(boundFB);
}
protected:
void UnwrapImpl() {
if (!mFB)
return;
MOZ_ASSERT(mGL->IsCurrent());
mGL->fDeleteFramebuffers(1, &mFB);
mFB = 0;
}
public:
GLuint FB() const {
return mFB;
}
bool IsComplete() const {
return mComplete;
}
};
// Wraps glEnable/Disable.
struct ScopedGLState
: public ScopedGLWrapper<ScopedGLState>
{
friend class ScopedGLWrapper<ScopedGLState>;
protected:
const GLenum mCapability;
bool mOldState;
public:
// Use |newState = true| to enable, |false| to disable.
ScopedGLState(GLContext* gl, GLenum capability, bool newState)
: ScopedGLWrapper<ScopedGLState>(gl)
, mCapability(capability)
{
MOZ_ASSERT(mGL->IsCurrent());
mOldState = mGL->fIsEnabled(mCapability);
// Early out if we're already in the right state.
if (newState == mOldState)
return;
if (newState)
mGL->fEnable(mCapability);
else
mGL->fDisable(mCapability);
}
protected:
void UnwrapImpl() {
MOZ_ASSERT(mGL->IsCurrent());
if (mOldState)
mGL->fEnable(mCapability);
else
mGL->fDisable(mCapability);
}
};
// Saves and restores with GetUserBoundFBO and BindUserFBO.
struct ScopedFramebufferBinding
: public ScopedGLWrapper<ScopedFramebufferBinding>
{
friend class ScopedGLWrapper<ScopedFramebufferBinding>;
protected:
GLuint mOldState;
private:
void Init() {
MOZ_ASSERT(mGL->IsCurrent());
mOldState = mGL->GetUserBoundFBO();
}
public:
ScopedFramebufferBinding(GLContext* gl)
: ScopedGLWrapper<ScopedFramebufferBinding>(gl)
{
Init();
}
ScopedFramebufferBinding(GLContext* gl, GLuint newFB)
: ScopedGLWrapper<ScopedFramebufferBinding>(gl)
{
Init();
mGL->BindUserFBO(newFB);
}
protected:
void UnwrapImpl() {
MOZ_ASSERT(mGL->IsCurrent());
mGL->BindUserFBO(mOldState);
}
};
} /* namespace gl */
} /* namespace mozilla */

View File

@ -154,6 +154,10 @@ public:
return true;
}
virtual bool IsCurrent() {
return [NSOpenGLContext currentContext] == mContext;
}
bool SetupLookupFunction()
{
return false;

View File

@ -507,6 +507,10 @@ public:
return succeeded;
}
virtual bool IsCurrent() {
return sEGLLibrary.fGetCurrentContext() == mContext;
}
#ifdef MOZ_WIDGET_QT
virtual bool
RenewSurface() {

View File

@ -851,6 +851,10 @@ TRY_AGAIN_NO_SHARING:
return succeeded;
}
virtual bool IsCurrent() {
return sGLXLibrary.xGetCurrentContext() == mContext;
}
bool SetupLookupFunction()
{
mLookupFunc = (PlatformLookupFunction)&GLXLibrary::xGetProcAddress;

View File

@ -193,6 +193,10 @@ public:
return succeeded;
}
virtual bool IsCurrent() {
return sOSMesaLibrary.fGetCurrentContext() == mContext;
}
bool SetupLookupFunction()
{
mLookupFunc = (PlatformLookupFunction)sOSMesaLibrary.fGetProcAddress;

View File

@ -334,6 +334,10 @@ public:
return succeeded;
}
virtual bool IsCurrent() {
return sWGLLib[mLibType].fGetCurrentContext() == mContext;
}
void SetIsDoubleBuffered(bool aIsDB) {
mIsDoubleBuffered = aIsDB;
}

View File

@ -145,6 +145,8 @@ struct GLContextSymbols
PFNGLGETVERTEXATTRIBFVPROC fGetVertexAttribfv;
typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBIVPROC) (GLuint, GLenum, GLint*);
PFNGLGETVERTEXATTRIBIVPROC fGetVertexAttribiv;
typedef void (GLAPIENTRY * PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint, GLenum, GLvoid**);
PFNGLGETVERTEXATTRIBPOINTERVPROC fGetVertexAttribPointerv;
typedef void (GLAPIENTRY * PFNGLHINTPROC) (GLenum target, GLenum mode);
PFNGLHINTPROC fHint;
typedef realGLboolean (GLAPIENTRY * PFNGLISBUFFERPROC) (GLuint buffer);

424
gfx/gl/GLContextUtils.cpp Normal file
View File

@ -0,0 +1,424 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "GLContext.h"
#include "mozilla/Preferences.h"
#include "mozilla/Assertions.h"
#include "mozilla/StandardInteger.h"
using namespace mozilla::gfx;
namespace mozilla {
namespace gl {
static const char kTexBlit_VertShaderSource[] = "\
attribute vec2 aPosition; \n\
\n\
varying vec2 vTexCoord; \n\
\n\
void main(void) { \n\
vTexCoord = aPosition; \n\
vec2 vertPos = aPosition * 2.0 - 1.0; \n\
gl_Position = vec4(vertPos, 0.0, 1.0); \n\
} \n\
";
static const char kTexBlit_FragShaderSource[] = "\
uniform sampler2D uTexUnit; \n\
\n\
varying vec2 vTexCoord; \n\
\n\
void main(void) { \n\
gl_FragColor = texture2D(uTexUnit, vTexCoord); \n\
} \n\
";
// Allowed to be destructive of state we restore in functions below.
bool
GLContext::UseTexQuadProgram()
{
bool success = false;
// Use do-while(false) to let us break on failure
do {
if (mTexBlit_Program) {
// Already have it...
success = true;
break;
}
/* CCW tri-strip:
* 2---3
* | \ |
* 0---1
*/
GLfloat verts[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
MOZ_ASSERT(!mTexBlit_Buffer);
fGenBuffers(1, &mTexBlit_Buffer);
fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
const size_t vertsSize = sizeof(verts);
MOZ_ASSERT(vertsSize >= 3 * sizeof(GLfloat)); // Make sure we have a sane size.
fBufferData(LOCAL_GL_ARRAY_BUFFER, vertsSize, verts, LOCAL_GL_STATIC_DRAW);
fEnableVertexAttribArray(0);
fVertexAttribPointer(0,
2,
LOCAL_GL_FLOAT,
false,
0,
nullptr);
MOZ_ASSERT(!mTexBlit_VertShader);
MOZ_ASSERT(!mTexBlit_FragShader);
const char* vertShaderSource = kTexBlit_VertShaderSource;
const char* fragShaderSource = kTexBlit_FragShaderSource;
mTexBlit_VertShader = fCreateShader(LOCAL_GL_VERTEX_SHADER);
fShaderSource(mTexBlit_VertShader, 1, &vertShaderSource, nullptr);
fCompileShader(mTexBlit_VertShader);
mTexBlit_FragShader = fCreateShader(LOCAL_GL_FRAGMENT_SHADER);
fShaderSource(mTexBlit_FragShader, 1, &fragShaderSource, nullptr);
fCompileShader(mTexBlit_FragShader);
mTexBlit_Program = fCreateProgram();
fAttachShader(mTexBlit_Program, mTexBlit_VertShader);
fAttachShader(mTexBlit_Program, mTexBlit_FragShader);
fBindAttribLocation(mTexBlit_Program, 0, "aPosition");
fLinkProgram(mTexBlit_Program);
if (DebugMode()) {
GLint status = 0;
fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_COMPILE_STATUS, &status);
if (status != LOCAL_GL_TRUE) {
NS_ERROR("Vert shader compilation failed.");
GLint length = 0;
fGetShaderiv(mTexBlit_VertShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
if (!length) {
printf_stderr("No shader info log available.\n");
break;
}
nsAutoArrayPtr<char> buffer(new char[length]);
fGetShaderInfoLog(mTexBlit_VertShader, length, nullptr, buffer);
printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
break;
}
status = 0;
fGetShaderiv(mTexBlit_FragShader, LOCAL_GL_COMPILE_STATUS, &status);
if (status != LOCAL_GL_TRUE) {
NS_ERROR("Frag shader compilation failed.");
GLint length = 0;
fGetShaderiv(mTexBlit_FragShader, LOCAL_GL_INFO_LOG_LENGTH, &length);
if (!length) {
printf_stderr("No shader info log available.\n");
break;
}
nsAutoArrayPtr<char> buffer(new char[length]);
fGetShaderInfoLog(mTexBlit_FragShader, length, nullptr, buffer);
printf_stderr("Shader info log (%d bytes): %s\n", length, buffer.get());
break;
}
}
GLint status = 0;
fGetProgramiv(mTexBlit_Program, LOCAL_GL_LINK_STATUS, &status);
if (status != LOCAL_GL_TRUE) {
if (DebugMode()) {
NS_ERROR("Linking blit program failed.");
GLint length = 0;
fGetProgramiv(mTexBlit_Program, LOCAL_GL_INFO_LOG_LENGTH, &length);
if (!length) {
printf_stderr("No program info log available.\n");
break;
}
nsAutoArrayPtr<char> buffer(new char[length]);
fGetProgramInfoLog(mTexBlit_Program, length, nullptr, buffer);
printf_stderr("Program info log (%d bytes): %s\n", length, buffer.get());
}
break;
}
MOZ_ASSERT(fGetAttribLocation(mTexBlit_Program, "aPosition") == 0);
GLuint texUnitLoc = fGetUniformLocation(mTexBlit_Program, "uTexUnit");
// Set uniforms here:
fUseProgram(mTexBlit_Program);
fUniform1i(texUnitLoc, 0);
success = true;
} while (false);
if (!success) {
NS_ERROR("Creating program for texture blit failed!");
// Clean up:
DeleteTexBlitProgram();
return false;
}
fUseProgram(mTexBlit_Program);
fEnableVertexAttribArray(0);
fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTexBlit_Buffer);
fVertexAttribPointer(0,
2,
LOCAL_GL_FLOAT,
false,
0,
nullptr);
return true;
}
void
GLContext::DeleteTexBlitProgram()
{
if (mTexBlit_Buffer) {
fDeleteBuffers(1, &mTexBlit_Buffer);
mTexBlit_Buffer = 0;
}
if (mTexBlit_VertShader) {
fDeleteShader(mTexBlit_VertShader);
mTexBlit_VertShader = 0;
}
if (mTexBlit_FragShader) {
fDeleteShader(mTexBlit_FragShader);
mTexBlit_FragShader = 0;
}
if (mTexBlit_Program) {
fDeleteProgram(mTexBlit_Program);
mTexBlit_Program = 0;
}
}
void
GLContext::BlitFramebufferToFramebuffer(GLuint srcFB, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize)
{
MOZ_ASSERT(!srcFB || fIsFramebuffer(srcFB));
MOZ_ASSERT(!destFB || fIsFramebuffer(destFB));
MOZ_ASSERT(IsExtensionSupported(EXT_framebuffer_blit) ||
IsExtensionSupported(ANGLE_framebuffer_blit));
ScopedFramebufferBinding boundFB(this);
ScopedGLState scissor(this, LOCAL_GL_SCISSOR_TEST, false);
BindUserReadFBO(srcFB);
BindUserDrawFBO(destFB);
fBlitFramebuffer(0, 0, srcSize.width, srcSize.height,
0, 0, destSize.width, destSize.height,
LOCAL_GL_COLOR_BUFFER_BIT,
LOCAL_GL_NEAREST);
}
void
GLContext::BlitTextureToFramebuffer(GLuint srcTex, GLuint destFB,
const gfxIntSize& srcSize,
const gfxIntSize& destSize)
{
MOZ_ASSERT(fIsTexture(srcTex));
MOZ_ASSERT(!destFB || fIsFramebuffer(destFB));
if (IsExtensionSupported(EXT_framebuffer_blit) ||
IsExtensionSupported(ANGLE_framebuffer_blit))
{
ScopedFramebufferTexture srcWrapper(this, srcTex);
MOZ_ASSERT(srcWrapper.IsComplete());
BlitFramebufferToFramebuffer(srcWrapper.FB(), destFB,
srcSize, destSize);
return;
}
ScopedFramebufferBinding boundFB(this, destFB);
GLuint boundTexUnit = 0;
GetUIntegerv(LOCAL_GL_ACTIVE_TEXTURE, &boundTexUnit);
fActiveTexture(LOCAL_GL_TEXTURE0);
GLuint boundTex = 0;
GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &boundTex);
fBindTexture(LOCAL_GL_TEXTURE_2D, srcTex);
GLuint boundProgram = 0;
GetUIntegerv(LOCAL_GL_CURRENT_PROGRAM, &boundProgram);
GLuint boundBuffer = 0;
GetUIntegerv(LOCAL_GL_ARRAY_BUFFER_BINDING, &boundBuffer);
/*
* fGetVertexAttribiv takes:
* VERTEX_ATTRIB_ARRAY_ENABLED
* VERTEX_ATTRIB_ARRAY_SIZE,
* VERTEX_ATTRIB_ARRAY_STRIDE,
* VERTEX_ATTRIB_ARRAY_TYPE,
* VERTEX_ATTRIB_ARRAY_NORMALIZED,
* VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
* CURRENT_VERTEX_ATTRIB
*
* CURRENT_VERTEX_ATTRIB is vertex shader state. \o/
* Others appear to be vertex array state,
* or alternatively in the internal vertex array state
* for a buffer object.
*/
GLint attrib0_enabled = 0;
GLint attrib0_size = 0;
GLint attrib0_stride = 0;
GLint attrib0_type = 0;
GLint attrib0_normalized = 0;
GLint attrib0_bufferBinding = 0;
void* attrib0_pointer = nullptr;
fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED, &attrib0_enabled);
fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE, &attrib0_size);
fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE, &attrib0_stride);
fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE, &attrib0_type);
fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &attrib0_normalized);
fGetVertexAttribiv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &attrib0_bufferBinding);
fGetVertexAttribPointerv(0, LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER, &attrib0_pointer);
// Note that uniform values are program state, so we don't need to rebind those.
ScopedGLState blend (this, LOCAL_GL_BLEND, false);
ScopedGLState cullFace (this, LOCAL_GL_CULL_FACE, false);
ScopedGLState depthTest (this, LOCAL_GL_DEPTH_TEST, false);
ScopedGLState dither (this, LOCAL_GL_DITHER, false);
ScopedGLState polyOffsFill(this, LOCAL_GL_POLYGON_OFFSET_FILL, false);
ScopedGLState sampleAToC (this, LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false);
ScopedGLState sampleCover (this, LOCAL_GL_SAMPLE_COVERAGE, false);
ScopedGLState scissor (this, LOCAL_GL_SCISSOR_TEST, false);
ScopedGLState stencil (this, LOCAL_GL_STENCIL_TEST, false);
realGLboolean colorMask[4];
fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorMask);
fColorMask(LOCAL_GL_TRUE,
LOCAL_GL_TRUE,
LOCAL_GL_TRUE,
LOCAL_GL_TRUE);
GLint viewport[4];
fGetIntegerv(LOCAL_GL_VIEWPORT, viewport);
fViewport(0, 0, destSize.width, destSize.height);
// Does destructive things to (only!) what we just saved above.
bool good = UseTexQuadProgram();
if (!good) {
// We're up against the wall, so bail.
// This should really be MOZ_CRASH(why) or MOZ_RUNTIME_ASSERT(good).
printf_stderr("[%s:%d] Fatal Error: Failed to prepare to blit texture->framebuffer.\n",
__FILE__, __LINE__);
MOZ_CRASH();
}
fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
fViewport(viewport[0], viewport[1],
viewport[2], viewport[3]);
fColorMask(colorMask[0],
colorMask[1],
colorMask[2],
colorMask[3]);
if (attrib0_enabled)
fEnableVertexAttribArray(0);
fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0_bufferBinding);
fVertexAttribPointer(0,
attrib0_size,
attrib0_type,
attrib0_normalized,
attrib0_stride,
attrib0_pointer);
fBindBuffer(LOCAL_GL_ARRAY_BUFFER, boundBuffer);
fUseProgram(boundProgram);
fBindTexture(LOCAL_GL_TEXTURE_2D, boundTex);
fActiveTexture(boundTexUnit);
}
void
GLContext::BlitFramebufferToTexture(GLuint srcFB, GLuint destTex,
const gfxIntSize& srcSize,
const gfxIntSize& destSize)
{
MOZ_ASSERT(!srcFB || fIsFramebuffer(srcFB));
MOZ_ASSERT(fIsTexture(destTex));
if (IsExtensionSupported(EXT_framebuffer_blit) ||
IsExtensionSupported(ANGLE_framebuffer_blit))
{
ScopedFramebufferTexture destWrapper(this, destTex);
MOZ_ASSERT(destWrapper.IsComplete());
BlitFramebufferToFramebuffer(srcFB, destWrapper.FB(),
srcSize, destSize);
return;
}
GLuint boundTex = 0;
GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &boundTex);
fBindTexture(LOCAL_GL_TEXTURE_2D, destTex);
ScopedFramebufferBinding boundFB(this, srcFB);
ScopedGLState scissor(this, LOCAL_GL_SCISSOR_TEST, false);
fCopyTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0,
0, 0,
0, 0,
srcSize.width, srcSize.height);
fBindTexture(LOCAL_GL_TEXTURE_2D, boundTex);
}
void
GLContext::BlitTextureToTexture(GLuint srcTex, GLuint destTex,
const gfxIntSize& srcSize,
const gfxIntSize& destSize)
{
MOZ_ASSERT(fIsTexture(srcTex));
MOZ_ASSERT(fIsTexture(destTex));
if (mTexBlit_UseDrawNotCopy) {
// Draw is texture->framebuffer
ScopedFramebufferTexture destWrapper(this, destTex);
MOZ_ASSERT(destWrapper.IsComplete());
BlitTextureToFramebuffer(srcTex, destWrapper.FB(),
srcSize, destSize);
return;
}
// Generally, just use the CopyTexSubImage path
ScopedFramebufferTexture srcWrapper(this, srcTex);
MOZ_ASSERT(srcWrapper.IsComplete());
BlitFramebufferToTexture(srcWrapper.FB(), destTex,
srcSize, destSize);
}
} /* namespace gl */
} /* namespace mozilla */

View File

@ -45,6 +45,7 @@ endif
CPPSRCS = \
GLContext.cpp \
GLContextUtils.cpp \
GLLibraryLoader.cpp \
GLContextProviderOSMesa.cpp \
$(NULL)

View File

@ -8,6 +8,7 @@
#include "GLContext.h"
#include "gfxUtils.h"
#include "gfxPlatform.h"
#include "mozilla/Preferences.h"
#include "BasicLayersImpl.h"
#include "nsXULAppAPI.h"
@ -25,6 +26,7 @@ public:
CanvasLayer(aLayerManager, static_cast<BasicImplData*>(this))
{
MOZ_COUNT_CTOR(BasicCanvasLayer);
mForceReadback = Preferences::GetBool("webgl.force-layers-readback", false);
}
virtual ~BasicCanvasLayer()
{
@ -60,6 +62,7 @@ protected:
bool mGLBufferIsPremultiplied;
bool mNeedsYFlip;
bool mForceReadback;
nsRefPtr<gfxImageSurface> mCachedTempSurface;
gfxIntSize mCachedSize;
@ -77,6 +80,7 @@ protected:
mCachedFormat = aFormat;
}
MOZ_ASSERT(mCachedTempSurface->Stride() == mCachedTempSurface->Width() * 4);
return mCachedTempSurface;
}
@ -144,52 +148,80 @@ BasicCanvasLayer::UpdateSurface(gfxASurface* aDestSurface, Layer* aMaskLayer)
mGLContext->MakeCurrent();
#if defined (MOZ_X11) && defined (MOZ_EGL_XRENDER_COMPOSITE)
mGLContext->GuaranteeResolve();
gfxASurface* offscreenSurface = mGLContext->GetOffscreenPixmapSurface();
if (!mForceReadback) {
mGLContext->GuaranteeResolve();
gfxASurface* offscreenSurface = mGLContext->GetOffscreenPixmapSurface();
// XRender can only blend premuliplied alpha, so only allow xrender
// path if we have premultiplied alpha or opaque content.
if (offscreenSurface && (mGLBufferIsPremultiplied || (GetContentFlags() & CONTENT_OPAQUE))) {
// XRender can only blend premuliplied alpha, so only allow xrender
// path if we have premultiplied alpha or opaque content.
if (offscreenSurface && (mGLBufferIsPremultiplied || (GetContentFlags() & CONTENT_OPAQUE))) {
mSurface = offscreenSurface;
mNeedsYFlip = false;
return;
}
}
#endif
nsRefPtr<gfxImageSurface> isurf;
gfxIntSize readSize(mBounds.width, mBounds.height);
gfxImageFormat format = (GetContentFlags() & CONTENT_OPAQUE)
? gfxASurface::ImageFormatRGB24
: gfxASurface::ImageFormatARGB32;
nsRefPtr<gfxImageSurface> readSurf;
nsRefPtr<gfxImageSurface> resultSurf;
bool usingTempSurface = false;
if (aDestSurface) {
DiscardTempSurface();
isurf = static_cast<gfxImageSurface*>(aDestSurface);
resultSurf = static_cast<gfxImageSurface*>(aDestSurface);
if (resultSurf->GetSize() != readSize ||
resultSurf->Stride() != resultSurf->Width() * 4)
{
readSurf = GetTempSurface(readSize, format);
usingTempSurface = true;
}
} else {
nsIntSize size(mBounds.width, mBounds.height);
gfxImageFormat format = (GetContentFlags() & CONTENT_OPAQUE)
? gfxASurface::ImageFormatRGB24
: gfxASurface::ImageFormatARGB32;
isurf = GetTempSurface(size, format);
resultSurf = GetTempSurface(readSize, format);
usingTempSurface = true;
}
if (!usingTempSurface)
DiscardTempSurface();
if (!isurf || isurf->CairoStatus() != 0) {
if (!readSurf)
readSurf = resultSurf;
if (!resultSurf || resultSurf->CairoStatus() != 0)
return;
}
NS_ASSERTION(isurf->Stride() == mBounds.width * 4, "gfxImageSurface stride isn't what we expect!");
MOZ_ASSERT(readSurf);
MOZ_ASSERT(readSurf->Stride() == mBounds.width * 4, "gfxImageSurface stride isn't what we expect!");
// We need to Flush() the surface before modifying it outside of cairo.
isurf->Flush();
mGLContext->ReadScreenIntoImageSurface(isurf);
isurf->MarkDirty();
readSurf->Flush();
mGLContext->ReadScreenIntoImageSurface(readSurf);
readSurf->MarkDirty();
// If the underlying GLContext doesn't have a framebuffer into which
// premultiplied values were written, we have to do this ourselves here.
// Note that this is a WebGL attribute; GL itself has no knowledge of
// premultiplied or unpremultiplied alpha.
if (!mGLBufferIsPremultiplied)
gfxUtils::PremultiplyImageSurface(isurf);
gfxUtils::PremultiplyImageSurface(readSurf);
if (readSurf != resultSurf) {
MOZ_ASSERT(resultSurf->Width() >= readSurf->Width());
MOZ_ASSERT(resultSurf->Height() >= readSurf->Height());
resultSurf->Flush();
resultSurf->CopyFrom(readSurf);
resultSurf->MarkDirty();
}
// stick our surface into mSurface, so that the Paint() path is the same
if (!aDestSurface) {
mSurface = isurf;
mSurface = resultSurf;
}
}
}
@ -247,7 +279,7 @@ BasicCanvasLayer::PaintWithOpacity(gfxContext* aContext,
FillWithMask(aContext, aOpacity, aMaskLayer);
#if defined (MOZ_X11) && defined (MOZ_EGL_XRENDER_COMPOSITE)
if (mGLContext) {
if (mGLContext && !mForceReadback) {
// Wait for X to complete all operations before continuing
// Otherwise gl context could get cleared before X is done.
mGLContext->WaitNative();
@ -362,6 +394,7 @@ BasicShadowableCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer)
}
if (mGLContext &&
!mForceReadback &&
BasicManager()->GetParentBackendType() == mozilla::layers::LAYERS_OPENGL) {
TextureImage::TextureShareType flags;
// if process type is default, then it is single-process (non-e10s)
@ -395,12 +428,14 @@ BasicShadowableCanvasLayer::Paint(gfxContext* aContext, Layer* aMaskLayer)
isOpaque != mBufferIsOpaque) {
DestroyBackBuffer();
mBufferIsOpaque = isOpaque;
if (!BasicManager()->AllocBuffer(
gfxIntSize(mBounds.width, mBounds.height),
isOpaque ?
gfxASurface::CONTENT_COLOR : gfxASurface::CONTENT_COLOR_ALPHA,
&mBackBuffer))
NS_RUNTIMEABORT("creating CanvasLayer back buffer failed!");
gfxIntSize size(mBounds.width, mBounds.height);
gfxASurface::gfxContentType type = isOpaque ?
gfxASurface::CONTENT_COLOR : gfxASurface::CONTENT_COLOR_ALPHA;
if (!BasicManager()->AllocBuffer(size, type, &mBackBuffer)) {
NS_RUNTIMEABORT("creating CanvasLayer back buffer failed!");
}
}
AutoOpenSurface autoBackSurface(OPEN_READ_WRITE, mBackBuffer);

View File

@ -77,7 +77,7 @@ CanvasLayerD3D10::Initialize(const Data& aData)
mUsingSharedTexture = false;
HANDLE shareHandle = mGLContext ? mGLContext->GetD3DShareHandle() : nullptr;
if (shareHandle) {
if (shareHandle && !mForceReadback) {
HRESULT hr = device()->OpenSharedResource(shareHandle, __uuidof(ID3D10Texture2D), getter_AddRefs(mTexture));
if (SUCCEEDED(hr))
mUsingSharedTexture = true;

View File

@ -10,6 +10,8 @@
#include "GLContext.h"
#include "gfxASurface.h"
#include "mozilla/Preferences.h"
namespace mozilla {
namespace layers {
@ -25,6 +27,7 @@ public:
, mHasAlpha(true)
{
mImplData = static_cast<LayerD3D10*>(this);
mForceReadback = Preferences::GetBool("webgl.force-layers-readback", false);
}
~CanvasLayerD3D10();
@ -52,6 +55,7 @@ private:
bool mIsD2DTexture;
bool mUsingSharedTexture;
bool mHasAlpha;
bool mForceReadback;
nsAutoArrayPtr<PRUint8> mCachedTempBlob;
PRUint32 mCachedTempBlob_Size;

View File

@ -187,6 +187,7 @@ CanvasLayerOGL::UpdateSurface()
#endif
if (mCanvasGLContext &&
!mForceReadback &&
mCanvasGLContext->GetContextType() == gl()->GetContextType())
{
DiscardTempSurface();
@ -252,7 +253,8 @@ CanvasLayerOGL::RenderLayer(int aPreviousDestination,
ShaderProgramOGL *program = nullptr;
bool useGLContext = mCanvasGLContext &&
mCanvasGLContext->GetContextType() == gl()->GetContextType();
!mForceReadback &&
mCanvasGLContext->GetContextType() == gl()->GetContextType();
nsIntRect drawRect = mBounds;

View File

@ -14,6 +14,8 @@
#include "mozilla/X11Util.h"
#endif
#include "mozilla/Preferences.h"
namespace mozilla {
namespace layers {
@ -33,6 +35,7 @@ public:
#endif
{
mImplData = static_cast<LayerOGL*>(this);
mForceReadback = Preferences::GetBool("webgl.force-layers-readback", false);
}
~CanvasLayerOGL() { Destroy(); }
@ -60,6 +63,7 @@ protected:
bool mDelayedUpdates;
bool mGLBufferIsPremultiplied;
bool mNeedsYFlip;
bool mForceReadback;
#if defined(MOZ_WIDGET_GTK2) && !defined(MOZ_PLATFORM_MAEMO)
GLXPixmap mPixmap;
#endif

View File

@ -1073,7 +1073,7 @@ LayerManagerOGL::CopyToTarget(gfxContext *aTarget)
NS_ASSERTION(imageSurface->Stride() == width * 4,
"Image Surfaces being created with weird stride!");
mGLContext->ReadPixelsIntoImageSurface(0, 0, width, height, imageSurface);
mGLContext->ReadPixelsIntoImageSurface(imageSurface);
aTarget->SetOperator(gfxContext::OPERATOR_SOURCE);
aTarget->Scale(1.0, -1.0);

View File

@ -388,8 +388,7 @@ AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayListBuilder* aBuilder
if (et) {
for (PRUint32 tranIdx = 0; tranIdx < et->mPropertyTransitions.Length(); tranIdx++) {
ElementPropertyTransition* pt = &et->mPropertyTransitions[tranIdx];
if (pt->mProperty != aProperty ||
!pt->CanPerformOnCompositor(et->mElement, currentTime)) {
if (pt->mProperty != aProperty || !pt->IsRunningAt(currentTime)) {
continue;
}
@ -419,7 +418,7 @@ AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayListBuilder* aBuilder
for (PRUint32 animIdx = 0; animIdx < ea->mAnimations.Length(); animIdx++) {
ElementAnimation* anim = &ea->mAnimations[animIdx];
if (!(anim->HasAnimationOfProperty(aProperty) &&
anim->CanPerformOnCompositor(ea->mElement, currentTime))) {
anim->IsRunningAt(currentTime))) {
continue;
}
AddAnimationsForProperty(frame, aProperty, anim,

View File

@ -14,6 +14,22 @@
namespace mozilla {
namespace css {
/* static */ bool
IsGeometricProperty(nsCSSProperty aProperty)
{
switch (aProperty) {
case eCSSProperty_bottom:
case eCSSProperty_height:
case eCSSProperty_left:
case eCSSProperty_right:
case eCSSProperty_top:
case eCSSProperty_width:
return true;
default:
return false;
}
}
CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext)
: mPresContext(aPresContext)
{
@ -218,12 +234,16 @@ ComputedTimingFunction::GetValue(double aPortion) const
bool
CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *aElement,
nsCSSProperty aProperty)
nsCSSProperty aProperty,
bool aHasGeometricProperties)
{
bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
nsIFrame* frame = aElement->GetPrimaryFrame();
if (aProperty == eCSSProperty_visibility) {
return true;
if (IsGeometricProperty(aProperty)) {
if (shouldLog) {
printf_stderr("Performance warning: Async animation of geometric property '%s' is disabled\n", aProperty);
}
return false;
}
if (aProperty == eCSSProperty_opacity) {
bool enabled = nsLayoutUtils::AreOpacityAnimationsEnabled();
@ -233,31 +253,32 @@ CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *a
return enabled;
}
if (aProperty == eCSSProperty_transform) {
if (frame &&
frame->Preserves3D() &&
if (frame->Preserves3D() &&
frame->Preserves3DChildren()) {
if (shouldLog) {
printf_stderr("Gecko bug: Async animation of 'preserve-3d' transforms is not supported. See bug 779598\n");
}
return false;
}
if (frame && frame->IsSVGTransformed()) {
if (frame->IsSVGTransformed()) {
if (shouldLog) {
printf_stderr("Gecko bug: Async 'transform' animations of frames with SVG transforms is not supported. See bug 779599\n");
}
return false;
}
if (aHasGeometricProperties) {
if (shouldLog) {
printf_stderr("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties\n");
}
return false;
}
bool enabled = nsLayoutUtils::AreTransformAnimationsEnabled();
if (!enabled && shouldLog) {
printf_stderr("Performance warning: Async animation of 'transform' is disabled\n");
}
return enabled;
}
if (shouldLog) {
const nsAFlatCString propName = nsCSSProps::GetStringValue(aProperty);
printf_stderr("Performance warning: Async animation cancelled because of unsupported property '%s'\n", propName.get());
}
return false;
return true;
}

View File

@ -19,9 +19,12 @@
class nsPresContext;
namespace mozilla {
namespace css {
bool IsGeometricProperty(nsCSSProperty aProperty);
struct CommonElementAnimationData;
class CommonAnimationManager : public nsIStyleRuleProcessor,
@ -150,7 +153,8 @@ struct CommonElementAnimationData : public PRCList
static bool
CanAnimatePropertyOnCompositor(const dom::Element *aElement,
nsCSSProperty aProperty);
nsCSSProperty aProperty,
bool aHasGeometricProperties);
dom::Element *mElement;
@ -160,6 +164,17 @@ struct CommonElementAnimationData : public PRCList
CommonAnimationManager *mManager;
// This style rule contains the style data for currently animating
// values. It only matches when styling with animation. When we
// style without animation, we need to not use it so that we can
// detect any new changes; if necessary we restyle immediately
// afterwards with animation.
// NOTE: If we don't need to apply any styles, mStyleRule will be
// null, but mStyleRuleRefreshTime will still be valid.
nsRefPtr<mozilla::css::AnimValuesStyleRule> mStyleRule;
// The refresh time associated with mStyleRule.
TimeStamp mStyleRuleRefreshTime;
#ifdef DEBUG
bool mCalledPropertyDtor;
#endif

View File

@ -15,6 +15,7 @@
#include "nsCSSFrameConstructor.h"
using namespace mozilla;
using namespace mozilla::css;
ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
nsAnimationManager *aAnimationManager)
@ -262,28 +263,10 @@ ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
}
}
static bool
CanPerformAnimationOnCompositor(const ElementAnimation* aAnim,
mozilla::dom::Element* aElement)
{
for (PRUint32 propIdx = 0, propEnd = aAnim->mProperties.Length();
propIdx != propEnd; ++propIdx) {
const AnimationProperty &prop = aAnim->mProperties[propIdx];
if (!mozilla::css::CommonElementAnimationData::
CanAnimatePropertyOnCompositor(aElement,
prop.mProperty)) {
return false;
}
}
return true;
}
bool
ElementAnimation::CanPerformOnCompositor(mozilla::dom::Element* aElement,
TimeStamp aTime) const
ElementAnimation::IsRunningAt(TimeStamp aTime) const
{
return CanPerformAnimationOnCompositor(this, aElement) &&
!IsPaused() && aTime > mStartTime &&
return !IsPaused() && aTime > mStartTime &&
(aTime - mStartTime) / mIterationDuration < mIterationCount;
}
@ -316,19 +299,42 @@ ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const
bool
ElementAnimations::CanPerformOnCompositorThread() const
{
if (mElementProperty != nsGkAtoms::animationsProperty)
if (mElementProperty != nsGkAtoms::animationsProperty) {
return false;
}
bool hasGeometricProperty = false;
nsIFrame* frame = mElement->GetPrimaryFrame();
TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
for (PRUint32 animIdx = mAnimations.Length(); animIdx-- != 0; ) {
const ElementAnimation &anim = mAnimations[animIdx];
const ElementAnimation& anim = mAnimations[animIdx];
for (PRUint32 propIdx = 0, propEnd = anim.mProperties.Length();
propIdx != propEnd; ++propIdx) {
if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) &&
anim.IsRunningAt(now)) {
hasGeometricProperty = true;
break;
}
}
}
for (PRUint32 animIdx = mAnimations.Length(); animIdx-- != 0; ) {
const ElementAnimation& anim = mAnimations[animIdx];
if (anim.mIterationDuration.ToMilliseconds() <= 0.0) {
// No animation data
continue;
}
if (!CanPerformAnimationOnCompositor(&anim, mElement))
return false;
}
for (PRUint32 propIdx = 0, propEnd = anim.mProperties.Length();
propIdx != propEnd; ++propIdx) {
const AnimationProperty& prop = anim.mProperties[propIdx];
if (!CanAnimatePropertyOnCompositor(mElement,
prop.mProperty,
hasGeometricProperty)) {
return false;
}
}
}
return true;
}

View File

@ -90,8 +90,7 @@ struct ElementAnimation
}
bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
bool CanPerformOnCompositor(mozilla::dom::Element* aElement,
mozilla::TimeStamp aTime) const;
bool IsRunningAt(mozilla::TimeStamp aTime) const;
mozilla::TimeStamp mStartTime; // with delay taken into account
mozilla::TimeStamp mPauseStart;
@ -154,16 +153,6 @@ struct ElementAnimations : public mozilla::css::CommonElementAnimationData
// True if this animation can be performed on the compositor thread.
bool CanPerformOnCompositorThread() const;
bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
// This style rule contains the style data for currently animating
// values. It only matches when styling with animation. When we
// style without animation, we need to not use it so that we can
// detect any new changes; if necessary we restyle immediately
// afterwards with animation.
// NOTE: If we don't need to apply any styles, mStyleRule will be
// null, but mStyleRuleRefreshTime will still be valid.
nsRefPtr<mozilla::css::AnimValuesStyleRule> mStyleRule;
// The refresh time associated with mStyleRule.
TimeStamp mStyleRuleRefreshTime;
// False when we know that our current style rule is valid
// indefinitely into the future (because all of our animations are

View File

@ -109,11 +109,8 @@ ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
}
bool
ElementPropertyTransition::CanPerformOnCompositor(mozilla::dom::Element* aElement,
TimeStamp aTime) const {
return css::CommonElementAnimationData::
CanAnimatePropertyOnCompositor(aElement, mProperty) && !IsRemovedSentinel() &&
mStartTime < aTime && aTime < mStartTime + mDuration;
ElementPropertyTransition::IsRunningAt(TimeStamp aTime) const {
return !IsRemovedSentinel() && mStartTime < aTime && aTime < mStartTime + mDuration;
}
bool
@ -130,13 +127,29 @@ ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const
bool
ElementTransitions::CanPerformOnCompositorThread() const
{
if (mElementProperty != nsGkAtoms::transitionsProperty) {
return false;
}
bool hasGeometricProperty = false;
nsIFrame* frame = mElement->GetPrimaryFrame();
TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
for (PRUint32 i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
const ElementPropertyTransition &pt = mPropertyTransitions[i];
const ElementPropertyTransition& pt = mPropertyTransitions[i];
if (css::IsGeometricProperty(pt.mProperty) && pt.IsRunningAt(now)) {
hasGeometricProperty = true;
break;
}
}
for (PRUint32 i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
const ElementPropertyTransition& pt = mPropertyTransitions[i];
if (pt.IsRemovedSentinel()) {
continue;
}
if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(mElement,
pt.mProperty)) {
pt.mProperty,
hasGeometricProperty)) {
return false;
}
}

View File

@ -65,8 +65,7 @@ struct ElementPropertyTransition
mStartTime = mozilla::TimeStamp();
}
bool CanPerformOnCompositor(mozilla::dom::Element* aElement,
mozilla::TimeStamp aTime) const;
bool IsRunningAt(mozilla::TimeStamp aTime) const;
};
struct ElementTransitions : public mozilla::css::CommonElementAnimationData
@ -82,16 +81,6 @@ struct ElementTransitions : public mozilla::css::CommonElementAnimationData
bool CanPerformOnCompositorThread() const;
// Either zero or one for each CSS property:
nsTArray<ElementPropertyTransition> mPropertyTransitions;
// This style rule overrides style data with the currently
// transitioning value for an element that is executing a transition.
// It only matches when styling with animation. When we style without
// animation, we need to not use it so that we can detect any new
// changes; if necessary we restyle immediately afterwards with
// animation.
nsRefPtr<mozilla::css::AnimValuesStyleRule> mStyleRule;
// The refresh time associated with mStyleRule.
mozilla::TimeStamp mStyleRuleRefreshTime;
};

View File

@ -51,6 +51,8 @@ const BLANK_URL_FOR_CLEARING = "data:text/html,%3C%21%2D%2DCLEAR%2D%2D%3E";
var gBrowser;
// Are we testing web content loaded in a separate process?
var gBrowserIsRemote; // bool
// Are we using <iframe mozbrowser>?
var gBrowserIsIframe; // bool
var gBrowserMessageManager;
var gCanvas1, gCanvas2;
// gCurrentCanvas is non-null between InitCurrentCanvasWithSnapshot and the next
@ -219,6 +221,12 @@ function OnRefTestLoad(win)
gBrowserIsRemote = false;
}
try {
gBrowserIsIframe = prefs.getBoolPref("reftest.browser.iframe.enabled");
} catch (e) {
gBrowserIsIframe = false;
}
if (win === undefined || win == null) {
win = window;
}
@ -226,7 +234,12 @@ function OnRefTestLoad(win)
gContainingWindow = win;
}
gBrowser = gContainingWindow.document.createElementNS(XUL_NS, "xul:browser");
if (gBrowserIsIframe) {
gBrowser = gContainingWindow.document.createElementNS(XHTML_NS, "iframe");
gBrowser.setAttribute("mozbrowser", "");
} else {
gBrowser = gContainingWindow.document.createElementNS(XUL_NS, "xul:browser");
}
gBrowser.setAttribute("id", "browser");
gBrowser.setAttribute("type", "content-primary");
gBrowser.setAttribute("remote", gBrowserIsRemote ? "true" : "false");

View File

@ -365,15 +365,21 @@ class B2GReftest(RefTest):
# Turn off the locale picker screen
fhandle = open(os.path.join(profileDir, "user.js"), 'a')
fhandle.write("""
user_pref("browser.homescreenURL", "data:text/html,<h1>reftests should start soon</h1>");
user_pref("browser.manifestURL", "dummy (bug 772307)");
user_pref("browser.firstrun.show.localepicker", false);
user_pref("browser.dom.window.dump.enabled", true);
user_pref("browser.homescreenURL","app://system.gaiamobile.org");\n
user_pref("browser.manifestURL","app://system.gaiamobile.org/manifest.webapp");\n
user_pref("browser.tabs.remote", true);\n
user_pref("dom.ipc.browser_frames.oop_by_default", true);\n
user_pref("dom.ipc.tabs.disabled", false);\n
user_pref("dom.mozBrowserFramesEnabled", true);\n
user_pref("dom.mozBrowserFramesWhitelist","app://system.gaiamobile.org");\n
user_pref("network.dns.localDomains","app://system.gaiamobile.org");\n
user_pref("font.size.inflation.emPerLine", 0);
user_pref("font.size.inflation.minTwips", 0);
user_pref("reftest.browser.iframe.enabled", true);
user_pref("reftest.remote", true);
user_pref("toolkit.telemetry.prompted", true);
user_pref("reftest.uri", "%s");
user_pref("toolkit.telemetry.prompted", true);
""" % reftestlist)
#workaround for jsreftests.

View File

@ -135,6 +135,7 @@ class OmxDecoder {
void CbYCrYFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
void SemiPlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
void SemiPlanarYVU420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
void SemiPlanarYVU420Packed32m4ka(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
bool ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
bool ToAudioFrame(AudioFrame *aFrame, int64_t aTimeUs, void *aData, size_t aDataOffset, size_t aSize,
int32_t aAudioChannels, int32_t aAudioSampleRate);
@ -278,7 +279,6 @@ bool OmxDecoder::Init() {
#ifdef MOZ_WIDGET_GONK
sp<IOMX> omx = GetOMX();
uint32_t flags = OMXCodec::kSoftwareCodecsOnly;
#else
// OMXClient::connect() always returns OK and abort's fatally if
// it can't connect. We may need to implement the connect functionality
@ -287,10 +287,11 @@ bool OmxDecoder::Init() {
LOG("OMXClient failed to connect");
}
sp<IOMX> omx = mClient.interface();
#endif
// Flag value of zero means return a hardware or software decoder
// depending on what the device supports.
uint32_t flags = 0;
#endif
sp<MediaSource> videoTrack;
sp<MediaSource> videoSource;
if (videoTrackIndex != -1 && (videoTrack = extractor->getTrack(videoTrackIndex)) != NULL) {
@ -465,8 +466,21 @@ void OmxDecoder::SemiPlanarYVU420Frame(VideoFrame *aFrame, int64_t aTimeUs, void
aFrame->Cr.mOffset = 0;
}
void OmxDecoder::SemiPlanarYVU420Packed32m4ka(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) {
size_t roundedSliceHeight = (mVideoSliceHeight + 31) & ~31;
size_t roundedStride = (mVideoStride + 31) & ~31;
void *y = aData;
void *uv = static_cast<uint8_t *>(y) + (roundedStride * roundedSliceHeight);
aFrame->Set(aTimeUs, aKeyFrame,
aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation,
y, mVideoStride, mVideoWidth, mVideoHeight, 0, 0,
uv, mVideoStride, mVideoWidth/2, mVideoHeight/2, 1, 1,
uv, mVideoStride, mVideoWidth/2, mVideoHeight/2, 0, 1);
}
bool OmxDecoder::ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) {
const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
const int OMX_QCOM_COLOR_FormatYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
switch (mVideoColorFormat) {
case OMX_COLOR_FormatYUV420Planar:
@ -481,7 +495,11 @@ bool OmxDecoder::ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData,
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
SemiPlanarYVU420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame);
break;
case OMX_QCOM_COLOR_FormatYVU420PackedSemiPlanar32m4ka:
SemiPlanarYVU420Packed32m4ka(aFrame, aTimeUs, aData, aSize, aKeyFrame);
break;
default:
LOG("Unknown video color format: %x", mVideoColorFormat);
return false;
}
return true;

View File

@ -54,7 +54,6 @@ public class AboutHomePromoBox extends LinearLayout implements View.OnClickListe
@Override
public void onClick(View v) {
Log.d(LOGTAG, "I work out.");
switch (mType) {
case SYNC:
final Context context = v.getContext();

View File

@ -352,6 +352,9 @@
@BINPATH@/components/AppsService.js
@BINPATH@/components/AppsService.manifest
@BINPATH@/components/TCPSocket.js
@BINPATH@/components/TCPSocket.manifest
; Modules
@BINPATH@/modules/*

View File

@ -438,6 +438,9 @@
@BINPATH@/components/TelemetryPing.js
@BINPATH@/components/TelemetryPing.manifest
@BINPATH@/components/TCPSocket.js
@BINPATH@/components/TCPSocket.manifest
; Modules
@BINPATH@/modules/*

View File

@ -3519,6 +3519,7 @@ pref("webgl.msaa-level", 2);
pref("webgl.msaa-force", false);
pref("webgl.prefer-16bpp", false);
pref("webgl.default-no-alpha", false);
pref("webgl.force-layers-readback", false);
#ifdef XP_WIN
// The default TCP send window on Windows is too small, and autotuning only occurs on receive

View File

@ -44,9 +44,11 @@ interface nsIFileInputStream : nsIInputStream
const long CLOSE_ON_EOF = 1<<2;
/**
* If this is set, the file will be reopened whenever Seek(0) occurs. If
* the file is already open and the seek occurs, it will happen naturally.
* (The file will only be reopened if it is closed for some reason.)
* If this is set, the file will be reopened whenever we reach the start of
* the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative
* seek that happen to reach the beginning of the file. If the file is
* already open and the seek occurs, it will happen naturally. (The file
* will only be reopened if it is closed for some reason.)
*/
const long REOPEN_ON_REWIND = 1<<3;

View File

@ -410,6 +410,14 @@ nsFileInputStream::Init(nsIFile* aFile, PRInt32 aIOFlags, PRInt32 aPerm,
NS_IMETHODIMP
nsFileInputStream::Close()
{
// Get the cache position at the time the file was close. This allows
// NS_SEEK_CUR on a closed file that has been opened with
// REOPEN_ON_REWIND.
if (mBehaviorFlags & REOPEN_ON_REWIND) {
// Get actual position. Not one modified by subclasses
nsFileStreamBase::Tell(&mCachedPosition);
}
// null out mLineBuffer in case Close() is called again after failing
PR_FREEIF(mLineBuffer);
nsresult rv = nsFileStreamBase::Close();
@ -461,9 +469,15 @@ nsFileInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
PR_FREEIF(mLineBuffer); // this invalidates the line buffer
if (!mFD) {
if (mBehaviorFlags & REOPEN_ON_REWIND) {
nsresult rv = Reopen();
if (NS_FAILED(rv)) {
return rv;
rv = Open(mFile, mIOFlags, mPerm);
NS_ENSURE_SUCCESS(rv, rv);
// If the file was closed, and we do a relative seek, use the
// position we cached when we closed the file to seek to the right
// location.
if (aWhence == NS_SEEK_CUR) {
aWhence = NS_SEEK_SET;
aOffset += mCachedPosition;
}
} else {
return NS_BASE_STREAM_CLOSED;
@ -473,6 +487,18 @@ nsFileInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
return nsFileStreamBase::Seek(aWhence, aOffset);
}
NS_IMETHODIMP
nsFileInputStream::Tell(PRInt64 *aResult)
{
return nsFileStreamBase::Tell(aResult);
}
NS_IMETHODIMP
nsFileInputStream::Available(PRUint64 *aResult)
{
return nsFileStreamBase::Available(aResult);
}
bool
nsFileInputStream::Read(const IPC::Message *aMsg, void **aIter)
{

View File

@ -114,10 +114,7 @@ public:
NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
NS_IMETHOD Close();
NS_IMETHOD Available(PRUint64* _retval)
{
return nsFileStreamBase::Available(_retval);
}
NS_IMETHOD Available(PRUint64* _retval);
NS_IMETHOD Read(char* aBuf, PRUint32 aCount, PRUint32* _retval);
NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
PRUint32 aCount, PRUint32* _retval)
@ -132,12 +129,11 @@ public:
// Overrided from nsFileStreamBase
NS_IMETHOD Seek(PRInt32 aWhence, PRInt64 aOffset);
NS_IMETHOD Tell(PRInt64 *aResult);
nsFileInputStream()
: mIOFlags(0), mPerm(0)
{
mLineBuffer = nullptr;
}
: mIOFlags(0), mPerm(0), mCachedPosition(0), mLineBuffer(nullptr)
{}
virtual ~nsFileInputStream()
{
@ -163,16 +159,17 @@ protected:
*/
PRInt32 mPerm;
/**
* Cached position for Tell for automatically reopening streams.
*/
PRInt64 mCachedPosition;
protected:
/**
* Internal, called to open a file. Parameters are the same as their
* Init() analogues.
*/
nsresult Open(nsIFile* file, PRInt32 ioFlags, PRInt32 perm);
/**
* Reopen the file (for OPEN_ON_READ only!)
*/
nsresult Reopen() { return Open(mFile, mIOFlags, mPerm); }
};
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,6 +1,6 @@
{
"talos.zip": {
"url": "http://build.mozilla.org/talos/zips/talos.07322bbe0f7d.zip",
"url": "http://build.mozilla.org/talos/zips/talos.2e963a8b59a0.zip",
"path": ""
}
}

View File

@ -12,6 +12,7 @@
[include:dom/plugins/test/unit/xpcshell.ini]
[include:dom/sms/tests/xpcshell.ini]
[include:dom/mms/tests/xpcshell.ini]
[include:dom/network/tests/unit/xpcshell.ini]
[include:dom/src/json/test/unit/xpcshell.ini]
[include:dom/system/gonk/tests/xpcshell.ini]
[include:dom/tests/unit/xpcshell.ini]

View File

@ -25,6 +25,7 @@ EXPORTS_NAMESPACES = mozilla
EXPORTS_mozilla = \
Telemetry.h \
ProcessedStack.h \
TelemetryHistograms.h \
$(NULL)

View File

@ -0,0 +1,79 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* 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 ProcessedStack_h__
#define ProcessedStack_h__
#include <string>
#include <vector>
namespace mozilla {
namespace Telemetry {
// This class represents a stack trace and the modules referenced in that trace.
// It is designed to be easy to read and write to disk or network and doesn't
// include any logic on how to collect or read the information it stores.
class ProcessedStack
{
public:
ProcessedStack();
size_t GetStackSize() const;
size_t GetNumModules() const;
struct Frame
{
// The offset of this program counter in its module or an absolute pc.
uintptr_t mOffset;
// The index to pass to GetModule to get the module this program counter
// was in.
uint16_t mModIndex;
};
struct Module
{
// The file name, /foo/bar/libxul.so for example.
std::string mName;
// The address it was loaded to.
// FIXME: remove this once chrome hang has switched to using offsets.
uintptr_t mStart;
// The size of this mapping. May or may not be the entire file.
// FIXME: remove this. It was only used as a sanity check.
size_t mMappingSize;
// Windows specific fields. On other platforms they are 0/empty.
int mPdbAge;
std::string mPdbSignature;
std::string mPdbName;
bool operator==(const Module& other) const;
};
const Frame &GetFrame(unsigned aIndex) const;
void AddFrame(const Frame& aFrame);
const Module &GetModule(unsigned aIndex) const;
void AddModule(const Module& aFrame);
void Clear();
// FIXME: remove these once chrome hang has switched to using offsets.
bool HasModule(const Module &aModule) const;
void RemoveModule(unsigned aIndex);
private:
std::vector<Module> mModules;
std::vector<Frame> mStack;
};
// Get the current list of loaded modules, filter and pair it to the provided
// stack. We let the caller collect the stack since different callers have
// different needs (current thread X main thread, stopping the thread, etc).
// FIXME: remove the aRelative option once chrome hang has switched to using
// offsets.
ProcessedStack
GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative);
} // namespace Telemetry
} // namespace mozilla
#endif // ProcessedStack_h__

View File

@ -3,6 +3,7 @@
* 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 <algorithm>
#include "base/histogram.h"
#include "base/pickle.h"
#include "nsIComponentManager.h"
@ -22,6 +23,7 @@
#include "nsBaseHashtable.h"
#include "nsXULAppAPI.h"
#include "nsThreadUtils.h"
#include "mozilla/ProcessedStack.h"
#include "mozilla/Mutex.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Preferences.h"
@ -102,8 +104,7 @@ public:
uint32_t delay);
#if defined(MOZ_ENABLE_PROFILER_SPS)
static void RecordChromeHang(uint32_t duration,
const Telemetry::HangStack &callStack,
SharedLibraryInfo &moduleMap);
Telemetry::ProcessedStack &aStack);
#endif
static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
struct Stat {
@ -117,10 +118,7 @@ public:
typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
struct HangReport {
uint32_t duration;
Telemetry::HangStack callStack;
#if defined(MOZ_ENABLE_PROFILER_SPS)
SharedLibraryInfo moduleMap;
#endif
Telemetry::ProcessedStack mStack;
};
private:
@ -1021,6 +1019,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
// Each hang report is an object in the 'chromeHangs' array
for (size_t i = 0; i < mHangReports.Length(); ++i) {
Telemetry::ProcessedStack &stack = mHangReports[i].mStack;
JSObject *reportObj = JS_NewObject(cx, NULL, NULL, NULL);
if (!reportObj) {
return NS_ERROR_FAILURE;
@ -1050,10 +1049,11 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
return NS_ERROR_FAILURE;
}
const uint32_t pcCount = mHangReports[i].callStack.Length();
const uint32_t pcCount = stack.GetStackSize();
for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
nsCAutoString pcString;
pcString.AppendPrintf("0x%p", mHangReports[i].callStack[pcIndex]);
const Telemetry::ProcessedStack::Frame &Frame = stack.GetFrame(pcIndex);
pcString.AppendPrintf("0x%p", Frame.mOffset);
JSString *str = JS_NewStringCopyZ(cx, pcString.get());
if (!str) {
return NS_ERROR_FAILURE;
@ -1076,12 +1076,11 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
return NS_ERROR_FAILURE;
}
#if defined(MOZ_ENABLE_PROFILER_SPS)
const uint32_t moduleCount = mHangReports[i].moduleMap.GetSize();
const uint32_t moduleCount = stack.GetNumModules();
for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
// Current module
const SharedLibrary &module =
mHangReports[i].moduleMap.GetEntry(moduleIndex);
const Telemetry::ProcessedStack::Module &module =
stack.GetModule(moduleIndex);
JSObject *moduleInfoArray = JS_NewArrayObject(cx, 0, nullptr);
if (!moduleInfoArray) {
@ -1094,7 +1093,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
// Start address
nsCAutoString addressString;
addressString.AppendPrintf("0x%p", module.GetStart());
addressString.AppendPrintf("0x%p", module.mStart);
JSString *str = JS_NewStringCopyZ(cx, addressString.get());
if (!str) {
return NS_ERROR_FAILURE;
@ -1105,7 +1104,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
}
// Module name
str = JS_NewStringCopyZ(cx, module.GetName());
str = JS_NewStringCopyZ(cx, module.mName.c_str());
if (!str) {
return NS_ERROR_FAILURE;
}
@ -1115,26 +1114,19 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
}
// Module size in memory
val = INT_TO_JSVAL(int32_t(module.GetEnd() - module.GetStart()));
val = INT_TO_JSVAL(int32_t(module.mMappingSize));
if (!JS_SetElement(cx, moduleInfoArray, 2, &val)) {
return NS_ERROR_FAILURE;
}
// "PDB Age" identifier
val = INT_TO_JSVAL(0);
#if defined(MOZ_PROFILING) && defined(XP_WIN)
val = INT_TO_JSVAL(module.GetPdbAge());
#endif
val = INT_TO_JSVAL(module.mPdbAge);
if (!JS_SetElement(cx, moduleInfoArray, 3, &val)) {
return NS_ERROR_FAILURE;
}
// "PDB Signature" GUID
char guidString[NSID_LENGTH] = { 0 };
#if defined(MOZ_PROFILING) && defined(XP_WIN)
module.GetPdbSignature().ToProvidedString(guidString);
#endif
str = JS_NewStringCopyZ(cx, guidString);
str = JS_NewStringCopyZ(cx, module.mPdbSignature.c_str());
if (!str) {
return NS_ERROR_FAILURE;
}
@ -1144,11 +1136,7 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
}
// Name of associated PDB file
const char *pdbName = "";
#if defined(MOZ_PROFILING) && defined(XP_WIN)
pdbName = module.GetPdbName();
#endif
str = JS_NewStringCopyZ(cx, pdbName);
str = JS_NewStringCopyZ(cx, module.mPdbName.c_str());
if (!str) {
return NS_ERROR_FAILURE;
}
@ -1157,7 +1145,6 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, jsval *ret)
return NS_ERROR_FAILURE;
}
}
#endif
}
return NS_OK;
@ -1424,8 +1411,7 @@ TelemetryImpl::RecordSlowStatement(const nsACString &sql,
#if defined(MOZ_ENABLE_PROFILER_SPS)
void
TelemetryImpl::RecordChromeHang(uint32_t duration,
const Telemetry::HangStack &callStack,
SharedLibraryInfo &moduleMap)
Telemetry::ProcessedStack &aStack)
{
MOZ_ASSERT(sTelemetry);
if (!sTelemetry->mCanRecord) {
@ -1436,17 +1422,19 @@ TelemetryImpl::RecordChromeHang(uint32_t duration,
// Only report the modules which changed since the first hang report
if (sTelemetry->mHangReports.Length()) {
SharedLibraryInfo &firstModuleMap =
sTelemetry->mHangReports[0].moduleMap;
for (size_t i = 0; i < moduleMap.GetSize(); ++i) {
if (firstModuleMap.Contains(moduleMap.GetEntry(i))) {
moduleMap.RemoveEntries(i, i + 1);
Telemetry::ProcessedStack &firstStack =
sTelemetry->mHangReports[0].mStack;
const uint32_t moduleCount = aStack.GetNumModules();
for (size_t i = 0; i < moduleCount; ++i) {
const Telemetry::ProcessedStack::Module &module = aStack.GetModule(i);
if (firstStack.HasModule(module)) {
aStack.RemoveModule(i);
--i;
}
}
}
HangReport newReport = { duration, callStack, moduleMap };
HangReport newReport = { duration, aStack };
sTelemetry->mHangReports.AppendElement(newReport);
}
#endif
@ -1534,13 +1522,195 @@ void Init()
#if defined(MOZ_ENABLE_PROFILER_SPS)
void RecordChromeHang(uint32_t duration,
const Telemetry::HangStack &callStack,
SharedLibraryInfo &moduleMap)
ProcessedStack &aStack)
{
TelemetryImpl::RecordChromeHang(duration, callStack, moduleMap);
TelemetryImpl::RecordChromeHang(duration, aStack);
}
#endif
ProcessedStack::ProcessedStack()
{
}
size_t ProcessedStack::GetStackSize() const
{
return mStack.size();
}
const ProcessedStack::Frame &ProcessedStack::GetFrame(unsigned aIndex) const
{
MOZ_ASSERT(aIndex < mStack.size());
return mStack[aIndex];
}
void ProcessedStack::AddFrame(const Frame &aFrame)
{
mStack.push_back(aFrame);
}
size_t ProcessedStack::GetNumModules() const
{
return mModules.size();
}
const ProcessedStack::Module &ProcessedStack::GetModule(unsigned aIndex) const
{
MOZ_ASSERT(aIndex < mModules.size());
return mModules[aIndex];
}
bool ProcessedStack::HasModule(const Module &aModule) const {
return mModules.end() !=
std::find(mModules.begin(), mModules.end(), aModule);
}
void ProcessedStack::RemoveModule(unsigned aIndex) {
mModules.erase(mModules.begin() + aIndex);
}
void ProcessedStack::AddModule(const Module &aModule)
{
mModules.push_back(aModule);
}
void ProcessedStack::Clear() {
mModules.clear();
mStack.clear();
}
bool ProcessedStack::Module::operator==(const Module& aOther) const {
return mName == aOther.mName &&
mStart == aOther.mStart &&
mMappingSize == aOther.mMappingSize &&
mPdbAge == aOther.mPdbAge &&
mPdbSignature == aOther.mPdbSignature &&
mPdbName == aOther.mPdbName;
}
struct StackFrame
{
uintptr_t mPC; // The program counter at this position in the call stack.
uint16_t mIndex; // The number of this frame in the call stack.
uint16_t mModIndex; // The index of module that has this program counter.
};
#ifdef MOZ_ENABLE_PROFILER_SPS
static bool CompareByPC(const StackFrame &a, const StackFrame &b)
{
return a.mPC < b.mPC;
}
static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
{
return a.mIndex < b.mIndex;
}
#endif
ProcessedStack GetStackAndModules(const std::vector<uintptr_t> &aPCs, bool aRelative)
{
std::vector<StackFrame> rawStack;
for (std::vector<uintptr_t>::const_iterator i = aPCs.begin(),
e = aPCs.end(); i != e; ++i) {
uintptr_t aPC = *i;
StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
std::numeric_limits<uint16_t>::max()};
rawStack.push_back(Frame);
}
#ifdef MOZ_ENABLE_PROFILER_SPS
// Remove all modules not referenced by a PC on the stack
std::sort(rawStack.begin(), rawStack.end(), CompareByPC);
size_t moduleIndex = 0;
size_t stackIndex = 0;
size_t stackSize = rawStack.size();
SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
rawModules.SortByAddress();
while (moduleIndex < rawModules.GetSize()) {
const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
uintptr_t moduleStart = module.GetStart();
uintptr_t moduleEnd = module.GetEnd() - 1;
// the interval is [moduleStart, moduleEnd)
bool moduleReferenced = false;
for (;stackIndex < stackSize; ++stackIndex) {
uintptr_t pc = rawStack[stackIndex].mPC;
if (pc >= moduleEnd)
break;
if (pc >= moduleStart) {
// If the current PC is within the current module, mark
// module as used
moduleReferenced = true;
if (aRelative)
rawStack[stackIndex].mPC -= moduleStart;
rawStack[stackIndex].mModIndex = moduleIndex;
} else {
// PC does not belong to any module. It is probably from
// the JIT. Use a fixed mPC so that we don't get different
// stacks on different runs.
rawStack[stackIndex].mPC =
std::numeric_limits<uintptr_t>::max();
}
}
if (moduleReferenced) {
++moduleIndex;
} else {
// Remove module if no PCs within its address range
rawModules.RemoveEntries(moduleIndex, moduleIndex + 1);
}
}
for (;stackIndex < stackSize; ++stackIndex) {
// These PCs are past the last module.
rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
}
std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
#endif
// Copy the information to the return value.
ProcessedStack Ret;
for (std::vector<StackFrame>::iterator i = rawStack.begin(),
e = rawStack.end(); i != e; ++i) {
const StackFrame &rawFrame = *i;
ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex };
Ret.AddFrame(frame);
}
#ifdef MOZ_ENABLE_PROFILER_SPS
for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
const SharedLibrary &info = rawModules.GetEntry(i);
ProcessedStack::Module module = {
info.GetName(),
info.GetStart(),
info.GetEnd() - info.GetStart(),
#ifdef XP_WIN
info.GetPdbAge(),
"", // mPdbSignature
info.GetPdbName(),
#else
0, // mPdbAge
"", // mPdbSignature
"" // mPdbName
#endif
};
#ifdef XP_WIN
char guidString[NSID_LENGTH] = { 0 };
info.GetPdbSignature().ToProvidedString(guidString);
module.mPdbSignature = guidString;
#endif
Ret.AddModule(module);
}
#endif
return Ret;
}
} // namespace Telemetry
} // namespace mozilla

View File

@ -127,10 +127,7 @@ void RecordSlowSQLStatement(const nsACString &statement,
*/
const PRUint32 kSlowStatementThreshold = 100;
/**
* nsTArray of pointers representing PCs on a call stack
*/
typedef nsTArray<uintptr_t> HangStack;
class ProcessedStack;
/**
* Record the main thread's call stack after it hangs.
@ -141,8 +138,7 @@ typedef nsTArray<uintptr_t> HangStack;
*/
#if defined(MOZ_ENABLE_PROFILER_SPS)
void RecordChromeHang(PRUint32 duration,
const HangStack &callStack,
SharedLibraryInfo &moduleMap);
ProcessedStack &aStack);
#endif
} // namespace Telemetry

View File

@ -59,7 +59,7 @@ nsProfiler::GetProfile(char **aProfile)
}
static void
AddSharedLibraryInfoToStream(std::ostream& aStream, SharedLibrary& aLib)
AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib)
{
aStream << "{";
aStream << "\"start\":" << aLib.GetStart();

View File

@ -140,7 +140,7 @@ public:
mEntries.push_back(entry);
}
SharedLibrary& GetEntry(size_t i)
const SharedLibrary& GetEntry(size_t i) const
{
return mEntries[i];
}

View File

@ -11,6 +11,7 @@
#include "mozilla/Scoped.h"
#include "mozilla/Mutex.h"
#include "mozilla/Telemetry.h"
#include "mozilla/ProcessedStack.h"
#include "nsStackWalk.h"
#include "nsPrintfCString.h"
#include "mach_override.h"
@ -38,131 +39,11 @@ struct FuncData {
// 'Function' after it has been replaced.
};
// FIXME: duplicated code. The HangMonitor could also report processed addresses,
// this class should be moved somewhere it can be shared.
class ProcessedStack
{
public:
ProcessedStack() : mProcessed(false)
{
}
void Reserve(unsigned int n)
{
mStack.reserve(n);
}
size_t GetStackSize()
{
return mStack.size();
}
struct ProcessedStackFrame
{
uintptr_t mOffset;
uint16_t mModIndex;
};
ProcessedStackFrame GetFrame(unsigned aIndex)
{
const StackFrame &Frame = mStack[aIndex];
ProcessedStackFrame Ret = { Frame.mPC, Frame.mModIndex };
return Ret;
}
size_t GetNumModules()
{
MOZ_ASSERT(mProcessed);
return mModules.GetSize();
}
const char *GetModuleName(unsigned aIndex)
{
MOZ_ASSERT(mProcessed);
return mModules.GetEntry(aIndex).GetName();
}
void AddStackFrame(uintptr_t aPC)
{
MOZ_ASSERT(!mProcessed);
StackFrame Frame = {aPC, static_cast<uint16_t>(mStack.size()),
std::numeric_limits<uint16_t>::max()};
mStack.push_back(Frame);
}
void Process()
{
mProcessed = true;
mModules = SharedLibraryInfo::GetInfoForSelf();
mModules.SortByAddress();
// Remove all modules not referenced by a PC on the stack
std::sort(mStack.begin(), mStack.end(), CompareByPC);
size_t moduleIndex = 0;
size_t stackIndex = 0;
size_t stackSize = mStack.size();
while (moduleIndex < mModules.GetSize()) {
SharedLibrary& module = mModules.GetEntry(moduleIndex);
uintptr_t moduleStart = module.GetStart();
uintptr_t moduleEnd = module.GetEnd() - 1;
// the interval is [moduleStart, moduleEnd)
bool moduleReferenced = false;
for (;stackIndex < stackSize; ++stackIndex) {
uintptr_t pc = mStack[stackIndex].mPC;
if (pc >= moduleEnd)
break;
if (pc >= moduleStart) {
// If the current PC is within the current module, mark
// module as used
moduleReferenced = true;
mStack[stackIndex].mPC -= moduleStart;
mStack[stackIndex].mModIndex = moduleIndex;
} else {
// PC does not belong to any module. It is probably from
// the JIT. Use a fixed mPC so that we don't get different
// stacks on different runs.
mStack[stackIndex].mPC =
std::numeric_limits<uintptr_t>::max();
}
}
if (moduleReferenced) {
++moduleIndex;
} else {
// Remove module if no PCs within its address range
mModules.RemoveEntries(moduleIndex, moduleIndex + 1);
}
}
for (;stackIndex < stackSize; ++stackIndex) {
// These PCs are past the last module.
mStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
}
std::sort(mStack.begin(), mStack.end(), CompareByIndex);
}
private:
struct StackFrame
{
uintptr_t mPC; // The program counter at this position in the call stack.
uint16_t mIndex; // The number of this frame in the call stack.
uint16_t mModIndex; // The index of module that has this program counter.
};
static bool CompareByPC(const StackFrame &a, const StackFrame &b)
{
return a.mPC < b.mPC;
}
static bool CompareByIndex(const StackFrame &a, const StackFrame &b)
{
return a.mIndex < b.mIndex;
}
SharedLibraryInfo mModules;
std::vector<StackFrame> mStack;
bool mProcessed;
};
void RecordStackWalker(void *aPC, void *aSP, void *aClosure)
{
ProcessedStack *stack = static_cast<ProcessedStack*>(aClosure);
stack->AddStackFrame(reinterpret_cast<uintptr_t>(aPC));
std::vector<uintptr_t> *stack =
static_cast<std::vector<uintptr_t>*>(aClosure);
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
}
char *sProfileDirectory = NULL;
@ -177,9 +58,10 @@ bool ValidWriteAssert(bool ok)
// Write the stack and loaded libraries to a file. We can get here
// concurrently from many writes, so we use multiple temporary files.
ProcessedStack stack;
NS_StackWalk(RecordStackWalker, 0, reinterpret_cast<void*>(&stack), 0);
stack.Process();
std::vector<uintptr_t> rawStack;
NS_StackWalk(RecordStackWalker, 0, reinterpret_cast<void*>(&rawStack), 0);
Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack, true);
nsPrintfCString nameAux("%s%s", sProfileDirectory,
"/Telemetry.LateWriteTmpXXXXXX");
@ -192,14 +74,15 @@ bool ValidWriteAssert(bool ok)
size_t numModules = stack.GetNumModules();
fprintf(f, "%zu\n", numModules);
for (int i = 0; i < numModules; ++i) {
const char *name = stack.GetModuleName(i);
fprintf(f, "%s\n", name ? name : "");
Telemetry::ProcessedStack::Module module = stack.GetModule(i);
fprintf(f, "%s\n", module.mName.c_str());
}
size_t numFrames = stack.GetStackSize();
fprintf(f, "%zu\n", numFrames);
for (size_t i = 0; i < numFrames; ++i) {
const ProcessedStack::ProcessedStackFrame &frame = stack.GetFrame(i);
const Telemetry::ProcessedStack::Frame &frame =
stack.GetFrame(i);
// NOTE: We write the offsets, while the atos tool expects a value with
// the virtual address added. For example, running otool -l on the the firefox
// binary shows

View File

@ -55,7 +55,7 @@ private:
const char* aFromRawSegment, PRUint32 aToOffset,
PRUint32 aCount, PRUint32 *aWriteCount);
nsCOMArray<nsIInputStream> mStreams;
nsTArray<nsCOMPtr<nsIInputStream> > mStreams;
PRUint32 mCurrentStream;
bool mStartedReadingCurrent;
nsresult mStatus;
@ -89,7 +89,7 @@ nsMultiplexInputStream::nsMultiplexInputStream()
NS_IMETHODIMP
nsMultiplexInputStream::GetCount(PRUint32 *aCount)
{
*aCount = mStreams.Count();
*aCount = mStreams.Length();
return NS_OK;
}
@ -97,14 +97,14 @@ nsMultiplexInputStream::GetCount(PRUint32 *aCount)
NS_IMETHODIMP
nsMultiplexInputStream::AppendStream(nsIInputStream *aStream)
{
return mStreams.AppendObject(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
return mStreams.AppendElement(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
/* void insertStream (in nsIInputStream stream, in unsigned long index); */
NS_IMETHODIMP
nsMultiplexInputStream::InsertStream(nsIInputStream *aStream, PRUint32 aIndex)
{
bool result = mStreams.InsertObjectAt(aStream, aIndex);
bool result = mStreams.InsertElementAt(aIndex, aStream);
NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
if (mCurrentStream > aIndex ||
(mCurrentStream == aIndex && mStartedReadingCurrent))
@ -116,8 +116,7 @@ nsMultiplexInputStream::InsertStream(nsIInputStream *aStream, PRUint32 aIndex)
NS_IMETHODIMP
nsMultiplexInputStream::RemoveStream(PRUint32 aIndex)
{
bool result = mStreams.RemoveObjectAt(aIndex);
NS_ENSURE_TRUE(result, NS_ERROR_NOT_AVAILABLE);
mStreams.RemoveElementAt(aIndex);
if (mCurrentStream > aIndex)
--mCurrentStream;
else if (mCurrentStream == aIndex)
@ -130,7 +129,7 @@ nsMultiplexInputStream::RemoveStream(PRUint32 aIndex)
NS_IMETHODIMP
nsMultiplexInputStream::GetStream(PRUint32 aIndex, nsIInputStream **_retval)
{
*_retval = mStreams.SafeObjectAt(aIndex);
*_retval = mStreams.SafeElementAt(aIndex, nullptr);
NS_ENSURE_TRUE(*_retval, NS_ERROR_NOT_AVAILABLE);
NS_ADDREF(*_retval);
@ -145,7 +144,7 @@ nsMultiplexInputStream::Close()
nsresult rv = NS_OK;
PRUint32 len = mStreams.Count();
PRUint32 len = mStreams.Length();
for (PRUint32 i = 0; i < len; ++i) {
nsresult rv2 = mStreams[i]->Close();
// We still want to close all streams, but we should return an error
@ -165,7 +164,7 @@ nsMultiplexInputStream::Available(PRUint64 *_retval)
nsresult rv;
PRUint64 avail = 0;
PRUint32 len = mStreams.Count();
PRUint32 len = mStreams.Length();
for (PRUint32 i = mCurrentStream; i < len; i++) {
PRUint64 streamAvail;
rv = mStreams[i]->Available(&streamAvail);
@ -193,7 +192,7 @@ nsMultiplexInputStream::Read(char * aBuf, PRUint32 aCount, PRUint32 *_retval)
nsresult rv = NS_OK;
PRUint32 len = mStreams.Count();
PRUint32 len = mStreams.Length();
while (mCurrentStream < len && aCount) {
PRUint32 read;
rv = mStreams[mCurrentStream]->Read(aBuf, aCount, &read);
@ -247,7 +246,7 @@ nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
state.mClosure = aClosure;
state.mDone = false;
PRUint32 len = mStreams.Count();
PRUint32 len = mStreams.Length();
while (mCurrentStream < len && aCount) {
PRUint32 read;
rv = mStreams[mCurrentStream]->ReadSegments(ReadSegCb, &state, aCount, &read);
@ -305,7 +304,7 @@ nsMultiplexInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
NS_IMETHODIMP
nsMultiplexInputStream::IsNonBlocking(bool *aNonBlocking)
{
PRUint32 len = mStreams.Count();
PRUint32 len = mStreams.Length();
if (len == 0) {
// Claim to be non-blocking, since we won't block the caller.
// On the other hand we'll never return NS_BASE_STREAM_WOULD_BLOCK,
@ -335,20 +334,208 @@ nsMultiplexInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
nsresult rv;
// rewinding to start is easy, and should be the most common case
if (aWhence == NS_SEEK_SET && aOffset == 0)
{
PRUint32 i, last;
last = mStartedReadingCurrent ? mCurrentStream+1 : mCurrentStream;
for (i = 0; i < last; ++i) {
nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStreams[i]);
NS_ENSURE_TRUE(stream, NS_ERROR_NO_INTERFACE);
PRUint32 oldCurrentStream = mCurrentStream;
bool oldStartedReadingCurrent = mStartedReadingCurrent;
rv = stream->Seek(NS_SEEK_SET, 0);
NS_ENSURE_SUCCESS(rv, rv);
if (aWhence == NS_SEEK_SET) {
PRInt64 remaining = aOffset;
if (aOffset == 0) {
mCurrentStream = 0;
}
mCurrentStream = 0;
mStartedReadingCurrent = false;
for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
nsCOMPtr<nsISeekableStream> stream =
do_QueryInterface(mStreams[i]);
if (!stream) {
return NS_ERROR_FAILURE;
}
// See if all remaining streams should be rewound
if (remaining == 0) {
if (i < oldCurrentStream ||
(i == oldCurrentStream && oldStartedReadingCurrent)) {
rv = stream->Seek(NS_SEEK_SET, 0);
NS_ENSURE_SUCCESS(rv, rv);
continue;
}
else {
break;
}
}
// Get position in current stream
PRInt64 streamPos;
if (i > oldCurrentStream ||
(i == oldCurrentStream && !oldStartedReadingCurrent)) {
streamPos = 0;
}
else {
rv = stream->Tell(&streamPos);
NS_ENSURE_SUCCESS(rv, rv);
}
// See if we need to seek current stream forward or backward
if (remaining < streamPos) {
rv = stream->Seek(NS_SEEK_SET, remaining);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentStream = i;
mStartedReadingCurrent = remaining != 0;
remaining = 0;
}
else if (remaining > streamPos) {
if (i < oldCurrentStream) {
// We're already at end so no need to seek this stream
remaining -= streamPos;
}
else {
PRUint64 avail;
rv = mStreams[i]->Available(&avail);
NS_ENSURE_SUCCESS(rv, rv);
PRInt64 newPos = streamPos +
NS_MIN((PRInt64)avail, remaining);
rv = stream->Seek(NS_SEEK_SET, newPos);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentStream = i;
mStartedReadingCurrent = true;
remaining -= newPos;
}
}
else {
NS_ASSERTION(remaining == streamPos, "Huh?");
remaining = 0;
}
}
return NS_OK;
}
if (aWhence == NS_SEEK_CUR && aOffset > 0) {
PRInt64 remaining = aOffset;
for (PRUint32 i = mCurrentStream; remaining && i < mStreams.Length(); ++i) {
nsCOMPtr<nsISeekableStream> stream =
do_QueryInterface(mStreams[i]);
PRUint64 avail;
rv = mStreams[i]->Available(&avail);
NS_ENSURE_SUCCESS(rv, rv);
PRInt64 seek = NS_MIN((PRInt64)avail, remaining);
rv = stream->Seek(NS_SEEK_CUR, seek);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentStream = i;
mStartedReadingCurrent = true;
remaining -= seek;
}
return NS_OK;
}
if (aWhence == NS_SEEK_CUR && aOffset < 0) {
PRInt64 remaining = -aOffset;
for (PRUint32 i = mCurrentStream; remaining && i != (PRUint32)-1; --i) {
nsCOMPtr<nsISeekableStream> stream =
do_QueryInterface(mStreams[i]);
PRInt64 pos;
rv = stream->Tell(&pos);
NS_ENSURE_SUCCESS(rv, rv);
PRInt64 seek = NS_MIN(pos, remaining);
rv = stream->Seek(NS_SEEK_CUR, -seek);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentStream = i;
mStartedReadingCurrent = seek != -pos;
remaining -= seek;
}
return NS_OK;
}
if (aWhence == NS_SEEK_CUR) {
NS_ASSERTION(aOffset == 0, "Should have handled all non-zero values");
return NS_OK;
}
if (aWhence == NS_SEEK_END) {
if (aOffset > 0) {
return NS_ERROR_INVALID_ARG;
}
PRInt64 remaining = aOffset;
for (PRUint32 i = mStreams.Length() - 1; i != (PRUint32)-1; --i) {
nsCOMPtr<nsISeekableStream> stream =
do_QueryInterface(mStreams[i]);
// See if all remaining streams should be seeked to end
if (remaining == 0) {
if (i >= oldCurrentStream) {
rv = stream->Seek(NS_SEEK_END, 0);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
break;
}
}
// Get position in current stream
PRInt64 streamPos;
if (i < oldCurrentStream) {
streamPos = 0;
} else {
PRUint64 avail;
rv = mStreams[i]->Available(&avail);
NS_ENSURE_SUCCESS(rv, rv);
streamPos = avail;
}
// See if we have enough data in the current stream.
if (NS_ABS(remaining) < streamPos) {
rv = stream->Seek(NS_SEEK_END, remaining);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentStream = i;
mStartedReadingCurrent = true;
remaining = 0;
} else if (NS_ABS(remaining) > streamPos) {
if (i > oldCurrentStream ||
(i == oldCurrentStream && !oldStartedReadingCurrent)) {
// We're already at start so no need to seek this stream
remaining += streamPos;
} else {
PRInt64 avail;
rv = stream->Tell(&avail);
NS_ENSURE_SUCCESS(rv, rv);
PRInt64 newPos = streamPos + NS_MIN(avail, NS_ABS(remaining));
rv = stream->Seek(NS_SEEK_END, -newPos);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentStream = i;
mStartedReadingCurrent = true;
remaining += newPos;
}
}
else {
NS_ASSERTION(remaining == streamPos, "Huh?");
remaining = 0;
}
}
return NS_OK;
}
@ -442,11 +629,11 @@ nsMultiplexInputStream::Write(IPC::Message *aMsg)
{
using IPC::WriteParam;
PRUint32 count = mStreams.Count();
PRUint32 count = mStreams.Length();
WriteParam(aMsg, count);
for (PRUint32 i = 0; i < count; i++) {
IPC::InputStream inputStream(mStreams.ObjectAt(i));
IPC::InputStream inputStream(mStreams[i]);
WriteParam(aMsg, inputStream);
}
@ -460,7 +647,7 @@ nsMultiplexInputStream::Serialize(InputStreamParams& aParams)
{
MultiplexInputStreamParams params;
PRUint32 streamCount = mStreams.Count();
PRUint32 streamCount = mStreams.Length();
if (streamCount) {
InfallibleTArray<InputStreamParams>& streams = params.streams();
@ -468,7 +655,7 @@ nsMultiplexInputStream::Serialize(InputStreamParams& aParams)
streams.SetCapacity(streamCount);
for (PRUint32 index = 0; index < streamCount; index++) {
nsCOMPtr<nsIIPCSerializableInputStream> serializable =
do_QueryInterface(mStreams.ObjectAt(index));
do_QueryInterface(mStreams[index]);
NS_ASSERTION(serializable, "Child stream isn't serializable!");
if (serializable) {

View File

@ -0,0 +1,135 @@
/* 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/. */
const Ci = Components.interfaces;
const Cr = Components.results;
const CC = Components.Constructor;
const Cc = Components.classes;
// The string we use as data.
const data = "0123456789";
// Number of streams in the multiplex stream.
const count = 10;
function test_multiplex_streams() {
try {
var MultiplexStream = CC("@mozilla.org/io/multiplex-input-stream;1",
"nsIMultiplexInputStream");
do_check_eq(1, 1);
var BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream");
var BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream");
var multiplex = new MultiplexStream();
for (var i = 0; i < count; ++i) {
let s = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
s.setData(data, data.length);
multiplex.appendStream(s);
}
var seekable = multiplex.QueryInterface(Ci.nsISeekableStream);
var sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
sis.init(seekable);
// Read some data.
var readData = sis.read(20);
do_check_eq(readData, data + data);
// -- Tests for NS_SEEK_SET
// Seek accross stream.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 35);
do_check_eq(seekable.tell(), 35);
do_check_eq(seekable.available(), 65);
readData = sis.read(5);
do_check_eq(readData, data.slice(5));
do_check_eq(seekable.available(), 60);
// Seek at stream boundaries.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 40);
do_check_eq(seekable.tell(), 40);
do_check_eq(seekable.available(), 60);
readData = sis.read(10);
do_check_eq(readData, data);
do_check_eq(seekable.tell(), 50);
do_check_eq(seekable.available(), 50);
// Rewind and read accross streams.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 39);
do_check_eq(seekable.tell(), 39);
do_check_eq(seekable.available(), 61);
readData = sis.read(11);
do_check_eq(readData, data.slice(9) + data);
do_check_eq(seekable.tell(), 50);
do_check_eq(seekable.available(), 50);
// Rewind to the beginning.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
do_check_eq(seekable.tell(), 0);
do_check_eq(seekable.available(), 100);
// Seek to some randome location
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 50);
// -- Tests for NS_SEEK_CUR
// Positive seek.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 15);
do_check_eq(seekable.tell(), 65);
do_check_eq(seekable.available(), 35);
readData = sis.read(10);
do_check_eq(readData, data.slice(5) + data.slice(0, 5));
do_check_eq(seekable.tell(), 75);
do_check_eq(seekable.available(), 25);
// Negative seek.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15);
do_check_eq(seekable.tell(), 60);
do_check_eq(seekable.available(), 40);
readData = sis.read(10);
do_check_eq(readData, data);
do_check_eq(seekable.tell(), 70);
do_check_eq(seekable.available(), 30);
// -- Tests for NS_SEEK_END
// Normal read.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -5);
do_check_eq(seekable.tell(), data.length * count - 5);
readData = sis.read(5);
do_check_eq(readData, data.slice(5));
do_check_eq(seekable.tell(), data.length * count);
// Read accros streams.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -15);
do_check_eq(seekable.tell(), data.length * count - 15);
readData = sis.read(15);
do_check_eq(readData, data.slice(5) + data);
do_check_eq(seekable.tell(), data.length * count);
// -- Try to do various edge cases
// Forward seek from the end, should throw.
var caught = false;
try {
seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, 15);
} catch(e) {
caught = true;
}
do_check_eq(caught, true);
do_check_eq(seekable.tell(), data.length * count);
// Backward seek from the beginning, should be clamped.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
do_check_eq(seekable.tell(), 0);
seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15);
do_check_eq(seekable.tell(), 0);
// Seek too far: shoul be clamped.
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
do_check_eq(seekable.tell(), 0);
seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 3 * data.length * count);
do_check_eq(seekable.tell(), 100);
seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, data.length * count);
do_check_eq(seekable.tell(), 100);
seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -2 * data.length * count);
do_check_eq(seekable.tell(), 0);
} catch(e) {
dump(e + "\n");
}
}
function run_test() {
test_multiplex_streams();
}

View File

@ -35,6 +35,7 @@ skip-if = os == "win" # See bug: 676412
[test_pipe.js]
[test_storagestream.js]
[test_streams.js]
[test_seek_multiplex.js]
[test_stringstream.js]
[test_symlinks.js]
# Bug 676998: test fails consistently on Android

View File

@ -7,6 +7,7 @@
#include "mozilla/Monitor.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/ProcessedStack.h"
#include "nsXULAppAPI.h"
#include "nsThreadUtils.h"
#include "nsStackWalk.h"
@ -112,75 +113,32 @@ static void
ChromeStackWalker(void *aPC, void *aSP, void *aClosure)
{
MOZ_ASSERT(aClosure);
Telemetry::HangStack *callStack =
reinterpret_cast< Telemetry::HangStack* >(aClosure);
if (callStack->Length() < MAX_CALL_STACK_PCS)
callStack->AppendElement(reinterpret_cast<uintptr_t>(aPC));
std::vector<uintptr_t> *stack =
static_cast<std::vector<uintptr_t>*>(aClosure);
if (stack->size() == MAX_CALL_STACK_PCS)
return;
MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS);
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
}
static void
GetChromeHangReport(Telemetry::HangStack &callStack, SharedLibraryInfo &moduleMap)
GetChromeHangReport(Telemetry::ProcessedStack &aStack)
{
MOZ_ASSERT(winMainThreadHandle);
// The thread we're about to suspend might have the alloc lock
// so allocate ahead of time
callStack.SetCapacity(MAX_CALL_STACK_PCS);
std::vector<uintptr_t> rawStack;
rawStack.reserve(MAX_CALL_STACK_PCS);
DWORD ret = ::SuspendThread(winMainThreadHandle);
if (ret == -1) {
callStack.Clear();
moduleMap.Clear();
if (ret == -1)
return;
}
NS_StackWalk(ChromeStackWalker, 0, &callStack,
NS_StackWalk(ChromeStackWalker, 0, reinterpret_cast<void*>(&rawStack),
reinterpret_cast<uintptr_t>(winMainThreadHandle));
ret = ::ResumeThread(winMainThreadHandle);
if (ret == -1) {
callStack.Clear();
moduleMap.Clear();
if (ret == -1)
return;
}
moduleMap = SharedLibraryInfo::GetInfoForSelf();
moduleMap.SortByAddress();
// Remove all modules not referenced by a PC on the stack
Telemetry::HangStack sortedStack = callStack;
sortedStack.Sort();
size_t moduleIndex = 0;
size_t stackIndex = 0;
bool unreferencedModule = true;
while (stackIndex < sortedStack.Length() && moduleIndex < moduleMap.GetSize()) {
uintptr_t pc = sortedStack[stackIndex];
SharedLibrary& module = moduleMap.GetEntry(moduleIndex);
uintptr_t moduleStart = module.GetStart();
uintptr_t moduleEnd = module.GetEnd() - 1;
if (moduleStart <= pc && pc <= moduleEnd) {
// If the current PC is within the current module, mark module as used
unreferencedModule = false;
++stackIndex;
} else if (pc > moduleEnd) {
if (unreferencedModule) {
// Remove module if no PCs within its address range
moduleMap.RemoveEntries(moduleIndex, moduleIndex + 1);
} else {
// Module was referenced on stack, but current PC belongs to later module
unreferencedModule = true;
++moduleIndex;
}
} else {
// PC does not belong to any module
++stackIndex;
}
}
// Clean up remaining unreferenced modules, i.e. module addresses > max(pc)
if (moduleIndex + 1 < moduleMap.GetSize()) {
moduleMap.RemoveEntries(moduleIndex + 1, moduleMap.GetSize());
}
aStack = Telemetry::GetStackAndModules(rawStack, false);
}
#endif
@ -198,8 +156,7 @@ ThreadMain(void*)
int waitCount = 0;
#ifdef REPORT_CHROME_HANGS
Telemetry::HangStack hangStack;
SharedLibraryInfo hangModuleMap;
Telemetry::ProcessedStack stack;
#endif
while (true) {
@ -224,7 +181,7 @@ ThreadMain(void*)
++waitCount;
if (waitCount == 2) {
#ifdef REPORT_CHROME_HANGS
GetChromeHangReport(hangStack, hangModuleMap);
GetChromeHangReport(stack);
#else
PRInt32 delay =
PRInt32(PR_IntervalToSeconds(now - timestamp));
@ -239,9 +196,8 @@ ThreadMain(void*)
#ifdef REPORT_CHROME_HANGS
if (waitCount >= 2) {
PRUint32 hangDuration = PR_IntervalToSeconds(now - lastTimestamp);
Telemetry::RecordChromeHang(hangDuration, hangStack, hangModuleMap);
hangStack.Clear();
hangModuleMap.Clear();
Telemetry::RecordChromeHang(hangDuration, stack);
stack.Clear();
}
#endif
lastTimestamp = timestamp;