Index: third_party/WebKit/Source/core/dom/Fullscreen.cpp |
diff --git a/third_party/WebKit/Source/core/dom/Fullscreen.cpp b/third_party/WebKit/Source/core/dom/Fullscreen.cpp |
index ebbdcfe3c8a255416cb66e0ae7fc2311619a9c1d..9341051bee6acff3500950b74294e032020efac8 100644 |
--- a/third_party/WebKit/Source/core/dom/Fullscreen.cpp |
+++ b/third_party/WebKit/Source/core/dom/Fullscreen.cpp |
@@ -29,8 +29,9 @@ |
#include "core/dom/Fullscreen.h" |
+#include "bindings/core/v8/ExceptionMessages.h" |
#include "core/dom/Document.h" |
-#include "core/dom/ElementTraversal.h" |
+#include "core/dom/StyleChangeReason.h" |
#include "core/dom/StyleEngine.h" |
#include "core/events/Event.h" |
#include "core/frame/FrameHost.h" |
@@ -38,12 +39,9 @@ |
#include "core/frame/LocalFrame.h" |
#include "core/frame/Settings.h" |
#include "core/frame/UseCounter.h" |
-#include "core/html/HTMLIFrameElement.h" |
+#include "core/html/HTMLFrameOwnerElement.h" |
#include "core/input/EventHandler.h" |
#include "core/inspector/ConsoleMessage.h" |
-#include "core/layout/LayoutBlockFlow.h" |
-#include "core/layout/LayoutFullScreen.h" |
-#include "core/layout/api/LayoutFullScreenItem.h" |
#include "core/page/ChromeClient.h" |
#include "core/svg/SVGSVGElement.h" |
#include "platform/ScopedOrientationChangeIndicator.h" |
@@ -53,6 +51,39 @@ namespace blink { |
namespace { |
+// https://fullscreen.spec.whatwg.org/#fullscreen-an-element |
+void fullscreen(Element& element, Fullscreen::RequestType requestType) { |
+ // To fullscreen an |element| within a |document|, set the |element|'s |
+ // fullscreen flag and add it to |document|'s top layer. |
+ DCHECK(!element.isFullscreen()); |
+ // DCHECK(!element.isIFrameFullscreen()); |
+ DCHECK(!element.isInTopLayer()); |
+ element.setIsFullscreen(true); |
+ element.document().addToTopLayer(&element); |
+} |
+ |
+// https://fullscreen.spec.whatwg.org/#unfullscreen-an-element |
+void unfullscreen(Element& element) { |
+ // To unfullscreen an |element| within a |document|, unset the element's |
+ // fullscreen flag and iframe fullscreen flag (if any), and remove it from |
+ // |document|'s top layer. |
+ DCHECK(element.isFullscreen()); |
+ DCHECK(element.isInTopLayer()); |
+ element.setIsFullscreen(true); |
+ // element.setIsIFrameFullscreen(false); |
+ element.document().removeFromTopLayer(&element); |
+} |
+ |
+// https://fullscreen.spec.whatwg.org/#unfullscreen-a-document |
+void unfullscreen(Document& document) { |
+ // To unfullscreen a |document|, unfullscreen all elements, within |
+ // |document|'s top layer, whose fullscreen flag is set. |
+ for (Element* element : document.topLayerElements()) { |
+ if (element->isFullscreen()) |
+ unfullscreen(*element); |
+ } |
+} |
+ |
// https://html.spec.whatwg.org/multipage/embedded-content.html#allowed-to-use |
bool allowedToUseFullscreen(const Frame* frame) { |
// To determine whether a Document object |document| is allowed to use the |
@@ -120,19 +151,6 @@ bool fullscreenElementReady(const Element& element) { |
if (!allowedToUseFullscreen(element.document().frame())) |
return false; |
- // |element|'s node document's fullscreen element stack is either empty or its |
- // top element is an inclusive ancestor of |element|. |
- if (const Element* topElement = |
- Fullscreen::fullscreenElementFrom(element.document())) { |
- if (!topElement->contains(&element)) |
- return false; |
- } |
- |
- // |element| has no ancestor element whose local name is iframe and namespace |
- // is the HTML namespace. |
- if (Traversal<HTMLIFrameElement>::firstAncestor(element)) |
- return false; |
- |
// |element|'s node document's browsing context either has a browsing context |
// container and the fullscreen element ready check returns true for |
// |element|'s node document's browsing context's browsing context container, |
@@ -145,17 +163,51 @@ bool fullscreenElementReady(const Element& element) { |
return true; |
} |
-bool isPrefixed(const AtomicString& type) { |
- return type == EventTypeNames::webkitfullscreenchange || |
- type == EventTypeNames::webkitfullscreenerror; |
+size_t countFullscreenInTopLayer(const Document& document) { |
+ size_t count = 0; |
+ for (Element* element : document.topLayerElements()) { |
+ if (element->isFullscreen()) |
+ ++count; |
+ } |
+ return count; |
} |
-Event* createEvent(const AtomicString& type, EventTarget& target) { |
- EventInit initializer; |
- initializer.setBubbles(isPrefixed(type)); |
- Event* event = Event::create(type, initializer); |
- event->setTarget(&target); |
- return event; |
+// https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen |
+HeapVector<Member<Document>> collectDocumentsToUnfullscreen(Document& doc) { |
+ // 1. If |doc|'s top layer consists of more than a single element that has |
+ // its fullscreen flag set, return the empty set. |
+ |
+ // 2. Let |docs| be an ordered set consisting of |doc|. |
+ |
+ // 3. While |docs|'s last document has a browsing context container whose |
+ // node document's top layer consists of a single element that has its |
+ // fullscreen flag set and does not have its iframe fullscreen flag set (if |
+ // any), append that node document to |docs|. |
+ // TODO(foolip): Support the iframe fullscreen flag. |
+ // https://crbug.com/644695 |
+ |
+ // OOPIF: Skip over remote frames, assuming that they have exactly one |
+ // element in their fullscreen element stacks, thereby erring on the side of |
+ // exiting fullscreen. |
+ // TODO(alexmos): Deal with nested fullscreen cases, see |
+ // https://crbug.com/617369. |
+ |
+ HeapVector<Member<Document>> docs; |
+ for (Frame* frame = doc.frame(); frame; frame = frame->tree().parent()) { |
+ if (frame->isRemoteFrame()) |
+ continue; |
+ if (Document* document = toLocalFrame(frame)->document()) { |
+ // TODO(foolip): Fix the spec to count number of "fullscreen flag" |
+ // elements in top layer, to avoid dialogs messing up the logic. |
+ if (countFullscreenInTopLayer(*document) == 1) |
+ docs.append(document); |
+ else |
+ break; |
+ } |
+ } |
+ |
+ // 4. Return |docs|. |
+ return docs; |
} |
// Helper to walk the ancestor chain and return the Document of the topmost |
@@ -174,47 +226,57 @@ Document& topmostLocalAncestor(Document& document) { |
return *topmost; |
} |
-// Helper to find the browsing context container in |doc| that embeds the |
-// |descendant| Document, possibly through multiple levels of nesting. This |
-// works even in OOPIF scenarios like A-B-A, where there may be remote frames |
-// in between |doc| and |descendant|. |
-HTMLFrameOwnerElement* findContainerForDescendant(const Document& doc, |
- const Document& descendant) { |
- Frame* frame = descendant.frame(); |
- while (frame->tree().parent() != doc.frame()) |
- frame = frame->tree().parent(); |
- return toHTMLFrameOwnerElement(frame->owner()); |
+void fireChangeEvent(Element& element, Fullscreen::RequestType requestType) { |
+ if (requestType == Fullscreen::UnprefixedRequest) { |
+ Event* event = Event::create(EventTypeNames::fullscreenchange); |
+ element.document().dispatchEvent(event); |
+ } else { |
+ bool wasConnected = element.isConnected(); |
+ |
+ Event* event = Event::createBubble(EventTypeNames::webkitfullscreenchange); |
+ element.dispatchEvent(event); |
+ |
+ // TODO(foolip): Talk to Edge and WebKit teams about what to do if the |
+ // element isn't connected. |
+ if (!wasConnected) { |
+ if (Element* documentElement = element.document().documentElement()) { |
+ Event* documentEvent = |
+ Event::createBubble(EventTypeNames::webkitfullscreenchange); |
+ documentElement->dispatchEvent(documentEvent); |
+ } |
+ } |
+ } |
} |
-} // anonymous namespace |
- |
-const char* Fullscreen::supplementName() { |
- return "Fullscreen"; |
+void fireErrorEvent(Element& element, Fullscreen::RequestType requestType) { |
+ if (requestType == Fullscreen::UnprefixedRequest) { |
+ Event* event = Event::create(EventTypeNames::fullscreenerror); |
+ element.document().dispatchEvent(event); |
+ } else { |
+ Event* event = Event::createBubble(EventTypeNames::webkitfullscreenerror); |
+ element.dispatchEvent(event); |
+ } |
} |
-Fullscreen& Fullscreen::from(Document& document) { |
- Fullscreen* fullscreen = fromIfExists(document); |
- if (!fullscreen) { |
- fullscreen = new Fullscreen(document); |
- Supplement<Document>::provideTo(document, supplementName(), fullscreen); |
- } |
+} // anonymous namespace |
- return *fullscreen; |
-} |
+Element* Fullscreen::fullscreenElement(Document& document) { |
+ // The fullscreen element is the topmost element in the document's top layer |
+ // whose fullscreen flag is set, if any, and null otherwise. |
-Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) { |
- return static_cast<Fullscreen*>( |
- Supplement<Document>::from(document, supplementName())); |
-} |
+ const auto& elements = document.topLayerElements(); |
+ for (auto it = elements.rbegin(); it != elements.rend(); ++it) { |
+ Element* element = (*it).get(); |
+ if (element->isFullscreen()) |
+ return element; |
+ } |
-Element* Fullscreen::fullscreenElementFrom(Document& document) { |
- if (Fullscreen* found = fromIfExists(document)) |
- return found->fullscreenElement(); |
return nullptr; |
} |
-Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) { |
- Element* element = fullscreenElementFrom(scope.document()); |
+// https://fullscreen.spec.whatwg.org/#fullscreen-element |
+Element* Fullscreen::fullscreenElementForBinding(TreeScope& scope) { |
+ Element* element = fullscreenElement(scope.document()); |
if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
return element; |
@@ -234,92 +296,42 @@ Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) { |
return scope.adjustedElement(*element); |
} |
-Element* Fullscreen::currentFullScreenElementFrom(Document& document) { |
- if (Fullscreen* found = fromIfExists(document)) |
- return found->currentFullScreenElement(); |
- return nullptr; |
-} |
- |
-Element* Fullscreen::currentFullScreenElementForBindingFrom( |
- Document& document) { |
- Element* element = currentFullScreenElementFrom(document); |
- if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
- return element; |
- |
- // For Shadow DOM V0 compatibility: We allow returning an element in V0 shadow |
- // tree, even though it leaks the Shadow DOM. |
- if (element->isInV0ShadowTree()) { |
- UseCounter::count(document, |
- UseCounter::DocumentFullscreenElementInV0Shadow); |
- return element; |
- } |
- return document.adjustedElement(*element); |
-} |
- |
-Fullscreen::Fullscreen(Document& document) |
- : ContextLifecycleObserver(&document), |
- m_fullScreenLayoutObject(nullptr), |
- m_eventQueueTimer(this, &Fullscreen::eventQueueTimerFired), |
- m_forCrossProcessDescendant(false) { |
- document.setHasFullscreenSupplement(); |
-} |
- |
-Fullscreen::~Fullscreen() {} |
- |
-inline Document* Fullscreen::document() { |
- return toDocument(lifecycleContext()); |
-} |
- |
-void Fullscreen::contextDestroyed() { |
- m_eventQueue.clear(); |
- |
- if (m_fullScreenLayoutObject) |
- m_fullScreenLayoutObject->destroy(); |
- |
- m_currentFullScreenElement = nullptr; |
- m_fullscreenElementStack.clear(); |
-} |
- |
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen |
-void Fullscreen::requestFullscreen(Element& element, |
- RequestType requestType, |
- bool forCrossProcessDescendant) { |
- Document& document = element.document(); |
- |
- // Use counters only need to be incremented in the process of the actual |
- // fullscreen element. |
- if (!forCrossProcessDescendant) { |
- if (document.isSecureContext()) { |
- UseCounter::count(document, UseCounter::FullscreenSecureOrigin); |
- } else { |
- UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); |
- HostsUsingFeatures::countAnyWorld( |
- document, HostsUsingFeatures::Feature::FullscreenInsecureHost); |
- } |
- } |
+void Fullscreen::requestFullscreen(Element& pending, RequestType requestType) { |
+ Document& document = pending.document(); |
// Ignore this request if the document is not in a live frame. |
+ // TODO(foolip): This would make sense as part of fullscreenIsSupported. |
if (!document.isActive()) |
return; |
- // If |element| is on top of |doc|'s fullscreen element stack, terminate these |
- // substeps. |
- if (&element == fullscreenElementFrom(document)) |
- return; |
+ if (document.isSecureContext()) { |
+ UseCounter::count(document, UseCounter::FullscreenSecureOrigin); |
+ } else { |
+ UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); |
+ HostsUsingFeatures::countAnyWorld( |
+ document, HostsUsingFeatures::Feature::FullscreenInsecureHost); |
+ } |
- do { |
- // 1. If any of the following conditions are false, then terminate these |
- // steps and queue a task to fire an event named fullscreenerror with its |
- // bubbles attribute set to true on the context object's node document: |
+ // 1. Let |pending| be the context object. |
+ |
+ // 2. Let |error| be false. |
+ |
+ // 3. Let |promise| be a new promise. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
- // |element|'s namespace is the HTML namespace or |element| is an SVG |
+ // 4. If any of the following conditions are false, then set |error| to |
+ // true: |
+ bool error = true; |
+ do { |
+ // |pending|'s namespace is the HTML namespace or |pending| is an SVG |
// svg or MathML math element. |
// Note: MathML is not supported. |
- if (!element.isHTMLElement() && !isSVGSVGElement(element)) |
+ if (!pending.isHTMLElement() && !isSVGSVGElement(pending)) |
break; |
- // The fullscreen element ready check for |element| returns true. |
- if (!fullscreenElementReady(element)) |
+ // The fullscreen element ready check for |pending| returns false. |
+ if (!fullscreenElementReady(pending)) |
break; |
// Fullscreen is supported. |
@@ -327,226 +339,259 @@ void Fullscreen::requestFullscreen(Element& element, |
break; |
// This algorithm is allowed to request fullscreen. |
- // OOPIF: If |forCrossProcessDescendant| is true, requestFullscreen was |
- // already called on a descendant element in another process, and |
- // getting here means that it was already allowed to request fullscreen. |
- if (!forCrossProcessDescendant && !allowedToRequestFullscreen(document)) |
+ if (!allowedToRequestFullscreen(document)) |
break; |
- // 2. Let doc be element's node document. (i.e. "this") |
- |
- // 3. Let docs be all doc's ancestor browsing context's documents (if any) |
- // and doc. |
- // |
- // For OOPIF scenarios, |docs| will only contain documents for local |
- // ancestors, and remote ancestors will be processed in their |
- // respective processes. This preserves the spec's event firing order |
- // for local ancestors, but not for remote ancestors. However, that |
- // difference shouldn't be observable in practice: a fullscreenchange |
- // event handler would need to postMessage a frame in another renderer |
- // process, where the message should be queued up and processed after |
- // the IPC that dispatches fullscreenchange. |
- HeapDeque<Member<Document>> docs; |
- |
- docs.prepend(&document); |
- for (Frame* frame = document.frame()->tree().parent(); frame; |
- frame = frame->tree().parent()) { |
- if (frame->isLocalFrame()) |
- docs.prepend(toLocalFrame(frame)->document()); |
+ error = false; |
+ } while (false); |
+ |
+ // 5. Return |promise|, and run the remaining steps in parallel. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
+ |
+ // 6. If |error| is false: Resize pending's top-level browsing context's |
+ // document's viewport's dimensions to match the dimensions of the screen of |
+ // the output device. Optionally display a message how the end user can |
+ // revert this. |
+ if (!error) { |
+ document.frameHost()->chromeClient().enterFullscreenForElement(&pending); |
+ } else { |
+ document.enqueueAnimationFrameTask(WTF::bind(&animationFrameTaskAfterEnter, |
+ wrapPersistent(&pending), |
+ requestType, true)); |
+ } |
+} |
+ |
+void Fullscreen::didEnterFullscreenForElement(Element& pending, |
+ RequestType requestType) { |
+ // 7. As part of the next animation frame task, run these substeps: |
+ pending.document().enqueueAnimationFrameTask( |
+ WTF::bind(&animationFrameTaskAfterEnter, wrapPersistent(&pending), |
+ requestType, false)); |
+} |
+ |
+void Fullscreen::animationFrameTaskAfterEnter(Element* element, |
+ RequestType requestType, |
+ bool error) { |
+ DCHECK(element); |
+ Element& pending = *element; |
+ |
+ // 7.1. If either |error| is true or the fullscreen element ready check for |
+ // |pending| returns false, fire an event named fullscreenerror on |
+ // |pending|'s node document, reject |promise| with a TypeError exception, |
+ // and terminate these steps. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
+ if (error || !fullscreenElementReady(pending)) { |
+ // TODO(foolip): What if we just entered fullscreen but the ready check |
+ // no longer passes, e.g. if the allowfullscreen attribute was just |
+ // removed? We need to exit fullscreen again, or at least maybe. |
+ fireErrorEvent(pending, requestType); |
+ return; |
+ } |
+ |
+ // 7.2. Let |fullscreenElements| be an ordered set initially consisting of |
+ // |pending|. |
+ HeapDeque<Member<Element>> fullscreenElements; |
+ fullscreenElements.append(pending); |
+ |
+ // 7.3. While the first element in |fullscreenElements| is in a nested |
+ // browsing context, prepend its browsing context container to |
+ // |fullscreenElements|. |
+ // |
+ // OOPIF: |fullscreenElements| will only contain elements for local |
+ // ancestors, and remote ancestors will be processed in their respective |
+ // processes. This preserves the spec's event firing order for local |
+ // ancestors, but not for remote ancestors. However, that difference |
+ // shouldn't be observable in practice: a fullscreenchange event handler |
+ // would need to postMessage a frame in another renderer process, where the |
+ // message should be queued up and processed after the IPC that dispatches |
+ // fullscreenchange. |
+ for (Frame* frame = pending.document().frame(); frame; |
+ frame = frame->tree().parent()) { |
+ if (frame->owner() && frame->owner()->isLocal()) { |
+ Element* element = toHTMLFrameOwnerElement(frame->owner()); |
+ if (!isFullscreenElement(*element)) |
+ fullscreenElements.prepend(element); |
} |
+ } |
- // 4. For each document in docs, run these substeps: |
- HeapDeque<Member<Document>>::iterator current = docs.begin(), |
- following = docs.begin(); |
- |
- do { |
- ++following; |
- |
- // 1. Let following document be the document after document in docs, or |
- // null if there is no such document. |
- Document* currentDoc = *current; |
- Document* followingDoc = following != docs.end() ? *following : nullptr; |
- |
- // 2. If following document is null, push context object on document's |
- // fullscreen element stack, and queue a task to fire an event named |
- // fullscreenchange with its bubbles attribute set to true on the |
- // document. |
- if (!followingDoc) { |
- from(*currentDoc).pushFullscreenElementStack(element, requestType); |
- from(document).enqueueChangeEvent(*currentDoc, requestType); |
- continue; |
- } |
+ // 7.4. Let |eventDocs| be an empty list. |
+ // Note: Rather than creating a separate |eventDocs| list with a subset of |
+ // the |fullscreenElements|' elements' documents, those elements are not |
+ // added to |fullscreenElements| at all, which simplifies the following. |
- // 3. Otherwise, if document's fullscreen element stack is either empty or |
- // its top element is not following document's browsing context container, |
- Element* topElement = fullscreenElementFrom(*currentDoc); |
- HTMLFrameOwnerElement* followingOwner = |
- findContainerForDescendant(*currentDoc, *followingDoc); |
- if (!topElement || topElement != followingOwner) { |
- // ...push following document's browsing context container on document's |
- // fullscreen element stack, and queue a task to fire an event named |
- // fullscreenchange with its bubbles attribute set to true on document. |
- from(*currentDoc) |
- .pushFullscreenElementStack(*followingOwner, requestType); |
- from(document).enqueueChangeEvent(*currentDoc, requestType); |
- continue; |
- } |
+ // 7.5. For each |element| in |fullscreenElements|, in order, run these |
+ // subsubsteps: |
+ for (Element* element : fullscreenElements) { |
+ // 7.5.1. Let |doc| be |element|'s node document. |
- // 4. Otherwise, do nothing for this document. It stays the same. |
- } while (++current != docs.end()); |
+ // 7.5.2. If |element| is |doc|'s fullscreen element, terminate these |
+ // subsubsteps. |
- from(document).m_forCrossProcessDescendant = forCrossProcessDescendant; |
+ // 7.5.3. Otherwise, append |doc| to |eventDocs|. |
- // 5. Return, and run the remaining steps asynchronously. |
- // 6. Optionally, perform some animation. |
- document.frameHost()->chromeClient().enterFullscreenForElement(&element); |
+ // 7.5.4. If |element| is |pending| and |pending| is an iframe element, |
+ // set |element|'s iframe fullscreen flag. |
+ // TODO(foolip): Support the iframe fullscreen flag. |
+ // https://crbug.com/644695 |
- // 7. Optionally, display a message indicating how the user can exit |
- // displaying the context object fullscreen. |
- return; |
- } while (false); |
+ // 7.5.5. Fullscreen |element| within |doc|. |
+ fullscreen(*element, requestType); |
+ } |
- from(document).enqueueErrorEvent(element, requestType); |
+ // 7.6. For each |doc| in |eventDocs|, in order, fire an event named |
+ // fullscreenchange on |doc|. |
+ for (Element* element : fullscreenElements) |
+ fireChangeEvent(*element, requestType); |
+ |
+ // 7.7. Fulfill |promise| with undefined. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
} |
// https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen |
void Fullscreen::fullyExitFullscreen(Document& document) { |
- // To fully exit fullscreen, run these steps: |
- |
- // 1. Let |doc| be the top-level browsing context's document. |
- // |
- // Since the top-level browsing context's document might be unavailable in |
- // OOPIF scenarios (i.e., when the top frame is remote), this actually uses |
- // the Document of the topmost local ancestor frame. Without OOPIF, this |
- // will be the top frame's document. With OOPIF, each renderer process for |
- // the current page will separately call fullyExitFullscreen to cover all |
- // local frames in each process. |
- Document& doc = topmostLocalAncestor(document); |
- |
- // 2. If |doc|'s fullscreen element stack is empty, terminate these steps. |
- if (!fullscreenElementFrom(doc)) |
+ // 1. If |document|'s fullscreen element is null, terminate these steps. |
+ Element* fullscreenElement = Fullscreen::fullscreenElement(document); |
+ if (!fullscreenElement) |
return; |
- // 3. Remove elements from |doc|'s fullscreen element stack until only the top |
- // element is left. |
- size_t stackSize = from(doc).m_fullscreenElementStack.size(); |
- from(doc).m_fullscreenElementStack.remove(0, stackSize - 1); |
- DCHECK_EQ(from(doc).m_fullscreenElementStack.size(), 1u); |
+ // 2. Unfullscreen elements whose fullscreen flag is set, within |
+ // |document|'s top layer, except for |document|'s fullscreen element. |
+ for (Element* element : document.topLayerElements()) { |
+ if (element->isFullscreen() && element != fullscreenElement) |
+ unfullscreen(*element); |
+ } |
- // 4. Act as if the exitFullscreen() method was invoked on |doc|. |
- exitFullscreen(doc); |
+ // 3. Exit fullscreen |document|. |
+ exitFullscreen(document); |
} |
// https://fullscreen.spec.whatwg.org/#exit-fullscreen |
-void Fullscreen::exitFullscreen(Document& document) { |
- // The exitFullscreen() method must run these steps: |
- |
- // 1. Let doc be the context object. (i.e. "this") |
- if (!document.isActive()) |
+void Fullscreen::exitFullscreen(Document& doc) { |
+ if (!doc.isActive()) |
return; |
- // 2. If doc's fullscreen element stack is empty, terminate these steps. |
- if (!fullscreenElementFrom(document)) |
+ // 1. Let |promise| be a new promise. |
+ // 2. If |doc|'s fullscreen element is null, reject |promise| with a |
+ // TypeError exception, and return |promise|. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
+ if (!fullscreenElement(doc)) |
return; |
- // 3. Let descendants be all the doc's descendant browsing context's documents |
- // with a non-empty fullscreen element stack (if any), ordered so that the |
- // child of the doc is last and the document furthest away from the doc is |
- // first. |
- HeapDeque<Member<Document>> descendants; |
- for (Frame* descendant = |
- document.frame() ? document.frame()->tree().traverseNext() : nullptr; |
- descendant; descendant = descendant->tree().traverseNext()) { |
- if (!descendant->isLocalFrame()) |
- continue; |
- DCHECK(toLocalFrame(descendant)->document()); |
- if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) |
- descendants.prepend(toLocalFrame(descendant)->document()); |
+ // 3. Let |resize| be false. |
+ bool resize = false; |
+ |
+ // 4. Let |docs| be the result of collecting documents to unfullscreen given |
+ // |doc|. |
+ HeapVector<Member<Document>> docs = collectDocumentsToUnfullscreen(doc); |
+ |
+ // 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document. |
+ Document& topLevelDoc = topmostLocalAncestor(doc); |
+ |
+ // 6. If |topLevelDoc| is in |docs|, set |resize| to true. |
+ // OOPIF: Use |isLocalRoot| as oppposed to |isMainFrame|, so that if the |
+ // main frame is in another process, we will still fully exit fullscreen |
+ // even though that's wrong if the main frame was in nested fullscreen. |
+ // TODO(alexmos): Deal with nested fullscreen cases, see |
+ // https://crbug.com/617369. |
+ if (!docs.isEmpty() && docs.last()->frame()->isLocalRoot()) |
+ resize = true; |
+ |
+ // 7. Return |promise|, and run the remaining steps in parallel. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
+ |
+ // 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal" |
+ // dimensions. |
+ if (resize) { |
+ topLevelDoc.frameHost()->chromeClient().exitFullscreenForElement( |
+ fullscreenElement(topLevelDoc)); |
+ } else { |
+ topLevelDoc.enqueueAnimationFrameTask( |
+ WTF::bind(&animationFrameTaskAfterExit, wrapPersistent(&topLevelDoc))); |
} |
+} |
- // 4. For each descendant in descendants, empty descendant's fullscreen |
- // element stack, and queue a task to fire an event named fullscreenchange |
- // with its bubbles attribute set to true on descendant. |
- for (auto& descendant : descendants) { |
- DCHECK(descendant); |
- RequestType requestType = |
- from(*descendant).m_fullscreenElementStack.last().second; |
- from(*descendant).clearFullscreenElementStack(); |
- from(document).enqueueChangeEvent(*descendant, requestType); |
- } |
+void Fullscreen::didExitFullscreen(Document& doc) { |
+ // 9. As part of the next animation frame task, run these substeps: |
+ doc.enqueueAnimationFrameTask( |
+ WTF::bind(&animationFrameTaskAfterExit, wrapPersistent(&doc))); |
+} |
- // 5. While doc is not null, run these substeps: |
- Element* newTop = nullptr; |
- for (Document* currentDoc = &document; currentDoc;) { |
- RequestType requestType = |
- from(*currentDoc).m_fullscreenElementStack.last().second; |
+void Fullscreen::animationFrameTaskAfterExit(Document* document) { |
+ DCHECK(document); |
+ Document& doc = *document; |
- // 1. Pop the top element of doc's fullscreen element stack. |
- from(*currentDoc).popFullscreenElementStack(); |
+ // 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen |
+ // given |doc|. |
+ HeapVector<Member<Document>> exitDocs = collectDocumentsToUnfullscreen(doc); |
- // If doc's fullscreen element stack is non-empty and the element now at |
- // the top is either not in a document or its node document is not doc, |
- // repeat this substep. |
- newTop = fullscreenElementFrom(*currentDoc); |
- if (newTop && (!newTop->isConnected() || newTop->document() != currentDoc)) |
- continue; |
+ // 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully |
+ // exit fullscreen |topLevelDoc|, reject promise with a TypeError exception, |
+ // and terminate these steps. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
+ if (exitDocs.isEmpty() || !exitDocs.last()->frame()->isLocalRoot()) { |
+ return; |
+ } |
- // 2. Queue a task to fire an event named fullscreenchange with its bubbles |
- // attribute set to true on doc. |
- from(document).enqueueChangeEvent(*currentDoc, requestType); |
- |
- // 3. If doc's fullscreen element stack is empty and doc's browsing context |
- // has a browsing context container, set doc to that browsing context |
- // container's node document. |
- // |
- // OOPIF: If browsing context container's document is in another |
- // process, keep moving up the ancestor chain and looking for a |
- // browsing context container with a local document. |
- // TODO(alexmos): Deal with nested fullscreen cases, see |
- // https://crbug.com/617369. |
- if (!newTop) { |
- Frame* frame = currentDoc->frame()->tree().parent(); |
- while (frame && frame->isRemoteFrame()) |
- frame = frame->tree().parent(); |
- if (frame) { |
- currentDoc = toLocalFrame(frame)->document(); |
- continue; |
+ // 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|. |
+ if (exitDocs.isEmpty()) |
+ exitDocs.append(&doc); |
+ |
+ // 9.4. If |exitDocs|'s last document has a browsing context container, |
+ // append that browsing context container's node document to |exitDocs|. |
+ // OOPIF: Skip over remote frames, assuming that they have exactly one |
+ // element in their fullscreen element stacks, thereby erring on the side of |
+ // exiting fullscreen. |
+ // TODO(alexmos): Deal with nested fullscreen cases, see |
+ // https://crbug.com/617369. |
+ for (Frame* frame = exitDocs.last()->frame()->tree().parent(); frame; |
+ frame = frame->tree().parent()) { |
+ if (frame->isLocalFrame()) { |
+ if (Document* document = toLocalFrame(frame)->document()) { |
+ exitDocs.append(document); |
+ break; |
} |
} |
- |
- // 4. Otherwise, set doc to null. |
- currentDoc = nullptr; |
} |
- // 6. Return, and run the remaining steps asynchronously. |
- // 7. Optionally, perform some animation. |
+ // 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s |
+ // descendant browsing contexts' documents whose fullscreen element is |
+ // non-null, if any, in *reverse* tree order. |
+ HeapDeque<Member<Document>> descendantDocs; |
+ for (Frame* descendant = doc.frame()->tree().firstChild(); descendant; |
+ descendant = descendant->tree().traverseNext(doc.frame())) { |
+ if (!descendant->isLocalFrame()) |
+ continue; |
+ DCHECK(toLocalFrame(descendant)->document()); |
+ if (fullscreenElement(*toLocalFrame(descendant)->document())) |
+ descendantDocs.prepend(toLocalFrame(descendant)->document()); |
+ } |
- FrameHost* host = document.frameHost(); |
+ HeapVector<Member<Element>> eventElements; |
- // Speculative fix for engaget.com/videos per crbug.com/336239. |
- // FIXME: This check is wrong. We DCHECK(document->isActive()) above |
- // so this should be redundant and should be removed! |
- if (!host) |
- return; |
+ // 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen |
+ // |descendantDoc|. |
+ for (Document* descendantDoc : descendantDocs) { |
+ eventElements.append(fullscreenElement(*descendantDoc)); |
+ unfullscreen(*descendantDoc); |
+ } |
- // Only exit out of full screen window mode if there are no remaining elements |
- // in the full screen stack. |
- if (!newTop) { |
- // FIXME: if the frame exiting fullscreen is not the frame that entered |
- // fullscreen (but a parent frame for example), |
- // m_currentFullScreenElement might be null. We want to pass an element |
- // that is part of the document so we will pass the documentElement in |
- // that case. This should be fix by exiting fullscreen for a frame |
- // instead of an element, see https://crbug.com/441259 |
- Element* currentFullScreenElement = currentFullScreenElementFrom(document); |
- host->chromeClient().exitFullscreenForElement( |
- currentFullScreenElement ? currentFullScreenElement |
- : document.documentElement()); |
- return; |
+ // 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s |
+ // fullscreen element. |
+ for (Document* exitDoc : exitDocs) { |
+ eventElements.append(fullscreenElement(*exitDoc)); |
+ unfullscreen(*fullscreenElement(*exitDoc)); |
} |
- // Otherwise, notify the chrome of the new full screen element. |
- host->chromeClient().enterFullscreenForElement(newTop); |
+ // 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an |
+ // event named fullscreenchange on |descendantDoc|. |
+ // 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named |
+ // fullscreenchange on |exitDoc|. |
+ // TODO(foolip): Fire the unprefixed event. |
+ for (Element* element : eventElements) |
+ fireChangeEvent(*element, PrefixedRequest); |
+ |
+ // 9.10. Fulfill |promise| with undefined. |
+ // TODO(foolip): Promises. https://crbug.com/644637 |
} |
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled |
@@ -558,223 +603,38 @@ bool Fullscreen::fullscreenEnabled(Document& document) { |
fullscreenIsSupported(document); |
} |
-void Fullscreen::didEnterFullscreenForElement(Element* element) { |
- DCHECK(element); |
- if (!document()->isActive()) |
- return; |
- |
- if (m_fullScreenLayoutObject) |
- m_fullScreenLayoutObject->unwrapLayoutObject(); |
- |
- m_currentFullScreenElement = element; |
- |
- // Create a placeholder block for a the full-screen element, to keep the page |
- // from reflowing when the element is removed from the normal flow. Only do |
- // this for a LayoutBox, as only a box will have a frameRect. The placeholder |
- // will be created in setFullScreenLayoutObject() during layout. |
- LayoutObject* layoutObject = m_currentFullScreenElement->layoutObject(); |
- bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox(); |
- if (shouldCreatePlaceholder) { |
- m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect(); |
- m_savedPlaceholderComputedStyle = |
- ComputedStyle::clone(layoutObject->styleRef()); |
- } |
- |
- // TODO(alexmos): When |m_forCrossProcessDescendant| is true, some of |
- // this layout work has already been done in another process, so it should |
- // not be necessary to repeat it here. |
- if (m_currentFullScreenElement != document()->documentElement()) |
- LayoutFullScreen::wrapLayoutObject( |
- layoutObject, layoutObject ? layoutObject->parent() : 0, document()); |
- |
- // When |m_forCrossProcessDescendant| is true, m_currentFullScreenElement |
- // corresponds to the HTMLFrameOwnerElement for the out-of-process iframe |
- // that contains the actual fullscreen element. Hence, it must also set |
- // the ContainsFullScreenElement flag (so that it gains the |
- // -webkit-full-screen-ancestor style). |
- if (m_forCrossProcessDescendant) { |
- DCHECK(m_currentFullScreenElement->isFrameOwnerElement()); |
- DCHECK(toHTMLFrameOwnerElement(m_currentFullScreenElement) |
- ->contentFrame() |
- ->isRemoteFrame()); |
- m_currentFullScreenElement->setContainsFullScreenElement(true); |
- } |
- |
- m_currentFullScreenElement |
- ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); |
- |
- document()->styleEngine().ensureUAStyleForFullscreen(); |
- m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); |
- |
- // FIXME: This should not call updateStyleAndLayoutTree. |
- document()->updateStyleAndLayoutTree(); |
- |
- m_currentFullScreenElement->didBecomeFullscreenElement(); |
- |
- if (document()->frame()) |
- document()->frame()->eventHandler().scheduleHoverStateUpdate(); |
- |
- m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); |
+void Fullscreen::didUpdateSize(Element& element) { |
+ // StyleAdjuster will set the size so we need to do a style recalc. |
+ // Normally changing size means layout so just doing a style recalc is a |
+ // bit surprising. |
+ element.setNeedsStyleRecalc( |
+ LocalStyleChange, |
+ StyleChangeReasonForTracing::create(StyleChangeReason::Fullscreen)); |
} |
-void Fullscreen::didExitFullscreen() { |
- if (!m_currentFullScreenElement) |
- return; |
- |
- if (!document()->isActive()) |
- return; |
- |
- m_currentFullScreenElement->willStopBeingFullscreenElement(); |
+void Fullscreen::elementRemoved(Element& node) { |
+ // Whenever the removing steps run with an oldNode, run these steps: |
- if (m_forCrossProcessDescendant) |
- m_currentFullScreenElement->setContainsFullScreenElement(false); |
+ // 1. Let |nodes| be |oldNode|'s inclusive descendants that have their |
+ // fullscreen flag set, in tree order. |
- m_currentFullScreenElement |
- ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); |
+ // 2. For each |node| in |nodes|, run these substeps: |
- if (m_fullScreenLayoutObject) |
- LayoutFullScreenItem(m_fullScreenLayoutObject).unwrapLayoutObject(); |
+ // Note: The iteration of descendants is done in |
+ // ContainerNode::notifyNodeRemoved, and Element::removedFrom calls |
+ // Fullscreen::elementRemoved only for fullscreen elements. |
+ DCHECK(node.isInTopLayer()); |
+ DCHECK(node.isFullscreen()); |
- document()->styleEngine().ensureUAStyleForFullscreen(); |
- m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); |
- m_currentFullScreenElement = nullptr; |
- |
- if (document()->frame()) |
- document()->frame()->eventHandler().scheduleHoverStateUpdate(); |
- |
- // When fullyExitFullscreen is called, we call exitFullscreen on the |
- // topDocument(). That means that the events will be queued there. So if we |
- // have no events here, start the timer on the exiting document. |
- Document* exitingDocument = document(); |
- if (m_eventQueue.isEmpty()) |
- exitingDocument = &topmostLocalAncestor(*document()); |
- DCHECK(exitingDocument); |
- from(*exitingDocument).m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); |
- |
- m_forCrossProcessDescendant = false; |
-} |
- |
-void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) { |
- if (layoutObject == m_fullScreenLayoutObject) |
+ // 2.1. If |node| is its node document's fullscreen element, exit fullscreen |
+ // that document. |
+ if (&node == fullscreenElement(node.document())) { |
+ exitFullscreen(node.document()); |
return; |
- |
- if (layoutObject && m_savedPlaceholderComputedStyle) { |
- layoutObject->createPlaceholder(m_savedPlaceholderComputedStyle.release(), |
- m_savedPlaceholderFrameRect); |
- } else if (layoutObject && m_fullScreenLayoutObject && |
- m_fullScreenLayoutObject->placeholder()) { |
- LayoutBlockFlow* placeholder = m_fullScreenLayoutObject->placeholder(); |
- layoutObject->createPlaceholder( |
- ComputedStyle::clone(placeholder->styleRef()), |
- placeholder->frameRect()); |
} |
- if (m_fullScreenLayoutObject) |
- m_fullScreenLayoutObject->unwrapLayoutObject(); |
- DCHECK(!m_fullScreenLayoutObject); |
- |
- m_fullScreenLayoutObject = layoutObject; |
-} |
- |
-void Fullscreen::fullScreenLayoutObjectDestroyed() { |
- m_fullScreenLayoutObject = nullptr; |
-} |
- |
-void Fullscreen::enqueueChangeEvent(Document& document, |
- RequestType requestType) { |
- Event* event; |
- if (requestType == UnprefixedRequest) { |
- event = createEvent(EventTypeNames::fullscreenchange, document); |
- } else { |
- DCHECK(document.hasFullscreenSupplement()); |
- Fullscreen& fullscreen = from(document); |
- EventTarget* target = fullscreen.fullscreenElement(); |
- if (!target) |
- target = fullscreen.currentFullScreenElement(); |
- if (!target) |
- target = &document; |
- event = createEvent(EventTypeNames::webkitfullscreenchange, *target); |
- } |
- m_eventQueue.append(event); |
- // NOTE: The timer is started in |
- // didEnterFullscreenForElement/didExitFullscreen. |
-} |
- |
-void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType) { |
- Event* event; |
- if (requestType == UnprefixedRequest) |
- event = createEvent(EventTypeNames::fullscreenerror, element.document()); |
- else |
- event = createEvent(EventTypeNames::webkitfullscreenerror, element); |
- m_eventQueue.append(event); |
- m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); |
-} |
- |
-void Fullscreen::eventQueueTimerFired(TimerBase*) { |
- HeapDeque<Member<Event>> eventQueue; |
- m_eventQueue.swap(eventQueue); |
- |
- while (!eventQueue.isEmpty()) { |
- Event* event = eventQueue.takeFirst(); |
- Node* target = event->target()->toNode(); |
- |
- // If the element was removed from our tree, also message the |
- // documentElement. |
- if (!target->isConnected() && document()->documentElement()) { |
- DCHECK(isPrefixed(event->type())); |
- eventQueue.append( |
- createEvent(event->type(), *document()->documentElement())); |
- } |
- |
- target->dispatchEvent(event); |
- } |
-} |
- |
-void Fullscreen::elementRemoved(Element& oldNode) { |
- // Whenever the removing steps run with an |oldNode| and |oldNode| is in its |
- // node document's fullscreen element stack, run these steps: |
- |
- // 1. If |oldNode| is at the top of its node document's fullscreen element |
- // stack, act as if the exitFullscreen() method was invoked on that document. |
- if (fullscreenElement() == &oldNode) { |
- exitFullscreen(oldNode.document()); |
- return; |
- } |
- |
- // 2. Otherwise, remove |oldNode| from its node document's fullscreen element |
- // stack. |
- for (size_t i = 0; i < m_fullscreenElementStack.size(); ++i) { |
- if (m_fullscreenElementStack[i].first.get() == &oldNode) { |
- m_fullscreenElementStack.remove(i); |
- return; |
- } |
- } |
- |
- // NOTE: |oldNode| was not in the fullscreen element stack. |
-} |
- |
-void Fullscreen::clearFullscreenElementStack() { |
- m_fullscreenElementStack.clear(); |
-} |
- |
-void Fullscreen::popFullscreenElementStack() { |
- if (m_fullscreenElementStack.isEmpty()) |
- return; |
- |
- m_fullscreenElementStack.removeLast(); |
-} |
- |
-void Fullscreen::pushFullscreenElementStack(Element& element, |
- RequestType requestType) { |
- m_fullscreenElementStack.append(std::make_pair(&element, requestType)); |
-} |
- |
-DEFINE_TRACE(Fullscreen) { |
- visitor->trace(m_currentFullScreenElement); |
- visitor->trace(m_fullscreenElementStack); |
- visitor->trace(m_eventQueue); |
- Supplement<Document>::trace(visitor); |
- ContextLifecycleObserver::trace(visitor); |
+ // 2.2. Otherwise, unfullscreen |node| within its node document. |
+ unfullscreen(node); |
} |
} // namespace blink |