Bug 805613 - Support multiple concurrent fullscreen documents. r=bz

This commit is contained in:
Chris Pearce 2013-02-26 18:40:53 +13:00
parent 445321c3bc
commit b8e8ab3f2f
12 changed files with 574 additions and 234 deletions

View File

@ -102,8 +102,8 @@ template<typename> class Sequence;
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0x4e6f7d97, 0x091e, 0x4eda, \
{ 0xb7, 0xd6, 0xfe, 0xb0, 0xb8, 0x01, 0x2a, 0x93 } }
{ 0x45ce048f, 0x5970, 0x411e, \
{ 0xaa, 0x99, 0x12, 0xed, 0x3a, 0x55, 0xc9, 0xc3 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -886,7 +886,7 @@ public:
* so that all its fullscreen element stacks are empty; we must continue the
* rollback in this parent process' doc tree branch which is fullscreen.
* Note that only one branch of the document tree can have its documents in
* fullscreen state at one time. We're in inconsistent state if the a
* fullscreen state at one time. We're in inconsistent state if a
* fullscreen document has a parent and that parent isn't fullscreen. We
* preserve this property across process boundaries.
*/
@ -904,6 +904,25 @@ public:
*/
virtual bool IsFullScreenDoc() = 0;
/**
* Returns true if this document is a fullscreen leaf document, i.e. it
* is in fullscreen mode and has no fullscreen children.
*/
virtual bool IsFullscreenLeaf() = 0;
/**
* Returns the document which is at the root of this document's branch
* in the in-process document tree. Returns nullptr if the document isn't
* fullscreen.
*/
virtual nsIDocument* GetFullscreenRoot() = 0;
/**
* Sets the fullscreen root to aRoot. This stores a weak reference to aRoot
* in this document.
*/
virtual void SetFullscreenRoot(nsIDocument* aRoot) = 0;
/**
* Sets whether this document is approved for fullscreen mode.
* Documents aren't approved for fullscreen until chrome has sent a
@ -913,12 +932,14 @@ public:
virtual void SetApprovedForFullscreen(bool aIsApproved) = 0;
/**
* Exits all documents from DOM full-screen mode, and moves the top-level
* browser window out of full-screen mode. If aRunAsync is true, this runs
* asynchronously.
* 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.
*/
static void ExitFullScreen(bool aRunAsync);
static void ExitFullscreen(nsIDocument* aDocument, bool aRunAsync);
virtual void RequestPointerLock(Element* aElement) = 0;

View File

@ -1298,7 +1298,7 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
nsContentUtils::eDOM_PROPERTIES,
"RemovedFullScreenElement");
// Fully exit full-screen.
nsIDocument::ExitFullScreen(false);
nsIDocument::ExitFullscreen(OwnerDoc(), /* async */ false);
}
if (HasPointerLock()) {
nsIDocument::UnlockPointer();

View File

@ -195,13 +195,6 @@ using namespace mozilla::dom;
typedef nsTArray<Link*> LinkArray;
// Reference to the document which requested DOM full-screen mode.
nsWeakPtr nsDocument::sFullScreenDoc = nullptr;
// Reference to the root document of the branch containing the document
// which requested DOM full-screen mode.
nsWeakPtr nsDocument::sFullScreenRootDoc = nullptr;
#ifdef PR_LOGGING
static PRLogModuleInfo* gDocumentLeakPRLog;
static PRLogModuleInfo* gCspPRLog;
@ -7782,18 +7775,26 @@ nsDocument::OnPageHide(bool aPersisted,
EnumerateFreezableElements(NotifyActivityChanged, nullptr);
if (IsFullScreenDoc()) {
// A full-screen doc has been hidden. We need to ensure we exit
// full-screen, i.e. remove full-screen state from all full-screen
// documents, and exit the top-level window from full-screen mode.
// By the time a doc is hidden, it has been removed from the doc tree,
// so nsIDocument::ExitFullScreen() won't be able to traverse to this
// document to reset its state, so reset full-screen state in *this*
// document. OnPageHide() is called in every hidden document, so doing
// this ensures all hidden documents have their full-screen state reset.
CleanupFullscreenState();
// If this document was fullscreen, we should exit fullscreen in this
// doctree branch. This ensures that if the user navigates while in
// fullscreen mode we don't leave its still visible ancestor documents
// in fullscreen mode. So exit fullscreen in the document's fullscreen
// root document, as this will exit fullscreen in all the root's
// descendant documents. Note that documents are removed from the
// doctree by the time OnPageHide() is called, so we must store a
// reference to the root (in nsDocument::mFullscreenRoot) since we can't
// just traverse the doctree to get the root.
nsIDocument::ExitFullscreen(this, /* async */ false);
// Next reset full-screen state in all visible documents in the doctree.
nsIDocument::ExitFullScreen(false);
// Since the document is removed from the doctree before OnPageHide() is
// called, ExitFullscreen() can't traverse from the root down to *this*
// document, so we must manually call CleanupFullscreenState() below too.
// Note that CleanupFullscreenState() clears nsDocument::mFullscreenRoot,
// so we *must* call it after ExitFullscreen(), not before.
// OnPageHide() is called in every hidden (i.e. descendant) document,
// so calling CleanupFullscreenState() here will ensure all hidden
// documents have their fullscreen state reset.
CleanupFullscreenState();
}
}
@ -8966,136 +8967,6 @@ nsIDocument::CreateTouchList(const Sequence<nsRefPtr<nsIDOMTouch> >& aTouches)
return retval.forget();
}
static void
DispatchFullScreenChange(nsIDocument* aTarget)
{
nsRefPtr<nsAsyncDOMEvent> e =
new nsAsyncDOMEvent(aTarget,
NS_LITERAL_STRING("mozfullscreenchange"),
true,
false);
e->PostDOMEvent();
}
NS_IMETHODIMP
nsDocument::MozCancelFullScreen()
{
nsIDocument::MozCancelFullScreen();
return NS_OK;
}
void
nsIDocument::MozCancelFullScreen()
{
// Only perform fullscreen changes if we're running in a webapp
// same-origin to the web app, or if we're in a user generated event
// handler.
if (NodePrincipal()->GetAppStatus() >= nsIPrincipal::APP_STATUS_INSTALLED ||
nsContentUtils::IsRequestFullScreenAllowed()) {
RestorePreviousFullScreenState();
}
}
// Runnable to set window full-screen mode. Used as a script runner
// to ensure we only call nsGlobalWindow::SetFullScreen() when it's safe to
// run script. nsGlobalWindow::SetFullScreen() dispatches a synchronous event
// (handled in chome code) which is unsafe to run if this is called in
// Element::UnbindFromTree().
class nsSetWindowFullScreen : public nsRunnable {
public:
nsSetWindowFullScreen(nsIDocument* aDoc, bool aValue)
: mDoc(aDoc), mValue(aValue) {}
NS_IMETHOD Run()
{
if (mDoc->GetWindow()) {
mDoc->GetWindow()->SetFullScreenInternal(mValue, false);
}
return NS_OK;
}
private:
nsCOMPtr<nsIDocument> mDoc;
bool mValue;
};
static void
SetWindowFullScreen(nsIDocument* aDoc, bool aValue)
{
nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue));
}
class nsCallExitFullScreen : public nsRunnable {
public:
NS_IMETHOD Run()
{
nsDocument::ExitFullScreen();
return NS_OK;
}
};
/* static */
void
nsIDocument::ExitFullScreen(bool aRunAsync)
{
if (aRunAsync) {
NS_DispatchToCurrentThread(new nsCallExitFullScreen());
return;
}
nsDocument::ExitFullScreen();
}
// Returns true if the document is a direct child of a cross process parent
// mozbrowser iframe. This is the case when the document has a null parent,
// and its DocShell reports that it is a browser frame.
static bool
HasCrossProcessParent(nsIDocument* aDocument)
{
if (XRE_GetProcessType() != GeckoProcessType_Content) {
return false;
}
if (aDocument->GetParentDocument() != nullptr) {
return false;
}
nsPIDOMWindow* win = aDocument->GetWindow();
if (!win) {
return false;
}
nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
if (!docShell) {
return false;
}
return docShell->GetIsBrowserOrApp();
}
static bool
ResetFullScreen(nsIDocument* aDocument, void* aData)
{
if (aDocument->IsFullScreenDoc()) {
static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
NS_ASSERTION(!aDocument->IsFullScreenDoc(), "Should reset full-screen");
nsTArray<nsIDocument*>* changed = reinterpret_cast<nsTArray<nsIDocument*>*>(aData);
changed->AppendElement(aDocument);
if (HasCrossProcessParent(aDocument)) {
// We're at the top of the content-process side doc tree. Ask the parent
// process to exit fullscreen.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(aDocument, "ask-parent-to-exit-fullscreen", nullptr);
}
// Dispatch a notification so that if this document has any
// cross-process subdocuments, they'll be notified to exit fullscreen.
// The BrowserElementParent listens for this event and performs the
// cross process notification if it has a remote child process.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(aDocument, "ask-children-to-exit-fullscreen", nullptr);
aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
}
return true;
}
already_AddRefed<nsDOMCaretPosition>
nsIDocument::CaretPositionFromPoint(float aX, float aY)
{
@ -9158,34 +9029,351 @@ nsDocument::CaretPositionFromPoint(float aX, float aY, nsISupports** aCaretPos)
return NS_OK;
}
namespace mozilla {
// Singleton class to manage the list of fullscreen documents which are the
// root of a branch which contains fullscreen documents. We maintain this list
// so that we can easily exit all windows from fullscreen when the user
// presses the escape key.
class FullscreenRoots {
public:
// Adds a root to the manager. Adding a root multiple times does not result
// in duplicate entries for that item, only one.
static void Add(nsIDocument* aRoot);
// Iterates over every root in the root list, and calls aFunction, passing
// each root once to aFunction. It is safe to call Add() and Remove() while
// iterating over the list (i.e. in aFunction). Documents that are removed
// from the manager during traversal are not traversed, and documents that
// are added to the manager during traversal are also not traversed.
static void ForEach(void(*aFunction)(nsIDocument* aDoc));
// Removes a specific root from the manager.
static void Remove(nsIDocument* aRoot);
// Returns true if all roots added to the list have been removed.
static bool IsEmpty();
private:
FullscreenRoots() {
MOZ_COUNT_CTOR(FullscreenRoots);
}
~FullscreenRoots() {
MOZ_COUNT_DTOR(FullscreenRoots);
}
enum {
NotFound = uint32_t(-1)
};
// Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
static uint32_t Find(nsIDocument* aRoot);
// Returns true if aRoot is in the list of fullscreen roots.
static bool Contains(nsIDocument* aRoot);
// Singleton instance of the FullscreenRoots. This is instantiated when a
// root is added, and it is deleted when the last root is removed.
static FullscreenRoots* sInstance;
// List of weak pointers to roots.
nsTArray<nsWeakPtr> mRoots;
};
FullscreenRoots* FullscreenRoots::sInstance = nullptr;
/* static */
void
nsDocument::ExitFullScreen()
FullscreenRoots::ForEach(void(*aFunction)(nsIDocument* aDoc))
{
// Clear full-screen stacks in all descendant documents.
nsCOMPtr<nsIDocument> root(do_QueryReferent(sFullScreenRootDoc));
if (!sInstance) {
return;
}
// Create a copy of the roots array, and iterate over the copy. This is so
// that if an element is removed from mRoots we don't mess up our iteration.
nsTArray<nsWeakPtr> roots(sInstance->mRoots);
// Call aFunction on all entries.
for (uint32_t i = 0; i < roots.Length(); i++) {
nsCOMPtr<nsIDocument> root = do_QueryReferent(roots[i]);
// Check that the root isn't in the manager. This is so that new additions
// while we were running don't get traversed.
if (root && FullscreenRoots::Contains(root)) {
aFunction(root);
}
}
}
/* static */
bool
FullscreenRoots::Contains(nsIDocument* aRoot)
{
return FullscreenRoots::Find(aRoot) != NotFound;
}
/* static */
void
FullscreenRoots::Add(nsIDocument* aRoot)
{
if (!FullscreenRoots::Contains(aRoot)) {
if (!sInstance) {
sInstance = new FullscreenRoots();
}
sInstance->mRoots.AppendElement(do_GetWeakReference(aRoot));
}
}
/* static */
uint32_t
FullscreenRoots::Find(nsIDocument* aRoot)
{
if (!sInstance) {
return NotFound;
}
nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
for (uint32_t i = 0; i < roots.Length(); i++) {
nsCOMPtr<nsIDocument> otherRoot(do_QueryReferent(roots[i]));
if (otherRoot == aRoot) {
return i;
}
}
return NotFound;
}
/* static */
void
FullscreenRoots::Remove(nsIDocument* aRoot)
{
uint32_t index = Find(aRoot);
NS_ASSERTION(index != NotFound,
"Should only try to remove roots which are still added!");
if (index == NotFound || !sInstance) {
return;
}
sInstance->mRoots.RemoveElementAt(index);
if (sInstance->mRoots.IsEmpty()) {
delete sInstance;
sInstance = nullptr;
}
}
/* static */
bool
FullscreenRoots::IsEmpty()
{
return !sInstance;
}
} // end namespace mozilla.
using mozilla::FullscreenRoots;
nsIDocument*
nsDocument::GetFullscreenRoot()
{
nsCOMPtr<nsIDocument> root = do_QueryReferent(mFullscreenRoot);
return root;
}
void
nsDocument::SetFullscreenRoot(nsIDocument* aRoot)
{
mFullscreenRoot = do_GetWeakReference(aRoot);
}
static void
DispatchFullScreenChange(nsIDocument* aTarget)
{
nsRefPtr<nsAsyncDOMEvent> e =
new nsAsyncDOMEvent(aTarget,
NS_LITERAL_STRING("mozfullscreenchange"),
true,
false);
e->PostDOMEvent();
}
NS_IMETHODIMP
nsDocument::MozCancelFullScreen()
{
nsIDocument::MozCancelFullScreen();
return NS_OK;
}
void
nsIDocument::MozCancelFullScreen()
{
// Only perform fullscreen changes if we're running in a webapp
// same-origin to the web app, or if we're in a user generated event
// handler.
if (NodePrincipal()->GetAppStatus() >= nsIPrincipal::APP_STATUS_INSTALLED ||
nsContentUtils::IsRequestFullScreenAllowed()) {
RestorePreviousFullScreenState();
}
}
// Runnable to set window full-screen mode. Used as a script runner
// to ensure we only call nsGlobalWindow::SetFullScreen() when it's safe to
// run script. nsGlobalWindow::SetFullScreen() dispatches a synchronous event
// (handled in chome code) which is unsafe to run if this is called in
// Element::UnbindFromTree().
class nsSetWindowFullScreen : public nsRunnable {
public:
nsSetWindowFullScreen(nsIDocument* aDoc, bool aValue)
: mDoc(aDoc), mValue(aValue) {}
NS_IMETHOD Run()
{
if (mDoc->GetWindow()) {
mDoc->GetWindow()->SetFullScreenInternal(mValue, false);
}
return NS_OK;
}
private:
nsCOMPtr<nsIDocument> mDoc;
bool mValue;
};
static void
SetWindowFullScreen(nsIDocument* aDoc, bool aValue)
{
// Maintain list of fullscreen root documents.
nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc);
if (aValue) {
FullscreenRoots::Add(root);
} else {
FullscreenRoots::Remove(root);
}
nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue));
}
class nsCallExitFullscreen : public nsRunnable {
public:
nsCallExitFullscreen(nsIDocument* aDoc)
: mDoc(aDoc) {}
NS_IMETHOD Run()
{
nsDocument::ExitFullscreen(mDoc);
return NS_OK;
}
private:
nsCOMPtr<nsIDocument> mDoc;
};
/* static */
void
nsIDocument::ExitFullscreen(nsIDocument* aDoc, bool aRunAsync)
{
if (aDoc && !aDoc->IsFullScreenDoc()) {
return;
}
if (aRunAsync) {
NS_DispatchToCurrentThread(new nsCallExitFullscreen(aDoc));
return;
}
nsDocument::ExitFullscreen(aDoc);
}
// Returns true if the document is a direct child of a cross process parent
// mozbrowser iframe. This is the case when the document has a null parent,
// and its DocShell reports that it is a browser frame.
static bool
HasCrossProcessParent(nsIDocument* aDocument)
{
if (XRE_GetProcessType() != GeckoProcessType_Content) {
return false;
}
if (aDocument->GetParentDocument() != nullptr) {
return false;
}
nsPIDOMWindow* win = aDocument->GetWindow();
if (!win) {
return false;
}
nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
if (!docShell) {
return false;
}
return docShell->GetIsBrowserOrApp();
}
static bool
CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData)
{
if (aDoc->IsFullScreenDoc()) {
uint32_t* count = static_cast<uint32_t*>(aData);
(*count)++;
}
return true;
}
static uint32_t
CountFullscreenSubDocuments(nsIDocument* aDoc)
{
uint32_t count = 0;
aDoc->EnumerateSubDocuments(CountFullscreenSubDocuments, &count);
return count;
}
bool
nsDocument::IsFullscreenLeaf()
{
// A fullscreen leaf document is fullscreen, and has no fullscreen
// subdocuments.
if (!IsFullScreenDoc()) {
return false;
}
return CountFullscreenSubDocuments(this) == 0;
}
static bool
ResetFullScreen(nsIDocument* aDocument, void* aData)
{
if (aDocument->IsFullScreenDoc()) {
NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
"Should have at most 1 fullscreen subdocument.");
static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
NS_ASSERTION(!aDocument->IsFullScreenDoc(), "Should reset full-screen");
nsTArray<nsIDocument*>* changed = reinterpret_cast<nsTArray<nsIDocument*>*>(aData);
changed->AppendElement(aDocument);
if (HasCrossProcessParent(aDocument)) {
// We're at the top of the content-process side doc tree. Ask the parent
// process to exit fullscreen.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(aDocument, "ask-parent-to-exit-fullscreen", nullptr);
}
// Dispatch a notification so that if this document has any
// cross-process subdocuments, they'll be notified to exit fullscreen.
// The BrowserElementParent listens for this event and performs the
// cross process notification if it has a remote child process.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(aDocument, "ask-children-to-exit-fullscreen", nullptr);
aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
}
return true;
}
static void
ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc)
{
MOZ_ASSERT(aMaybeNotARootDoc);
nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot();
NS_ASSERTION(root, "Should have root when in fullscreen!");
if (!root) {
// Not in full-screen mode.
return;
}
NS_ASSERTION(root->IsFullScreenDoc(),
"Full-screen root should be a full-screen doc...");
"Fullscreen root should be a fullscreen doc...");
// Stores a list of documents to which we must dispatch "mozfullscreenchange".
// We're required by the spec to dispatch the events in leaf-to-root
// order when exiting full-screen, but we traverse the doctree in a
// order when exiting fullscreen, but we traverse the doctree in a
// root-to-leaf order, so we save references to the documents we must
// dispatch to so that we dispatch in the specified order.
nsAutoTArray<nsIDocument*, 8> changed;
// We may also need to unlock the pointer, if it's locked.
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(nsEventStateManager::sPointerLockedElement);
if (pointerLockedElement) {
UnlockPointer();
}
// Walk the tree of full-screen documents, and reset their full-screen state.
// Walk the tree of fullscreen documents, and reset their fullscreen state.
ResetFullScreen(root, static_cast<void*>(&changed));
// Dispatch "mozfullscreenchange" events. Note this loop is in reverse
@ -9195,23 +9383,75 @@ nsDocument::ExitFullScreen()
DispatchFullScreenChange(changed[changed.Length() - i - 1]);
}
// Reset global state. Do this before we move the window out of full-screen
// mode, as that calls nsGlobalWindow::SetFullScreen() which calls back into
// nsIDocument::ExitFullScreen().
sFullScreenRootDoc = nullptr;
sFullScreenDoc = nullptr;
NS_ASSERTION(!root->IsFullScreenDoc(),
"Fullscreen root should no longer be a fullscreen doc...");
// Move the top-level window out of full-screen mode.
// Move the top-level window out of fullscreen mode.
SetWindowFullScreen(root, false);
}
/* static */
void
nsDocument::ExitFullscreen(nsIDocument* aDoc)
{
// Unlock the pointer, if it's locked.
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(nsEventStateManager::sPointerLockedElement);
if (pointerLockedElement) {
UnlockPointer();
}
if (aDoc) {
ExitFullscreenInDocTree(aDoc);
return;
}
// Clear fullscreen stacks in all fullscreen roots' descendant documents.
FullscreenRoots::ForEach(&ExitFullscreenInDocTree);
NS_ASSERTION(FullscreenRoots::IsEmpty(),
"Should have exited all fullscreen roots from fullscreen");
}
bool
GetFullscreenLeaf(nsIDocument* aDoc, void* aData)
{
if (aDoc->IsFullscreenLeaf()) {
nsIDocument** result = static_cast<nsIDocument**>(aData);
*result = aDoc;
return false;
} else if (aDoc->IsFullScreenDoc()) {
aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData);
}
return true;
}
static nsIDocument*
GetFullscreenLeaf(nsIDocument* aDoc)
{
nsIDocument* leaf = nullptr;
GetFullscreenLeaf(aDoc, &leaf);
if (leaf) {
return leaf;
}
// 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);
// Check that the root is actually fullscreen so we don't waste time walking
// around its descendants.
if (!root->IsFullScreenDoc()) {
return nullptr;
}
GetFullscreenLeaf(root, &leaf);
return leaf;
}
void
nsDocument::RestorePreviousFullScreenState()
{
NS_ASSERTION(!IsFullScreenDoc() || sFullScreenDoc != nullptr,
"Should have a full-screen doc when full-screen!");
NS_ASSERTION(!IsFullScreenDoc() || !FullscreenRoots::IsEmpty(),
"Should have at least 1 fullscreen root when fullscreen!");
if (!IsFullScreenDoc() || !GetWindow() || !sFullScreenDoc) {
if (!IsFullScreenDoc() || !GetWindow() || FullscreenRoots::IsEmpty()) {
return;
}
@ -9222,7 +9462,7 @@ nsDocument::RestorePreviousFullScreenState()
UnlockPointer();
}
nsCOMPtr<nsIDocument> fullScreenDoc(do_QueryReferent(sFullScreenDoc));
nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this);
// The fullscreen document may contain a <iframe mozbrowser> element which
// has a cross process child. So send a notification so that its browser
@ -9289,18 +9529,15 @@ nsDocument::RestorePreviousFullScreenState()
os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
}
sFullScreenDoc = do_GetWeakReference(doc);
break;
}
}
if (doc == nullptr) {
// We moved all documents out of full-screen mode, reset global full-screen
// state and move the top-level window out of full-screen mode.
DebugOnly< nsCOMPtr<nsIDocument> > root(do_QueryReferent(sFullScreenRootDoc));
NS_ASSERTION(!root->IsFullScreenDoc(), "Should have cleared all docs' stacks");
sFullScreenDoc = nullptr;
sFullScreenRootDoc = 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(),
"Should have cleared all docs' stacks");
SetWindowFullScreen(this, false);
}
}
@ -9406,6 +9643,7 @@ nsDocument::CleanupFullscreenState()
}
SetApprovedForFullscreen(false);
RemoveFullscreenApprovedObserver();
mFullscreenRoot = nullptr;
}
bool
@ -9584,7 +9822,7 @@ nsDocument::RequestFullScreen(Element* aElement,
if (GetFullScreenElement() &&
!nsContentUtils::ContentIsDescendantOf(aElement, GetFullScreenElement())) {
// If this document is full-screen, only grant full-screen requests from
// a descendent of the current full-screen element.
// a descendant of the current full-screen element.
LogFullScreenDenied(true, "FullScreenDeniedNotDescendant", this);
return;
}
@ -9608,6 +9846,10 @@ nsDocument::RequestFullScreen(Element* aElement,
}
}
// Stash a reference to any existing fullscreen doc, we'll use this later
// to detect if the origin which is fullscreen has changed.
nsCOMPtr<nsIDocument> previousFullscreenDoc = GetFullscreenLeaf(this);
AddFullscreenApprovedObserver();
// Stores a list of documents which we must dispatch "mozfullscreenchange"
@ -9620,11 +9862,9 @@ 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);
sFullScreenRootDoc = do_GetWeakReference(fullScreenRootDoc);
// If a document is already in fullscreen, then unlock the mouse pointer
// before setting a new document to fullscreen
if (sFullScreenDoc) {
if (fullScreenRootDoc->IsFullScreenDoc()) {
// A document is already in fullscreen, unlock the mouse pointer
// before setting a new document to fullscreen
UnlockPointer();
}
@ -9650,6 +9890,9 @@ nsDocument::RequestFullScreen(Element* aElement,
// visible there. Stop when we reach the root document.
nsIDocument* child = this;
while (true) {
child->SetFullscreenRoot(fullScreenRootDoc);
NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
"Fullscreen root should be set!");
nsIDocument* parent = child->GetParentDocument();
if (!parent) {
break;
@ -9689,7 +9932,6 @@ nsDocument::RequestFullScreen(Element* aElement,
// If this document, or a document with the same principal has not
// already been approved for fullscreen this fullscreen-session, dispatch
// an event so that chrome knows to pop up a warning/approval UI.
nsCOMPtr<nsIDocument> previousFullscreenDoc(do_QueryReferent(sFullScreenDoc));
// Note previousFullscreenDoc=nullptr upon first entry, so we always
// take this path on the first time we enter fullscreen in a fullscreen
// session.
@ -9703,10 +9945,6 @@ nsDocument::RequestFullScreen(Element* aElement,
e->PostDOMEvent();
}
// Remember this is the requesting full-screen document.
sFullScreenDoc = do_GetWeakReference(static_cast<nsIDocument*>(this));
NS_ASSERTION(sFullScreenDoc, "nsDocument should support weak ref!");
#ifdef DEBUG
// Note assertions must run before SetWindowFullScreen() as that does
// synchronous event dispatch which can run script which exits full-screen!
@ -9802,24 +10040,12 @@ nsDocument::MozFullScreenEnabled()
return IsFullScreenEnabled(nsContentUtils::IsCallerChrome(), false);
}
static bool
HasFullScreenSubDocument(nsIDocument* aDoc, void* aData)
{
if (aDoc->IsFullScreenDoc()) {
// This subdocument is full-screen. Set result and return false to
// stop iteration.
*static_cast<bool*>(aData) = true;
return false;
}
return true;
}
static bool
HasFullScreenSubDocument(nsIDocument* aDoc)
{
bool result = false;
aDoc->EnumerateSubDocuments(&HasFullScreenSubDocument, static_cast<void*>(&result));
return result;
uint32_t count = CountFullscreenSubDocuments(aDoc);
NS_ASSERTION(count <= 1, "Fullscreen docs should have at most 1 fullscreen child!");
return count >= 1;
}
bool

View File

@ -943,14 +943,17 @@ public:
virtual Element* GetFullScreenElement();
virtual void AsyncRequestFullScreen(Element* aElement);
virtual void RestorePreviousFullScreenState();
virtual bool IsFullscreenLeaf();
virtual bool IsFullScreenDoc();
virtual void SetApprovedForFullscreen(bool aIsApproved);
virtual nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
const nsAString& aNewOrigin);
virtual nsresult RemoteFrameFullscreenReverted();
virtual nsIDocument* GetFullscreenRoot();
virtual void SetFullscreenRoot(nsIDocument* aRoot);
static void ExitFullScreen();
static void ExitFullscreen(nsIDocument* aDoc);
// This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
// to move this document into full-screen mode if allowed. aWasCallerChrome
@ -1163,17 +1166,6 @@ protected:
// is a weak reference to avoid leaks due to circular references.
nsWeakPtr mScopeObject;
// The document which requested (and was granted) full-screen. All ancestors
// of this document will also be full-screen.
static nsWeakPtr sFullScreenDoc;
// The root document of the doctree containing the document which requested
// full-screen. This root document will also be in full-screen state, as will
// all the descendents down to the document which requested full-screen. This
// reference allows us to reset full-screen state on all documents when a
// document is hidden/navigation occurs.
static nsWeakPtr sFullScreenRootDoc;
// Weak reference to the document which owned the pending pointer lock
// element, at the time it requested pointer lock.
static nsWeakPtr sPendingPointerLockDoc;
@ -1188,6 +1180,10 @@ protected:
// pop one off this stack, restoring the previous full-screen state
nsTArray<nsWeakPtr> mFullScreenStack;
// The root of the doc tree in which this document is in. This is only
// non-null when this document is in fullscreen mode.
nsWeakPtr mFullscreenRoot;
nsRefPtr<nsEventListenerManager> mListenerManager;
nsCOMPtr<nsIDOMStyleSheetList> mDOMStyleSheets;
nsRefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;

View File

@ -255,6 +255,8 @@ MOCHITEST_FILES = \
file_fullscreen-esc-exit-inner.html \
file_fullscreen-rollback.html \
file_fullscreen-svg-element.html \
file_fullscreen-multiple.html \
file_fullscreen-multiple-inner.html \
test_li_attributes_reflection.html \
test_link_attributes_reflection.html \
test_ol_attributes_reflection.html \

View File

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Bug 724554</title>
<script type="application/javascript" src="file_fullscreen-utils.js"></script>
</head>
<body>
<script type="application/javascript">
/** Test for Bug 545812 **/
function begin(id) {
addFullscreenErrorContinuation(function() {
opener.ok(false, "Fullscreen denied " + id);
});
addFullscreenChangeContinuation("enter",
function() {
opener.enteredFullscreen(id);
});
document.body.mozRequestFullScreen();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=724554
Test that multiple windows can be fullscreen at the same time.
Open one window, focus it and enter fullscreen, then open another, focus
it and enter fullscreen, and check that both are still fullscreen.
-->
<head>
<title>Test for Bug 724554</title>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="file_fullscreen-utils.js"></script>
</head>
<body>
<script type="application/javascript">
/** Test for Bug 545812 **/
function ok(condition, msg) {
opener.ok(condition, "[multiple] " + msg);
}
function is(a, b, msg) {
opener.is(a, b, "[multiple] " + msg);
}
var window1, window2;
function openWindow(id) {
var w = window.open("file_fullscreen-multiple-inner.html", "", "width=500,height=500");
w.addEventListener("load", function onload() {
w.focus();
SimpleTest.waitForFocus(function(){w.begin(id)}, w);
});
return w;
}
function begin() {
window1 = openWindow("one");
}
function enteredFullscreen(id) {
if (id == "one") {
window2 = openWindow("two");
} else if (id == "two") {
ok(window1.document.mozFullScreenElement &&
window2.document.mozFullScreenElement,
"Both windows should be fullscreen concurrently");
window1.close();
window2.close();
opener.nextTest();
}
}
</script>
</pre>
<div id="full-screen-element"></div>
</body>
</html>

View File

@ -34,6 +34,7 @@ SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
// run in an iframe, which by default will not have the allowfullscreen
// attribute set, so full-screen won't work.
var gTestWindows = [
"file_fullscreen-multiple.html",
"file_fullscreen-rollback.html",
"file_fullscreen-esc-exit.html",
"file_fullscreen-denied.html",

View File

@ -2955,7 +2955,7 @@ nsDOMWindowUtils::ExitFullscreen()
return NS_ERROR_DOM_SECURITY_ERR;
}
nsIDocument::ExitFullScreen(/* async = */ false);
nsIDocument::ExitFullscreen(nullptr, /* async */ false);
return NS_OK;
}

View File

@ -1166,7 +1166,7 @@ nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
contentToFocus->OwnerDoc(),
nsContentUtils::eDOM_PROPERTIES,
"FocusedWindowedPluginWhileFullScreen");
nsIDocument::ExitFullScreen(true);
nsIDocument::ExitFullscreen(contentToFocus->OwnerDoc(), /* async */ true);
}
#endif

