зеркало из https://github.com/mozilla/gecko-dev.git
Bug 799595 - Add nsIMessageLoop::PostIdleTask and use it to take screenshots. r=cjones, a=blocking-basecamp
This commit is contained in:
Родитель
03d3af33a8
Коммит
9ebe402337
|
@ -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")
|
||||
});
|
||||
},
|
||||
|
|
|
@ -3802,3 +3802,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);
|
||||
|
|
|
@ -42,6 +42,7 @@ CPPSRCS = \
|
|||
nsErrorAsserts.cpp \
|
||||
nsGZFileWriter.cpp \
|
||||
MemoryInfoDumper.cpp \
|
||||
nsMessageLoop.cpp \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(OS_ARCH),Linux)
|
||||
|
@ -135,6 +136,7 @@ XPIDLSRCS = \
|
|||
nsIMutable.idl \
|
||||
nsIMemoryReporter.idl \
|
||||
nsIGZFileWriter.idl \
|
||||
nsIMessageLoop.idl \
|
||||
$(NULL)
|
||||
|
||||
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
|
@ -78,3 +78,4 @@
|
|||
COMPONENT(MEMORY_REPORTER_MANAGER, nsMemoryReporterManagerConstructor)
|
||||
COMPONENT(IOUTIL, nsIOUtilConstructor)
|
||||
COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor)
|
||||
COMPONENT(MESSAGE_LOOP, nsMessageLoopConstructor)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче