Bug 1160014 part 1 - Implement common part of fullscreen transition. r=roc,smaug,dao

This patch implements the code which is shared by all platforms for
fullscreen transition.

It adds two prefs for the duration of fullscreen transition. They can
also be used to completely suppress the transition.

In addition, this patch uses the newly added prefs to suppress the
transition in all tests which use the DOM fullscreen.
This commit is contained in:
Xidorn Quan 2015-07-13 20:44:36 +10:00
parent 8808558a82
commit 50ab569356
14 changed files with 301 additions and 32 deletions

View File

@ -8,6 +8,7 @@ var FullScreen = {
"DOMFullscreen:Request",
"DOMFullscreen:NewOrigin",
"DOMFullscreen:Exit",
"DOMFullscreen:Painted",
],
init: function() {
@ -166,6 +167,10 @@ var FullScreen = {
this._windowUtils.remoteFrameFullscreenReverted();
break;
}
case "DOMFullscreen:Painted": {
Services.obs.notifyObservers(window, "fullscreen-painted", "");
break;
}
}
},

View File

@ -624,8 +624,10 @@ let DOMFullscreenHandler = {
addMessageListener("DOMFullscreen:Approved", this);
addMessageListener("DOMFullscreen:CleanUp", this);
addEventListener("MozDOMFullscreen:Request", this);
addEventListener("MozDOMFullscreen:Entered", this);
addEventListener("MozDOMFullscreen:NewOrigin", this);
addEventListener("MozDOMFullscreen:Exit", this);
addEventListener("MozDOMFullscreen:Exited", this);
},
get _windowUtils() {
@ -678,6 +680,16 @@ let DOMFullscreenHandler = {
sendAsyncMessage("DOMFullscreen:Exit");
break;
}
case "MozDOMFullscreen:Entered":
case "MozDOMFullscreen:Exited": {
addEventListener("MozAfterPaint", this);
break;
}
case "MozAfterPaint": {
removeEventListener("MozAfterPaint", this);
sendAsyncMessage("DOMFullscreen:Painted");
break;
}
}
}
};

View File

