Merge inbound to m-c. a=merge

CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2014-11-25 16:57:18 -05:00
commit 6a8fa342ed
98 changed files with 1502 additions and 608 deletions

View File

@ -361,14 +361,51 @@ nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus)
return NS_OK;
}
static void
ReplayImageStatus(imgIRequest* aRequest, imgINotificationObserver* aObserver)
{
if (!aRequest) {
return;
}
uint32_t status = 0;
nsresult rv = aRequest->GetImageStatus(&status);
if (NS_FAILED(rv)) {
return;
}
if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE, nullptr);
}
if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE, nullptr);
}
if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) {
aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY, nullptr);
}
if (status & imgIRequest::STATUS_IS_ANIMATED) {
aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr);
}
if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE, nullptr);
}
if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE, nullptr);
}
}
NS_IMETHODIMP
nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
{
NS_ENSURE_ARG_POINTER(aObserver);
if (!mObserverList.mObserver) {
mObserverList.mObserver = aObserver;
// Don't touch the linking of the list!
mObserverList.mObserver = aObserver;
ReplayImageStatus(mCurrentRequest, aObserver);
ReplayImageStatus(mPendingRequest, aObserver);
return NS_OK;
}
@ -384,6 +421,9 @@ nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
return NS_ERROR_OUT_OF_MEMORY;
}
ReplayImageStatus(mCurrentRequest, aObserver);
ReplayImageStatus(mPendingRequest, aObserver);
return NS_OK;
}

View File

@ -107,6 +107,23 @@ AppendToString(std::stringstream& aStream, const nsIntRect& r,
aStream << sfx;
}
void
AppendToString(std::stringstream& aStream, const nsRegion& r,
const char* pfx, const char* sfx)
{
aStream << pfx;
nsRegionRectIterator it(r);
aStream << "< ";
while (const nsRect* sr = it.Next()) {
AppendToString(aStream, *sr);
aStream << "; ";
}
aStream << ">";
aStream << sfx;
}
void
AppendToString(std::stringstream& aStream, const nsIntRegion& r,
const char* pfx, const char* sfx)

View File

@ -13,7 +13,7 @@
#include "mozilla/layers/CompositorTypes.h" // for TextureFlags
#include "nsAString.h"
#include "nsPrintfCString.h" // for nsPrintfCString
#include "nsRegion.h" // for nsIntRegion
#include "nsRegion.h" // for nsRegion, nsIntRegion
#include "nscore.h" // for nsACString, etc
struct gfxRGBA;
@ -93,6 +93,10 @@ AppendToString(std::stringstream& aStream, const mozilla::gfx::IntRectTyped<T>&
aStream << sfx;
}
void
AppendToString(std::stringstream& aStream, const nsRegion& r,
const char* pfx="", const char* sfx="");
void
AppendToString(std::stringstream& aStream, const nsIntRegion& r,
const char* pfx="", const char* sfx="");

View File

@ -9,6 +9,7 @@
#include "AsyncPanZoomController.h"
#include "gfxPrefs.h"
#include "InputBlockState.h"
#include "LayersLogging.h"
#include "OverscrollHandoffState.h"
#define INPQ_LOG(...)

View File

@ -207,13 +207,11 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
MOZ_ASSERT(tex.get());
compositable->UseTextureHost(tex);
if (IsAsync()) {
if (IsAsync() && compositable->GetLayer()) {
ScheduleComposition(op);
// Async layer updates don't trigger invalidation, manually tell the layer
// that its content have changed.
if (compositable->GetLayer()) {
compositable->GetLayer()->SetInvalidRectToVisibleRegion();
}
compositable->GetLayer()->SetInvalidRectToVisibleRegion();
}
break;
}

View File

@ -828,6 +828,18 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount,
if (mGIFStruct.disposal_method == 4) {
mGIFStruct.disposal_method = 3;
}
{
int32_t method =
FrameBlender::FrameDisposalMethod(mGIFStruct.disposal_method);
if (method == FrameBlender::kDisposeClearAll ||
method == FrameBlender::kDisposeClear) {
// We may have to display the background under this image during
// animation playback, so we regard it as transparent.
PostHasTransparency();
}
}
mGIFStruct.delay_time = GETINT16(q + 1) * 10;
GETN(1, gif_consume_block);
break;

View File

