Bug 799595 - Add nsIMessageLoop::PostIdleTask and use it to take screenshots. r=cjones

This commit is contained in:
Justin Lebar 2012-10-25 11:36:24 -04:00
parent 97eb9e0f3b
commit 73af9cbc6d
9 changed files with 279 additions and 18 deletions

View File

@ -453,29 +453,54 @@ BrowserElementChild.prototype = {
_recvGetScreenshot: function(data) {
debug("Received getScreenshot message: (" + data.json.id + ")");
let self = this;
let maxWidth = data.json.args.width;
let maxHeight = data.json.args.height;
let domRequestID = data.json.id;
let takeScreenshotClosure = function() {
self._takeScreenshot(maxWidth, maxHeight, domRequestID);
};
let maxDelayMS = 2000;
try {
maxDelayMS = Services.prefs.getIntPref('dom.browserElement.maxScreenshotDelayMS');
}
catch(e) {}
// Try to wait for the event loop to go idle before we take the screenshot,
// but once we've waited maxDelayMS milliseconds, go ahead and take it
// anyway.
Cc['@mozilla.org/message-loop;1'].getService(Ci.nsIMessageLoop).postIdleTask(
takeScreenshotClosure, maxDelayMS);
},
/**
* Actually take a screenshot and foward the result up to our parent, given
* the desired maxWidth and maxHeight, and given the DOMRequest ID associated
* with the request from the parent.
*/
_takeScreenshot: function(maxWidth, maxHeight, domRequestID) {
// You can think of the screenshotting algorithm as carrying out the
// following steps:
//
// - Let max-width be data.json.args.width, and let max-height be
// data.json.args.height.
//
// - Let scale-width be the factor by which we'd need to downscale the
// viewport so it would fit within max-width. (If the viewport's width
// is less than max-width, let scale-width be 1.) Compute scale-height
// - Let scaleWidth be the factor by which we'd need to downscale the
// viewport so it would fit within maxWidth. (If the viewport's width
// is less than maxWidth, let scaleWidth be 1.) Compute scaleHeight
// the same way.
//
// - Scale the viewport by max(scale-width, scale-height). Now either the
// viewport's width is no larger than max-width, the viewport's height is
// no larger than max-height, or both.
// - Scale the viewport by max(scaleWidth, scaleHeight). Now either the
// viewport's width is no larger than maxWidth, the viewport's height is
// no larger than maxHeight, or both.
//
// - Crop the viewport so its width is no larger than max-width and its
// height is no larger than max-height.
// - Crop the viewport so its width is no larger than maxWidth and its
// height is no larger than maxHeight.
//
// - Return a screenshot of the page's viewport scaled and cropped per
// above.
let maxWidth = data.json.args.width;
let maxHeight = data.json.args.height;
debug("Taking a screenshot: maxWidth=" + maxWidth +
", maxHeight=" + maxHeight +
", domRequestID=" + domRequestID + ".");
let scaleWidth = Math.min(1, maxWidth / content.innerWidth);
let scaleHeight = Math.min(1, maxHeight / content.innerHeight);
@ -497,10 +522,11 @@ BrowserElementChild.prototype = {
"rgb(255,255,255)");
sendAsyncMsg('got-screenshot', {
id: data.json.id,
// Hack around the fact that we can't specify opaque PNG, this requires
// us to unpremultiply the alpha channel which is expensive on ARM
// processors because they lack a hardware integer division instruction.
id: domRequestID,
// Use JPEG to hack around the fact that we can't specify opaque PNG.
// This requires us to unpremultiply the alpha channel, which is
// expensive on ARM processors because they lack a hardware integer
// division instruction.
successRv: canvas.toDataURL("image/jpeg")
});
},

View File

@ -3811,3 +3811,7 @@ pref("dom.mozApps.maxLocalId", 1000);
// they are handled separately. This pref is only read once at startup:
// a restart is required to enable a new value.
pref("network.activity.blipIntervalMilliseconds", 0);
// When we're asked to take a screenshot, don't wait more than 2000ms for the
// event loop to become idle before actually taking the screenshot.
pref("dom.browserElement.maxScreenshotDelayMS", 2000);

View File

@ -42,6 +42,7 @@ CPPSRCS = \
nsErrorAsserts.cpp \
nsGZFileWriter.cpp \
MemoryInfoDumper.cpp \
nsMessageLoop.cpp \
$(NULL)
ifeq ($(OS_ARCH),Linux)
@ -137,6 +138,7 @@ XPIDLSRCS = \
nsIMutable.idl \
nsIMemoryReporter.idl \
nsIGZFileWriter.idl \
nsIMessageLoop.idl \
$(NULL)
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))