@ -121,6 +121,10 @@ let gTests = [
];
add_task(function* () {
yield pushPrefs(
["full-screen-api.transition-duration.enter", "0 0"],
["full-screen-api.transition-duration.leave", "0 0"]);
let tab = gBrowser.addTab("about:robots");
let browser = tab.linkedBrowser;
gBrowser.selectedTab = tab;

View File

@ -507,6 +507,8 @@ function runTest(testNum) {
subwindow.addEventListener("mozfullscreenchange", openDomFullScreen, false);
SpecialPowers.setBoolPref("full-screen-api.approval-required", false);
SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
SpecialPowers.setCharPref("full-screen-api.transition-duration.enter", "0 0");
SpecialPowers.setCharPref("full-screen-api.transition-duration.leave", "0 0");
full_screen_element.mozRequestFullScreen();
},

View File

@ -6030,6 +6030,211 @@ FinishDOMFullscreenChange(nsIDocument* aDoc, bool aInDOMFullscreen)
}
}
struct FullscreenTransitionDuration
{
// The unit of the durations is millisecond
uint16_t mFadeIn = 0;
uint16_t mFadeOut = 0;
bool IsSuppressed() const
{
return mFadeIn == 0 && mFadeOut == 0;
}
};
static void
GetFullscreenTransitionDuration(bool aEnterFullscreen,
FullscreenTransitionDuration* aDuration)
{
const char* pref = aEnterFullscreen ?
"full-screen-api.transition-duration.enter" :
"full-screen-api.transition-duration.leave";
nsAdoptingCString prefValue = Preferences::GetCString(pref);
if (!prefValue.IsEmpty()) {
sscanf(prefValue.get(), "%hu%hu",
&aDuration->mFadeIn, &aDuration->mFadeOut);
}
}
class FullscreenTransitionTask : public nsRunnable
{
public:
FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
nsGlobalWindow* aWindow, bool aFullscreen,
nsIWidget* aWidget, nsIScreen* aScreen,
nsISupports* aTransitionData)
: mWindow(aWindow)
, mWidget(aWidget)
, mScreen(aScreen)
, mTransitionData(aTransitionData)
, mDuration(aDuration)
, mStage(eBeforeToggle)
, mFullscreen(aFullscreen)
{}
NS_IMETHOD Run() override;
private:
enum Stage {
// BeforeToggle stage happens before we enter or leave fullscreen
// state. In this stage, the task triggers the pre-toggle fullscreen
// transition on the widget.
eBeforeToggle,
// ToggleFullscreen stage actually executes the fullscreen toggle,
// and wait for the next paint on the content to continue.
eToggleFullscreen,
// AfterToggle stage happens after we toggle the fullscreen state.
// In this stage, the task triggers the post-toggle fullscreen
// transition on the widget.
eAfterToggle
};
class Observer final : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
explicit Observer(FullscreenTransitionTask* aTask)
: mTask(aTask) { }
private:
~Observer() {}
nsRefPtr<FullscreenTransitionTask> mTask;
};
static const uint32_t kNextPaintTimeout = 1000; // ms
static const char* const kPaintedTopic;
nsRefPtr<nsGlobalWindow> mWindow;
nsCOMPtr<nsIWidget> mWidget;
nsCOMPtr<nsIScreen> mScreen;
nsCOMPtr<nsITimer> mTimer;
nsCOMPtr<nsISupports> mTransitionData;
FullscreenTransitionDuration mDuration;
Stage mStage;
bool mFullscreen;
};
const char* const
FullscreenTransitionTask::kPaintedTopic = "fullscreen-painted";
NS_IMETHODIMP
FullscreenTransitionTask::Run()
{
Stage stage = mStage;
mStage = Stage(mStage + 1);
if (stage == eBeforeToggle) {
mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
mDuration.mFadeIn, mTransitionData,
this);
} else if (stage == eToggleFullscreen) {
if (MOZ_UNLIKELY(mWindow->mFullScreen != mFullscreen)) {
// This could happen in theory if several fullscreen requests in
// different direction happen continuously in a short time. We
// need to ensure the fullscreen state matches our target here,
// otherwise the widget would change the window state as if we
// toggle for Fullscreen Mode instead of Fullscreen API.
NS_WARNING("The fullscreen state of the window does not match");
mWindow->mFullScreen = mFullscreen;
}
// Toggle the fullscreen state on the widget
mWidget->MakeFullScreen(mFullscreen, mScreen);
// Set observer for the next content paint.
nsCOMPtr<nsIObserver> observer = new Observer(this);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->AddObserver(observer, kPaintedTopic, false);
// There are several edge cases where we may never get the paint
// notification, including:
// 1. the window/tab is closed before the next paint;
// 2. the user has switched to another tab before we get here.
// Completely fixing those cases seems to be tricky, and since they
// should rarely happen, it probably isn't worth to fix. Hence we
// simply add a timeout here to ensure we never hang forever.
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
mTimer->Init(observer, kNextPaintTimeout, nsITimer::TYPE_ONE_SHOT);
} else if (stage == eAfterToggle) {
mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
mDuration.mFadeOut, mTransitionData,
this);
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver)
NS_IMETHODIMP
FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
bool shouldContinue = false;
if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aSubject));
nsCOMPtr<nsIWidget> widget = win ?
static_cast<nsGlobalWindow*>(win.get())->GetMainWidget() : nullptr;
if (widget == mTask->mWidget) {
// The paint notification arrives first. Cancel the timer.
mTask->mTimer->Cancel();
shouldContinue = true;
}
} else {
#ifdef DEBUG
MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
"Should only get fullscreen-painted or timer-callback");
nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
MOZ_ASSERT(timer && timer == mTask->mTimer,
"Should only trigger this with the timer the task created");
#endif
shouldContinue = true;
}
if (shouldContinue) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->RemoveObserver(this, kPaintedTopic);
mTask->mTimer = nullptr;
mTask->Run();
}
return NS_OK;
}
static bool
MakeWidgetFullscreen(nsGlobalWindow* aWindow, gfx::VRHMDInfo* aHMD,
nsPIDOMWindow::FullscreenReason aReason, bool aFullscreen)
{
nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
if (!widget) {
return false;
}
FullscreenTransitionDuration duration;
bool performTransition = false;
nsCOMPtr<nsISupports> transitionData;
if (aReason == nsPIDOMWindow::eForFullscreenAPI) {
GetFullscreenTransitionDuration(aFullscreen, &duration);
if (!duration.IsSuppressed()) {
performTransition = widget->
PrepareForFullscreenTransition(getter_AddRefs(transitionData));
}
}
nsCOMPtr<nsIScreen> screen = aHMD ? aHMD->GetScreen() : nullptr;
if (!performTransition) {
if (aReason == nsPIDOMWindow::eForFullscreenMode) {
// If we enter fullscreen for fullscreen mode, we want
// the native system behavior.
widget->MakeFullScreenWithNativeTransition(aFullscreen, screen);
} else {
widget->MakeFullScreen(aFullscreen, screen);
}
} else {
nsCOMPtr<nsIRunnable> task =
new FullscreenTransitionTask(duration, aWindow, aFullscreen,
widget, screen, transitionData);
task->Run();
}
return true;
}
nsresult
nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
bool aFullScreen,
@ -6041,8 +6246,7 @@ nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
// Only chrome can change our fullscreen mode. Otherwise, the state
// can only be changed for DOM fullscreen.
if (aFullScreen == FullScreen() ||
(aReason == eForFullscreenMode && !nsContentUtils::IsCallerChrome())) {
if (aReason == eForFullscreenMode && !nsContentUtils::IsCallerChrome()) {
return NS_OK;
}
@ -6098,19 +6302,7 @@ nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
// dimensions to appear to increase when entering fullscreen mode; we just
// want the content to fill the entire client area of the emulator window.
if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
if (widget) {
nsCOMPtr<nsIScreen> screen;
if (aHMD) {
screen = aHMD->GetScreen();
}
if (aReason == eForFullscreenMode) {
// If we enter fullscreen for fullscreen mode, we want
// the native system behavior.
widget->MakeFullScreenWithNativeTransition(aFullScreen, screen);
} else {
widget->MakeFullScreen(aFullScreen, screen);
}
if (MakeWidgetFullscreen(this, aHMD, aReason, aFullScreen)) {
// The rest of code for switching fullscreen is in nsGlobalWindow::
// FinishFullscreenChange() which will be called after sizemodechange
// event is dispatched.

View File

@ -484,6 +484,9 @@ public:
// Inner windows only.
virtual void RefreshCompartmentPrincipal() override;
// For accessing protected field mFullScreen
friend class FullscreenTransitionTask;
// Outer windows only.
virtual nsresult SetFullscreenInternal(
FullscreenReason aReason, bool aIsFullscreen,

View File

@ -86,8 +86,12 @@ is(window.fullScreen, false, "Shouldn't be able to set window fullscreen from co
// to write
addLoadEvent(function() {
SpecialPowers.pushPrefEnv({
"set":[["full-screen-api.enabled", true],
["full-screen-api.allow-trusted-requests-only", false]]}, nextTest);
"set": [
["full-screen-api.enabled", true],
["full-screen-api.allow-trusted-requests-only", false],
["full-screen-api.transition-duration.enter", "0 0"],
["full-screen-api.transition-duration.leave", "0 0"]
]}, nextTest);
});
SimpleTest.waitForExplicitFinish();
</script>

View File

@ -32,8 +32,12 @@ var principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getNoAppCodebasePrincipal(uri);
pm.removeFromPrincipal(principal, "fullscreen");
SpecialPowers.pushPrefEnv({"set": [['full-screen-api.enabled', true],
['full-screen-api.allow-trusted-requests-only', false]]}, setup);
SpecialPowers.pushPrefEnv({"set": [
['full-screen-api.enabled', true],
['full-screen-api.allow-trusted-requests-only', false],
['full-screen-api.transition-duration.enter', '0 0'],
['full-screen-api.transition-duration.leave', '0 0']
]}, setup);
function setup() {
newwindow = window.open("MozDomFullscreen_chrome.xul", "_blank","chrome,resizable=yes,width=400,height=400");

View File

@ -28,16 +28,17 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
SimpleTest.waitForExplicitFinish();
// Ensure the full-screen api is enabled, and will be disabled on test exit.
SpecialPowers.setBoolPref("full-screen-api.enabled", true);
// Disable the requirement for trusted contexts only, so the tests are easier to write.
SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
// Grant "fullscreen" permission on the test domain. This means fullscreen will be
// automatically approved, so pointer lock in the tests will be too.
SpecialPowers.setFullscreenAllowed(document);
SpecialPowers.pushPrefEnv({"set": [
["full-screen-api.enabled", true],
["full-screen-api.allow-trusted-requests-only", false],
["full-screen-api.transition-duration.enter", "0 0"],
["full-screen-api.transition-duration.leave", "0 0"]
]}, nextTest);
// Run the tests which go full-screen in new window, as Mochitests
// normally run in an iframe, which by default will not have the
// allowfullscreen attribute set, so full-screen won't work.
@ -72,8 +73,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
var gTestIndex = 0;
function finish() {
SpecialPowers.clearUserPref("full-screen-api.enabled");
SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
SpecialPowers.removeFullscreenAllowed(document)
SimpleTest.finish();
}
@ -107,8 +106,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
finish();
}
}
addLoadEvent(nextTest);
</script>
</pre>
</body>

View File

@ -4342,6 +4342,9 @@ pref("alerts.disableSlidingEffect", false);
pref("full-screen-api.enabled", false);
pref("full-screen-api.allow-trusted-requests-only", true);
pref("full-screen-api.pointer-lock.enabled", true);
// transition duration of fade-to-black and fade-from-black, unit: ms
pref("full-screen-api.transition-duration.enter", "400 400");
pref("full-screen-api.transition-duration.leave", "400 400");
// DOM idle observers API
pref("dom.idle-observers-api.enabled", true);

View File

@ -285,7 +285,6 @@ public:
nsIWidget *aWidget, bool aActivate) override;
NS_IMETHOD SetSizeMode(int32_t aMode) override;
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
NS_IMETHOD MakeFullScreen(
bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override final;

View File

@ -770,6 +770,16 @@ NS_IMETHODIMP nsBaseWidget::HideWindowChrome(bool aShouldHide)
return NS_ERROR_NOT_IMPLEMENTED;
}
/* virtual */ void
nsBaseWidget::PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration,
nsISupports* aData,
nsIRunnable* aCallback)
{
MOZ_ASSERT_UNREACHABLE(
"Should never call PerformFullscreenTransition on nsBaseWidget");
}
//-------------------------------------------------------------------------
//
// Put the window into full-screen mode