View File

@ -4731,7 +4731,7 @@ nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust)
GetFullScreen(&rootWinFullScreen);
// Only chrome can change our fullScreen mode, unless we're running in
// untrusted mode.
if (aFullScreen == rootWinFullScreen ||
if (aFullScreen == rootWinFullScreen ||
(aRequireTrust && !nsContentUtils::IsCallerChrome())) {
return NS_OK;
}
@ -4792,7 +4792,8 @@ nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust)
// DOM full-screen mode and the user exits full-screen mode with
// the browser full-screen mode toggle keyboard-shortcut, we'll detect
// that and leave DOM API full-screen mode too.
nsIDocument::ExitFullScreen(false);
nsCOMPtr<nsIDocument> doc(do_QueryInterface(mDocument));
nsIDocument::ExitFullscreen(doc, /* async */ false);
}
if (!mWakeLock && mFullScreen) {

View File

@ -6630,16 +6630,17 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsEventStatus* aStatus)
(root = nsContentUtils::GetRootDocument(doc)) &&
root->IsFullScreenDoc()) {
// Prevent default action on ESC key press when exiting
// DOM full-screen mode. This prevents the browser ESC key
// DOM fullscreen mode. This prevents the browser ESC key
// handler from stopping all loads in the document, which
// would cause <video> loads to stop.
aEvent->mFlags.mDefaultPrevented = true;
aEvent->mFlags.mOnlyChromeDispatch = true;
if (aEvent->message == NS_KEY_UP) {
// ESC key released while in DOM full-screen mode.
// Exit full-screen mode.
nsIDocument::ExitFullScreen(true);
// ESC key released while in DOM fullscreen mode.
// Fully exit all browser windows and documents from
// fullscreen mode.
nsIDocument::ExitFullscreen(nullptr, /* async */ true);
}
}
// Else not full-screen mode or key code is unrestricted, fall