View File

@ -0,0 +1,36 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIRunnable;
/**
* This service allows access to the current thread's Chromium MessageLoop
* instance, with some extra sugar added. If you're calling from C++, it may
* or may not make sense for you to use this interface. If you're calling from
* JS, you don't have a choice!
*
* Right now, you can only call PostIdleTask(), but nothing is stopping you
* from adding other methods.
*
* nsIMessageLoop's contractid is "@mozilla.org/message-loop;1".
*/
[scriptable, uuid(3E8C58E8-E52C-43E0-8E66-669CA788FF5F)]
interface nsIMessageLoop : nsISupports
{
/**
* Posts a task to be run when this thread's message loop is idle, or after
* ensureRunsAfterMS milliseconds have elapsed. (That is, the task is
* guaranteed to run /eventually/.)
*
* Note that if the event loop is busy, we will hold a reference to the task
* until ensureRunsAfterMS milliseconds have elapsed. Be careful when
* specifying long timeouts and tasks which hold references to windows or
* other large objects, because you can leak memory in a difficult-to-detect
* way!
*/
void postIdleTask(in nsIRunnable task, in uint32_t ensureRunsAfterMS);
};

View File

@ -0,0 +1,159 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsMessageLoop.h"
#include "mozilla/WeakPtr.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "nsIRunnable.h"
#include "nsITimer.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsThreadUtils.h"
using namespace mozilla;
namespace {
/**
* This Task runs its nsIRunnable when Run() is called, or after
* aEnsureRunsAfterMS milliseconds have elapsed since the object was
* constructed.
*
* Note that the MessageLoop owns this object and will delete it after it calls
* Run(). Tread lightly.
*/
class MessageLoopIdleTask
: public Task
, public SupportsWeakPtr<MessageLoopIdleTask>
{
public:
MessageLoopIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS);
virtual ~MessageLoopIdleTask() {}
virtual void Run();
private:
nsresult Init(uint32_t aEnsureRunsAfterMS);
nsCOMPtr<nsIRunnable> mTask;
nsCOMPtr<nsITimer> mTimer;
};
/**
* This timer callback calls MessageLoopIdleTask::Run() when its timer fires.
* (The timer can't call back into MessageLoopIdleTask directly since that's
* not a refcounted object; it's owned by the MessageLoop.)
*
* We keep a weak reference to the MessageLoopIdleTask, although a raw pointer
* should in theory suffice: When the MessageLoopIdleTask runs (right before
* the MessageLoop deletes it), it cancels its timer. But the weak pointer
* saves us from worrying about an edge case somehow messing us up here.
*/
class MessageLoopTimerCallback
: public nsITimerCallback
{
public:
MessageLoopTimerCallback(MessageLoopIdleTask* aTask);
virtual ~MessageLoopTimerCallback() {};
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
private:
WeakPtr<MessageLoopIdleTask> mTask;
};
MessageLoopIdleTask::MessageLoopIdleTask(nsIRunnable* aTask,
uint32_t aEnsureRunsAfterMS)
: mTask(aTask)
{
// Init() really shouldn't fail, but if it does, we schedule our runnable
// immediately, because it's more important to guarantee that we run the task
// eventually than it is to run the task when we're idle.
nsresult rv = Init(aEnsureRunsAfterMS);
if (NS_FAILED(rv)) {
NS_WARNING("Running idle task early because we couldn't initialize our timer.");
NS_DispatchToCurrentThread(mTask);
mTask = nullptr;
mTimer = nullptr;
}
}
nsresult
MessageLoopIdleTask::Init(uint32_t aEnsureRunsAfterMS)
{
mTimer = do_CreateInstance("@mozilla.org/timer;1");
NS_ENSURE_STATE(mTimer);
nsRefPtr<MessageLoopTimerCallback> callback =
new MessageLoopTimerCallback(this);
return mTimer->InitWithCallback(callback, aEnsureRunsAfterMS,
nsITimer::TYPE_ONE_SHOT);
}
/* virtual */ void
MessageLoopIdleTask::Run()
{
// Null out our pointers because if Run() was called by the timer, this
// object will be kept alive by the MessageLoop until the MessageLoop calls
// Run().
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
if (mTask) {
mTask->Run();
mTask = nullptr;
}
}
MessageLoopTimerCallback::MessageLoopTimerCallback(MessageLoopIdleTask* aTask)
: mTask(aTask->asWeakPtr())
{}
NS_IMETHODIMP
MessageLoopTimerCallback::Notify(nsITimer* aTimer)
{
// We don't expect to hit the case when the timer fires but mTask has been
// deleted, because mTask should cancel the timer before the mTask is
// deleted. But you never know...
NS_WARN_IF_FALSE(mTask, "This timer shouldn't have fired.");
if (mTask) {
mTask->Run();
}
return NS_OK;
}
NS_IMPL_ISUPPORTS1(MessageLoopTimerCallback, nsITimerCallback)
} // anonymous namespace
NS_IMPL_ISUPPORTS1(nsMessageLoop, nsIMessageLoop)
NS_IMETHODIMP
nsMessageLoop::PostIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS)
{
// The message loop owns MessageLoopIdleTask and deletes it after calling
// Run(). Be careful...
MessageLoop::current()->PostIdleTask(FROM_HERE,
new MessageLoopIdleTask(aTask, aEnsureRunsAfterMS));
return NS_OK;
}
nsresult
nsMessageLoopConstructor(nsISupports* aOuter,
const nsIID& aIID,
void** aInstancePtr)
{
NS_ENSURE_FALSE(aOuter, NS_ERROR_NO_AGGREGATION);
nsISupports* messageLoop = new nsMessageLoop();
return messageLoop->QueryInterface(aIID, aInstancePtr);
}