View File

@ -143,6 +143,11 @@ public:
virtual void SetShowsFullScreenButton(bool aShow) override {}
virtual void SetWindowAnimationType(WindowAnimationType aType) override {}
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
virtual bool PrepareForFullscreenTransition(nsISupports** aData) override { return false; }
virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration,
nsISupports* aData,
nsIRunnable* aCallback) override;
NS_IMETHOD MakeFullScreen(bool aFullScreen, nsIScreen* aScreen = nullptr) override;
virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,

View File

@ -37,6 +37,7 @@ class ViewWrapper;
class nsIWidgetListener;
class nsIntRegion;
class nsIScreen;
class nsIRunnable;
namespace mozilla {
class CompositorVsyncDispatcher;
@ -119,8 +120,8 @@ typedef void* nsNativeWidget;
#define NS_NATIVE_PLUGIN_ID 105
#define NS_IWIDGET_IID \
{ 0xb81e1264, 0x9f79, 0x4962, \
{ 0x8d, 0x9a, 0x64, 0xdd, 0x21, 0x5d, 0x6a, 0x01 } }
{ 0x22b4504e, 0xddba, 0x4211, \
{ 0xa1, 0x49, 0x6e, 0x11, 0x73, 0xc4, 0x11, 0x45 } }
/*
* Window shadow styles
@ -1696,6 +1697,34 @@ class nsIWidget : public nsISupports {
*/
NS_IMETHOD HideWindowChrome(bool aShouldHide) = 0;
enum FullscreenTransitionStage
{
eBeforeFullscreenToggle,
eAfterFullscreenToggle
};
/**
* Prepares for fullscreen transition and returns whether the widget
* supports fullscreen transition. If this method returns false,
* PerformFullscreenTransition() must never be called. Otherwise,
* caller should call that method twice with "before" and "after"
* stages respectively in order. In the latter case, this method may
* return some data via aData pointer. Caller must pass that data to
* PerformFullscreenTransition() if any, and caller is responsible
* for releasing that data.
*/
virtual bool PrepareForFullscreenTransition(nsISupports** aData) = 0;
/**
* Performs fullscreen transition. This method returns immediately,
* and will post aCallback to the main thread when the transition
* finishes.
*/
virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration,
nsISupports* aData,
nsIRunnable* aCallback) = 0;
/**
* Put the toplevel window into or out of fullscreen mode.
* If aTargetScreen is given, attempt to go fullscreen on that screen,