@ -59,6 +59,9 @@ nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount,
// Post our size to the superclass
PostSize(mWidth, mHeight);
PostHasTransparency();
if (HasError()) {
// Setting the size led to an error.
mState = iconStateFinished;

View File

@ -155,9 +155,17 @@ void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
// infrastructure. Just use it as long as it matches up.
nsIntRect neededRect(x_offset, y_offset, width, height);
nsRefPtr<imgFrame> currentFrame = GetCurrentFrame();
if (mNumFrames != 0 || !currentFrame->GetRect().IsEqualEdges(neededRect)) {
if (!currentFrame->GetRect().IsEqualEdges(neededRect)) {
if (mNumFrames == 0) {
// We need padding on the first frame, which means that we don't draw into
// part of the image at all. Report that as transparency.
PostHasTransparency();
}
NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
} else if (mNumFrames == 0) {
} else if (mNumFrames != 0) {
NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
} else {
// Our preallocated frame matches up, with the possible exception of alpha.
if (format == gfx::SurfaceFormat::B8G8R8X8) {
currentFrame->SetHasNoAlpha();
@ -176,6 +184,12 @@ void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
#ifdef PNG_APNG_SUPPORTED
if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
mAnimInfo = AnimFrameInfo(mPNG, mInfo);
if (mAnimInfo.mDispose == FrameBlender::kDisposeClear) {
// We may have to display the background under this image during
// animation playback, so we regard it as transparent.
PostHasTransparency();
}
}
#endif
}

View File

@ -69,7 +69,7 @@ native nsIntSizeByVal(nsIntSize);
*
* Internally, imgIContainer also manages animation of images.
*/
[scriptable, builtinclass, uuid(f8bb7671-5f36-490b-b828-3f4c6ad38665)]
[scriptable, builtinclass, uuid(14ea6fa5-183e-4409-ac88-110bd2e05292)]
interface imgIContainer : nsISupports
{
/**
@ -215,12 +215,9 @@ interface imgIContainer : nsISupports
in uint32_t aFlags);
/**
* Whether the given frame is opaque; that is, needs the background painted
* behind it.
*
* @param aWhichFrame Frame specifier of the FRAME_* variety.
* Whether this image is opaque (i.e., needs a background painted behind it).
*/
[notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame);
[notxpcom] boolean isOpaque();
/**
* Attempts to create an ImageContainer (and Image) containing the current

View File

@ -19,7 +19,7 @@ interface nsIPrincipal;
* @version 0.1
* @see imagelib2
*/
[scriptable, builtinclass, uuid(dc61f0ea-4139-4c2a-ae69-cec82d33e089)]
[scriptable, builtinclass, uuid(83a7708b-5c35-409f-bab3-7fc08be6a264)]
interface imgIRequest : nsIRequest
{
/**
@ -54,6 +54,10 @@ interface imgIRequest : nsIRequest
* completely decoded.
*
* STATUS_DECODE_COMPLETE: The whole image has been decoded.
*
* STATUS_IS_ANIMATED: The image is animated.
*
* STATUS_HAS_TRANSPARENCY: The image is partially or completely transparent.
*/
//@{
const long STATUS_NONE = 0x0;
@ -63,6 +67,8 @@ interface imgIRequest : nsIRequest
const long STATUS_DECODE_STARTED = 0x8;
const long STATUS_FRAME_COMPLETE = 0x10;
const long STATUS_DECODE_COMPLETE = 0x20;
const long STATUS_IS_ANIMATED = 0x40;
const long STATUS_HAS_TRANSPARENCY = 0x80;
//@}
/**

View File

@ -215,6 +215,15 @@ protected:
Orientation aOrientation = Orientation());
// Called by decoders if they determine that the image has transparency.
//
// This should be fired as early as possible to allow observers to do things
// that affect content, so it's necessarily pessimistic - if there's a
// possibility that the image has transparency, for example because its header
// specifies that it has an alpha channel, we fire PostHasTransparency
// immediately. PostFrameStop's aFrameAlpha argument, on the other hand, is
// only used internally to ImageLib. Because PostFrameStop isn't delivered
// until the entire frame has been decoded, decoders may take into account the
// actual contents of the frame and give a more accurate result.
void PostHasTransparency();
// Called by decoders when they begin a frame. Informs the image, sends

View File

@ -228,7 +228,7 @@ DynamicImage::GetFrame(uint32_t aWhichFrame,
}
NS_IMETHODIMP_(bool)
DynamicImage::FrameIsOpaque(uint32_t aWhichFrame)
DynamicImage::IsOpaque()
{
// XXX(seth): For performance reasons it'd be better to return true here, but
// I'm not sure how we can guarantee it for an arbitrary gfxDrawable.

View File

@ -50,12 +50,6 @@ FrozenImage::GetFrame(uint32_t aWhichFrame,
return InnerImage()->GetFrame(FRAME_FIRST, aFlags);
}
NS_IMETHODIMP_(bool)
FrozenImage::FrameIsOpaque(uint32_t aWhichFrame)
{
return InnerImage()->FrameIsOpaque(FRAME_FIRST);
}
NS_IMETHODIMP
FrozenImage::GetImageContainer(layers::LayerManager* aManager,
layers::ImageContainer** _retval)

View File

@ -38,7 +38,6 @@ public:
NS_IMETHOD GetAnimated(bool* aAnimated) MOZ_OVERRIDE;
NS_IMETHOD_(TemporaryRef<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) MOZ_OVERRIDE;
NS_IMETHOD_(bool) FrameIsOpaque(uint32_t aWhichFrame) MOZ_OVERRIDE;
NS_IMETHOD GetImageContainer(layers::LayerManager* aManager,
layers::ImageContainer** _retval) MOZ_OVERRIDE;
NS_IMETHOD Draw(gfxContext* aContext,

View File

@ -209,9 +209,9 @@ ImageWrapper::GetFrame(uint32_t aWhichFrame,
}
NS_IMETHODIMP_(bool)
ImageWrapper::FrameIsOpaque(uint32_t aWhichFrame)
ImageWrapper::IsOpaque()
{
return mInnerImage->FrameIsOpaque(aWhichFrame);
return mInnerImage->IsOpaque();
}
NS_IMETHODIMP

View File

@ -112,7 +112,7 @@ OrientedImage::GetFrame(uint32_t aWhichFrame,
// Determine an appropriate format for the surface.
gfx::SurfaceFormat surfaceFormat;
if (InnerImage()->FrameIsOpaque(aWhichFrame)) {
if (InnerImage()->IsOpaque()) {
surfaceFormat = gfx::SurfaceFormat::B8G8R8X8;
} else {
surfaceFormat = gfx::SurfaceFormat::B8G8R8A8;

View File

@ -79,7 +79,6 @@ CheckProgressConsistency(Progress aProgress)
MOZ_ASSERT(aProgress & FLAG_SIZE_AVAILABLE);
}
if (aProgress & FLAG_HAS_TRANSPARENCY) {
MOZ_ASSERT(aProgress & FLAG_DECODE_STARTED);
MOZ_ASSERT(aProgress & FLAG_SIZE_AVAILABLE);
}
if (aProgress & FLAG_IS_MULTIPART) {
@ -153,6 +152,12 @@ ProgressTracker::GetImageStatus() const
if (mProgress & FLAG_LOAD_COMPLETE) {
status |= imgIRequest::STATUS_LOAD_COMPLETE;
}
if (mProgress & FLAG_IS_ANIMATED) {
status |= imgIRequest::STATUS_IS_ANIMATED;
}
if (mProgress & FLAG_HAS_TRANSPARENCY) {
status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
}
if (mProgress & FLAG_HAS_ERROR) {
status |= imgIRequest::STATUS_ERROR;
}

View File

@ -34,8 +34,8 @@ enum {
FLAG_LOAD_COMPLETE = 1u << 4, // STATUS_LOAD_COMPLETE
FLAG_ONLOAD_BLOCKED = 1u << 5,
FLAG_ONLOAD_UNBLOCKED = 1u << 6,
FLAG_IS_ANIMATED = 1u << 7,
FLAG_HAS_TRANSPARENCY = 1u << 8,
FLAG_IS_ANIMATED = 1u << 7, // STATUS_IS_ANIMATED
FLAG_HAS_TRANSPARENCY = 1u << 8, // STATUS_HAS_TRANSPARENCY
FLAG_IS_MULTIPART = 1u << 9,
FLAG_LAST_PART_COMPLETE = 1u << 10,
FLAG_HAS_ERROR = 1u << 11 // STATUS_ERROR
@ -101,6 +101,9 @@ public:
// Get the current image status (as in imgIRequest).
uint32_t GetImageStatus() const;
// Get the current Progress.
Progress GetProgress() const { return mProgress; }
// Schedule an asynchronous "replaying" of all the notifications that would
// have to happen to put us in the current state.
// We will also take note of any notifications that happen between the time

View File

@ -654,33 +654,22 @@ RasterImage::GetFirstFrameRect()
return nsIntRect(nsIntPoint(0,0), mSize);
}
//******************************************************************************
/* [notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame); */
NS_IMETHODIMP_(bool)
RasterImage::FrameIsOpaque(uint32_t aWhichFrame)
RasterImage::IsOpaque()
{
if (aWhichFrame > FRAME_MAX_VALUE) {
NS_WARNING("aWhichFrame outside valid range!");
if (mError) {
return false;
}
if (mError)
Progress progress = mProgressTracker->GetProgress();
// If we haven't yet finished decoding, the safe answer is "not opaque".
if (!(progress & FLAG_DECODE_COMPLETE)) {
return false;
}
// See if we can get an image frame.
nsRefPtr<imgFrame> frame =
LookupFrameNoDecode(GetRequestedFrameIndex(aWhichFrame));
// If we don't get a frame, the safe answer is "not opaque".
if (!frame)
return false;
// Other, the frame is transparent if either:
// 1. It needs a background.
// 2. Its size doesn't cover our entire area.
nsIntRect framerect = frame->GetRect();
return !frame->GetNeedsBackground() &&
framerect.IsEqualInterior(nsIntRect(0, 0, mSize.width, mSize.height));
// Other, we're opaque if FLAG_HAS_TRANSPARENCY is not set.
return !(progress & FLAG_HAS_TRANSPARENCY);
}
nsIntRect
@ -767,7 +756,7 @@ RasterImage::CopyFrame(uint32_t aWhichFrame,
if (mError)
return nullptr;
if (!ApplyDecodeFlags(aFlags, aWhichFrame))
if (!ApplyDecodeFlags(aFlags))
return nullptr;
// Get the frame. If it's not there, it's probably the caller's fault for
@ -844,7 +833,7 @@ RasterImage::GetFrameInternal(uint32_t aWhichFrame,
if (mError)
return nullptr;
if (!ApplyDecodeFlags(aFlags, aWhichFrame))
if (!ApplyDecodeFlags(aFlags))
return nullptr;
// Get the frame. If it's not there, it's probably the caller's fault for
@ -1120,7 +1109,7 @@ RasterImage::InternalAddFrame(uint32_t framenum,
}
bool
RasterImage::ApplyDecodeFlags(uint32_t aNewFlags, uint32_t aWhichFrame)
RasterImage::ApplyDecodeFlags(uint32_t aNewFlags)
{
if (mFrameDecodeFlags == (aNewFlags & DECODE_FLAGS_MASK))
return true; // Not asking very much of us here.
@ -1133,7 +1122,7 @@ RasterImage::ApplyDecodeFlags(uint32_t aNewFlags, uint32_t aWhichFrame)
(mFrameDecodeFlags & DECODE_FLAGS_MASK) & ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
uint32_t newNonAlphaFlags =
(aNewFlags & DECODE_FLAGS_MASK) & ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
if (currentNonAlphaFlags == newNonAlphaFlags && FrameIsOpaque(aWhichFrame)) {
if (currentNonAlphaFlags == newNonAlphaFlags && IsOpaque()) {
return true;
}
@ -2537,8 +2526,7 @@ RasterImage::Draw(gfxContext* aContext,
// is opaque.
bool haveDefaultFlags = (mFrameDecodeFlags == DECODE_FLAGS_DEFAULT);
bool haveSafeAlphaFlags =
(mFrameDecodeFlags == FLAG_DECODE_NO_PREMULTIPLY_ALPHA) &&
FrameIsOpaque(FRAME_CURRENT);
(mFrameDecodeFlags == FLAG_DECODE_NO_PREMULTIPLY_ALPHA) && IsOpaque();
if (!(haveDefaultFlags || haveSafeAlphaFlags)) {
if (!CanForciblyDiscardAndRedecode())

View File

@ -338,7 +338,7 @@ private:
nsresult DoImageDataComplete();
bool ApplyDecodeFlags(uint32_t aNewFlags, uint32_t aWhichFrame);
bool ApplyDecodeFlags(uint32_t aNewFlags);
already_AddRefed<layers::Image> GetCurrentImage();
void UpdateImageContainer();

View File

@ -687,15 +687,9 @@ VectorImage::GetFirstFrameDelay()
return 0;
}
//******************************************************************************
/* [notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame); */
NS_IMETHODIMP_(bool)
VectorImage::FrameIsOpaque(uint32_t aWhichFrame)
VectorImage::IsOpaque()
{
if (aWhichFrame > FRAME_MAX_VALUE)
NS_WARNING("aWhichFrame outside valid range!");
return false; // In general, SVG content is not opaque.
}

View File

@ -584,12 +584,6 @@ SurfaceFormat imgFrame::GetFormat() const
return mFormat;
}
bool imgFrame::GetNeedsBackground() const
{
// We need a background painted if we have alpha or we're incomplete.
return (mFormat == SurfaceFormat::B8G8R8A8 || !ImageComplete());
}
uint32_t imgFrame::GetImageBytesPerRow() const
{
if (mVBuf)

View File

@ -88,7 +88,6 @@ public:
bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
int32_t GetStride() const;
SurfaceFormat GetFormat() const;
bool GetNeedsBackground() const;
uint32_t GetImageBytesPerRow() const;
uint32_t GetImageDataLength() const;
bool GetIsPaletted() const;

View File

@ -80,7 +80,7 @@ function AnimationTest(pollFreq, timeout, referenceElementId, imageElementId,
{
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
this.wereFailures = false;
this.pollFreq = pollFreq;

View File

@ -4,6 +4,16 @@
// Note that this is use by tests elsewhere in the source tree. When in doubt,
// check mxr before removing or changing functionality.
// Helper function to clear both the content and chrome image caches
function clearAllImageCaches()
{
var tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
.getService(SpecialPowers.Ci.imgITools);
var imageCache = tools.getImgCacheForDocument(window.document);
imageCache.clearCache(true); // true=chrome
imageCache.clearCache(false); // false=content
}
// Helper function to clear the image cache of content images
function clearImageCache()
{

View File

@ -32,7 +32,7 @@ const gImg = document.getElementsByTagName("img")[0];
var gMyDecoderObserver; // value will be set in main()
var gReferenceSnapshot; // value will be set in takeReferenceSnapshot()
var gOnStopFrameCounter = 0;
var gPollCounter = 0;
var gIsTestFinished = false;
@ -54,29 +54,29 @@ function takeReferenceSnapshot() {
"reference div should disappear when it becomes display:none");
}
function myOnStopFrame() {
gOnStopFrameCounter++;
ok(true, "myOnStopFrame called");
function myPoll() {
gPollCounter++;
ok(true, "myPoll called");
let currentSnapshot = snapshotWindow(window, false);
if (compareSnapshots(currentSnapshot, gReferenceSnapshot, true)[0]) {
// SUCCESS!
ok(true, "Animated image looks correct, " +
"at call #" + gOnStopFrameCounter + " to onStopFrame");
"at call #" + gPollCounter + " to myPoll");
cleanUpAndFinish();
}
else
setTimeout(myOnStopFrame, 1);
setTimeout(myPoll, 1);
}
function failTest() {
ok(false, "timing out after " + FAILURE_TIMEOUT + "ms. " +
"Animated image still doesn't look correct, " +
"after call #" + gOnStopFrameCounter + " to onStopFrame");
"after call #" + gPollCounter + " to myPoll");
cleanUpAndFinish();
}
function cleanUpAndFinish() {
// On the off chance that failTest and myOnStopFrame are triggered
// On the off chance that failTest and myPoll are triggered
// back-to-back, use a flag to prevent multiple calls to SimpleTest.finish.
if (gIsTestFinished) {
return;
@ -90,11 +90,11 @@ function main() {
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
setTimeout(myOnStopFrame, 1);
setTimeout(myPoll, 1);
// kick off image-loading! myOnStopFrame handles the rest.
// kick off image-loading! myPoll handles the rest.
gImg.setAttribute("src", "lime-anim-100x100.svg");
// In case something goes wrong, fail earlier than mochitest timeout,

View File

@ -32,9 +32,8 @@ const gImg = document.getElementsByTagName("img")[0];
var gMyDecoderObserver; // value will be set in main()
var gReferenceSnapshot; // value will be set in takeReferenceSnapshot()
var gOnStopFrameCounter = 0;
var gOnFrameUpdateCounter = 0;
var gIsTestFinished = false;
var gTimer = null;
function takeReferenceSnapshot() {
@ -55,30 +54,33 @@ function takeReferenceSnapshot() {
"reference div should disappear when it becomes display:none");
}
function myOnStopFrame(aRequest) {
gOnStopFrameCounter++;
ok(true, "myOnStopFrame called");
function myOnFrameUpdate(aRequest) {
if (gIsTestFinished) {
return;
}
gOnFrameUpdateCounter++;
ok(true, "myOnFrameUpdate called");
let currentSnapshot = snapshotWindow(window, false);
if (compareSnapshots(currentSnapshot, gReferenceSnapshot, true)[0]) {
// SUCCESS!
ok(true, "Animated image looks correct, " +
"at call #" + gOnStopFrameCounter + " to onStopFrame");
"at call #" + gOnFrameUpdateCounter + " to myOnFrameUpdate");
cleanUpAndFinish();
}
if (!gTimer)
gTimer = setTimeout(function() { gTimer = null; myOnStopFrame(0, 0); }, 1000);
}
function failTest() {
if (gIsTestFinished) {
return;
}
ok(false, "timing out after " + FAILURE_TIMEOUT + "ms. " +
"Animated image still doesn't look correct, " +
"after call #" + gOnStopFrameCounter + " to onStopFrame");
"after call #" + gOnFrameUpdateCounter + " to myOnFrameUpdate");
cleanUpAndFinish();
}
function cleanUpAndFinish() {
clearTimeout(gTimer);
// On the off chance that failTest and myOnStopFrame are triggered
// On the off chance that failTest and myOnFrameUpdate are triggered
// back-to-back, use a flag to prevent multiple calls to SimpleTest.finish.
if (gIsTestFinished) {
return;
@ -94,7 +96,7 @@ function main() {
// Create, customize & attach decoder observer
observer = new ImageDecoderObserverStub();
observer.frameComplete = myOnStopFrame;
observer.frameUpdate = myOnFrameUpdate;
gMyDecoderObserver =
Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
.createScriptedObserver(observer);
@ -103,9 +105,9 @@ function main() {
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
// kick off image-loading! myOnStopFrame handles the rest.
// kick off image-loading! myOnFrameUpdate handles the rest.
gImg.setAttribute("src", "lime-anim-100x100-2.svg");
// In case something goes wrong, fail earlier than mochitest timeout,

View File

@ -27,6 +27,8 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const gContent = document.getElementById("content");
var gCanvas;
var gCanvasCtx;
var gImg;
var gMyDecoderObserver;
var gIsTestFinished = false;
@ -99,6 +101,11 @@ function onLoad() {
return;
}
ok(true, "Should successfully load " + gImg.src);
// Force decoding of the image.
SimpleTest.executeSoon(function() {
gCanvasCtx.drawImage(gImg, 0, 0);
});
}
function failTest() {
@ -119,6 +126,8 @@ function cleanUpAndFinish() {
function main() {
gFiles = testFiles();
gCanvas = document.createElement('canvas');
gCanvasCtx = gCanvas.getContext('2d');
gImg = new Image();
gImg.onload = onLoad;
gImg.onerror = onError;
@ -135,7 +144,7 @@ function main() {
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
// Load the first image.
loadNext();

View File

@ -109,7 +109,7 @@ function main() {
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
// kick off image-loading! myOnStopFrame handles the rest.
gImg.setAttribute("src", gFiles.next());

View File

@ -109,7 +109,7 @@ function main() {
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
// kick off image-loading! myOnStopFrame handles the rest.
gImg.setAttribute("src", gFiles.next());

View File

@ -106,7 +106,7 @@ function main() {
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
clearAllImageCaches();
// These are two copies of the same image; hence, they have the same frame rate.
gImg1.src = "animated1.gif";

View File

@ -36,7 +36,7 @@ Message::Message()
#ifdef MOZ_TASK_TRACER
header()->source_event_id = 0;
header()->parent_task_id = 0;
header()->source_event_type = SourceEventType::UNKNOWN;
header()->source_event_type = SourceEventType::Unknown;
#endif
InitLoggingVariables();
}
@ -61,7 +61,7 @@ Message::Message(int32_t routing_id, msgid_t type, PriorityValue priority,
#ifdef MOZ_TASK_TRACER
header()->source_event_id = 0;
header()->parent_task_id = 0;
header()->source_event_type = SourceEventType::UNKNOWN;
header()->source_event_type = SourceEventType::Unknown;
#endif
InitLoggingVariables(name);
}

View File

@ -79,7 +79,7 @@ AsmJSFrameIterator::settle()
fp_ = nullptr;
MOZ_ASSERT(done());
break;
case AsmJSModule::CodeRange::IonFFI:
case AsmJSModule::CodeRange::JitFFI:
case AsmJSModule::CodeRange::SlowFFI:
case AsmJSModule::CodeRange::Interrupt:
case AsmJSModule::CodeRange::Inline:
@ -458,7 +458,7 @@ AsmJSProfilingFrameIterator::initFromFP(const AsmJSActivation &activation)
callerFP_ = CallerFPFromFP(fp);
AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, fp);
break;
case AsmJSModule::CodeRange::IonFFI:
case AsmJSModule::CodeRange::JitFFI:
case AsmJSModule::CodeRange::SlowFFI:
case AsmJSModule::CodeRange::Interrupt:
case AsmJSModule::CodeRange::Inline:
@ -513,7 +513,7 @@ AsmJSProfilingFrameIterator::AsmJSProfilingFrameIterator(const AsmJSActivation &
const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(state.pc);
switch (codeRange->kind()) {
case AsmJSModule::CodeRange::Function:
case AsmJSModule::CodeRange::IonFFI:
case AsmJSModule::CodeRange::JitFFI:
case AsmJSModule::CodeRange::SlowFFI:
case AsmJSModule::CodeRange::Interrupt:
case AsmJSModule::CodeRange::Thunk: {
@ -615,7 +615,7 @@ AsmJSProfilingFrameIterator::operator++()
callerPC_ = nullptr;
break;
case AsmJSModule::CodeRange::Function:
case AsmJSModule::CodeRange::IonFFI:
case AsmJSModule::CodeRange::JitFFI:
case AsmJSModule::CodeRange::SlowFFI:
case AsmJSModule::CodeRange::Interrupt:
case AsmJSModule::CodeRange::Inline:
@ -672,15 +672,15 @@ AsmJSProfilingFrameIterator::label() const
//
// NB: these labels are regexp-matched by
// browser/devtools/profiler/cleopatra/js/parserWorker.js.
const char *ionFFIDescription = "fast FFI trampoline (in asm.js)";
const char *jitFFIDescription = "fast FFI trampoline (in asm.js)";
const char *slowFFIDescription = "slow FFI trampoline (in asm.js)";
const char *interruptDescription = "interrupt due to out-of-bounds or long execution (in asm.js)";
switch (AsmJSExit::ExtractReasonKind(exitReason_)) {
case AsmJSExit::Reason_None:
break;
case AsmJSExit::Reason_IonFFI:
return ionFFIDescription;
case AsmJSExit::Reason_JitFFI:
return jitFFIDescription;
case AsmJSExit::Reason_SlowFFI:
return slowFFIDescription;
case AsmJSExit::Reason_Interrupt:
@ -693,7 +693,7 @@ AsmJSProfilingFrameIterator::label() const
switch (codeRange->kind()) {
case AsmJSModule::CodeRange::Function: return codeRange->functionProfilingLabel(*module_);
case AsmJSModule::CodeRange::Entry: return "entry trampoline (in asm.js)";
case AsmJSModule::CodeRange::IonFFI: return ionFFIDescription;
case AsmJSModule::CodeRange::JitFFI: return jitFFIDescription;
case AsmJSModule::CodeRange::SlowFFI: return slowFFIDescription;
case AsmJSModule::CodeRange::Interrupt: return interruptDescription;
case AsmJSModule::CodeRange::Inline: return "inline stub (in asm.js)";

View File

@ -69,7 +69,7 @@ namespace AsmJSExit
// handler).
enum ReasonKind {
Reason_None,
Reason_IonFFI,
Reason_JitFFI,
Reason_SlowFFI,
Reason_Interrupt,
Reason_Builtin
@ -106,7 +106,7 @@ namespace AsmJSExit
typedef uint32_t Reason;
static const uint32_t None = Reason_None;
static const uint32_t IonFFI = Reason_IonFFI;
static const uint32_t JitFFI = Reason_JitFFI;
static const uint32_t SlowFFI = Reason_SlowFFI;
static const uint32_t Interrupt = Reason_Interrupt;
static inline Reason Builtin(BuiltinKind builtin) {

View File

@ -105,11 +105,11 @@ AsmJSModule::~AsmJSModule()
if (code_) {
for (unsigned i = 0; i < numExits(); i++) {
AsmJSModule::ExitDatum &exitDatum = exitIndexToGlobalDatum(i);
if (!exitDatum.ionScript)
if (!exitDatum.baselineScript)
continue;
jit::DependentAsmJSModuleExit exit(this, i);
exitDatum.ionScript->removeDependentAsmJSModule(exit);
exitDatum.baselineScript->removeDependentAsmJSModule(exit);
}
DeallocateExecutableMemory(code_, pod.totalBytes_, AsmJSPageSize);
@ -501,23 +501,31 @@ CoerceInPlace_ToNumber(MutableHandleValue val)
}
static bool
TryEnablingIon(JSContext *cx, AsmJSModule &module, HandleFunction fun, uint32_t exitIndex,
TryEnablingJit(JSContext *cx, AsmJSModule &module, HandleFunction fun, uint32_t exitIndex,
int32_t argc, Value *argv)
{
if (!fun->hasScript())
return true;
// Test if the function is Ion compiled
// Test if the function is JIT compiled.
JSScript *script = fun->nonLazyScript();
if (!script->hasIonScript())
if (!script->hasBaselineScript()) {
MOZ_ASSERT(!script->hasIonScript());
return true;
}
// Currently we can't rectify arguments. Therefore disabling if argc is too low.
if (fun->nargs() > size_t(argc))
return true;
// Normally the types should correspond, since we just ran with those types,
// but there are reports this is asserting. Therefore doing it as a check, instead of DEBUG only.
// Ensure the argument types are included in the argument TypeSets stored in
// the TypeScript. This is necessary for Ion, because the FFI exit will
// use the skip-arg-checks entry point.
//
// Note that the TypeScript is never discarded while the script has a
// BaselineScript, so if those checks hold now they must hold at least until
// the BaselineScript is discarded and when that happens the FFI exit is
// patched back.
if (!types::TypeScript::ThisTypes(script)->hasType(types::Type::UndefinedType()))
return true;
for (uint32_t i = 0; i < fun->nargs(); i++) {
@ -533,11 +541,11 @@ TryEnablingIon(JSContext *cx, AsmJSModule &module, HandleFunction fun, uint32_t
if (module.exitIsOptimized(exitIndex))
return true;
IonScript *ionScript = script->ionScript();
if (!ionScript->addDependentAsmJSModule(cx, DependentAsmJSModuleExit(&module, exitIndex)))
BaselineScript *baselineScript = script->baselineScript();
if (!baselineScript->addDependentAsmJSModule(cx, DependentAsmJSModuleExit(&module, exitIndex)))
return false;
module.optimizeExit(exitIndex, ionScript);
module.optimizeExit(exitIndex, baselineScript);
return true;
}
@ -553,7 +561,7 @@ InvokeFromAsmJS(AsmJSActivation *activation, int32_t exitIndex, int32_t argc, Va
if (!Invoke(cx, UndefinedValue(), fval, argc, argv, rval))
return false;
return TryEnablingIon(cx, module, fun, exitIndex, argc, argv);
return TryEnablingJit(cx, module, fun, exitIndex, argc, argv);
}
// Use an int32_t return type instead of bool since bool does not have a
@ -748,7 +756,7 @@ AsmJSModule::staticallyLink(ExclusiveContext *cx)
AsmJSModule::ExitDatum &exitDatum = exitIndexToGlobalDatum(i);
exitDatum.exit = interpExitTrampoline(exits_[i]);
exitDatum.fun = nullptr;
exitDatum.ionScript = nullptr;
exitDatum.baselineScript = nullptr;
}
MOZ_ASSERT(isStaticallyLinked());
@ -904,7 +912,7 @@ AsmJSModule::detachHeap(JSContext *cx)
// Even if this->active(), to reach here, the activation must have called
// out via an FFI stub. FFI stubs check if heapDatum() is null on reentry
// and throw an exception if so.
MOZ_ASSERT_IF(active(), activation()->exitReason() == AsmJSExit::Reason_IonFFI ||
MOZ_ASSERT_IF(active(), activation()->exitReason() == AsmJSExit::Reason_JitFFI ||
activation()->exitReason() == AsmJSExit::Reason_SlowFFI);
restoreHeapToInitialState(maybeHeap_);
@ -1338,7 +1346,7 @@ AsmJSModule::CodeRange::CodeRange(Kind kind, uint32_t begin, uint32_t profilingR
MOZ_ASSERT(begin_ < profilingReturn_);
MOZ_ASSERT(profilingReturn_ < end_);
MOZ_ASSERT(u.kind_ == IonFFI || u.kind_ == SlowFFI || u.kind_ == Interrupt);
MOZ_ASSERT(u.kind_ == JitFFI || u.kind_ == SlowFFI || u.kind_ == Interrupt);
}
AsmJSModule::CodeRange::CodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin,

View File

@ -371,7 +371,7 @@ class AsmJSModule
unsigned ffiIndex_;
unsigned globalDataOffset_;
unsigned interpCodeOffset_;
unsigned ionCodeOffset_;
unsigned jitCodeOffset_;
friend class AsmJSModule;
@ -379,7 +379,7 @@ class AsmJSModule
Exit() {}
Exit(unsigned ffiIndex, unsigned globalDataOffset)
: ffiIndex_(ffiIndex), globalDataOffset_(globalDataOffset),
interpCodeOffset_(0), ionCodeOffset_(0)
interpCodeOffset_(0), jitCodeOffset_(0)
{}
unsigned ffiIndex() const {
return ffiIndex_;
@ -391,13 +391,13 @@ class AsmJSModule
MOZ_ASSERT(!interpCodeOffset_);
interpCodeOffset_ = off;
}
void initIonOffset(unsigned off) {
MOZ_ASSERT(!ionCodeOffset_);
ionCodeOffset_ = off;
void initJitOffset(unsigned off) {
MOZ_ASSERT(!jitCodeOffset_);
jitCodeOffset_ = off;
}
void updateOffsets(jit::MacroAssembler &masm) {
interpCodeOffset_ = masm.actualOffset(interpCodeOffset_);
ionCodeOffset_ = masm.actualOffset(ionCodeOffset_);
jitCodeOffset_ = masm.actualOffset(jitCodeOffset_);
}
size_t serializedSize() const;
@ -419,7 +419,7 @@ class AsmJSModule
struct ExitDatum
{
uint8_t *exit;
jit::IonScript *ionScript;
jit::BaselineScript *baselineScript;
HeapPtrFunction fun;
};
@ -558,7 +558,7 @@ class AsmJSModule
void setDeltas(uint32_t entry, uint32_t profilingJump, uint32_t profilingEpilogue);
public:
enum Kind { Function, Entry, IonFFI, SlowFFI, Interrupt, Thunk, Inline };
enum Kind { Function, Entry, JitFFI, SlowFFI, Interrupt, Thunk, Inline };
CodeRange() {}
CodeRange(uint32_t nameIndex, uint32_t lineNumber, const AsmJSFunctionLabels &l);
@ -570,7 +570,7 @@ class AsmJSModule
Kind kind() const { return Kind(u.kind_); }
bool isFunction() const { return kind() == Function; }
bool isEntry() const { return kind() == Entry; }
bool isFFI() const { return kind() == IonFFI || kind() == SlowFFI; }
bool isFFI() const { return kind() == JitFFI || kind() == SlowFFI; }
bool isInterrupt() const { return kind() == Interrupt; }
bool isThunk() const { return kind() == Thunk; }
@ -1325,10 +1325,10 @@ class AsmJSModule
MOZ_ASSERT(exit.interpCodeOffset_);
return code_ + exit.interpCodeOffset_;
}
uint8_t *ionExitTrampoline(const Exit &exit) const {
uint8_t *jitExitTrampoline(const Exit &exit) const {
MOZ_ASSERT(isFinished());
MOZ_ASSERT(exit.ionCodeOffset_);
return code_ + exit.ionCodeOffset_;
MOZ_ASSERT(exit.jitCodeOffset_);
return code_ + exit.jitCodeOffset_;
}
public:
@ -1463,17 +1463,17 @@ class AsmJSModule
ExitDatum &exitDatum = exitIndexToGlobalDatum(exitIndex);
return exitDatum.exit != interpExitTrampoline(exit(exitIndex));
}
void optimizeExit(unsigned exitIndex, jit::IonScript *ionScript) const {
void optimizeExit(unsigned exitIndex, jit::BaselineScript *baselineScript) const {
MOZ_ASSERT(!exitIsOptimized(exitIndex));
ExitDatum &exitDatum = exitIndexToGlobalDatum(exitIndex);
exitDatum.exit = ionExitTrampoline(exit(exitIndex));
exitDatum.ionScript = ionScript;
exitDatum.exit = jitExitTrampoline(exit(exitIndex));
exitDatum.baselineScript = baselineScript;
}
void detachIonCompilation(size_t exitIndex) const {
void detachJitCompilation(size_t exitIndex) const {
MOZ_ASSERT(isFinished());
ExitDatum &exitDatum = exitIndexToGlobalDatum(exitIndex);
exitDatum.exit = interpExitTrampoline(exit(exitIndex));
exitDatum.ionScript = nullptr;
exitDatum.baselineScript = nullptr;
}
/*************************************************************************/

View File

@ -1870,13 +1870,13 @@ class MOZ_STACK_CLASS ModuleCompiler
uint32_t end = masm_.currentOffset();
return module_->addCodeRange(AsmJSModule::CodeRange::SlowFFI, beg, pret, end);
}
bool finishGeneratingIonExit(unsigned exitIndex, Label *begin, Label *profilingReturn) {
bool finishGeneratingJitExit(unsigned exitIndex, Label *begin, Label *profilingReturn) {
MOZ_ASSERT(finishedFunctionBodies_);
uint32_t beg = begin->offset();
module_->exit(exitIndex).initIonOffset(beg);
module_->exit(exitIndex).initJitOffset(beg);
uint32_t pret = profilingReturn->offset();
uint32_t end = masm_.currentOffset();
return module_->addCodeRange(AsmJSModule::CodeRange::IonFFI, beg, pret, end);
return module_->addCodeRange(AsmJSModule::CodeRange::JitFFI, beg, pret, end);
}
bool finishGeneratingInterrupt(Label *begin, Label *profilingReturn) {
MOZ_ASSERT(finishedFunctionBodies_);
@ -8453,7 +8453,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit
unsigned framePushed = Max(ionFrameSize, coerceFrameSize);
Label begin;
GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::IonFFI, &begin);
GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::JitFFI, &begin);
// 1. Descriptor
size_t argOffset = offsetToIonArgs;
@ -8501,7 +8501,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit
argOffset += exit.sig().args().length() * sizeof(Value);
MOZ_ASSERT(argOffset == offsetToIonArgs + ionArgBytes);
// 6. Ion will clobber all registers, even non-volatiles. GlobalReg and
// 6. Jit code will clobber all registers, even non-volatiles. GlobalReg and
// HeapReg are removed from the general register set for asm.js code, so
// these will not have been saved by the caller like all other registers,
// so they must be explicitly preserved. Only save GlobalReg since
@ -8553,7 +8553,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit
// 2. Call
AssertStackAlignment(masm, AsmJSStackAlignment);
masm.callIonFromAsmJS(callee);
masm.callJitFromAsmJS(callee);
AssertStackAlignment(masm, AsmJSStackAlignment);
{
@ -8625,7 +8625,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit
GenerateCheckForHeapDetachment(m, ABIArgGenerator::NonReturn_VolatileReg0);
Label profilingReturn;
GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::IonFFI, &profilingReturn);
GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::JitFFI, &profilingReturn);
if (oolConvert.used()) {
masm.bind(&oolConvert);
@ -8669,7 +8669,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit
MOZ_ASSERT(masm.framePushed() == 0);
return m.finishGeneratingIonExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
return m.finishGeneratingJitExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
}
// See "asm.js FFI calls" comment above.

View File

@ -383,6 +383,14 @@ class LifoAlloc
return n;
}
// Get the total size of the arena chunks (including unused space).
size_t computedSizeOfExcludingThis() const {
size_t n = 0;
for (BumpChunk *chunk = first; chunk; chunk = chunk->next())
n += chunk->computedSizeOfIncludingThis();
return n;
}
// Like sizeOfExcludingThis(), but includes the size of the LifoAlloc itself.
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);

View File

@ -147,7 +147,6 @@ struct MovingTracer : JSTracer {
};
#endif
} /* namespace gc */
} /* namespace js */

View File

@ -599,13 +599,14 @@ class GCRuntime
void decommitAllWithoutUnlocking(const AutoLockGC &lock);
void decommitArenas(AutoLockGC &lock);
void expireChunksAndArenas(bool shouldShrink, AutoLockGC &lock);
void sweepBackgroundThings();
void queueZonesForBackgroundSweep(js::gc::ZoneList& zones);
void sweepBackgroundThings(js::gc::ZoneList &zones, ThreadType threadType);
void assertBackgroundSweepingFinished();
bool shouldCompact();
bool compactPhase(bool lastGC);
#ifdef JSGC_COMPACTING
void sweepTypesAfterCompacting(Zone *zone);
void sweepZoneAfterCompacting(Zone *zone);
void compactPhase(bool lastGC);
ArenaHeader *relocateArenas();
void updateAllCellPointersParallel(ArenasToUpdate &source);
void updateAllCellPointersSerial(MovingTracer *trc, ArenasToUpdate &source);
@ -758,9 +759,8 @@ class GCRuntime
/* Whether any black->gray edges were found during marking. */
bool foundBlackGrayEdges;
/* List head of zones to be swept in the background. */
JS::Zone *sweepingZones;
/* Singly linekd list of zones to be swept in the background. */
js::gc::ZoneList backgroundSweepZones;
/*
* Free LIFO blocks are transferred to this allocator before being freed on
* the background GC thread.

View File

@ -19,6 +19,8 @@
using namespace js;
using namespace js::gc;
Zone * const Zone::NotOnList = reinterpret_cast<Zone *>(1);
JS::Zone::Zone(JSRuntime *rt)
: JS::shadow::Zone(rt, &rt->gc.marker),
allocator(this),
@ -37,7 +39,8 @@ JS::Zone::Zone(JSRuntime *rt)
gcState_(NoGC),
gcScheduled_(false),
gcPreserveCode_(false),
jitUsingBarriers_(false)
jitUsingBarriers_(false),
listNext_(NotOnList)
{
/* Ensure that there are no vtables to mess us up here. */
MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone *>(this) ==
@ -265,3 +268,95 @@ js::ZonesIter::atAtomsZone(JSRuntime *rt)
{
return rt->isAtomsZone(*it);
}
bool Zone::isOnList()
{
return listNext_ != NotOnList;
}
ZoneList::ZoneList()
: head(nullptr), tail(nullptr)
{}
ZoneList::ZoneList(Zone *zone)
: head(zone), tail(zone)
{
MOZ_ASSERT(!zone->isOnList());
zone->listNext_ = nullptr;
}
void
ZoneList::check() const
{
#ifdef DEBUG
MOZ_ASSERT((head == nullptr) == (tail == nullptr));
if (head) {
Zone *zone = head;
while (zone != tail) {
zone = zone->listNext_;
MOZ_ASSERT(zone);
}
MOZ_ASSERT(!zone->listNext_);
}
#endif
}
bool ZoneList::isEmpty() const
{
return head == nullptr;
}
Zone *
ZoneList::front() const
{
MOZ_ASSERT(!isEmpty());
return head;
}
void
ZoneList::append(Zone *zone)
{
ZoneList singleZone(zone);
append(singleZone);
}
void
ZoneList::append(ZoneList &other)
{
check();
other.check();
MOZ_ASSERT(tail != other.tail);
if (tail)
tail->listNext_ = other.head;
else
head = other.head;
tail = other.tail;
}
Zone *
ZoneList::removeFront()
{
MOZ_ASSERT(!isEmpty());
check();
Zone *front = head;
head = head->listNext_;
if (!head)
tail = nullptr;
front->listNext_ = Zone::NotOnList;
return front;
}
void
ZoneList::transferFrom(ZoneList& other)
{
MOZ_ASSERT(isEmpty());
other.check();
head = other.head;
tail = other.tail;
other.head = nullptr;
other.tail = nullptr;
}

View File

@ -250,6 +250,10 @@ struct Zone : public JS::shadow::Zone,
js::jit::JitZone *createJitZone(JSContext *cx);
bool isQueuedForBackgroundSweep() {
return isOnList();
}
public:
js::Allocator allocator;
@ -314,6 +318,12 @@ struct Zone : public JS::shadow::Zone,
bool gcPreserveCode_;
bool jitUsingBarriers_;
// Allow zones to be linked into a list
friend class js::gc::ZoneList;
static Zone * const NotOnList;
Zone *listNext_;
bool isOnList();
friend bool js::CurrentThreadCanAccessZone(Zone *zone);
friend class js::gc::GCRuntime;
};

View File

@ -39,33 +39,33 @@ enum MemoryBarrierBits {
MembarAllbits = 31,
};
inline MemoryBarrierBits
static inline MOZ_CONSTEXPR MemoryBarrierBits
operator|(MemoryBarrierBits a, MemoryBarrierBits b)
{
return MemoryBarrierBits(int(a) | int(b));
}
inline MemoryBarrierBits
static inline MOZ_CONSTEXPR MemoryBarrierBits
operator&(MemoryBarrierBits a, MemoryBarrierBits b)
{
return MemoryBarrierBits(int(a) & int(b));
}
inline MemoryBarrierBits
static inline MOZ_CONSTEXPR MemoryBarrierBits
operator~(MemoryBarrierBits a)
{
return MemoryBarrierBits(~int(a));
}
// Standard barrier bits for a full barrier.
static const MemoryBarrierBits MembarFull = MembarLoadLoad|MembarLoadStore|MembarStoreLoad|MembarStoreStore;
static MOZ_CONSTEXPR_VAR MemoryBarrierBits MembarFull = MembarLoadLoad|MembarLoadStore|MembarStoreLoad|MembarStoreStore;
// Standard sets of barrier bits for atomic loads and stores.
// See http://gee.cs.oswego.edu/dl/jmm/cookbook.html for more.
static const MemoryBarrierBits MembarBeforeLoad = MembarNobits;
static const MemoryBarrierBits MembarAfterLoad = MembarLoadLoad|MembarLoadStore;
static const MemoryBarrierBits MembarBeforeStore = MembarStoreStore;
static const MemoryBarrierBits MembarAfterStore = MembarStoreLoad;
static MOZ_CONSTEXPR_VAR MemoryBarrierBits MembarBeforeLoad = MembarNobits;
static MOZ_CONSTEXPR_VAR MemoryBarrierBits MembarAfterLoad = MembarLoadLoad|MembarLoadStore;
static MOZ_CONSTEXPR_VAR MemoryBarrierBits MembarBeforeStore = MembarStoreStore;
static MOZ_CONSTEXPR_VAR MemoryBarrierBits MembarAfterStore = MembarStoreLoad;
} // namespace jit
} // namespace js

View File

@ -8,6 +8,7 @@
#include "mozilla/MemoryReporting.h"
#include "asmjs/AsmJSModule.h"
#include "jit/BaselineCompiler.h"
#include "jit/BaselineIC.h"
#include "jit/CompileInfo.h"
@ -45,6 +46,7 @@ BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
: method_(nullptr),
templateScope_(nullptr),
fallbackStubSpace_(),
dependentAsmJSModules_(nullptr),
prologueOffset_(prologueOffset),
epilogueOffset_(epilogueOffset),
#ifdef DEBUG
@ -440,9 +442,54 @@ BaselineScript::Destroy(FreeOp *fop, BaselineScript *script)
MOZ_ASSERT(fop->runtime()->gc.nursery.isEmpty());
#endif
script->unlinkDependentAsmJSModules(fop);
fop->delete_(script);
}
void
BaselineScript::unlinkDependentAsmJSModules(FreeOp *fop)
{
// Remove any links from AsmJSModules that contain optimized FFI calls into
// this BaselineScript.
if (dependentAsmJSModules_) {
for (size_t i = 0; i < dependentAsmJSModules_->length(); i++) {
DependentAsmJSModuleExit exit = (*dependentAsmJSModules_)[i];
exit.module->detachJitCompilation(exit.exitIndex);
}
fop->delete_(dependentAsmJSModules_);
dependentAsmJSModules_ = nullptr;
}
}
bool
BaselineScript::addDependentAsmJSModule(JSContext *cx, DependentAsmJSModuleExit exit)
{
if (!dependentAsmJSModules_) {
dependentAsmJSModules_ = cx->new_<Vector<DependentAsmJSModuleExit> >(cx);
if (!dependentAsmJSModules_)
return false;
}
return dependentAsmJSModules_->append(exit);
}
void
BaselineScript::removeDependentAsmJSModule(DependentAsmJSModuleExit exit)
{
if (!dependentAsmJSModules_)
return;
for (size_t i = 0; i < dependentAsmJSModules_->length(); i++) {
if ((*dependentAsmJSModules_)[i].module == exit.module &&
(*dependentAsmJSModules_)[i].exitIndex == exit.exitIndex)
{
dependentAsmJSModules_->erase(dependentAsmJSModules_->begin() + i);
break;
}
}
}
ICEntry &
BaselineScript::icEntry(size_t index)
{

View File

@ -93,6 +93,19 @@ struct PCMappingIndexEntry
uint32_t bufferOffset;
};
// Describes a single AsmJSModule which jumps (via an FFI exit with the given
// index) directly to a BaselineScript or IonScript.
struct DependentAsmJSModuleExit
{
const AsmJSModule *module;
size_t exitIndex;
DependentAsmJSModuleExit(const AsmJSModule *module, size_t exitIndex)
: module(module),
exitIndex(exitIndex)
{ }
};
struct BaselineScript
{
public:
@ -114,6 +127,10 @@ struct BaselineScript
// Allocated space for fallback stubs.
FallbackICStubSpace fallbackStubSpace_;
// If non-null, the list of AsmJSModules that contain an optimized call
// directly to this script.
Vector<DependentAsmJSModuleExit> *dependentAsmJSModules_;
// Native code offset right before the scope chain is initialized.
uint32_t prologueOffset_;
@ -347,6 +364,10 @@ struct BaselineScript
jsbytecode *pcForNativeAddress(JSScript *script, uint8_t *nativeAddress);
jsbytecode *pcForNativeOffset(JSScript *script, uint32_t nativeOffset);
bool addDependentAsmJSModule(JSContext *cx, DependentAsmJSModuleExit exit);
void unlinkDependentAsmJSModules(FreeOp *fop);
void removeDependentAsmJSModule(DependentAsmJSModuleExit exit);
private:
jsbytecode *pcForNativeOffset(JSScript *script, uint32_t nativeOffset, bool isReturn);

View File

@ -1217,8 +1217,8 @@ PrepareAndExecuteRegExp(JSContext *cx, MacroAssembler &masm, Register regexp, Re
}
static void
CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, Register scratch,
size_t fromWidth, size_t toWidth);
CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len,
Register byteOpScratch, size_t fromWidth, size_t toWidth);
static void
CreateDependentString(MacroAssembler &masm, const JSAtomState &names,
@ -5921,8 +5921,8 @@ CodeGenerator::visitConcatPar(LConcatPar *lir)
}
static void
CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, Register scratch,
size_t fromWidth, size_t toWidth)
CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len,
Register byteOpScratch, size_t fromWidth, size_t toWidth)
{
// Copy |len| char16_t code units from |from| to |to|. Assumes len > 0
// (checked below in debug builds), and when done |to| must point to the
@ -5942,13 +5942,13 @@ CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len,
Label start;
masm.bind(&start);
if (fromWidth == 2)
masm.load16ZeroExtend(Address(from, 0), scratch);
masm.load16ZeroExtend(Address(from, 0), byteOpScratch);
else
masm.load8ZeroExtend(Address(from, 0), scratch);
masm.load8ZeroExtend(Address(from, 0), byteOpScratch);
if (toWidth == 2)
masm.store16(scratch, Address(to, 0));
masm.store16(byteOpScratch, Address(to, 0));
else
masm.store8(scratch, Address(to, 0));
masm.store8(byteOpScratch, Address(to, 0));
masm.addPtr(Imm32(fromWidth), from);
masm.addPtr(Imm32(toWidth), to);
masm.branchSub32(Assembler::NonZero, Imm32(1), len, &start);
@ -6065,8 +6065,12 @@ bool CodeGenerator::visitSubstr(LSubstr *lir)
Register length = ToRegister(lir->length());
Register output = ToRegister(lir->output());
Register temp = ToRegister(lir->temp());
Register temp2 = ToRegister(lir->temp2());
Register temp3 = ToRegister(lir->temp3());
// On x86 there are not enough registers. In that case reuse the string
// register as temporary.
Register temp2 = lir->temp2()->isBogusTemp() ? string : ToRegister(lir->temp2());
Address stringFlags(string, JSString::offsetOfFlags());
Label isLatin1, notInline, nonZero, isInlinedLatin1;
@ -6110,18 +6114,24 @@ bool CodeGenerator::visitSubstr(LSubstr *lir)
masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS),
Address(output, JSString::offsetOfFlags()));
masm.computeEffectiveAddress(stringStorage, temp);
if (temp2 == string)
masm.push(string);
BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t)));
masm.computeEffectiveAddress(chars, temp2);
masm.computeEffectiveAddress(outputStorage, temp);
CopyStringChars(masm, temp, temp2, length, temp3, sizeof(char16_t), sizeof(char16_t));
masm.load32(Address(output, JSString::offsetOfLength()), length);
masm.store16(Imm32(0), Address(temp, 0));
if (temp2 == string)
masm.pop(string);
masm.jump(done);
}
masm.bind(&isInlinedLatin1);
{
masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS | JSString::LATIN1_CHARS_BIT),
Address(output, JSString::offsetOfFlags()));
if (temp2 == string)
masm.push(string);
masm.computeEffectiveAddress(stringStorage, temp2);
static_assert(sizeof(char) == 1, "begin index shouldn't need scaling");
masm.addPtr(begin, temp2);
@ -6129,6 +6139,8 @@ bool CodeGenerator::visitSubstr(LSubstr *lir)
CopyStringChars(masm, temp, temp2, length, temp3, sizeof(char), sizeof(char));
masm.load32(Address(output, JSString::offsetOfLength()), length);
masm.store8(Imm32(0), Address(temp, 0));
if (temp2 == string)
masm.pop(string);
masm.jump(done);
}

View File

@ -12,7 +12,6 @@
#include "jscompartment.h"
#include "jsprf.h"
#include "asmjs/AsmJSModule.h"
#include "gc/Marking.h"
#include "jit/AliasAnalysis.h"
#include "jit/BacktrackingAllocator.h"
@ -844,7 +843,6 @@ IonScript::IonScript()
parallelAge_(0),
recompileInfo_(),
osrPcMismatchCounter_(0),
dependentAsmJSModules(nullptr),
pendingBuilder_(nullptr)
{
}
@ -1222,32 +1220,9 @@ IonScript::destroyCaches()
getCacheFromIndex(i).destroy();
}
bool
IonScript::addDependentAsmJSModule(JSContext *cx, DependentAsmJSModuleExit exit)
{
if (!dependentAsmJSModules) {
dependentAsmJSModules = cx->new_<Vector<DependentAsmJSModuleExit> >(cx);
if (!dependentAsmJSModules)
return false;
}
return dependentAsmJSModules->append(exit);
}
void
IonScript::unlinkFromRuntime(FreeOp *fop)
{
// Remove any links from AsmJSModules that contain optimized FFI calls into
// this IonScript.
if (dependentAsmJSModules) {
for (size_t i = 0; i < dependentAsmJSModules->length(); i++) {
DependentAsmJSModuleExit exit = dependentAsmJSModules->begin()[i];
exit.module->detachIonCompilation(exit.exitIndex);
}
fop->delete_(dependentAsmJSModules);
dependentAsmJSModules = nullptr;
}
// The writes to the executable buffer below may clobber backedge jumps, so
// make sure that those backedges are unlinked from the runtime and not
// reclobbered with garbage if an interrupt is requested.

View File

@ -160,19 +160,6 @@ class IonCache;
struct PatchableBackedgeInfo;
struct CacheLocation;
// Describes a single AsmJSModule which jumps (via an FFI exit with the given
// index) directly into an IonScript.
struct DependentAsmJSModuleExit
{
const AsmJSModule *module;
size_t exitIndex;
DependentAsmJSModuleExit(const AsmJSModule *module, size_t exitIndex)
: module(module),
exitIndex(exitIndex)
{ }
};
// An IonScript attaches Ion-generated information to a JSScript.
struct IonScript
{
@ -298,10 +285,6 @@ struct IonScript
// a LOOPENTRY pc other than osrPc_.
uint32_t osrPcMismatchCounter_;
// If non-null, the list of AsmJSModules
// that contain an optimized call directly into this IonScript.
Vector<DependentAsmJSModuleExit> *dependentAsmJSModules;
IonBuilder *pendingBuilder_;
private:
@ -352,19 +335,6 @@ struct IonScript
PatchableBackedge *backedgeList() {
return (PatchableBackedge *) &bottomBuffer()[backedgeList_];
}
bool addDependentAsmJSModule(JSContext *cx, DependentAsmJSModuleExit exit);
void removeDependentAsmJSModule(DependentAsmJSModuleExit exit) {
if (!dependentAsmJSModules)
return;
for (size_t i = 0; i < dependentAsmJSModules->length(); i++) {
if (dependentAsmJSModules->begin()[i].module == exit.module &&
dependentAsmJSModules->begin()[i].exitIndex == exit.exitIndex)
{
dependentAsmJSModules->erase(dependentAsmJSModules->begin() + i);
break;
}
}
}
private:
void trace(JSTracer *trc);

View File

@ -2181,21 +2181,6 @@ LIRGenerator::visitStringReplace(MStringReplace *ins)
return defineReturn(lir, ins) && assignSafepoint(lir, ins);
}
bool
LIRGenerator::visitSubstr(MSubstr *ins)
{
// The last temporary need to be a register that can handle 8bit moves, but
// there is no way to signal that to register allocator, except to give a
// fixed temporary that is able to do this.
LSubstr *lir = new (alloc()) LSubstr(useRegister(ins->string()),
useRegister(ins->begin()),
useRegister(ins->length()),
temp(),
temp(),
tempFixed(CallTempReg1));
return define(lir, ins) && assignSafepoint(lir, ins);
}
bool
LIRGenerator::visitLambda(MLambda *ins)
{

View File

@ -146,7 +146,6 @@ class LIRGenerator : public LIRGeneratorSpecific
bool visitCharCodeAt(MCharCodeAt *ins);
bool visitFromCharCode(MFromCharCode *ins);
bool visitStringSplit(MStringSplit *ins);
bool visitSubstr(MSubstr *ins);
bool visitStart(MStart *start);
bool visitOsrEntry(MOsrEntry *entry);
bool visitNop(MNop *nop);

View File

@ -55,6 +55,12 @@ LIRGeneratorARM::useByteOpRegisterOrNonDoubleConstant(MDefinition *mir)
return useRegisterOrNonDoubleConstant(mir);
}
LDefinition
LIRGeneratorARM::tempByteOpRegister()
{
return temp();
}
bool
LIRGeneratorARM::lowerConstantDouble(double d, MInstruction *mir)
{
@ -674,3 +680,15 @@ LIRGeneratorARM::visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap *ins)
return define(lir, ins);
}
bool
LIRGeneratorARM::visitSubstr(MSubstr *ins)
{
LSubstr *lir = new (alloc()) LSubstr(useRegister(ins->string()),
useRegister(ins->begin()),
useRegister(ins->length()),
temp(),
temp(),
tempByteOpRegister());
return define(lir, ins) && assignSafepoint(lir, ins);
}

View File

@ -30,6 +30,7 @@ class LIRGeneratorARM : public LIRGeneratorShared
// stores and loads; on ARM all registers are okay.
LAllocation useByteOpRegister(MDefinition *mir);
LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
LDefinition tempByteOpRegister();
inline LDefinition tempToUnbox() {
return LDefinition::BogusTemp();
@ -111,6 +112,7 @@ class LIRGeneratorARM : public LIRGeneratorShared
bool visitSimdValueX4(MSimdValueX4 *ins);
bool visitCompareExchangeTypedArrayElement(MCompareExchangeTypedArrayElement *ins);
bool visitAtomicTypedArrayElementBinop(MAtomicTypedArrayElementBinop *ins);
bool visitSubstr(MSubstr *ins);
};
typedef LIRGeneratorARM LIRGeneratorSpecific;

View File

@ -1861,11 +1861,11 @@ MacroAssemblerARMCompat::callIon(Register callee)
}
void
MacroAssemblerARMCompat::callIonFromAsmJS(Register callee)
MacroAssemblerARMCompat::callJitFromAsmJS(Register callee)
{
ma_callIonNoPush(callee);
// The Ion ABI has the callee pop the return address off the stack.
// The JIT ABI has the callee pop the return address off the stack.
// The asm.js caller assumes that the call leaves sp unchanged, so bump
// the stack.
subPtr(Imm32(sizeof(void*)), sp);

View File

@ -1293,7 +1293,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM
// Makes an Ion call using the only two methods that it is sane for
// independent code to make a call.
void callIon(Register callee);
void callIonFromAsmJS(Register callee);
void callJitFromAsmJS(Register callee);
void reserveStack(uint32_t amount);
void freeStack(uint32_t amount);

View File

@ -57,6 +57,12 @@ LIRGeneratorMIPS::useByteOpRegisterOrNonDoubleConstant(MDefinition *mir)
return useRegisterOrNonDoubleConstant(mir);
}
LDefinition
LIRGeneratorMIPS::tempByteOpRegister()
{
return temp();
}
bool
LIRGeneratorMIPS::lowerConstantDouble(double d, MInstruction *mir)
{
@ -525,6 +531,18 @@ LIRGeneratorMIPS::lowerTruncateFToInt32(MTruncateToInt32 *ins)
return define(new(alloc()) LTruncateFToInt32(useRegister(opd), LDefinition::BogusTemp()), ins);
}
bool
LIRGeneratorMIPS::visitSubstr(MSubstr *ins)
{
LSubstr *lir = new (alloc()) LSubstr(useRegister(ins->string()),
useRegister(ins->begin()),
useRegister(ins->length()),
temp(),
temp(),
tempByteOpRegister());
return define(lir, ins) && assignSafepoint(lir, ins);
}
bool
LIRGeneratorMIPS::visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins)
{

View File

@ -30,6 +30,7 @@ class LIRGeneratorMIPS : public LIRGeneratorShared
// stores and loads; on MIPS all registers are okay.
LAllocation useByteOpRegister(MDefinition *mir);
LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
LDefinition tempByteOpRegister();
inline LDefinition tempToUnbox() {
return LDefinition::BogusTemp();
@ -109,6 +110,7 @@ class LIRGeneratorMIPS : public LIRGeneratorShared
bool visitSimdValueX4(MSimdValueX4 *ins);
bool visitCompareExchangeTypedArrayElement(MCompareExchangeTypedArrayElement *ins);
bool visitAtomicTypedArrayElementBinop(MAtomicTypedArrayElementBinop *ins);
bool visitSubstr(MSubstr *ins);
};
typedef LIRGeneratorMIPS LIRGeneratorSpecific;

View File

@ -1549,11 +1549,11 @@ MacroAssemblerMIPSCompat::callIon(Register callee)
}
}
void
MacroAssemblerMIPSCompat::callIonFromAsmJS(Register callee)
MacroAssemblerMIPSCompat::callJitFromAsmJS(Register callee)
{
ma_callIonNoPush(callee);
// The Ion ABI has the callee pop the return address off the stack.
// The JIT ABI has the callee pop the return address off the stack.
// The asm.js caller assumes that the call leaves sp unchanged, so bump
// the stack.
subPtr(Imm32(sizeof(void*)), StackPointer);

View File

@ -1140,7 +1140,7 @@ public:
// Makes an Ion call using the only two methods that it is sane for
// indep code to make a call
void callIon(Register callee);
void callIonFromAsmJS(Register callee);
void callJitFromAsmJS(Register callee);
void reserveStack(uint32_t amount);
void freeStack(uint32_t amount);

View File

@ -29,6 +29,7 @@ class LIRGeneratorNone : public LIRGeneratorShared
LAllocation useByteOpRegister(MDefinition *) { MOZ_CRASH(); }
LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *) { MOZ_CRASH(); }
LDefinition tempByteOpRegister() { MOZ_CRASH(); }
LDefinition tempToUnbox() { MOZ_CRASH(); }
bool needTempForPostBarrier() { MOZ_CRASH(); }
LDefinition tempForDispatchCache(MIRType v = MIRType_None) { MOZ_CRASH(); }

View File

@ -191,7 +191,7 @@ class MacroAssemblerNone : public Assembler
void callWithExitFrame(JitCode *, Register) { MOZ_CRASH(); }
void callIon(Register callee) { MOZ_CRASH(); }
void callIonFromAsmJS(Register callee) { MOZ_CRASH(); }
void callJitFromAsmJS(Register callee) { MOZ_CRASH(); }
void nop() { MOZ_CRASH(); }
void breakpoint() { MOZ_CRASH(); }

View File

@ -1205,7 +1205,7 @@ class MacroAssemblerX86Shared : public Assembler
void callIon(Register callee) {
call(callee);
}
void callIonFromAsmJS(Register callee) {
void callJitFromAsmJS(Register callee) {
call(callee);
}
void call(AsmJSImmPtr target) {

View File

@ -49,6 +49,12 @@ LIRGeneratorX64::useByteOpRegisterOrNonDoubleConstant(MDefinition *mir)
return useRegisterOrNonDoubleConstant(mir);
}
LDefinition
LIRGeneratorX64::tempByteOpRegister()
{
return temp();
}
LDefinition
LIRGeneratorX64::tempToUnbox()
{
@ -190,6 +196,18 @@ LIRGeneratorX64::visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins)
return define(new(alloc()) LAsmJSLoadFuncPtr(useRegister(ins->index()), temp()), ins);
}
bool
LIRGeneratorX64::visitSubstr(MSubstr *ins)
{
LSubstr *lir = new (alloc()) LSubstr(useRegister(ins->string()),
useRegister(ins->begin()),
useRegister(ins->length()),
temp(),
temp(),
tempByteOpRegister());
return define(lir, ins) && assignSafepoint(lir, ins);
}
bool
LIRGeneratorX64::visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins)
{

View File

@ -32,6 +32,7 @@ class LIRGeneratorX64 : public LIRGeneratorX86Shared
// stores and loads; on x64 all registers are okay.
LAllocation useByteOpRegister(MDefinition *mir);
LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
LDefinition tempByteOpRegister();
LDefinition tempToUnbox();
@ -53,6 +54,7 @@ class LIRGeneratorX64 : public LIRGeneratorX86Shared
bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
bool visitSubstr(MSubstr *ins);
static bool allowInlineForkJoinGetSlice() {
return true;

View File

@ -76,6 +76,12 @@ LIRGeneratorX86::useByteOpRegisterOrNonDoubleConstant(MDefinition *mir)
return useFixed(mir, eax);
}
LDefinition
LIRGeneratorX86::tempByteOpRegister()
{
return tempFixed(eax);
}
bool
LIRGeneratorX86::visitBox(MBox *box)
{
@ -309,3 +315,18 @@ LIRGeneratorX86::visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins)
{
return define(new(alloc()) LAsmJSLoadFuncPtr(useRegisterAtStart(ins->index())), ins);
}
bool
LIRGeneratorX86::visitSubstr(MSubstr *ins)
{
// Due to lack of registers on x86, we reuse the string register as
// temporary. As a result we only need two temporary registers and take a
// bugos temporary as fifth argument.
LSubstr *lir = new (alloc()) LSubstr(useRegister(ins->string()),
useRegister(ins->begin()),
useRegister(ins->length()),
temp(),
LDefinition::BogusTemp(),
tempByteOpRegister());
return define(lir, ins) && assignSafepoint(lir, ins);
}

View File

@ -33,6 +33,7 @@ class LIRGeneratorX86 : public LIRGeneratorX86Shared
// give us one of {al,bl,cl,dl}. For now, just useFixed(al).
LAllocation useByteOpRegister(MDefinition *mir);
LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
LDefinition tempByteOpRegister();
inline LDefinition tempToUnbox() {
return LDefinition::BogusTemp();
@ -55,6 +56,7 @@ class LIRGeneratorX86 : public LIRGeneratorX86Shared
bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
bool visitSubstr(MSubstr *ins);
bool lowerPhi(MPhi *phi);
static bool allowTypedElementHoleCheck() {

View File

@ -560,7 +560,7 @@ FinalizeTypedArenas(FreeOp *fop,
{
// When operating in the foreground, take the lock at the top.
Maybe<AutoLockGC> maybeLock;
if (!fop->runtime()->gc.isBackgroundSweeping())
if (!fop->onBackgroundThread())
maybeLock.emplace(fop->runtime());
/*
@ -580,9 +580,9 @@ FinalizeTypedArenas(FreeOp *fop,
if (nmarked) {
dest.insertAt(aheader, nfree);
} else if (keepArenas) {
} else if (keepArenas == ArenaLists::KEEP_ARENAS) {
aheader->chunk()->recycleArena(aheader, dest, thingKind, thingsPerArena);
} else if (fop->runtime()->gc.isBackgroundSweeping()) {
} else if (fop->onBackgroundThread()) {
// When background sweeping, take the lock around each release so
// that we do not block the foreground for extended periods.
AutoLockGC lock(fop->runtime());
@ -1159,7 +1159,6 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
lastMarkSlice(false),
sweepOnBackgroundThread(false),
foundBlackGrayEdges(false),
sweepingZones(nullptr),
freeLifoAlloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
zoneGroupIndex(0),
zoneGroups(nullptr),
@ -2869,7 +2868,6 @@ inline void
ArenaLists::queueForBackgroundSweep(FreeOp *fop, AllocKind thingKind)
{
MOZ_ASSERT(IsBackgroundFinalized(thingKind));
MOZ_ASSERT(!fop->runtime()->gc.isBackgroundSweeping());
ArenaList *al = &arenaLists[thingKind];
if (al->isEmpty()) {
@ -2885,10 +2883,11 @@ ArenaLists::queueForBackgroundSweep(FreeOp *fop, AllocKind thingKind)
}
/*static*/ void
ArenaLists::backgroundFinalize(FreeOp *fop, ArenaHeader *listHead)
ArenaLists::backgroundFinalize(FreeOp *fop, ArenaHeader *listHead, ArenaHeader **empty)
{
MOZ_ASSERT(listHead);
MOZ_ASSERT(!InParallelSection());
MOZ_ASSERT(empty);
AllocKind thingKind = listHead->getAllocKind();
Zone *zone = listHead->zone;
@ -2897,9 +2896,11 @@ ArenaLists::backgroundFinalize(FreeOp *fop, ArenaHeader *listHead)
SortedArenaList finalizedSorted(thingsPerArena);
SliceBudget budget;
FinalizeArenas(fop, &listHead, finalizedSorted, thingKind, budget, RELEASE_ARENAS);
FinalizeArenas(fop, &listHead, finalizedSorted, thingKind, budget, KEEP_ARENAS);
MOZ_ASSERT(!listHead);
finalizedSorted.extractEmpty(empty);
// When arenas are queued for background finalization, all arenas are moved
// to arenaListsToSweep[], leaving the arenaLists[] empty. However, new
// arenas may be allocated before background finalization finishes; now that
@ -3444,38 +3445,41 @@ GCRuntime::expireChunksAndArenas(bool shouldShrink, AutoLockGC &lock)
}
void
GCRuntime::sweepBackgroundThings()
GCRuntime::sweepBackgroundThings(ZoneList &zones, ThreadType threadType)
{
/*
* We must finalize in the correct order, see comments in
* finalizeObjects.
*/
FreeOp fop(rt);
for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
for (Zone *zone = sweepingZones; zone; zone = zone->gcNextGraphNode) {
// We must finalize thing kinds in the order specified by BackgroundFinalizePhases.
FreeOp fop(rt, threadType);
while (!zones.isEmpty()) {
Zone *zone = zones.front();
ArenaHeader *emptyArenas = nullptr;
for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
for (unsigned index = 0 ; index < BackgroundFinalizePhases[phase].length ; ++index) {
AllocKind kind = BackgroundFinalizePhases[phase].kinds[index];
ArenaHeader *arenas = zone->allocator.arenas.arenaListsToSweep[kind];
if (arenas)
ArenaLists::backgroundFinalize(&fop, arenas);
ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas);
}
}
}
sweepingZones = nullptr;
AutoLockGC lock(rt);
ReleaseArenaList(rt, emptyArenas, lock);
zones.removeFront();
}
}
void
GCRuntime::assertBackgroundSweepingFinished()
{
#ifdef DEBUG
MOZ_ASSERT(!sweepingZones);
MOZ_ASSERT(backgroundSweepZones.isEmpty());
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
MOZ_ASSERT(!zone->isOnList());
for (unsigned i = 0; i < FINALIZE_LIMIT; ++i) {
MOZ_ASSERT(!zone->allocator.arenas.arenaListsToSweep[i]);
MOZ_ASSERT(zone->allocator.arenas.doneBackgroundFinalize(AllocKind(i)));
}
}
MOZ_ASSERT(freeLifoAlloc.computedSizeOfExcludingThis() == 0);
#endif
}
@ -3622,22 +3626,43 @@ BackgroundAllocTask::run()
}
void
GCHelperState::startBackgroundSweep()
GCRuntime::queueZonesForBackgroundSweep(ZoneList &zones)
{
AutoLockHelperThreadState helperLock;
AutoLockGC lock(rt);
backgroundSweepZones.append(zones);
helperState.maybeStartBackgroundSweep(lock);
}
void
GCRuntime::freeUnusedLifoBlocksAfterSweeping(LifoAlloc *lifo)
{
MOZ_ASSERT(isHeapBusy());
AutoLockGC lock(rt);
freeLifoAlloc.transferUnusedFrom(lifo);
}
void
GCRuntime::freeAllLifoBlocksAfterSweeping(LifoAlloc *lifo)
{
MOZ_ASSERT(isHeapBusy());
AutoLockGC lock(rt);
freeLifoAlloc.transferFrom(lifo);
}
void
GCHelperState::maybeStartBackgroundSweep(const AutoLockGC &lock)
{
MOZ_ASSERT(CanUseExtraThreads());
AutoLockHelperThreadState helperLock;
AutoLockGC lock(rt);
MOZ_ASSERT(state() == IDLE);
MOZ_ASSERT(!sweepFlag);
sweepFlag = true;
shrinkFlag = false;
startBackgroundThread(SWEEPING);
if (state() == IDLE)
startBackgroundThread(SWEEPING);
}
/* Must be called with the GC lock taken. */
void
GCHelperState::startBackgroundShrink()
GCHelperState::startBackgroundShrink(const AutoLockGC &lock)
{
MOZ_ASSERT(CanUseExtraThreads());
switch (state()) {
@ -3667,13 +3692,16 @@ GCHelperState::waitBackgroundSweepEnd()
void
GCHelperState::doSweep(AutoLockGC &lock)
{
if (sweepFlag) {
while (sweepFlag) {
sweepFlag = false;
ZoneList zones;
zones.transferFrom(rt->gc.backgroundSweepZones);
LifoAlloc freeLifoAlloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
freeLifoAlloc.transferFrom(&rt->gc.freeLifoAlloc);
AutoUnlockGC unlock(lock);
rt->gc.sweepBackgroundThings();
rt->gc.freeLifoAlloc.freeAll();
rt->gc.sweepBackgroundThings(zones, BackgroundThread);
freeLifoAlloc.freeAll();
}
bool shrinking = shrinkFlag;
@ -3762,6 +3790,8 @@ Zone::sweepCompartments(FreeOp *fop, bool keepAtleastOne, bool lastGC)
void
GCRuntime::sweepZones(FreeOp *fop, bool lastGC)
{
AutoLockGC lock(rt); // Avoid race with background sweeping.
JSZoneCallback callback = rt->destroyZoneCallback;
/* Skip the atomsCompartment zone. */
@ -3775,10 +3805,13 @@ GCRuntime::sweepZones(FreeOp *fop, bool lastGC)
Zone *zone = *read++;
if (zone->wasGCStarted()) {
if ((zone->allocator.arenas.arenaListsAreEmpty() && !zone->hasMarkedCompartments()) ||
lastGC)
if ((!zone->isQueuedForBackgroundSweep() &&
zone->allocator.arenas.arenaListsAreEmpty() &&
!zone->hasMarkedCompartments()) || lastGC)
{
zone->allocator.arenas.checkEmptyFreeLists();
AutoUnlockGC unlock(lock);
if (callback)
callback(zone);
zone->sweepCompartments(fop, false, lastGC);
@ -3793,27 +3826,12 @@ GCRuntime::sweepZones(FreeOp *fop, bool lastGC)
zones.resize(write - zones.begin());
}
void
GCRuntime::freeUnusedLifoBlocksAfterSweeping(LifoAlloc *lifo)
{
MOZ_ASSERT(isHeapBusy());
freeLifoAlloc.transferUnusedFrom(lifo);
}
void
GCRuntime::freeAllLifoBlocksAfterSweeping(LifoAlloc *lifo)
{
MOZ_ASSERT(isHeapBusy());
freeLifoAlloc.transferFrom(lifo);
}
void
GCRuntime::purgeRuntime()
{
for (GCCompartmentsIter comp(rt); !comp.done(); comp.next())
comp->purge();
freeUnusedLifoBlocksAfterSweeping(&rt->tempLifoAlloc);
rt->interpreterStack().purge(rt);
@ -4264,6 +4282,8 @@ js::gc::MarkingValidator::nonIncrementalMark()
JSRuntime *runtime = gc->rt;
GCMarker *gcmarker = &gc->marker;
gc->waitBackgroundSweepEnd();
/* Save existing mark bits. */
for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next()) {
ChunkBitmap *bitmap = &chunk->bitmap;
@ -4372,6 +4392,8 @@ js::gc::MarkingValidator::validate()
if (!initialized)
return;
gc->waitBackgroundSweepEnd();
for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next()) {
BitmapMap::Ptr ptr = map.lookup(chunk);
if (!ptr)
@ -5169,12 +5191,20 @@ GCRuntime::beginSweepingZoneGroup()
void
GCRuntime::endSweepingZoneGroup()
{
/* Update the GC state for zones we have swept and unlink the list. */
/* Update the GC state for zones we have swept. */
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
MOZ_ASSERT(zone->isGCSweeping());
zone->setGCState(Zone::Finished);
}
/* Start background thread to sweep zones if required. */
if (sweepOnBackgroundThread) {
ZoneList zones;
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next())
zones.append(zone);
queueZonesForBackgroundSweep(zones);
}
/* Reset the list of arenas marked as being allocated during sweep phase. */
while (ArenaHeader *arena = arenasAllocatedDuringSweep) {
arenasAllocatedDuringSweep = arena->getNextAllocDuringSweep();
@ -5206,7 +5236,7 @@ GCRuntime::beginSweepPhase(bool lastGC)
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
sweepOnBackgroundThread =
!lastGC && !TraceEnabled() && CanUseExtraThreads() && !shouldCompact();
!lastGC && !TraceEnabled() && CanUseExtraThreads();
releaseObservedTypes = shouldReleaseObservedTypes();
@ -5483,18 +5513,14 @@ GCRuntime::endSweepPhase(bool lastGC)
grayBitsValid = true;
}
/* Set up list of zones for sweeping of background things. */
MOZ_ASSERT(!sweepingZones);
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
zone->gcNextGraphNode = sweepingZones;
sweepingZones = zone;
}
/* If not sweeping on background thread then we must do it here. */
if (!sweepOnBackgroundThread) {
gcstats::AutoPhase ap(stats, gcstats::PHASE_DESTROY);
sweepBackgroundThings();
ZoneList zones;
for (GCZonesIter zone(rt); !zone.done(); zone.next())
zones.append(zone);
sweepBackgroundThings(zones, MainThread);
/*
* Destroy arenas after we finished the sweeping so finalizers can
@ -5534,20 +5560,28 @@ GCRuntime::endSweepPhase(bool lastGC)
}
}
#endif
if (sweepOnBackgroundThread)
helperState.startBackgroundSweep();
}
#ifdef JSGC_COMPACTING
void
bool
GCRuntime::compactPhase(bool lastGC)
{
MOZ_ASSERT(rt->gc.nursery.isEmpty());
MOZ_ASSERT(!sweepOnBackgroundThread);
#ifndef JSGC_COMPACTING
MOZ_CRASH();
#else
gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
if (isIncremental) {
// Poll for end of background sweeping
AutoLockGC lock(rt);
if (isBackgroundSweeping())
return false;
} else {
waitBackgroundSweepEnd();
}
MOZ_ASSERT(rt->gc.nursery.isEmpty());
assertBackgroundSweepingFinished();
ArenaHeader *relocatedList = relocateArenas();
updatePointersToRelocatedCells();
@ -5583,8 +5617,10 @@ GCRuntime::compactPhase(bool lastGC)
zone->allocator.arenas.checkEmptyFreeLists();
}
#endif
}
#endif // JSGC_COMPACTING
return true;
}
void
GCRuntime::finishCollection()
@ -5710,6 +5746,8 @@ GCRuntime::resetIncrementalGC(const char *reason)
rt->setNeedsIncrementalBarrier(false);
AssertNeedsBarrierFlagsConsistent(rt);
freeLifoAlloc.freeAll();
incrementalState = NO_INCREMENTAL;
MOZ_ASSERT(!marker.shouldCheckCompartments());
@ -5735,6 +5773,11 @@ GCRuntime::resetIncrementalGC(const char *reason)
break;
}
#ifdef JSGC_COMPACTING
case COMPACT:
break;
#endif
default:
MOZ_CRASH("Invalid incremental GC state");
}
@ -5927,24 +5970,31 @@ GCRuntime::incrementalCollectSlice(SliceBudget &budget, JS::gcreason::Reason rea
/* fall through */
}
case SWEEP: {
bool finished = sweepPhase(budget);
if (!finished)
break;
case SWEEP:
{
bool finished = sweepPhase(budget);
if (!finished)
break;
}
endSweepPhase(lastGC);
#ifdef JSGC_COMPACTING
incrementalState = COMPACT;
/* Yield before compacting since it is not incremental. */
if (shouldCompact() && isIncremental)
break;
case COMPACT:
if (shouldCompact()) {
incrementalState = COMPACT;
compactPhase(lastGC);
bool finished = compactPhase(lastGC);
if (!finished)
break;
}
#endif
finishCollection();
incrementalState = NO_INCREMENTAL;
break;
}
default:
MOZ_ASSERT(false);
@ -6078,9 +6128,8 @@ GCRuntime::gcCycle(bool incremental, SliceBudget &budget, JSGCInvocationKind gck
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
// As we are about to purge caches and clear the mark bits, wait for
// background finalization to finish. It cannot run between slices
// so we only need to wait on the first slice.
// As we are about to clear the mark bits, wait for background
// finalization to finish. We only need to wait on the first slice.
if (incrementalState == NO_INCREMENTAL)
waitBackgroundSweepEnd();
@ -6389,7 +6438,7 @@ GCRuntime::shrinkBuffers()
MOZ_ASSERT(!rt->isHeapBusy());
if (CanUseExtraThreads())
helperState.startBackgroundShrink();
helperState.startBackgroundShrink(lock);
else
expireChunksAndArenas(true, lock);
}

View File

@ -39,6 +39,12 @@ enum HeapState {
MinorCollecting // doing a GC of the minor heap (nursery)
};
enum ThreadType
{
MainThread,
BackgroundThread
};
namespace jit {
class JitCode;
}
@ -52,9 +58,7 @@ enum State {
MARK_ROOTS,
MARK,
SWEEP,
#ifdef JSGC_COMPACTING
COMPACT
#endif
};
/* Return a printable string for the given kind, for diagnostic purposes. */
@ -845,7 +849,7 @@ class ArenaLists
bool foregroundFinalize(FreeOp *fop, AllocKind thingKind, SliceBudget &sliceBudget,
SortedArenaList &sweepList);
static void backgroundFinalize(FreeOp *fop, ArenaHeader *listHead);
static void backgroundFinalize(FreeOp *fop, ArenaHeader *listHead, ArenaHeader **empty);
void wipeDuringParallelExecution(JSRuntime *rt);
@ -1040,11 +1044,8 @@ class GCHelperState
void work();
/* Must be called with the GC lock taken. */
void startBackgroundSweep();
/* Must be called with the GC lock taken. */
void startBackgroundShrink();
void maybeStartBackgroundSweep(const AutoLockGC &lock);
void startBackgroundShrink(const AutoLockGC &lock);
/* Must be called without the GC lock taken. */
void waitBackgroundSweepEnd();
@ -1457,6 +1458,34 @@ class AutoEnterOOMUnsafeRegion {};
bool
IsInsideGGCNursery(const gc::Cell *cell);
// A singly linked list of zones.
class ZoneList
{
static Zone * const End;
Zone *head;
Zone *tail;
public:
ZoneList();
explicit ZoneList(Zone *singleZone);
bool isEmpty() const;
Zone *front() const;
void append(Zone *zone);
void append(ZoneList& list);
Zone *removeFront();
void transferFrom(ZoneList &other);
private:
void check() const;
ZoneList(const ZoneList &other) MOZ_DELETE;
ZoneList &operator=(const ZoneList &other) MOZ_DELETE;
};
} /* namespace gc */
#ifdef DEBUG

View File

@ -364,14 +364,15 @@ class NewObjectCache
class FreeOp : public JSFreeOp
{
Vector<void *, 0, SystemAllocPolicy> freeLaterList;
ThreadType threadType;
public:
static FreeOp *get(JSFreeOp *fop) {
return static_cast<FreeOp *>(fop);
}
explicit FreeOp(JSRuntime *rt)
: JSFreeOp(rt)
explicit FreeOp(JSRuntime *rt, ThreadType thread = MainThread)
: JSFreeOp(rt), threadType(thread)
{}
~FreeOp() {
@ -379,6 +380,10 @@ class FreeOp : public JSFreeOp
free_(freeLaterList[i]);
}
bool onBackgroundThread() {
return threadType == BackgroundThread;
}
inline void free_(void *p);
inline void freeLater(void *p);

View File

@ -1949,11 +1949,13 @@ nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
#ifdef MOZ_DUMP_PAINTING
void
nsDisplaySolidColor::WriteDebugInfo(nsACString& aTo)
nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream)
{
aTo += nsPrintfCString(" (rgba %d,%d,%d,%d)",
NS_GET_R(mColor), NS_GET_G(mColor),
NS_GET_B(mColor), NS_GET_A(mColor));
aStream << " (rgba "
<< (int)NS_GET_R(mColor) << ","
<< (int)NS_GET_G(mColor) << ","
<< (int)NS_GET_B(mColor) << ","
<< (int)NS_GET_A(mColor) << ")";
}
#endif
@ -2663,9 +2665,9 @@ nsDisplayThemedBackground::~nsDisplayThemedBackground()
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayThemedBackground::WriteDebugInfo(nsACString& aTo)
nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream)
{
aTo += nsPrintfCString(" (themed, appearance:%d)", mAppearance);
aStream << " (themed, appearance:" << (int)mAppearance << ")";
}
#endif
@ -2868,11 +2870,10 @@ nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder,
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayBackgroundColor::WriteDebugInfo(nsACString& aTo)
nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream)
{
aTo += nsPrintfCString(" (rgba %f,%f,%f,%f)",
mColor.r, mColor.g,
mColor.b, mColor.a);
aStream << " (rgba " << mColor.r << "," << mColor.g << ","
<< mColor.b << "," << mColor.a << ")";
}
#endif
@ -2987,6 +2988,22 @@ nsDisplayLayerEventRegions::AddInactiveScrollPort(const nsRect& aRect)
mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, aRect);
}
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayLayerEventRegions::WriteDebugInfo(std::stringstream& aStream)
{
if (!mHitRegion.IsEmpty()) {
AppendToString(aStream, mHitRegion, " (hitRegion ", ")");
}
if (!mMaybeHitRegion.IsEmpty()) {
AppendToString(aStream, mMaybeHitRegion, " (maybeHitRegion ", ")");
}
if (!mDispatchToContentHitRegion.IsEmpty()) {
AppendToString(aStream, mDispatchToContentHitRegion, " (dispatchToContentRegion ", ")");
}
}
#endif
nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder,
nsIFrame* aCaretFrame)
: nsDisplayItem(aBuilder, aCaretFrame)
@ -3678,9 +3695,9 @@ bool nsDisplayOpacity::TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* a
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayOpacity::WriteDebugInfo(nsACString& aTo)
nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream)
{
aTo += nsPrintfCString(" (opacity %f)", mOpacity);
aStream << " (opacity " << mOpacity << ")";
}
#endif
@ -4443,10 +4460,10 @@ nsDisplayScrollLayer::GetScrollLayerCount()
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayScrollLayer::WriteDebugInfo(nsACString& aTo)
nsDisplayScrollLayer::WriteDebugInfo(std::stringstream& aStream)
{
aTo += nsPrintfCString(" (scrollframe %p scrolledframe %p)",
mScrollFrame, mScrolledFrame);
aStream << " (scrollframe " << mScrollFrame
<< " scrolledFrame " << mScrolledFrame << ")";
}
#endif
@ -5631,11 +5648,9 @@ bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayTransform::WriteDebugInfo(nsACString& aTo)
nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream)
{
std::stringstream ss;
AppendToString(ss, GetTransform());
aTo += ss.str().c_str();
AppendToString(aStream, GetTransform());
}
#endif

View File

@ -1345,7 +1345,7 @@ public:
*/
virtual const char* Name() = 0;
virtual void WriteDebugInfo(nsACString& aTo) {}
virtual void WriteDebugInfo(std::stringstream& aStream) {}
#endif
nsDisplayItem* GetAbove() { return mAbove; }
@ -2165,7 +2165,7 @@ public:
}
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR)
@ -2326,7 +2326,7 @@ public:
nsRegion* aInvalidRegion) MOZ_OVERRIDE;
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
protected:
nsRect GetBoundsInternal();
@ -2389,7 +2389,7 @@ public:
NS_DISPLAY_DECL_NAME("BackgroundColor", TYPE_BACKGROUND_COLOR)
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
protected:
@ -2629,6 +2629,10 @@ public:
const nsRegion& MaybeHitRegion() { return mMaybeHitRegion; }
const nsRegion& DispatchToContentHitRegion() { return mDispatchToContentHitRegion; }
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
private:
// Relative to aFrame's reference frame.
// These are the points that are definitely in the hit region.
@ -2834,7 +2838,7 @@ public:
bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder);
NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY)
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE;
@ -3132,7 +3136,7 @@ public:
virtual nsIFrame* GetScrolledFrame() { return mScrolledFrame; }
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
bool IsDisplayPortOpaque() { return mDisplayPortContentsOpaque; }
@ -3543,7 +3547,7 @@ public:
bool ShouldPrerender(nsDisplayListBuilder* aBuilder);
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
private:

View File

@ -191,9 +191,7 @@ PrintDisplayItemTo(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
}
// Display item specific debug info
nsCString itemStr;
aItem->WriteDebugInfo(itemStr);
aStream << itemStr.get();
aItem->WriteDebugInfo(aStream);
if (aDumpHtml && aItem->Painted()) {
aStream << "</a>";

View File

@ -286,11 +286,13 @@ nsDisplayCanvasBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
#ifdef MOZ_DUMP_PAINTING
void
nsDisplayCanvasBackgroundColor::WriteDebugInfo(nsACString& aTo)
nsDisplayCanvasBackgroundColor::WriteDebugInfo(std::stringstream& aStream)
{
aTo += nsPrintfCString(" (rgba %d,%d,%d,%d)",
NS_GET_R(mColor), NS_GET_G(mColor),
NS_GET_B(mColor), NS_GET_A(mColor));
aStream << " (rgba "
<< (int)NS_GET_R(mColor) << ","
<< (int)NS_GET_G(mColor) << ","
<< (int)NS_GET_B(mColor) << ","
<< (int)NS_GET_A(mColor) << ")";
}
#endif

View File

@ -214,7 +214,7 @@ public:
NS_DISPLAY_DECL_NAME("CanvasBackgroundColor", TYPE_CANVAS_BACKGROUND_COLOR)
#ifdef MOZ_DUMP_PAINTING
virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
virtual void WriteDebugInfo(std::stringstream& aStream) MOZ_OVERRIDE;
#endif
private:

View File

@ -1453,9 +1453,7 @@ nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
bool* aSnap)
{
*aSnap = true;
bool animated;
if (mImage && mImage->GetAnimated(&animated) == NS_OK && !animated &&
mImage->FrameIsOpaque(imgIContainer::FRAME_CURRENT)) {
if (mImage && mImage->IsOpaque()) {
// OK, the entire region painted by the image is opaque. But what is that
// region? It's the image's "dest rect" (the rect where a full copy of
// the image is mapped), clipped to the container's content box (which is

View File

@ -2054,8 +2054,8 @@ nsStyleImage::IsOpaque() const
mImage->GetImage(getter_AddRefs(imageContainer));
NS_ABORT_IF_FALSE(imageContainer, "IsComplete() said image container is ready");
// Check if the crop region of the current image frame is opaque.
if (imageContainer->FrameIsOpaque(imgIContainer::FRAME_CURRENT)) {
// Check if the crop region of the image is opaque.
if (imageContainer->IsOpaque()) {
if (!mCropRect)
return true;

View File

@ -723,7 +723,7 @@ int ParseFTPList(const char *line, struct list_state *state,
* "07-21-00 01:19PM 52275 Name Plate.jpg"
* "07-14-00 01:38PM 2250540 Valentineoffprank-HiRes.jpg"
*/
if ((numtoks >= 4) && toklen[0] == 8 && toklen[1] == 7 &&
if ((numtoks >= 4) && (toklen[0] == 8 || toklen[0] == 10) && toklen[1] == 7 &&
(*tokens[2] == '<' || isdigit(*tokens[2])) )
{
p = tokens[0];

View File

@ -1007,6 +1007,89 @@ static struct nsMyTrustedEVInfo myTrustedEVInfos[] = {
"VQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=",
"RFc0JFuBiZs18s64KztbpybwdSg=",
nullptr
},
{
// CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
"1.3.6.1.4.1.6449.1.2.1.5.1",
"COMODORSACertificationAuthority",
SEC_OID_UNKNOWN,
{ 0x52, 0xF0, 0xE1, 0xC4, 0xE5, 0x8E, 0xC6, 0x29, 0x29, 0x1B, 0x60,
0x31, 0x7F, 0x07, 0x46, 0x71, 0xB8, 0x5D, 0x7E, 0xA8, 0x0D, 0x5B,
0x07, 0x27, 0x34, 0x63, 0x53, 0x4B, 0x32, 0xB4, 0x02, 0x34 },
"MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAw"
"DgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkG"
"A1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
"TKr5yttjb+Af907YWwOGnQ==",
nullptr
},
{
// CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US
"1.3.6.1.4.1.6449.1.2.1.5.1",
"USERTrustRSACertificationAuthority",
SEC_OID_UNKNOWN,
{ 0xE7, 0x93, 0xC9, 0xB0, 0x2F, 0xD8, 0xAA, 0x13, 0xE2, 0x1C, 0x31,
0x22, 0x8A, 0xCC, 0xB0, 0x81, 0x19, 0x64, 0x3B, 0x74, 0x9C, 0x89,
0x89, 0x64, 0xB1, 0x74, 0x6D, 0x46, 0xC3, 0xD4, 0xCB, 0xD2 },
"MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxML"
"SmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwG"
"A1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
"Af1tMPyjylGoG7xkDjUDLQ==",
nullptr
},
{
// CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US
"1.3.6.1.4.1.6449.1.2.1.5.1",
"USERTrust ECC Certification Authority",
SEC_OID_UNKNOWN,
{ 0x4F, 0xF4, 0x60, 0xD5, 0x4B, 0x9C, 0x86, 0xDA, 0xBF, 0xBC, 0xFC,
0x57, 0x12, 0xE0, 0x40, 0x0D, 0x2B, 0xED, 0x3F, 0xBC, 0x4D, 0x4F,
0xBD, 0xAA, 0x86, 0xE0, 0x6A, 0xDC, 0xD2, 0xA9, 0xAD, 0x7A },
"MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxML"
"SmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwG"
"A1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
"XIuZxVqUxdJxVt7NiYDMJg==",
nullptr
},
{
// CN=GlobalSign,O=GlobalSign,OU=GlobalSign ECC Root CA - R4
"1.3.6.1.4.1.4146.1.1",
"GlobalSign ECC Root CA - R4",
SEC_OID_UNKNOWN,
{ 0xBE, 0xC9, 0x49, 0x11, 0xC2, 0x95, 0x56, 0x76, 0xDB, 0x6C, 0x0A,
0x55, 0x09, 0x86, 0xD7, 0x6E, 0x3B, 0xA0, 0x05, 0x66, 0x7C, 0x44,
0x2C, 0x97, 0x62, 0xB4, 0xFB, 0xB7, 0x73, 0xDE, 0x22, 0x8C },
"MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UE"
"ChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==",
"KjikHJYKBN5CsiilC+g0mAI=",
nullptr
},
{
// CN=GlobalSign,O=GlobalSign,OU=GlobalSign ECC Root CA - R5
"1.3.6.1.4.1.4146.1.1",
"GlobalSign ECC Root CA - R5",
SEC_OID_UNKNOWN,
{ 0x17, 0x9F, 0xBC, 0x14, 0x8A, 0x3D, 0xD0, 0x0F, 0xD2, 0x4E, 0xA1,
0x34, 0x58, 0xCC, 0x43, 0xBF, 0xA7, 0xF5, 0x9C, 0x81, 0x82, 0xD7,
0x83, 0xA5, 0x13, 0xF6, 0xEB, 0xEC, 0x10, 0x0C, 0x89, 0x24 },
"MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UE"
"ChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==",
"YFlJ4CYuu1X5CneKcflK2Gw=",
nullptr
},
{
// CN=Entrust.net Certification Authority (2048),OU=(c) 1999 Entrust.net Limited,OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.),O=Entrust.net
"2.16.840.1.114028.10.1.2",
"EntrustCA2048",
SEC_OID_UNKNOWN,
{ 0x6D, 0xC4, 0x71, 0x72, 0xE0, 0x1C, 0xBC, 0xB0, 0xBF, 0x62, 0x58,
0x0D, 0x89, 0x5F, 0xE2, 0xB8, 0xAC, 0x9A, 0xD4, 0xF8, 0x73, 0x80,
0x1E, 0x0C, 0x10, 0xB9, 0xC8, 0x37, 0xD2, 0x1E, 0xB1, 0x77 },
"MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3Qu"
"bmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMG"
"A1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
"cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp",
"OGPe+A==",
nullptr
}
};

View File

@ -13,7 +13,7 @@ do_get_profile(); // must be called before getting nsIX509CertDB
const certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
const SERVER_PORT = 8080;
const SERVER_PORT = 8888;
function start_ocsp_responder(expectedCertNames, expectedPaths,
expectedMethods) {

View File

@ -22,7 +22,7 @@ def generate_certs():
[noise_file, pwd_file] = CertUtils.init_nss_db(srcdir)
generate_ca_cert(srcdir, srcdir, noise_file, 'ca')
generate_child_cert(srcdir, srcdir, noise_file, 'int', 'ca', False, '')
ocsp_url = "http://www.example.com:8080/"
ocsp_url = "http://www.example.com:8888/"
generate_child_cert(srcdir, srcdir, noise_file, "a", 'int', True, ocsp_url)
generate_child_cert(srcdir, srcdir, noise_file, "b", 'int', True, ocsp_url)

View File

@ -91,12 +91,8 @@ run-sequentially = hardcoded ports
skip-if = os == "android"
[test_ocsp_url.js]
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android
skip-if = os == "android"
[test_ocsp_fetch_method.js]
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android
skip-if = os == "android"
[test_ocsp_no_hsts_upgrade.js]
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android
@ -105,9 +101,8 @@ skip-if = os == "android"
[test_keysize.js]
[test_keysize_ev.js]
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android
# Bug 1008316: B2G doesn't have EV enabled
skip-if = os == "android" || buildapp == "b2g"
skip-if = buildapp == "b2g"
[test_cert_chains.js]
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android

View File

@ -0,0 +1,46 @@
Actions
=======
.. py:currentmodule:: marionette
Action Sequences
----------------
:class:`Actions` are designed as a way to simulate user input as closely as possible
on a touch device like a smart phone. A common operation is to tap the screen
and drag your finger to another part of the screen and lift it off.
This can be simulated using an Action::
from marionette import Actions
start_element = marionette.find_element('id', 'start')
end_element = marionette.find_element('id', 'end')
action = Actions(marionette)
action.press(start_element).wait(1).move(end_element).release()
action.perform()
This will simulate pressing an element, waiting for one second, moving the
finger over to another element and then lifting the finger off the screen. The
wait is optional in this case, but can be useful for simulating delays typical
to a users behaviour.
Multi-Action Sequences
----------------------
Sometimes it may be necessary to simulate multiple actions at the same time.
For example a user may be dragging one finger while tapping another. This is
where :class:`MultiActions` come in. MultiActions are simply a way of combining
two or more actions together and performing them all at the same time::
action1 = Actions(marionette)
action1.press(start_element).move(end_element).release()
action2 = Actions(marionette)
action2.press(another_element).wait(1).release()
multi = MultiActions(marionette)
multi.add(action1)
multi.add(action2)
multi.perform()

View File

@ -0,0 +1,54 @@
Debugging
=========
.. py:currentmodule:: marionette
Sometimes when working with Marionette you'll run into unexpected behaviour and
need to do some debugging. This page outlines some of the Marionette methods
that can be useful to you.
Please note that the best tools for debugging are the `ones that ship with
Gecko`_. This page doesn't describe how to use those with Marionette. Also see
a related topic about `using the debugger with Marionette`_ on MDN.
.. _ones that ship with Gecko: https://developer.mozilla.org/en-US/docs/Tools
.. _using the debugger with Marionette: https://developer.mozilla.org/en-US/docs/Marionette/Debugging
Storing Logs on the Server
~~~~~~~~~~~~~~~~~~~~~~~~~~
By calling `~Marionette.log` it is possible to store a message on the server.
Logs can later be retrieved using `~Marionette.get_logs`. For example::
try:
marionette.log("Sending a click event") # logged at INFO level
elem.click()
except:
marionette.log("Something went wrong!", "ERROR")
print(marionette.get_logs())
Disclaimer: Example for illustrative purposes only, don't actually hide
tracebacks like that!
Seeing What's on the Page
~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes it's difficult to tell what is actually on the page that is being
manipulated. Either because it happens too fast, the window isn't big enough or
you are manipulating a remote server! There are two methods that can help you
out. The first is `~Marionette.screenshot`::
marionette.screenshot() # takes screenshot of entire frame
elem = marionette.find_element(By.ID, 'some-div')
marionette.screenshot(elem) # takes a screenshot of only the given element
Sometimes you just want to see the DOM layout. You can do this with the
`~Marionette.page_source` property. Note that the page source depends on the
context you are in::
print(marionette.page_source)
marionette.set_context('chrome')
print(marionette.page_source)

View File

@ -0,0 +1,126 @@
Finding Elements
================
.. py:currentmodule:: marionette
One of the most common and yet often most difficult tasks in Marionette is
finding a DOM element on a webpage or in the chrome UI. Marionette provides
several different search strategies to use when finding elements. All search
strategies work with both :func:`~Marionette.find_element` and
:func:`~Marionette.find_elements`, though some strategies are not implemented
in chrome scope.
In the event that more than one element is matched by the query,
:func:`~Marionette.find_element` will only return the first element found. In
the event that no elements are matched by the query,
:func:`~Marionette.find_element` will raise `NoSuchElementException` while
:func:`~Marionette.find_elements` will return an empty list.
Search Strategies
-----------------
Search strategies are defined in the :class:`By` class::
from marionette import By
print(By.ID)
The strategies are:
* `id` - The easiest way to find an element is to refer to its id directly::
container = client.find_element(By.ID, 'container')
* `class name` - To find elements belonging to a certain class, use `class name`::
buttons = client.find_elements(By.CLASS_NAME, 'button')
* `css selector` - It's also possible to find elements using a `css selector`_::
container_buttons = client.find_elements(By.CSS_SELECTOR, '#container .buttons')
* `name` - Find elements by their name attribute (not implemented in chrome
scope)::
form = client.find_element(By.NAME, 'signup')
* `tag name` - To find all the elements with a given tag, use `tag name`::
paragraphs = client.find_elements(By.TAG_NAME, 'p')
* `link text` - A convenience strategy for finding link elements by their
innerHTML (not implemented in chrome scope)::
link = client.find_element(By.LINK_TEXT, 'Click me!')
* `partial link text` - Same as `link text` except substrings of the innerHTML
are matched (not implemented in chrome scope)::
link = client.find_element(By.PARTIAL_LINK_TEXT, 'Clic')
* `xpath` - Find elements using an xpath_ query::
elem = client.find_element(By.XPATH, './/*[@id="foobar"')
.. _css selector: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors
.. _xpath: https://developer.mozilla.org/en-US/docs/Web/XPath
Chaining Searches
-----------------
In addition to the methods on the Marionette object, HTMLElement objects also
provide :func:`~HTMLElement.find_element` and :func:`~HTMLElement.find_elements`
methods. The difference is that only child nodes of the element will be searched.
Consider the following html snippet::
<div id="content">
<span id="main"></span>
</div>
<div id="footer"></div>
Doing the following will work::
client.find_element(By.ID, 'container').find_element(By.ID, 'main')
But this will raise a `NoSuchElementException`::
client.find_element(By.ID, 'container').find_element(By.ID, 'footer')
Finding Anonymous Nodes
-----------------------
When working in chrome scope, for example manipulating the Firefox user
interface, you may run into something called an anonymous node.
Firefox uses a markup language called XUL_ for its interface. XUL is similar
to HTML in that it has a DOM and tags that render controls on the display. One
ability of XUL is to create re-useable widgets that are made up out of several
smaller XUL elements. These widgets can be bound to the DOM using something
called the `XML binding language (XBL)`_.
The end result is that the DOM sees the widget as a single entity. It doesn't
know anything about how that widget is made up. All of the smaller XUL elements
that make up the widget are called `anonymous content`_. It is not possible to
query such elements using traditional DOM methods like `getElementById`.
Marionette provides two special strategies used for finding anonymous content.
Unlike normal elements, anonymous nodes can only be seen by their parent. So
it's necessary to first find the parent element and then search for the
anonymous children from there.
* `anon` - Finds all anonymous children of the element, there is no search term
so `None` must be passed in::
anon_children = client.find_element('id', 'parent').find_elements('anon', None)
* `anon attribute` - Find an anonymous child based on an attribute. An
unofficial convention is for anonymous nodes to have an
`anonid` attribute::
anon_child = client.find_element('id', 'parent').find_element('anon attribute', {'anonid': 'container'})
.. _XUL: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL
.. _XML binding language (XBL): https://developer.mozilla.org/en-US/docs/XBL
.. _anonymous content: https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/Anonymous_Content

View File

@ -0,0 +1,13 @@
Advanced Topics
===============
Here are a collection of articles explaining some of the more complicated
aspects of Marionette.
.. toctree::
:maxdepth: 1
findelement
stale
actions
debug

View File

@ -0,0 +1,71 @@
Dealing with Stale Elements
===========================
.. py:currentmodule:: marionette
Marionette does not keep a live representation of the DOM saved. All it can do
is send commands to the Marionette server which queries the DOM on the client's
behalf. References to elements are also not passed from server to client. A
unique id is generated for each element that gets referenced and a mapping of
id to element object is stored on the server. When commands such as
:func:`~HTMLElement.click()` are run, the client sends the element's id along
with the command. The server looks up the proper DOM element in its reference
table and executes the command on it.
In practice this means that the DOM can change state and Marionette will never
know until it sends another query. For example, look at the following HTML::
<head>
<script type=text/javascript>
function addDiv() {
var div = document.createElement("div");
document.getElementById("container").appendChild(div);
}
</script>
</head>
<body>
<div id="container">
</div>
<input id="button" type=button onclick="addDiv();">
</body>
Care needs to be taken as the DOM is being modified after the page has loaded.
The following code has a race condition::
button = client.find_element('id', 'button')
button.click()
assert len(client.find_elements('css selector', '#container div')) > 0
Explicit Waiting and Expected Conditions
----------------------------------------
To avoid the above scenario, manual synchronisation is needed. Waits are used
to pause program execution until a given condition is true. This is a useful
technique to employ when documents load new content or change after
``Document.readyState``'s value changes to "complete".
The :class:`Wait` helper class provided by Marionette avoids some of the
caveats of ``time.sleep(n)``. It will return immediately once the provided
condition evaluates to true.
To avoid the race condition in the above example, one could do::
button = client.find_element('id', 'button')
button.click()
def find_divs():
return client.find_elements('css selector', '#container div')
divs = Wait(client).until(find_divs)
assert len(divs) > 0
This avoids the race condition. Because finding elements is a common condition
to wait for, it is built in to Marionette. Instead of the above, you could
write::
button = client.find_element('id', 'button')
button.click()
assert len(Wait(client).until(expected.elements_present('css selector', '#container div'))) > 0
For a full list of built-in conditions, see :mod:`~marionette.expected`.

View File

@ -0,0 +1,185 @@
.. py:currentmodule:: marionette
Marionette Python Client
========================
The Marionette python client library allows you to remotely control a
Gecko-based browser or device which is running a Marionette_
server. This includes desktop Firefox and FirefoxOS (support for
Firefox for Android is planned, but not yet fully implemented).
The Marionette server is built directly into Gecko and can be started by
passing in a command line option to Gecko, or by using a Marionette-enabled
build. The server listens for connections from various clients. Clients can
then control Gecko by sending commands to the server.
This is the official python client for Marionette. There also exists a
`NodeJS client`_ maintained by the Firefox OS automation team.
.. _Marionette: https://developer.mozilla.org/en-US/docs/Marionette
.. _NodeJS client: https://github.com/mozilla-b2g/marionette-js-client
Getting the Client
------------------
The python client is officially supported. To install it, first make sure you
have `pip installed`_ then run:
.. parsed-literal::
pip install marionette_client
It's highly recommended to use virtualenv_ when installing Marionette to avoid
package conflicts and other general nastiness.
You should now be ready to start using Marionette. The best way to learn is to
play around with it. Start a `Marionette-enabled instance of Firefox`_, fire up
a python shell and follow along with the
:doc:`interactive tutorial <interactive>`!
.. _pip installed: https://pip.pypa.io/en/latest/installing.html
.. _virtualenv: http://virtualenv.readthedocs.org/en/latest/
.. _Marionette-enabled instance of Firefox: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/Builds
Using the Client for Testing
----------------------------
Please visit the `Marionette Tests`_ section on MDN for information regarding
testing with Marionette.
.. _Marionette Tests: https://developer.mozilla.org/en/Marionette/Tests
Session Management
------------------
A session is a single instance of a Marionette client connected to a Marionette
server. Before you can start executing commands, you need to start a session
with :func:`start_session() <Marionette.start_session>`:
.. parsed-literal::
client = Marionette('localhost', port=2828)
client.start_session()
This returns a session id and an object listing the capabilities of the
Marionette server. For example, a server running on a Firefox OS device will
have the ability to rotate the window, while a server running from Firefox
won't. It's also possible to access the capabilities using the
:attr:`~Marionette.session_capabilities` attribute. After finishing with a
session, you can delete it with :func:`~Marionette.delete_session()`. Note that
this will also happen automatically when the Marionette object is garbage
collected.
Context Management
------------------
Commands can only be executed in a single window, frame and scope at a time. In
order to run commands elsewhere, it's necessary to explicitly switch to the
appropriate context.
Use :func:`~Marionette.switch_to_window` to execute commands in the context of a
new window:
.. parsed-literal::
original_window = client.current_window_handle
for handle in client.window_handles:
if handle != original_window:
client.switch_to_window(handle)
print("Switched to window with '{}' loaded.".format(client.get_url()))
client.switch_to_window(original_window)
Similarly, use :func:`~Marionette.switch_to_frame` to execute commands in the
context of a new frame (e.g an <iframe> element):
.. parsed-literal::
iframe = client.find_element(By.TAG_NAME, 'iframe')
client.switch_to_frame(iframe)
assert iframe == client.get_active_frame()
Finally Marionette can switch between `chrome` and `content` scope. Chrome is a
privileged scope where you can access things like the Firefox UI itself or the
system app in Firefox OS. Content scope is where things like webpages or normal
Firefox OS apps live. You can switch between `chrome` and `content` using the
:func:`~Marionette.set_context` and :func:`~Marionette.using_context` functions:
.. parsed-literal::
client.set_context(client.CONTEXT_CONTENT)
# content scope
with client.using_context(client.CONTEXT_CHROME):
#chrome scope
... do stuff ...
# content scope restored
Navigation
----------
Use :func:`~Marionette.navigate` to open a new website. It's also possible to
move through the back/forward cache using :func:`~Marionette.go_forward` and
:func:`~Marionette.go_back` respectively. To retrieve the currently
open website, use :func:`~Marionette.get_url`:
.. parsed-literal::
url = 'http://mozilla.org'
client.navigate(url)
client.go_back()
client.go_forward()
assert client.get_url() == url
DOM Elements
------------
In order to inspect or manipulate actual DOM elements, they must first be found
using the :func:`~Marionette.find_element` or :func:`~Marionette.find_elements`
methods:
.. parsed-literal::
from marionette import HTMLElement
element = client.find_element(By.ID, 'my-id')
assert type(element) == HTMLElement
elements = client.find_elements(By.TAG_NAME, 'a')
assert type(elements) == list
For a full list of valid search strategies, see :doc:`advanced/findelement`.
Now that an element has been found, it's possible to manipulate it:
.. parsed-literal::
element.click()
element.send_keys('hello!')
print(element.get_attribute('style'))
For the full list of possible commands, see the :class:`HTMLElement`
reference.
Be warned that a reference to an element object can become stale if it was
modified or removed from the document. See :doc:`advanced/stale` for tips
on working around this limitation.
Script Execution
----------------
Sometimes Marionette's provided APIs just aren't enough and it is necessary to
run arbitrary javascript. This is accomplished with the
:func:`~Marionette.execute_script` and :func:`~Marionette.execute_async_script`
functions. They accomplish what their names suggest, the former executes some
synchronous JavaScript, while the latter provides a callback mechanism for
running asynchronous JavaScript:
.. parsed-literal::
result = client.execute_script("return arguments[0] + arguments[1];",
script_args=[2, 3])
assert result == 5
The async method works the same way, except it won't return until a special
`marionetteScriptFinished()` function is called:
.. parsed-literal::
result = client.execute_async_script("""
setTimeout(function() {
marionetteScriptFinished("all done");
}, arguments[0]);
""", script_args=[1000])
assert result == "all done"
Beware that running asynchronous scripts can potentially hang the program
indefinitely if they are not written properly. It is generally a good idea to
set a script timeout using :func:`~Marionette.set_script_timeout` and handling
`ScriptTimeoutException`.

View File

@ -95,8 +95,20 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd:
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
pass
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.

View File

@ -1,220 +1,16 @@
.. Marionette Python Client documentation master file, created by
sphinx-quickstart on Tue Aug 6 13:54:46 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Marionette Python Client
========================
The Marionette python client library allows you to remotely control a
Gecko-based browser or device which is running a Marionette_
server. This includes desktop Firefox and FirefoxOS (support for
Firefox for Android is planned, but not yet fully implemented).
.. _Marionette: https://developer.mozilla.org/en-US/docs/Marionette
Getting Started
---------------
Getting the Client
^^^^^^^^^^^^^^^^^^
We officially support a python client. The latest supported version of
the client is available on pypi_, so you can download it via pip.
This client should be used within a virtual environment to ensure that
your environment is pristine:
.. parsed-literal::
virtualenv venv
source venv/bin/activate
pip install marionette_client
.. _pypi: https://pypi.python.org/pypi/marionette_client/
Using the Client Interactively
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Once you installed the client and have Marionette running, you can fire
up your favourite interactive python environment and start playing with
Marionette. Let's use a typical python shell:
.. parsed-literal::
python
First, import Marionette:
.. parsed-literal::
from marionette import Marionette
Now create the client for this session. Assuming you're using the default
port on a Marionette instance running locally:
.. parsed-literal::
client = Marionette(host='localhost', port=2828)
client.start_session()
This will return some id representing your session id. Now that you've
established a connection, let's start doing interesting things:
.. parsed-literal::
client.execute_script("alert('o hai there!');")
You should now see this alert pop up! How exciting! Okay, let's do
something practical. Close the dialog and try this:
.. parsed-literal::
client.navigate("http://www.mozilla.org")
Now you're at mozilla.org! You can even verify it using the following:
.. parsed-literal::
client.get_url()
You can even find an element and click on it. Let's say you want to get
the first link:
.. parsed-literal::
first_link = client.find_element("tag name", "a")
first_link now holds a reference to the first link on the page. You can click it:
.. parsed-literal::
first_link.click()
Using the Client for Testing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Please visit, our `Marionette Tests`_ section for information regarding testing.
.. _Marionette Tests: https://developer.mozilla.org/en/Marionette/Tests
.. automodule:: marionette
Marionette Objects
------------------
.. autoclass:: Marionette
Session Management
^^^^^^^^^^^^^^^^^^
.. automethod:: Marionette.start_session
.. automethod:: Marionette.delete_session
.. autoattribute:: Marionette.session_capabilities
.. automethod:: Marionette.get_cookie
.. automethod:: Marionette.get_cookies
.. automethod:: Marionette.add_cookie
.. automethod:: Marionette.delete_all_cookies
Context Management
^^^^^^^^^^^^^^^^^^
.. autoattribute:: Marionette.current_window_handle
.. autoattribute:: Marionette.window_handles
.. automethod:: Marionette.set_context
.. automethod:: Marionette.switch_to_frame
.. automethod:: Marionette.switch_to_window
.. automethod:: Marionette.get_active_frame
.. automethod:: Marionette.close
Navigation Methods
^^^^^^^^^^^^^^^^^^
.. autoattribute:: Marionette.title
.. automethod:: Marionette.navigate
.. automethod:: Marionette.get_url
.. automethod:: Marionette.go_back
.. automethod:: Marionette.go_forward
.. automethod:: Marionette.refresh
.. automethod:: Marionette.absolute_url
.. automethod:: Marionette.get_window_type
DOM Element Methods
^^^^^^^^^^^^^^^^^^^
.. automethod:: Marionette.set_search_timeout
.. automethod:: Marionette.find_element
.. automethod:: Marionette.find_elements
Script Execution
^^^^^^^^^^^^^^^^
.. automethod:: Marionette.execute_script
.. automethod:: Marionette.execute_async_script
.. automethod:: Marionette.set_script_timeout
Debugging
^^^^^^^^^
.. autoattribute:: Marionette.page_source
.. automethod:: Marionette.log
.. automethod:: Marionette.get_logs
.. automethod:: Marionette.screenshot
Querying and Modifying Document Content
---------------------------------------
.. autoclass:: HTMLElement
:members:
.. autoclass:: DateTimeValue
:members:
Action Objects
--------------
Action Sequences
^^^^^^^^^^^^^^^^
.. autoclass:: Actions
:members:
Multi-action Sequences
^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: MultiActions
:members:
Explicit Waiting and Expected Conditions
----------------------------------------
Waits are used to pause program execution
until a given condition is true.
This is a useful technique to employ
when documents load new content or change
after ``Document.readyState``'s value changes to "complete".
Because Marionette returns control to the user
when the document is completely loaded,
any subsequent interaction with elements
are subject to manual synchronisation.
The reason for this is that Marionette
does not keep a direct representation of the DOM,
but instead exposes a way for the user to
query the browser's DOM state.
The `Wait` helper class provided by Marionette
avoids some of the caveats of ``time.sleep(n)``,
which sets the condition to an exact time period to wait.
It will return immediately
once the provided condition evaluates to true.
In addition to writing your own custom conditions
you can combine `Wait`
with a number of ready-made expected conditions
that are listed below.
Waits
^^^^^
.. autoclass:: marionette.wait.Wait
:members:
:special-members:
.. autoattribute marionette.wait.DEFAULT_TIMEOUT
.. autoattribute marionette.wait.DEFAULT_INTERVAL
Expected Conditions
^^^^^^^^^^^^^^^^^^^
.. automodule:: marionette.expected
:members:
.. include:: basics.rst
Indices and tables
==================
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. toctree::
:hidden:
Getting Started <basics>
Interactive Tutorial <interactive>
advanced/landing
reference

View File

@ -0,0 +1,55 @@
Using the Client Interactively
==============================
Once you installed the client and have Marionette running, you can fire
up your favourite interactive python environment and start playing with
Marionette. Let's use a typical python shell:
.. parsed-literal::
python
First, import Marionette:
.. parsed-literal::
from marionette import Marionette
Now create the client for this session. Assuming you're using the default
port on a Marionette instance running locally:
.. parsed-literal::
client = Marionette(host='localhost', port=2828)
client.start_session()
This will return some id representing your session id. Now that you've
established a connection, let's start doing interesting things:
.. parsed-literal::
client.execute_script("alert('o hai there!');")
You should now see this alert pop up! How exciting! Okay, let's do
something practical. Close the dialog and try this:
.. parsed-literal::
client.navigate("http://www.mozilla.org")
Now you're at mozilla.org! You can even verify it using the following:
.. parsed-literal::
client.get_url()
You can even find an element and click on it. Let's say you want to get
the first link:
.. parsed-literal::
from marionette import By
first_link = client.find_element(By.TAG_NAME, "a")
first_link now holds a reference to the first link on the page. You can click it:
.. parsed-literal::
first_link.click()

View File

@ -0,0 +1,43 @@
=============
API Reference
=============
.. py:currentmodule:: marionette
Marionette
----------
.. autoclass:: Marionette
:members:
HTMLElement
-----------
.. autoclass:: HTMLElement
:members:
DateTimeValue
-------------
.. autoclass:: DateTimeValue
:members:
Actions
-------
.. autoclass:: Actions
:members:
MultiActions
------------
.. autoclass:: MultiActions
:members:
Wait
----
.. autoclass:: Wait
:members:
:special-members:
.. autoattribute marionette.wait.DEFAULT_TIMEOUT
.. autoattribute marionette.wait.DEFAULT_INTERVAL
Built-in Conditions
^^^^^^^^^^^^^^^^^^^
.. automodule:: marionette.expected
:members:

View File

@ -125,7 +125,7 @@ class Wait(object):
except self.exceptions as e:
last_exc = sys.exc_info()
if isinstance(rv, bool) and not rv:
if not rv:
self.clock.sleep(self.interval)
continue

View File

@ -16,6 +16,10 @@
#include "nsSetDllDirectory.h"
#endif
#if defined(MOZ_METRO) || defined(__GNUC__)
#define XRE_DONT_SUPPORT_XPSP2
#endif
#ifndef XRE_DONT_SUPPORT_XPSP2
#include "WindowsCrtPatch.h"
#endif
@ -80,7 +84,7 @@ FreeAllocStrings(int argc, char **argv)
int wmain(int argc, WCHAR **argv)
{
#if !defined(XRE_DONT_SUPPORT_XPSP2) && !defined(MOZ_METRO)
#if !defined(XRE_DONT_SUPPORT_XPSP2)
WindowsCrtPatch::Init();
#endif