| 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
|
|
|