View File

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIMessageLoop.h"
/*
* nsMessageLoop implements nsIMessageLoop, which wraps Chromium's MessageLoop
* class and adds a bit of sugar.
*/
class nsMessageLoop : public nsIMessageLoop
{
NS_DECL_ISUPPORTS
NS_DECL_NSIMESSAGELOOP
virtual ~nsMessageLoop() {}
};
#define NS_MESSAGE_LOOP_CID \
{0x67b3ac0c, 0xd806, 0x4d48, \
{0x93, 0x9e, 0x6a, 0x81, 0x9e, 0x6c, 0x24, 0x8f}}
extern nsresult
nsMessageLoopConstructor(nsISupports* outer,
const nsIID& aIID,
void* *aInstancePtr);

View File

@ -78,3 +78,4 @@
COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor)
COMPONENT(IOUTIL, nsIOUtilConstructor)
COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor)
COMPONENT(MESSAGE_LOOP, nsMessageLoopConstructor)

View File

@ -75,6 +75,11 @@
*/
#define NS_CYCLE_COLLECTOR_LOGGER_CONTRACTID "@mozilla.org/cycle-collector-logger;1"
/**
* nsMessageLoop contract id
*/
#define NS_MESSAGE_LOOP_CONTRACTID "@mozilla.org/message-loop;1"
/**
* The following are the CIDs and Contract IDs of the nsISupports wrappers for
* primative types.

View File

@ -96,6 +96,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
#include "nsSystemInfo.h"
#include "nsMemoryReporterManager.h"
#include "nsMessageLoop.h"
#include <locale.h>
#include "mozilla/Services.h"