mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge last PGO-green changeset of mozilla-inbound to mozilla-central
This commit is contained in:
commit
a227f14256
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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')
|
||||
});
|
||||
|
@ -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
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -42,6 +42,7 @@ nsReferencedElement.h \
|
||||
nsTreeSanitizer.h \
|
||||
nsXMLNameSpaceMap.h \
|
||||
nsIXFormsUtilityService.h \
|
||||
nsBlobProtocolHandler.h \
|
||||
$(NULL)
|
||||
|
||||
EXPORTS_NAMESPACES = mozilla/dom mozilla
|
||||
|
51
content/base/public/nsBlobProtocolHandler.h
Normal file
51
content/base/public/nsBlobProtocolHandler.h
Normal 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 */
|
@ -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);
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -1618,6 +1622,13 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
|
||||
} else {
|
||||
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) {
|
||||
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
|
@ -19,6 +19,7 @@ XPIDLSRCS = \
|
||||
nsIDOMMobileConnection.idl \
|
||||
nsIMobileConnectionProvider.idl \
|
||||
nsIDOMUSSDReceivedEvent.idl \
|
||||
nsIDOMTCPSocket.idl \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
219
dom/network/interfaces/nsIDOMTCPSocket.idl
Normal file
219
dom/network/interfaces/nsIDOMTCPSocket.idl
Normal 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;
|
||||
};
|
||||
|
@ -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
|
||||
|
556
dom/network/src/TCPSocket.js
Normal file
556
dom/network/src/TCPSocket.js
Normal 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]);
|
4
dom/network/src/TCPSocket.manifest
Normal file
4
dom/network/src/TCPSocket.manifest
Normal 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
|
@ -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
|
||||
|
27
dom/network/tests/test_tcpsocket_default_permissions.html
Normal file
27
dom/network/tests/test_tcpsocket_default_permissions.html
Normal 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>
|
35
dom/network/tests/test_tcpsocket_enabled_no_perm.html
Normal file
35
dom/network/tests/test_tcpsocket_enabled_no_perm.html
Normal 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>
|
31
dom/network/tests/test_tcpsocket_enabled_with_perm.html
Normal file
31
dom/network/tests/test_tcpsocket_enabled_with_perm.html
Normal 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>
|
479
dom/network/tests/unit/test_tcpsocket.js
Normal file
479
dom/network/tests/unit/test_tcpsocket.js
Normal 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();
|
||||
}
|
5
dom/network/tests/unit/xpcshell.ini
Normal file
5
dom/network/tests/unit/xpcshell.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_tcpsocket.js]
|
@ -533,7 +533,8 @@ var interfaceNamesInGlobalScope =
|
||||
"CameraCapabilities",
|
||||
"CameraManager",
|
||||
"CSSSupportsRule",
|
||||
"MozMobileCellInfo"
|
||||
"MozMobileCellInfo",
|
||||
"TCPSocket"
|
||||
]
|
||||
|
||||
for (var i in Components.interfaces) {
|
||||
|
@ -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, ¤tPackAlignment);
|
||||
|
||||
@ -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)
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -154,6 +154,10 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool IsCurrent() {
|
||||
return [NSOpenGLContext currentContext] == mContext;
|
||||
}
|
||||
|
||||
bool SetupLookupFunction()
|
||||
{
|
||||
return false;
|
||||
|
@ -507,6 +507,10 @@ public:
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
virtual bool IsCurrent() {
|
||||
return sEGLLibrary.fGetCurrentContext() == mContext;
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_QT
|
||||
virtual bool
|
||||
RenewSurface() {
|
||||
|
@ -851,6 +851,10 @@ TRY_AGAIN_NO_SHARING:
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
virtual bool IsCurrent() {
|
||||
return sGLXLibrary.xGetCurrentContext() == mContext;
|
||||
}
|
||||
|
||||
bool SetupLookupFunction()
|
||||
{
|
||||
mLookupFunc = (PlatformLookupFunction)&GLXLibrary::xGetProcAddress;
|
||||
|
@ -193,6 +193,10 @@ public:
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
virtual bool IsCurrent() {
|
||||
return sOSMesaLibrary.fGetCurrentContext() == mContext;
|
||||
}
|
||||
|
||||
bool SetupLookupFunction()
|
||||
{
|
||||
mLookupFunc = (PlatformLookupFunction)sOSMesaLibrary.fGetProcAddress;
|
||||
|
@ -334,6 +334,10 @@ public:
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
virtual bool IsCurrent() {
|
||||
return sWGLLib[mLibType].fGetCurrentContext() == mContext;
|
||||
}
|
||||
|
||||
void SetIsDoubleBuffered(bool aIsDB) {
|
||||
mIsDoubleBuffered = aIsDB;
|
||||
}
|
||||
|
@ -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
424
gfx/gl/GLContextUtils.cpp
Normal 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 */
|
@ -45,6 +45,7 @@ endif
|
||||
|
||||
CPPSRCS = \
|
||||
GLContext.cpp \
|
||||
GLContextUtils.cpp \
|
||||
GLLibraryLoader.cpp \
|
||||
GLContextProviderOSMesa.cpp \
|
||||
$(NULL)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -352,6 +352,9 @@
|
||||
@BINPATH@/components/AppsService.js
|
||||
@BINPATH@/components/AppsService.manifest
|
||||
|
||||
@BINPATH@/components/TCPSocket.js
|
||||
@BINPATH@/components/TCPSocket.manifest
|
||||
|
||||
; Modules
|
||||
@BINPATH@/modules/*
|
||||
|
||||
|
@ -438,6 +438,9 @@
|
||||
@BINPATH@/components/TelemetryPing.js
|
||||
@BINPATH@/components/TelemetryPing.manifest
|
||||
|
||||
@BINPATH@/components/TCPSocket.js
|
||||
@BINPATH@/components/TCPSocket.manifest
|
||||
|
||||
; Modules
|
||||
@BINPATH@/modules/*
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,14 +129,13 @@ 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()
|
||||
virtual ~nsFileInputStream()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
@ -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); }
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -25,6 +25,7 @@ EXPORTS_NAMESPACES = mozilla
|
||||
|
||||
EXPORTS_mozilla = \
|
||||
Telemetry.h \
|
||||
ProcessedStack.h \
|
||||
TelemetryHistograms.h \
|
||||
$(NULL)
|
||||
|
||||
|
79
toolkit/components/telemetry/ProcessedStack.h
Normal file
79
toolkit/components/telemetry/ProcessedStack.h
Normal 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__
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -140,7 +140,7 @@ public:
|
||||
mEntries.push_back(entry);
|
||||
}
|
||||
|
||||
SharedLibrary& GetEntry(size_t i)
|
||||
const SharedLibrary& GetEntry(size_t i) const
|
||||
{
|
||||
return mEntries[i];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
135
xpcom/tests/unit/test_seek_multiplex.js
Normal file
135
xpcom/tests/unit/test_seek_multiplex.js
Normal 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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user