Bug 843849 - Make DOM/HTML fullscreen API work in Metrofox. r=bz,jimm

This commit is contained in:
Chris Pearce 2013-02-28 12:28:27 +13:00
parent 60ecbc5a2d
commit 2a8daae8a9
10 changed files with 170 additions and 43 deletions

View File

@ -580,3 +580,18 @@ pref("media.realtime_decoder.enabled", true);
// Mobile manages state by autodetection
pref("network.manage-offline-status", true);
// Enable HTML fullscreen API in content.
pref("full-screen-api.enabled", true);
// But don't require approval when content enters fullscreen; we'll keep our
// UI/chrome visible still, so there's no need to approve entering fullscreen.
pref("full-screen-api.approval-required", false);
// Don't allow fullscreen requests to percolate across content/chrome boundary,
// so that our chrome/UI remains visible after content enters fullscreen.
pref("full-screen-api.content-only", true);
// Don't make top-level widgets fullscreen. This only applies when running in
// "metrodesktop" mode, not when running in full metro mode. This prevents the
// window from changing size when we go fullscreen; the content expands to fill
// the window, the window size doesn't change. This pref has no effect when
// running in actual Metro mode, as the widget will already be fullscreen then.
pref("full-screen-api.ignore-widgets", true);

View File

@ -1810,6 +1810,32 @@ public:
*/
static bool IsRequestFullScreenAllowed();
/**
* Returns true if the DOM fullscreen API is restricted to content only.
* This mirrors the pref "full-screen-api.content-only". If this is true,
* fullscreen requests in chrome are denied, and fullscreen requests in
* content stop percolating upwards before they reach chrome documents.
* That is, when an element in content requests fullscreen, only its
* containing frames that are in content are also made fullscreen, not
* the containing frame in the chrome document.
*
* Note if the fullscreen API is running in content only mode then multiple
* branches of a doctree can be fullscreen at the same time, but no fullscreen
* document will have a common ancestor with another fullscreen document
* that is also fullscreen (since the only common ancestor they can have
* is the chrome document, and that can't be fullscreen). i.e. multiple
* child documents of the chrome document can be fullscreen, but the chrome
* document won't be fullscreen.
*
* Making the fullscreen API content only is useful on platforms where we
* still want chrome to be visible or accessible while content is
* fullscreen, like on Windows 8 in Metro mode.
*
* Note that if the fullscreen API is content only, chrome can still go
* fullscreen by setting the "fullScreen" attribute on its XUL window.
*/
static bool IsFullscreenApiContentOnly();
/**
* Returns true if the idle observers API is enabled.
*/
@ -1832,10 +1858,12 @@ public:
static bool HasPluginWithUncontrolledEventDispatch(nsIContent* aContent);
/**
* Returns the root document in a document hierarchy. Normally this will
* be the chrome document.
* Returns the document that is the closest ancestor to aDoc that is
* fullscreen. If aDoc is fullscreen this returns aDoc. If aDoc is not
* fullscreen and none of aDoc's ancestors are fullscreen this returns
* nullptr.
*/
static nsIDocument* GetRootDocument(nsIDocument* aDoc);
static nsIDocument* GetFullscreenAncestor(nsIDocument* aDoc);
/**
* Returns the time limit on handling user input before
@ -2161,6 +2189,7 @@ private:
static bool sAllowXULXBL_for_file;
static bool sIsFullScreenApiEnabled;
static bool sTrustedFullScreenOnly;
static bool sFullscreenApiIsContentOnly;
static uint32_t sHandlingInputTimeout;
static bool sIsIdleObserverAPIEnabled;

View File

@ -868,10 +868,15 @@ public:
virtual Element* GetFullScreenElement() = 0;
/**
* Asynchronously requests that the document make aElement the full-screen
* element, and move into full-screen mode. The current full-screen element
* (if any) is pushed onto the full-screen element stack, and it can be
* returned to full-screen status by calling RestorePreviousFullScreenState().
* Asynchronously requests that the document make aElement the fullscreen
* element, and move into fullscreen mode. The current fullscreen element
* (if any) is pushed onto the fullscreen element stack, and it can be
* returned to fullscreen status by calling RestorePreviousFullScreenState().
*
* Note that requesting fullscreen in a document also makes the element which
* contains this document in this document's parent document fullscreen. i.e.
* the <iframe> or <browser> that contains this document is also mode
* fullscreen. This happens recursively in all ancestor documents.
*/
virtual void AsyncRequestFullScreen(Element* aElement) = 0;
@ -936,12 +941,25 @@ public:
virtual void SetApprovedForFullscreen(bool aIsApproved) = 0;
/**
* Exits documents out of DOM fullscreen mode. If aDocument is non null,
* only its ancestors and descendants exit fullscreen, i.e. if there are
* multiple windows/doctrees in fullscreen mode, only the one containing
* aDocument exits fullscreen mode. If aDocument is null, all windows
* and documents exit fullscreen. If aRunAsync is true, fullscreen is
* executed asynchronously.
* Exits documents out of DOM fullscreen mode.
*
* If aDocument is null, all fullscreen documents in all browser windows
* exit fullscreen.
*
* If aDocument is non null, all documents from aDocument's fullscreen root
* to the fullscreen leaf exit fullscreen.
*
* Note that the fullscreen leaf is the bottom-most document which is
* fullscreen, it may have non-fullscreen child documents. The fullscreen
* root is usually the chrome document, but if fullscreen is content-only,
* (see the comment in nsContentUtils.h on IsFullscreenApiContentOnly())
* the fullscreen root will be a direct child of the chrome document, and
* there may be other branches of the same doctree that are fullscreen.
*
* If aRunAsync is true, fullscreen is executed asynchronously.
*
* Note if aDocument is not fullscreen this function has no effect, even if
* aDocument has fullscreen ancestors.
*/
static void ExitFullscreen(nsIDocument* aDocument, bool aRunAsync);

View File

@ -2575,6 +2575,13 @@ Element::AttrValueToCORSMode(const nsAttrValue* aValue)
static const char*
GetFullScreenError(nsIDocument* aDoc)
{
// Block fullscreen requests in the chrome document when the fullscreen API
// is configured for content only.
if (nsContentUtils::IsFullscreenApiContentOnly() &&
nsContentUtils::IsChromeDoc(aDoc)) {
return "FullScreenDeniedContentOnly";
}
nsCOMPtr<nsPIDOMWindow> win = aDoc->GetWindow();
if (aDoc->NodePrincipal()->GetAppStatus() >= nsIPrincipal::APP_STATUS_INSTALLED) {
// Request is in a web app and in the same origin as the web app.

View File

@ -232,6 +232,7 @@ nsString* nsContentUtils::sModifierSeparator = nullptr;
bool nsContentUtils::sInitialized = false;
bool nsContentUtils::sIsFullScreenApiEnabled = false;
bool nsContentUtils::sTrustedFullScreenOnly = true;
bool nsContentUtils::sFullscreenApiIsContentOnly = false;
bool nsContentUtils::sIsIdleObserverAPIEnabled = false;
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
@ -419,6 +420,12 @@ nsContentUtils::Init()
Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled,
"full-screen-api.enabled");
// Note: We deliberately read this pref here because this code runs
// before the profile loads, so users' changes to this pref in about:config
// won't have any effect on behaviour. We don't really want users messing
// with this pref, as it affects the security model of the fullscreen API.
sFullscreenApiIsContentOnly = Preferences::GetBool("full-screen-api.content-only", false);
Preferences::AddBoolVarCache(&sTrustedFullScreenOnly,
"full-screen-api.allow-trusted-requests-only");
@ -6520,12 +6527,14 @@ nsContentUtils::SetUpChannelOwner(nsIPrincipal* aLoadingPrincipal,
return false;
}
/* static */
bool
nsContentUtils::IsFullScreenApiEnabled()
{
return sIsFullScreenApiEnabled;
}
/* static */
bool
nsContentUtils::IsRequestFullScreenAllowed()
{
@ -6534,6 +6543,13 @@ nsContentUtils::IsRequestFullScreenAllowed()
IsCallerChrome();
}
/* static */
bool
nsContentUtils::IsFullscreenApiContentOnly()
{
return sFullscreenApiIsContentOnly;
}
/* static */
bool
nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2)
@ -6620,16 +6636,16 @@ nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
/* static */
nsIDocument*
nsContentUtils::GetRootDocument(nsIDocument* aDoc)
nsContentUtils::GetFullscreenAncestor(nsIDocument* aDoc)
{
if (!aDoc) {
return nullptr;
}
nsIDocument* doc = aDoc;
while (doc->GetParentDocument()) {
while (doc) {
if (doc->IsFullScreenDoc()) {
return doc;
}
doc = doc->GetParentDocument();
}
return doc;
return nullptr;
}
// static

View File

@ -9255,17 +9255,35 @@ private:
bool mValue;
};
static nsIDocument*
GetFullscreenRootDocument(nsIDocument* aDoc)
{
if (!aDoc) {
return nullptr;
}
nsIDocument* doc = aDoc;
nsIDocument* parent;
while ((parent = doc->GetParentDocument()) &&
(!nsContentUtils::IsFullscreenApiContentOnly() ||
!nsContentUtils::IsChromeDoc(parent))) {
doc = parent;
}
return doc;
}
static void
SetWindowFullScreen(nsIDocument* aDoc, bool aValue)
{
// Maintain list of fullscreen root documents.
nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc);
nsCOMPtr<nsIDocument> root = GetFullscreenRootDocument(aDoc);
if (aValue) {
FullscreenRoots::Add(root);
} else {
FullscreenRoots::Remove(root);
}
nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue));
if (!nsContentUtils::IsFullscreenApiContentOnly()) {
nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue));
}
}
class nsCallExitFullscreen : public nsRunnable {
@ -9458,7 +9476,7 @@ GetFullscreenLeaf(nsIDocument* aDoc)
}
// Otherwise we could be either in a non-fullscreen doc tree, or we're
// below the fullscreen doc. Start the search from the root.
nsIDocument* root = nsContentUtils::GetRootDocument(aDoc);
nsIDocument* root = GetFullscreenRootDocument(aDoc);
// Check that the root is actually fullscreen so we don't waste time walking
// around its descendants.
if (!root->IsFullScreenDoc()) {
@ -9473,6 +9491,10 @@ nsDocument::RestorePreviousFullScreenState()
{
NS_ASSERTION(!IsFullScreenDoc() || !FullscreenRoots::IsEmpty(),
"Should have at least 1 fullscreen root when fullscreen!");
NS_ASSERTION(!nsContentUtils::IsFullscreenApiContentOnly() ||
!nsContentUtils::IsChromeDoc(this),
"Should not run RestorePreviousFullScreenState() on "
"chrome document when fullscreen is content only");
if (!IsFullScreenDoc() || !GetWindow() || FullscreenRoots::IsEmpty()) {
return;
@ -9547,7 +9569,7 @@ nsDocument::RestorePreviousFullScreenState()
// as necessary.
nsAutoString origin;
nsContentUtils::GetUTFOrigin(doc->NodePrincipal(), origin);
nsIDocument* root = nsContentUtils::GetRootDocument(doc);
nsIDocument* root = GetFullscreenRootDocument(doc);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
}
@ -9559,7 +9581,7 @@ nsDocument::RestorePreviousFullScreenState()
if (doc == nullptr) {
// We moved all documents in this doctree out of fullscreen mode,
// move the top-level window out of fullscreen mode.
NS_ASSERTION(!nsContentUtils::GetRootDocument(this)->IsFullScreenDoc(),
NS_ASSERTION(!GetFullscreenRootDocument(this)->IsFullScreenDoc(),
"Should have cleared all docs' stacks");
SetWindowFullScreen(this, false);
}
@ -9629,7 +9651,10 @@ LogFullScreenDenied(bool aLogFailure, const char* aMessage, nsIDocument* aDoc)
nsresult
nsDocument::AddFullscreenApprovedObserver()
{
NS_ASSERTION(!mHasFullscreenApprovedObserver, "Don't add observer twice.");
if (mHasFullscreenApprovedObserver ||
!Preferences::GetBool("full-screen-api.approval-required")) {
return NS_OK;
}
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
@ -9811,7 +9836,7 @@ nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
// it can show a warning or approval UI.
if (!aOrigin.IsEmpty()) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(nsContentUtils::GetRootDocument(this),
os->NotifyObservers(GetFullscreenRootDocument(this),
"fullscreen-origin-change",
PromiseFlatString(aOrigin).get());
}
@ -9847,6 +9872,13 @@ nsDocument::RequestFullScreen(Element* aElement,
LogFullScreenDenied(true, "FullScreenDeniedLostWindow", this);
return;
}
if (nsContentUtils::IsFullscreenApiContentOnly() &&
nsContentUtils::IsChromeDoc(this)) {
// Block fullscreen requests in the chrome document when the fullscreen API
// is configured for content only.
LogFullScreenDenied(true, "FullScreenDeniedContentOnly", this);
return;
}
if (!IsFullScreenEnabled(aWasCallerChrome, true)) {
// IsFullScreenEnabled calls LogFullScreenDenied, no need to log.
return;
@ -9893,7 +9925,7 @@ nsDocument::RequestFullScreen(Element* aElement,
// Remember the root document, so that if a full-screen document is hidden
// we can reset full-screen state in the remaining visible full-screen documents.
nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
nsIDocument* fullScreenRootDoc = GetFullscreenRootDocument(this);
if (fullScreenRootDoc->IsFullScreenDoc()) {
// A document is already in fullscreen, unlock the mouse pointer
// before setting a new document to fullscreen
@ -9925,10 +9957,10 @@ nsDocument::RequestFullScreen(Element* aElement,
child->SetFullscreenRoot(fullScreenRootDoc);
NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
"Fullscreen root should be set!");
nsIDocument* parent = child->GetParentDocument();
if (!parent) {
if (child == fullScreenRootDoc) {
break;
}
nsIDocument* parent = child->GetParentDocument();
Element* element = parent->FindContentForSubDocument(child)->AsElement();
if (static_cast<nsDocument*>(parent)->FullScreenStackPush(element)) {
changed.AppendElement(parent);
@ -9999,7 +10031,7 @@ nsDocument::RequestFullScreen(Element* aElement,
if (aNotifyOnOriginChange &&
!nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
nsIDocument* root = nsContentUtils::GetRootDocument(this);
nsIDocument* root = GetFullscreenRootDocument(this);
nsAutoString origin;
nsContentUtils::GetUTFOrigin(NodePrincipal(), origin);
os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
@ -10046,8 +10078,9 @@ Element*
nsDocument::GetFullScreenElement()
{
Element* element = FullScreenStackTop();
NS_ASSERTION(!element || element->IsFullScreenAncestor(),
"Should have full-screen styles applied!");
NS_ASSERTION(!element ||
element->IsFullScreenAncestor(),
"Fullscreen element should have fullscreen styles applied");
return element;
}

View File

@ -1152,21 +1152,22 @@ nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
}
// Exit full-screen if we're focusing a windowed plugin on a non-MacOSX
// Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
// system. We don't control event dispatch to windowed plugins on non-MacOSX,
// so we can't display the "Press ESC to leave full-screen mode" warning on
// key input if a windowed plugin is focused, so just exit full-screen
// so we can't display the "Press ESC to leave fullscreen mode" warning on
// key input if a windowed plugin is focused, so just exit fullscreen
// to guard against phishing.
#ifndef XP_MACOSX
nsIDocument* fullscreenAncestor;
if (contentToFocus &&
nsContentUtils::GetRootDocument(contentToFocus->OwnerDoc())->IsFullScreenDoc() &&
(fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) &&
nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
"DOM",
contentToFocus->OwnerDoc(),
nsContentUtils::eDOM_PROPERTIES,
"FocusedWindowedPluginWhileFullScreen");
nsIDocument::ExitFullscreen(contentToFocus->OwnerDoc(), /* async */ true);
nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true);
}
#endif

View File

@ -80,6 +80,7 @@ FullScreenDeniedLostWindow=Request for full-screen was denied because we no long
FullScreenDeniedSubDocFullScreen=Request for full-screen was denied because a subdocument of the document requesting full-screen is already full-screen.
FullScreenDeniedNotDescendant=Request for full-screen was denied because requesting element is not a descendant of the current full-screen element.
FullScreenDeniedNotFocusedTab=Request for full-screen was denied because requesting element is not in the currently focused tab.
FullScreenDeniedContentOnly=Request for full-screen was denied because requesting element is in the chrome document and the fullscreen API is configured for content only.
RemovedFullScreenElement=Exited full-screen because full-screen element was removed from document.
FocusedWindowedPluginWhileFullScreen=Exited full-screen because windowed plugin was focused.
HTMLMultipartXHRWarning=HTML parsing in XMLHttpRequest is not supported for multipart responses.

View File

@ -6625,10 +6625,9 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsEventStatus* aStatus)
case NS_KEY_UP: {
nsIDocument *doc = mCurrentEventContent ?
mCurrentEventContent->OwnerDoc() : nullptr;
nsIDocument* root = nullptr;
nsIDocument* fullscreenAncestor = nullptr;
if (static_cast<const nsKeyEvent*>(aEvent)->keyCode == NS_VK_ESCAPE &&
(root = nsContentUtils::GetRootDocument(doc)) &&
root->IsFullScreenDoc()) {
(fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(doc))) {
// Prevent default action on ESC key press when exiting
// DOM fullscreen mode. This prevents the browser ESC key
// handler from stopping all loads in the document, which
@ -6638,9 +6637,16 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsEventStatus* aStatus)
if (aEvent->message == NS_KEY_UP) {
// ESC key released while in DOM fullscreen mode.
// Fully exit all browser windows and documents from
// fullscreen mode.
nsIDocument::ExitFullscreen(nullptr, /* async */ true);
// If fullscreen is running in content-only mode, exit the target
// doctree branch from fullscreen, otherwise fully exit all
// browser windows and documents from fullscreen mode.
// Note: in the content-only fullscreen case, we pass the
// fullscreenAncestor since |doc| may not actually be fullscreen
// here, and ExitFullscreen() has no affect when passed a
// non-fullscreen document.
nsIDocument::ExitFullscreen(
nsContentUtils::IsFullscreenApiContentOnly() ? fullscreenAncestor : nullptr,
/* async */ true);
}
}
// Else not full-screen mode or key code is unrestricted, fall

View File

@ -3982,6 +3982,7 @@ pref("alerts.disableSlidingEffect", false);
// DOM full-screen API.
pref("full-screen-api.enabled", false);
pref("full-screen-api.allow-trusted-requests-only", true);
pref("full-screen-api.content-only", false);
pref("full-screen-api.pointer-lock.enabled", true);
// DOM idle observers API