| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) | 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) | 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| 4 * (C) 2001 Dirk Mueller (mueller@kde.org) | 4 * (C) 2001 Dirk Mueller (mueller@kde.org) |
| 5 * (C) 2006 Alexey Proskuryakov (ap@webkit.org) | 5 * (C) 2006 Alexey Proskuryakov (ap@webkit.org) |
| 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All | 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All |
| 7 * rights reserved. | 7 * rights reserved. |
| 8 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. | 8 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. |
| 9 * (http://www.torchmobile.com/) | 9 * (http://www.torchmobile.com/) |
| 10 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) | 10 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 * | 22 * |
| 23 * You should have received a copy of the GNU Library General Public License | 23 * You should have received a copy of the GNU Library General Public License |
| 24 * along with this library; see the file COPYING.LIB. If not, write to | 24 * along with this library; see the file COPYING.LIB. If not, write to |
| 25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 26 * Boston, MA 02110-1301, USA. | 26 * Boston, MA 02110-1301, USA. |
| 27 * | 27 * |
| 28 */ | 28 */ |
| 29 | 29 |
| 30 #include "core/dom/Fullscreen.h" | 30 #include "core/dom/Fullscreen.h" |
| 31 | 31 |
| 32 #include "bindings/core/v8/ExceptionMessages.h" |
| 32 #include "core/dom/Document.h" | 33 #include "core/dom/Document.h" |
| 33 #include "core/dom/ElementTraversal.h" | 34 #include "core/dom/StyleChangeReason.h" |
| 34 #include "core/dom/StyleEngine.h" | 35 #include "core/dom/StyleEngine.h" |
| 35 #include "core/events/Event.h" | 36 #include "core/events/Event.h" |
| 36 #include "core/frame/FrameHost.h" | 37 #include "core/frame/FrameHost.h" |
| 37 #include "core/frame/HostsUsingFeatures.h" | 38 #include "core/frame/HostsUsingFeatures.h" |
| 38 #include "core/frame/LocalFrame.h" | 39 #include "core/frame/LocalFrame.h" |
| 39 #include "core/frame/Settings.h" | 40 #include "core/frame/Settings.h" |
| 40 #include "core/frame/UseCounter.h" | 41 #include "core/frame/UseCounter.h" |
| 41 #include "core/html/HTMLIFrameElement.h" | 42 #include "core/html/HTMLFrameOwnerElement.h" |
| 42 #include "core/input/EventHandler.h" | 43 #include "core/input/EventHandler.h" |
| 43 #include "core/inspector/ConsoleMessage.h" | 44 #include "core/inspector/ConsoleMessage.h" |
| 44 #include "core/layout/LayoutBlockFlow.h" | |
| 45 #include "core/layout/LayoutFullScreen.h" | |
| 46 #include "core/layout/api/LayoutFullScreenItem.h" | |
| 47 #include "core/page/ChromeClient.h" | 45 #include "core/page/ChromeClient.h" |
| 48 #include "core/svg/SVGSVGElement.h" | 46 #include "core/svg/SVGSVGElement.h" |
| 49 #include "platform/ScopedOrientationChangeIndicator.h" | 47 #include "platform/ScopedOrientationChangeIndicator.h" |
| 50 #include "platform/UserGestureIndicator.h" | 48 #include "platform/UserGestureIndicator.h" |
| 51 | 49 |
| 52 namespace blink { | 50 namespace blink { |
| 53 | 51 |
| 54 namespace { | 52 namespace { |
| 55 | 53 |
| 54 // https://fullscreen.spec.whatwg.org/#fullscreen-an-element |
| 55 void fullscreen(Element& element, Fullscreen::RequestType requestType) { |
| 56 // To fullscreen an |element| within a |document|, set the |element|'s |
| 57 // fullscreen flag and add it to |document|'s top layer. |
| 58 DCHECK(!element.isFullscreen()); |
| 59 // DCHECK(!element.isIFrameFullscreen()); |
| 60 DCHECK(!element.isInTopLayer()); |
| 61 element.setIsFullscreen(true); |
| 62 element.document().addToTopLayer(&element); |
| 63 } |
| 64 |
| 65 // https://fullscreen.spec.whatwg.org/#unfullscreen-an-element |
| 66 void unfullscreen(Element& element) { |
| 67 // To unfullscreen an |element| within a |document|, unset the element's |
| 68 // fullscreen flag and iframe fullscreen flag (if any), and remove it from |
| 69 // |document|'s top layer. |
| 70 DCHECK(element.isFullscreen()); |
| 71 DCHECK(element.isInTopLayer()); |
| 72 element.setIsFullscreen(true); |
| 73 // element.setIsIFrameFullscreen(false); |
| 74 element.document().removeFromTopLayer(&element); |
| 75 } |
| 76 |
| 77 // https://fullscreen.spec.whatwg.org/#unfullscreen-a-document |
| 78 void unfullscreen(Document& document) { |
| 79 // To unfullscreen a |document|, unfullscreen all elements, within |
| 80 // |document|'s top layer, whose fullscreen flag is set. |
| 81 for (Element* element : document.topLayerElements()) { |
| 82 if (element->isFullscreen()) |
| 83 unfullscreen(*element); |
| 84 } |
| 85 } |
| 86 |
| 56 // https://html.spec.whatwg.org/multipage/embedded-content.html#allowed-to-use | 87 // https://html.spec.whatwg.org/multipage/embedded-content.html#allowed-to-use |
| 57 bool allowedToUseFullscreen(const Frame* frame) { | 88 bool allowedToUseFullscreen(const Frame* frame) { |
| 58 // To determine whether a Document object |document| is allowed to use the | 89 // To determine whether a Document object |document| is allowed to use the |
| 59 // feature indicated by attribute name |allowattribute|, run these steps: | 90 // feature indicated by attribute name |allowattribute|, run these steps: |
| 60 | 91 |
| 61 // 1. If |document| has no browsing context, then return false. | 92 // 1. If |document| has no browsing context, then return false. |
| 62 if (!frame) | 93 if (!frame) |
| 63 return false; | 94 return false; |
| 64 | 95 |
| 65 // 2. If |document|'s browsing context is a top-level browsing context, then | 96 // 2. If |document|'s browsing context is a top-level browsing context, then |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 113 | 144 |
| 114 // |element| is in a document. | 145 // |element| is in a document. |
| 115 if (!element.isConnected()) | 146 if (!element.isConnected()) |
| 116 return false; | 147 return false; |
| 117 | 148 |
| 118 // |element|'s node document is allowed to use the feature indicated by | 149 // |element|'s node document is allowed to use the feature indicated by |
| 119 // attribute name allowfullscreen. | 150 // attribute name allowfullscreen. |
| 120 if (!allowedToUseFullscreen(element.document().frame())) | 151 if (!allowedToUseFullscreen(element.document().frame())) |
| 121 return false; | 152 return false; |
| 122 | 153 |
| 123 // |element|'s node document's fullscreen element stack is either empty or its | |
| 124 // top element is an inclusive ancestor of |element|. | |
| 125 if (const Element* topElement = | |
| 126 Fullscreen::fullscreenElementFrom(element.document())) { | |
| 127 if (!topElement->contains(&element)) | |
| 128 return false; | |
| 129 } | |
| 130 | |
| 131 // |element| has no ancestor element whose local name is iframe and namespace | |
| 132 // is the HTML namespace. | |
| 133 if (Traversal<HTMLIFrameElement>::firstAncestor(element)) | |
| 134 return false; | |
| 135 | |
| 136 // |element|'s node document's browsing context either has a browsing context | 154 // |element|'s node document's browsing context either has a browsing context |
| 137 // container and the fullscreen element ready check returns true for | 155 // container and the fullscreen element ready check returns true for |
| 138 // |element|'s node document's browsing context's browsing context container, | 156 // |element|'s node document's browsing context's browsing context container, |
| 139 // or it has no browsing context container. | 157 // or it has no browsing context container. |
| 140 if (const Element* owner = element.document().localOwner()) { | 158 if (const Element* owner = element.document().localOwner()) { |
| 141 if (!fullscreenElementReady(*owner)) | 159 if (!fullscreenElementReady(*owner)) |
| 142 return false; | 160 return false; |
| 143 } | 161 } |
| 144 | 162 |
| 145 return true; | 163 return true; |
| 146 } | 164 } |
| 147 | 165 |
| 148 bool isPrefixed(const AtomicString& type) { | 166 size_t countFullscreenInTopLayer(const Document& document) { |
| 149 return type == EventTypeNames::webkitfullscreenchange || | 167 size_t count = 0; |
| 150 type == EventTypeNames::webkitfullscreenerror; | 168 for (Element* element : document.topLayerElements()) { |
| 169 if (element->isFullscreen()) |
| 170 ++count; |
| 171 } |
| 172 return count; |
| 151 } | 173 } |
| 152 | 174 |
| 153 Event* createEvent(const AtomicString& type, EventTarget& target) { | 175 // https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen |
| 154 EventInit initializer; | 176 HeapVector<Member<Document>> collectDocumentsToUnfullscreen(Document& doc) { |
| 155 initializer.setBubbles(isPrefixed(type)); | 177 // 1. If |doc|'s top layer consists of more than a single element that has |
| 156 Event* event = Event::create(type, initializer); | 178 // its fullscreen flag set, return the empty set. |
| 157 event->setTarget(&target); | 179 |
| 158 return event; | 180 // 2. Let |docs| be an ordered set consisting of |doc|. |
| 181 |
| 182 // 3. While |docs|'s last document has a browsing context container whose |
| 183 // node document's top layer consists of a single element that has its |
| 184 // fullscreen flag set and does not have its iframe fullscreen flag set (if |
| 185 // any), append that node document to |docs|. |
| 186 // TODO(foolip): Support the iframe fullscreen flag. |
| 187 // https://crbug.com/644695 |
| 188 |
| 189 // OOPIF: Skip over remote frames, assuming that they have exactly one |
| 190 // element in their fullscreen element stacks, thereby erring on the side of |
| 191 // exiting fullscreen. |
| 192 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 193 // https://crbug.com/617369. |
| 194 |
| 195 HeapVector<Member<Document>> docs; |
| 196 for (Frame* frame = doc.frame(); frame; frame = frame->tree().parent()) { |
| 197 if (frame->isRemoteFrame()) |
| 198 continue; |
| 199 if (Document* document = toLocalFrame(frame)->document()) { |
| 200 // TODO(foolip): Fix the spec to count number of "fullscreen flag" |
| 201 // elements in top layer, to avoid dialogs messing up the logic. |
| 202 if (countFullscreenInTopLayer(*document) == 1) |
| 203 docs.append(document); |
| 204 else |
| 205 break; |
| 206 } |
| 207 } |
| 208 |
| 209 // 4. Return |docs|. |
| 210 return docs; |
| 159 } | 211 } |
| 160 | 212 |
| 161 // Helper to walk the ancestor chain and return the Document of the topmost | 213 // Helper to walk the ancestor chain and return the Document of the topmost |
| 162 // local ancestor frame. Note that this is not the same as the topmost frame's | 214 // local ancestor frame. Note that this is not the same as the topmost frame's |
| 163 // Document, which might be unavailable in OOPIF scenarios. For example, with | 215 // Document, which might be unavailable in OOPIF scenarios. For example, with |
| 164 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in | 216 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in |
| 165 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. | 217 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. |
| 166 Document& topmostLocalAncestor(Document& document) { | 218 Document& topmostLocalAncestor(Document& document) { |
| 167 Document* topmost = &document; | 219 Document* topmost = &document; |
| 168 Frame* frame = document.frame(); | 220 Frame* frame = document.frame(); |
| 169 while (frame) { | 221 while (frame) { |
| 170 frame = frame->tree().parent(); | 222 frame = frame->tree().parent(); |
| 171 if (frame && frame->isLocalFrame()) | 223 if (frame && frame->isLocalFrame()) |
| 172 topmost = toLocalFrame(frame)->document(); | 224 topmost = toLocalFrame(frame)->document(); |
| 173 } | 225 } |
| 174 return *topmost; | 226 return *topmost; |
| 175 } | 227 } |
| 176 | 228 |
| 177 // Helper to find the browsing context container in |doc| that embeds the | 229 void fireChangeEvent(Element& element, Fullscreen::RequestType requestType) { |
| 178 // |descendant| Document, possibly through multiple levels of nesting. This | 230 if (requestType == Fullscreen::UnprefixedRequest) { |
| 179 // works even in OOPIF scenarios like A-B-A, where there may be remote frames | 231 Event* event = Event::create(EventTypeNames::fullscreenchange); |
| 180 // in between |doc| and |descendant|. | 232 element.document().dispatchEvent(event); |
| 181 HTMLFrameOwnerElement* findContainerForDescendant(const Document& doc, | 233 } else { |
| 182 const Document& descendant) { | 234 bool wasConnected = element.isConnected(); |
| 183 Frame* frame = descendant.frame(); | 235 |
| 184 while (frame->tree().parent() != doc.frame()) | 236 Event* event = Event::createBubble(EventTypeNames::webkitfullscreenchange); |
| 185 frame = frame->tree().parent(); | 237 element.dispatchEvent(event); |
| 186 return toHTMLFrameOwnerElement(frame->owner()); | 238 |
| 239 // TODO(foolip): Talk to Edge and WebKit teams about what to do if the |
| 240 // element isn't connected. |
| 241 if (!wasConnected) { |
| 242 if (Element* documentElement = element.document().documentElement()) { |
| 243 Event* documentEvent = |
| 244 Event::createBubble(EventTypeNames::webkitfullscreenchange); |
| 245 documentElement->dispatchEvent(documentEvent); |
| 246 } |
| 247 } |
| 248 } |
| 249 } |
| 250 |
| 251 void fireErrorEvent(Element& element, Fullscreen::RequestType requestType) { |
| 252 if (requestType == Fullscreen::UnprefixedRequest) { |
| 253 Event* event = Event::create(EventTypeNames::fullscreenerror); |
| 254 element.document().dispatchEvent(event); |
| 255 } else { |
| 256 Event* event = Event::createBubble(EventTypeNames::webkitfullscreenerror); |
| 257 element.dispatchEvent(event); |
| 258 } |
| 187 } | 259 } |
| 188 | 260 |
| 189 } // anonymous namespace | 261 } // anonymous namespace |
| 190 | 262 |
| 191 const char* Fullscreen::supplementName() { | 263 Element* Fullscreen::fullscreenElement(Document& document) { |
| 192 return "Fullscreen"; | 264 // The fullscreen element is the topmost element in the document's top layer |
| 193 } | 265 // whose fullscreen flag is set, if any, and null otherwise. |
| 194 | 266 |
| 195 Fullscreen& Fullscreen::from(Document& document) { | 267 const auto& elements = document.topLayerElements(); |
| 196 Fullscreen* fullscreen = fromIfExists(document); | 268 for (auto it = elements.rbegin(); it != elements.rend(); ++it) { |
| 197 if (!fullscreen) { | 269 Element* element = (*it).get(); |
| 198 fullscreen = new Fullscreen(document); | 270 if (element->isFullscreen()) |
| 199 Supplement<Document>::provideTo(document, supplementName(), fullscreen); | 271 return element; |
| 200 } | 272 } |
| 201 | 273 |
| 202 return *fullscreen; | |
| 203 } | |
| 204 | |
| 205 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) { | |
| 206 return static_cast<Fullscreen*>( | |
| 207 Supplement<Document>::from(document, supplementName())); | |
| 208 } | |
| 209 | |
| 210 Element* Fullscreen::fullscreenElementFrom(Document& document) { | |
| 211 if (Fullscreen* found = fromIfExists(document)) | |
| 212 return found->fullscreenElement(); | |
| 213 return nullptr; | 274 return nullptr; |
| 214 } | 275 } |
| 215 | 276 |
| 216 Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) { | 277 // https://fullscreen.spec.whatwg.org/#fullscreen-element |
| 217 Element* element = fullscreenElementFrom(scope.document()); | 278 Element* Fullscreen::fullscreenElementForBinding(TreeScope& scope) { |
| 279 Element* element = fullscreenElement(scope.document()); |
| 218 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | 280 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
| 219 return element; | 281 return element; |
| 220 | 282 |
| 221 // TODO(kochi): Once V0 code is removed, we can use the same logic for | 283 // TODO(kochi): Once V0 code is removed, we can use the same logic for |
| 222 // Document and ShadowRoot. | 284 // Document and ShadowRoot. |
| 223 if (!scope.rootNode().isShadowRoot()) { | 285 if (!scope.rootNode().isShadowRoot()) { |
| 224 // For Shadow DOM V0 compatibility: We allow returning an element in V0 | 286 // For Shadow DOM V0 compatibility: We allow returning an element in V0 |
| 225 // shadow tree, even though it leaks the Shadow DOM. | 287 // shadow tree, even though it leaks the Shadow DOM. |
| 226 if (element->isInV0ShadowTree()) { | 288 if (element->isInV0ShadowTree()) { |
| 227 UseCounter::count(scope.document(), | 289 UseCounter::count(scope.document(), |
| 228 UseCounter::DocumentFullscreenElementInV0Shadow); | 290 UseCounter::DocumentFullscreenElementInV0Shadow); |
| 229 return element; | 291 return element; |
| 230 } | 292 } |
| 231 } else if (!toShadowRoot(scope.rootNode()).isV1()) { | 293 } else if (!toShadowRoot(scope.rootNode()).isV1()) { |
| 232 return nullptr; | 294 return nullptr; |
| 233 } | 295 } |
| 234 return scope.adjustedElement(*element); | 296 return scope.adjustedElement(*element); |
| 235 } | 297 } |
| 236 | 298 |
| 237 Element* Fullscreen::currentFullScreenElementFrom(Document& document) { | |
| 238 if (Fullscreen* found = fromIfExists(document)) | |
| 239 return found->currentFullScreenElement(); | |
| 240 return nullptr; | |
| 241 } | |
| 242 | |
| 243 Element* Fullscreen::currentFullScreenElementForBindingFrom( | |
| 244 Document& document) { | |
| 245 Element* element = currentFullScreenElementFrom(document); | |
| 246 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | |
| 247 return element; | |
| 248 | |
| 249 // For Shadow DOM V0 compatibility: We allow returning an element in V0 shadow | |
| 250 // tree, even though it leaks the Shadow DOM. | |
| 251 if (element->isInV0ShadowTree()) { | |
| 252 UseCounter::count(document, | |
| 253 UseCounter::DocumentFullscreenElementInV0Shadow); | |
| 254 return element; | |
| 255 } | |
| 256 return document.adjustedElement(*element); | |
| 257 } | |
| 258 | |
| 259 Fullscreen::Fullscreen(Document& document) | |
| 260 : ContextLifecycleObserver(&document), | |
| 261 m_fullScreenLayoutObject(nullptr), | |
| 262 m_eventQueueTimer(this, &Fullscreen::eventQueueTimerFired), | |
| 263 m_forCrossProcessDescendant(false) { | |
| 264 document.setHasFullscreenSupplement(); | |
| 265 } | |
| 266 | |
| 267 Fullscreen::~Fullscreen() {} | |
| 268 | |
| 269 inline Document* Fullscreen::document() { | |
| 270 return toDocument(lifecycleContext()); | |
| 271 } | |
| 272 | |
| 273 void Fullscreen::contextDestroyed() { | |
| 274 m_eventQueue.clear(); | |
| 275 | |
| 276 if (m_fullScreenLayoutObject) | |
| 277 m_fullScreenLayoutObject->destroy(); | |
| 278 | |
| 279 m_currentFullScreenElement = nullptr; | |
| 280 m_fullscreenElementStack.clear(); | |
| 281 } | |
| 282 | |
| 283 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen | 299 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen |
| 284 void Fullscreen::requestFullscreen(Element& element, | 300 void Fullscreen::requestFullscreen(Element& pending, RequestType requestType) { |
| 285 RequestType requestType, | 301 Document& document = pending.document(); |
| 286 bool forCrossProcessDescendant) { | |
| 287 Document& document = element.document(); | |
| 288 | |
| 289 // Use counters only need to be incremented in the process of the actual | |
| 290 // fullscreen element. | |
| 291 if (!forCrossProcessDescendant) { | |
| 292 if (document.isSecureContext()) { | |
| 293 UseCounter::count(document, UseCounter::FullscreenSecureOrigin); | |
| 294 } else { | |
| 295 UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); | |
| 296 HostsUsingFeatures::countAnyWorld( | |
| 297 document, HostsUsingFeatures::Feature::FullscreenInsecureHost); | |
| 298 } | |
| 299 } | |
| 300 | 302 |
| 301 // Ignore this request if the document is not in a live frame. | 303 // Ignore this request if the document is not in a live frame. |
| 304 // TODO(foolip): This would make sense as part of fullscreenIsSupported. |
| 302 if (!document.isActive()) | 305 if (!document.isActive()) |
| 303 return; | 306 return; |
| 304 | 307 |
| 305 // If |element| is on top of |doc|'s fullscreen element stack, terminate these | 308 if (document.isSecureContext()) { |
| 306 // substeps. | 309 UseCounter::count(document, UseCounter::FullscreenSecureOrigin); |
| 307 if (&element == fullscreenElementFrom(document)) | 310 } else { |
| 308 return; | 311 UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); |
| 312 HostsUsingFeatures::countAnyWorld( |
| 313 document, HostsUsingFeatures::Feature::FullscreenInsecureHost); |
| 314 } |
| 309 | 315 |
| 316 // 1. Let |pending| be the context object. |
| 317 |
| 318 // 2. Let |error| be false. |
| 319 |
| 320 // 3. Let |promise| be a new promise. |
| 321 // TODO(foolip): Promises. https://crbug.com/644637 |
| 322 |
| 323 // 4. If any of the following conditions are false, then set |error| to |
| 324 // true: |
| 325 bool error = true; |
| 310 do { | 326 do { |
| 311 // 1. If any of the following conditions are false, then terminate these | 327 // |pending|'s namespace is the HTML namespace or |pending| is an SVG |
| 312 // steps and queue a task to fire an event named fullscreenerror with its | |
| 313 // bubbles attribute set to true on the context object's node document: | |
| 314 | |
| 315 // |element|'s namespace is the HTML namespace or |element| is an SVG | |
| 316 // svg or MathML math element. | 328 // svg or MathML math element. |
| 317 // Note: MathML is not supported. | 329 // Note: MathML is not supported. |
| 318 if (!element.isHTMLElement() && !isSVGSVGElement(element)) | 330 if (!pending.isHTMLElement() && !isSVGSVGElement(pending)) |
| 319 break; | 331 break; |
| 320 | 332 |
| 321 // The fullscreen element ready check for |element| returns true. | 333 // The fullscreen element ready check for |pending| returns false. |
| 322 if (!fullscreenElementReady(element)) | 334 if (!fullscreenElementReady(pending)) |
| 323 break; | 335 break; |
| 324 | 336 |
| 325 // Fullscreen is supported. | 337 // Fullscreen is supported. |
| 326 if (!fullscreenIsSupported(document)) | 338 if (!fullscreenIsSupported(document)) |
| 327 break; | 339 break; |
| 328 | 340 |
| 329 // This algorithm is allowed to request fullscreen. | 341 // This algorithm is allowed to request fullscreen. |
| 330 // OOPIF: If |forCrossProcessDescendant| is true, requestFullscreen was | 342 if (!allowedToRequestFullscreen(document)) |
| 331 // already called on a descendant element in another process, and | |
| 332 // getting here means that it was already allowed to request fullscreen. | |
| 333 if (!forCrossProcessDescendant && !allowedToRequestFullscreen(document)) | |
| 334 break; | 343 break; |
| 335 | 344 |
| 336 // 2. Let doc be element's node document. (i.e. "this") | 345 error = false; |
| 337 | 346 } while (false); |
| 338 // 3. Let docs be all doc's ancestor browsing context's documents (if any) | 347 |
| 339 // and doc. | 348 // 5. Return |promise|, and run the remaining steps in parallel. |
| 340 // | 349 // TODO(foolip): Promises. https://crbug.com/644637 |
| 341 // For OOPIF scenarios, |docs| will only contain documents for local | 350 |
| 342 // ancestors, and remote ancestors will be processed in their | 351 // 6. If |error| is false: Resize pending's top-level browsing context's |
| 343 // respective processes. This preserves the spec's event firing order | 352 // document's viewport's dimensions to match the dimensions of the screen of |
| 344 // for local ancestors, but not for remote ancestors. However, that | 353 // the output device. Optionally display a message how the end user can |
| 345 // difference shouldn't be observable in practice: a fullscreenchange | 354 // revert this. |
| 346 // event handler would need to postMessage a frame in another renderer | 355 if (!error) { |
| 347 // process, where the message should be queued up and processed after | 356 document.frameHost()->chromeClient().enterFullscreenForElement(&pending); |
| 348 // the IPC that dispatches fullscreenchange. | 357 } else { |
| 349 HeapDeque<Member<Document>> docs; | 358 document.enqueueAnimationFrameTask(WTF::bind(&animationFrameTaskAfterEnter, |
| 350 | 359 wrapPersistent(&pending), |
| 351 docs.prepend(&document); | 360 requestType, true)); |
| 352 for (Frame* frame = document.frame()->tree().parent(); frame; | 361 } |
| 353 frame = frame->tree().parent()) { | 362 } |
| 354 if (frame->isLocalFrame()) | 363 |
| 355 docs.prepend(toLocalFrame(frame)->document()); | 364 void Fullscreen::didEnterFullscreenForElement(Element& pending, |
| 365 RequestType requestType) { |
| 366 // 7. As part of the next animation frame task, run these substeps: |
| 367 pending.document().enqueueAnimationFrameTask( |
| 368 WTF::bind(&animationFrameTaskAfterEnter, wrapPersistent(&pending), |
| 369 requestType, false)); |
| 370 } |
| 371 |
| 372 void Fullscreen::animationFrameTaskAfterEnter(Element* element, |
| 373 RequestType requestType, |
| 374 bool error) { |
| 375 DCHECK(element); |
| 376 Element& pending = *element; |
| 377 |
| 378 // 7.1. If either |error| is true or the fullscreen element ready check for |
| 379 // |pending| returns false, fire an event named fullscreenerror on |
| 380 // |pending|'s node document, reject |promise| with a TypeError exception, |
| 381 // and terminate these steps. |
| 382 // TODO(foolip): Promises. https://crbug.com/644637 |
| 383 if (error || !fullscreenElementReady(pending)) { |
| 384 // TODO(foolip): What if we just entered fullscreen but the ready check |
| 385 // no longer passes, e.g. if the allowfullscreen attribute was just |
| 386 // removed? We need to exit fullscreen again, or at least maybe. |
| 387 fireErrorEvent(pending, requestType); |
| 388 return; |
| 389 } |
| 390 |
| 391 // 7.2. Let |fullscreenElements| be an ordered set initially consisting of |
| 392 // |pending|. |
| 393 HeapDeque<Member<Element>> fullscreenElements; |
| 394 fullscreenElements.append(pending); |
| 395 |
| 396 // 7.3. While the first element in |fullscreenElements| is in a nested |
| 397 // browsing context, prepend its browsing context container to |
| 398 // |fullscreenElements|. |
| 399 // |
| 400 // OOPIF: |fullscreenElements| will only contain elements for local |
| 401 // ancestors, and remote ancestors will be processed in their respective |
| 402 // processes. This preserves the spec's event firing order for local |
| 403 // ancestors, but not for remote ancestors. However, that difference |
| 404 // shouldn't be observable in practice: a fullscreenchange event handler |
| 405 // would need to postMessage a frame in another renderer process, where the |
| 406 // message should be queued up and processed after the IPC that dispatches |
| 407 // fullscreenchange. |
| 408 for (Frame* frame = pending.document().frame(); frame; |
| 409 frame = frame->tree().parent()) { |
| 410 if (frame->owner() && frame->owner()->isLocal()) { |
| 411 Element* element = toHTMLFrameOwnerElement(frame->owner()); |
| 412 if (!isFullscreenElement(*element)) |
| 413 fullscreenElements.prepend(element); |
| 356 } | 414 } |
| 357 | 415 } |
| 358 // 4. For each document in docs, run these substeps: | 416 |
| 359 HeapDeque<Member<Document>>::iterator current = docs.begin(), | 417 // 7.4. Let |eventDocs| be an empty list. |
| 360 following = docs.begin(); | 418 // Note: Rather than creating a separate |eventDocs| list with a subset of |
| 361 | 419 // the |fullscreenElements|' elements' documents, those elements are not |
| 362 do { | 420 // added to |fullscreenElements| at all, which simplifies the following. |
| 363 ++following; | 421 |
| 364 | 422 // 7.5. For each |element| in |fullscreenElements|, in order, run these |
| 365 // 1. Let following document be the document after document in docs, or | 423 // subsubsteps: |
| 366 // null if there is no such document. | 424 for (Element* element : fullscreenElements) { |
| 367 Document* currentDoc = *current; | 425 // 7.5.1. Let |doc| be |element|'s node document. |
| 368 Document* followingDoc = following != docs.end() ? *following : nullptr; | 426 |
| 369 | 427 // 7.5.2. If |element| is |doc|'s fullscreen element, terminate these |
| 370 // 2. If following document is null, push context object on document's | 428 // subsubsteps. |
| 371 // fullscreen element stack, and queue a task to fire an event named | 429 |
| 372 // fullscreenchange with its bubbles attribute set to true on the | 430 // 7.5.3. Otherwise, append |doc| to |eventDocs|. |
| 373 // document. | 431 |
| 374 if (!followingDoc) { | 432 // 7.5.4. If |element| is |pending| and |pending| is an iframe element, |
| 375 from(*currentDoc).pushFullscreenElementStack(element, requestType); | 433 // set |element|'s iframe fullscreen flag. |
| 376 from(document).enqueueChangeEvent(*currentDoc, requestType); | 434 // TODO(foolip): Support the iframe fullscreen flag. |
| 377 continue; | 435 // https://crbug.com/644695 |
| 378 } | 436 |
| 379 | 437 // 7.5.5. Fullscreen |element| within |doc|. |
| 380 // 3. Otherwise, if document's fullscreen element stack is either empty or | 438 fullscreen(*element, requestType); |
| 381 // its top element is not following document's browsing context container, | 439 } |
| 382 Element* topElement = fullscreenElementFrom(*currentDoc); | 440 |
| 383 HTMLFrameOwnerElement* followingOwner = | 441 // 7.6. For each |doc| in |eventDocs|, in order, fire an event named |
| 384 findContainerForDescendant(*currentDoc, *followingDoc); | 442 // fullscreenchange on |doc|. |
| 385 if (!topElement || topElement != followingOwner) { | 443 for (Element* element : fullscreenElements) |
| 386 // ...push following document's browsing context container on document's | 444 fireChangeEvent(*element, requestType); |
| 387 // fullscreen element stack, and queue a task to fire an event named | 445 |
| 388 // fullscreenchange with its bubbles attribute set to true on document. | 446 // 7.7. Fulfill |promise| with undefined. |
| 389 from(*currentDoc) | 447 // TODO(foolip): Promises. https://crbug.com/644637 |
| 390 .pushFullscreenElementStack(*followingOwner, requestType); | |
| 391 from(document).enqueueChangeEvent(*currentDoc, requestType); | |
| 392 continue; | |
| 393 } | |
| 394 | |
| 395 // 4. Otherwise, do nothing for this document. It stays the same. | |
| 396 } while (++current != docs.end()); | |
| 397 | |
| 398 from(document).m_forCrossProcessDescendant = forCrossProcessDescendant; | |
| 399 | |
| 400 // 5. Return, and run the remaining steps asynchronously. | |
| 401 // 6. Optionally, perform some animation. | |
| 402 document.frameHost()->chromeClient().enterFullscreenForElement(&element); | |
| 403 | |
| 404 // 7. Optionally, display a message indicating how the user can exit | |
| 405 // displaying the context object fullscreen. | |
| 406 return; | |
| 407 } while (false); | |
| 408 | |
| 409 from(document).enqueueErrorEvent(element, requestType); | |
| 410 } | 448 } |
| 411 | 449 |
| 412 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen | 450 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen |
| 413 void Fullscreen::fullyExitFullscreen(Document& document) { | 451 void Fullscreen::fullyExitFullscreen(Document& document) { |
| 414 // To fully exit fullscreen, run these steps: | 452 // 1. If |document|'s fullscreen element is null, terminate these steps. |
| 415 | 453 Element* fullscreenElement = Fullscreen::fullscreenElement(document); |
| 416 // 1. Let |doc| be the top-level browsing context's document. | 454 if (!fullscreenElement) |
| 417 // | 455 return; |
| 418 // Since the top-level browsing context's document might be unavailable in | 456 |
| 419 // OOPIF scenarios (i.e., when the top frame is remote), this actually uses | 457 // 2. Unfullscreen elements whose fullscreen flag is set, within |
| 420 // the Document of the topmost local ancestor frame. Without OOPIF, this | 458 // |document|'s top layer, except for |document|'s fullscreen element. |
| 421 // will be the top frame's document. With OOPIF, each renderer process for | 459 for (Element* element : document.topLayerElements()) { |
| 422 // the current page will separately call fullyExitFullscreen to cover all | 460 if (element->isFullscreen() && element != fullscreenElement) |
| 423 // local frames in each process. | 461 unfullscreen(*element); |
| 424 Document& doc = topmostLocalAncestor(document); | 462 } |
| 425 | 463 |
| 426 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps. | 464 // 3. Exit fullscreen |document|. |
| 427 if (!fullscreenElementFrom(doc)) | 465 exitFullscreen(document); |
| 428 return; | |
| 429 | |
| 430 // 3. Remove elements from |doc|'s fullscreen element stack until only the top | |
| 431 // element is left. | |
| 432 size_t stackSize = from(doc).m_fullscreenElementStack.size(); | |
| 433 from(doc).m_fullscreenElementStack.remove(0, stackSize - 1); | |
| 434 DCHECK_EQ(from(doc).m_fullscreenElementStack.size(), 1u); | |
| 435 | |
| 436 // 4. Act as if the exitFullscreen() method was invoked on |doc|. | |
| 437 exitFullscreen(doc); | |
| 438 } | 466 } |
| 439 | 467 |
| 440 // https://fullscreen.spec.whatwg.org/#exit-fullscreen | 468 // https://fullscreen.spec.whatwg.org/#exit-fullscreen |
| 441 void Fullscreen::exitFullscreen(Document& document) { | 469 void Fullscreen::exitFullscreen(Document& doc) { |
| 442 // The exitFullscreen() method must run these steps: | 470 if (!doc.isActive()) |
| 443 | 471 return; |
| 444 // 1. Let doc be the context object. (i.e. "this") | 472 |
| 445 if (!document.isActive()) | 473 // 1. Let |promise| be a new promise. |
| 446 return; | 474 // 2. If |doc|'s fullscreen element is null, reject |promise| with a |
| 447 | 475 // TypeError exception, and return |promise|. |
| 448 // 2. If doc's fullscreen element stack is empty, terminate these steps. | 476 // TODO(foolip): Promises. https://crbug.com/644637 |
| 449 if (!fullscreenElementFrom(document)) | 477 if (!fullscreenElement(doc)) |
| 450 return; | 478 return; |
| 451 | 479 |
| 452 // 3. Let descendants be all the doc's descendant browsing context's documents | 480 // 3. Let |resize| be false. |
| 453 // with a non-empty fullscreen element stack (if any), ordered so that the | 481 bool resize = false; |
| 454 // child of the doc is last and the document furthest away from the doc is | 482 |
| 455 // first. | 483 // 4. Let |docs| be the result of collecting documents to unfullscreen given |
| 456 HeapDeque<Member<Document>> descendants; | 484 // |doc|. |
| 457 for (Frame* descendant = | 485 HeapVector<Member<Document>> docs = collectDocumentsToUnfullscreen(doc); |
| 458 document.frame() ? document.frame()->tree().traverseNext() : nullptr; | 486 |
| 459 descendant; descendant = descendant->tree().traverseNext()) { | 487 // 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document. |
| 488 Document& topLevelDoc = topmostLocalAncestor(doc); |
| 489 |
| 490 // 6. If |topLevelDoc| is in |docs|, set |resize| to true. |
| 491 // OOPIF: Use |isLocalRoot| as oppposed to |isMainFrame|, so that if the |
| 492 // main frame is in another process, we will still fully exit fullscreen |
| 493 // even though that's wrong if the main frame was in nested fullscreen. |
| 494 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 495 // https://crbug.com/617369. |
| 496 if (!docs.isEmpty() && docs.last()->frame()->isLocalRoot()) |
| 497 resize = true; |
| 498 |
| 499 // 7. Return |promise|, and run the remaining steps in parallel. |
| 500 // TODO(foolip): Promises. https://crbug.com/644637 |
| 501 |
| 502 // 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal" |
| 503 // dimensions. |
| 504 if (resize) { |
| 505 topLevelDoc.frameHost()->chromeClient().exitFullscreenForElement( |
| 506 fullscreenElement(topLevelDoc)); |
| 507 } else { |
| 508 topLevelDoc.enqueueAnimationFrameTask( |
| 509 WTF::bind(&animationFrameTaskAfterExit, wrapPersistent(&topLevelDoc))); |
| 510 } |
| 511 } |
| 512 |
| 513 void Fullscreen::didExitFullscreen(Document& doc) { |
| 514 // 9. As part of the next animation frame task, run these substeps: |
| 515 doc.enqueueAnimationFrameTask( |
| 516 WTF::bind(&animationFrameTaskAfterExit, wrapPersistent(&doc))); |
| 517 } |
| 518 |
| 519 void Fullscreen::animationFrameTaskAfterExit(Document* document) { |
| 520 DCHECK(document); |
| 521 Document& doc = *document; |
| 522 |
| 523 // 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen |
| 524 // given |doc|. |
| 525 HeapVector<Member<Document>> exitDocs = collectDocumentsToUnfullscreen(doc); |
| 526 |
| 527 // 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully |
| 528 // exit fullscreen |topLevelDoc|, reject promise with a TypeError exception, |
| 529 // and terminate these steps. |
| 530 // TODO(foolip): Promises. https://crbug.com/644637 |
| 531 if (exitDocs.isEmpty() || !exitDocs.last()->frame()->isLocalRoot()) { |
| 532 return; |
| 533 } |
| 534 |
| 535 // 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|. |
| 536 if (exitDocs.isEmpty()) |
| 537 exitDocs.append(&doc); |
| 538 |
| 539 // 9.4. If |exitDocs|'s last document has a browsing context container, |
| 540 // append that browsing context container's node document to |exitDocs|. |
| 541 // OOPIF: Skip over remote frames, assuming that they have exactly one |
| 542 // element in their fullscreen element stacks, thereby erring on the side of |
| 543 // exiting fullscreen. |
| 544 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 545 // https://crbug.com/617369. |
| 546 for (Frame* frame = exitDocs.last()->frame()->tree().parent(); frame; |
| 547 frame = frame->tree().parent()) { |
| 548 if (frame->isLocalFrame()) { |
| 549 if (Document* document = toLocalFrame(frame)->document()) { |
| 550 exitDocs.append(document); |
| 551 break; |
| 552 } |
| 553 } |
| 554 } |
| 555 |
| 556 // 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s |
| 557 // descendant browsing contexts' documents whose fullscreen element is |
| 558 // non-null, if any, in *reverse* tree order. |
| 559 HeapDeque<Member<Document>> descendantDocs; |
| 560 for (Frame* descendant = doc.frame()->tree().firstChild(); descendant; |
| 561 descendant = descendant->tree().traverseNext(doc.frame())) { |
| 460 if (!descendant->isLocalFrame()) | 562 if (!descendant->isLocalFrame()) |
| 461 continue; | 563 continue; |
| 462 DCHECK(toLocalFrame(descendant)->document()); | 564 DCHECK(toLocalFrame(descendant)->document()); |
| 463 if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) | 565 if (fullscreenElement(*toLocalFrame(descendant)->document())) |
| 464 descendants.prepend(toLocalFrame(descendant)->document()); | 566 descendantDocs.prepend(toLocalFrame(descendant)->document()); |
| 465 } | 567 } |
| 466 | 568 |
| 467 // 4. For each descendant in descendants, empty descendant's fullscreen | 569 HeapVector<Member<Element>> eventElements; |
| 468 // element stack, and queue a task to fire an event named fullscreenchange | 570 |
| 469 // with its bubbles attribute set to true on descendant. | 571 // 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen |
| 470 for (auto& descendant : descendants) { | 572 // |descendantDoc|. |
| 471 DCHECK(descendant); | 573 for (Document* descendantDoc : descendantDocs) { |
| 472 RequestType requestType = | 574 eventElements.append(fullscreenElement(*descendantDoc)); |
| 473 from(*descendant).m_fullscreenElementStack.last().second; | 575 unfullscreen(*descendantDoc); |
| 474 from(*descendant).clearFullscreenElementStack(); | 576 } |
| 475 from(document).enqueueChangeEvent(*descendant, requestType); | 577 |
| 476 } | 578 // 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s |
| 477 | 579 // fullscreen element. |
| 478 // 5. While doc is not null, run these substeps: | 580 for (Document* exitDoc : exitDocs) { |
| 479 Element* newTop = nullptr; | 581 eventElements.append(fullscreenElement(*exitDoc)); |
| 480 for (Document* currentDoc = &document; currentDoc;) { | 582 unfullscreen(*fullscreenElement(*exitDoc)); |
| 481 RequestType requestType = | 583 } |
| 482 from(*currentDoc).m_fullscreenElementStack.last().second; | 584 |
| 483 | 585 // 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an |
| 484 // 1. Pop the top element of doc's fullscreen element stack. | 586 // event named fullscreenchange on |descendantDoc|. |
| 485 from(*currentDoc).popFullscreenElementStack(); | 587 // 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named |
| 486 | 588 // fullscreenchange on |exitDoc|. |
| 487 // If doc's fullscreen element stack is non-empty and the element now at | 589 // TODO(foolip): Fire the unprefixed event. |
| 488 // the top is either not in a document or its node document is not doc, | 590 for (Element* element : eventElements) |
| 489 // repeat this substep. | 591 fireChangeEvent(*element, PrefixedRequest); |
| 490 newTop = fullscreenElementFrom(*currentDoc); | 592 |
| 491 if (newTop && (!newTop->isConnected() || newTop->document() != currentDoc)) | 593 // 9.10. Fulfill |promise| with undefined. |
| 492 continue; | 594 // TODO(foolip): Promises. https://crbug.com/644637 |
| 493 | |
| 494 // 2. Queue a task to fire an event named fullscreenchange with its bubbles | |
| 495 // attribute set to true on doc. | |
| 496 from(document).enqueueChangeEvent(*currentDoc, requestType); | |
| 497 | |
| 498 // 3. If doc's fullscreen element stack is empty and doc's browsing context | |
| 499 // has a browsing context container, set doc to that browsing context | |
| 500 // container's node document. | |
| 501 // | |
| 502 // OOPIF: If browsing context container's document is in another | |
| 503 // process, keep moving up the ancestor chain and looking for a | |
| 504 // browsing context container with a local document. | |
| 505 // TODO(alexmos): Deal with nested fullscreen cases, see | |
| 506 // https://crbug.com/617369. | |
| 507 if (!newTop) { | |
| 508 Frame* frame = currentDoc->frame()->tree().parent(); | |
| 509 while (frame && frame->isRemoteFrame()) | |
| 510 frame = frame->tree().parent(); | |
| 511 if (frame) { | |
| 512 currentDoc = toLocalFrame(frame)->document(); | |
| 513 continue; | |
| 514 } | |
| 515 } | |
| 516 | |
| 517 // 4. Otherwise, set doc to null. | |
| 518 currentDoc = nullptr; | |
| 519 } | |
| 520 | |
| 521 // 6. Return, and run the remaining steps asynchronously. | |
| 522 // 7. Optionally, perform some animation. | |
| 523 | |
| 524 FrameHost* host = document.frameHost(); | |
| 525 | |
| 526 // Speculative fix for engaget.com/videos per crbug.com/336239. | |
| 527 // FIXME: This check is wrong. We DCHECK(document->isActive()) above | |
| 528 // so this should be redundant and should be removed! | |
| 529 if (!host) | |
| 530 return; | |
| 531 | |
| 532 // Only exit out of full screen window mode if there are no remaining elements | |
| 533 // in the full screen stack. | |
| 534 if (!newTop) { | |
| 535 // FIXME: if the frame exiting fullscreen is not the frame that entered | |
| 536 // fullscreen (but a parent frame for example), | |
| 537 // m_currentFullScreenElement might be null. We want to pass an element | |
| 538 // that is part of the document so we will pass the documentElement in | |
| 539 // that case. This should be fix by exiting fullscreen for a frame | |
| 540 // instead of an element, see https://crbug.com/441259 | |
| 541 Element* currentFullScreenElement = currentFullScreenElementFrom(document); | |
| 542 host->chromeClient().exitFullscreenForElement( | |
| 543 currentFullScreenElement ? currentFullScreenElement | |
| 544 : document.documentElement()); | |
| 545 return; | |
| 546 } | |
| 547 | |
| 548 // Otherwise, notify the chrome of the new full screen element. | |
| 549 host->chromeClient().enterFullscreenForElement(newTop); | |
| 550 } | 595 } |
| 551 | 596 |
| 552 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled | 597 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled |
| 553 bool Fullscreen::fullscreenEnabled(Document& document) { | 598 bool Fullscreen::fullscreenEnabled(Document& document) { |
| 554 // The fullscreenEnabled attribute's getter must return true if the context | 599 // The fullscreenEnabled attribute's getter must return true if the context |
| 555 // object is allowed to use the feature indicated by attribute name | 600 // object is allowed to use the feature indicated by attribute name |
| 556 // allowfullscreen and fullscreen is supported, and false otherwise. | 601 // allowfullscreen and fullscreen is supported, and false otherwise. |
| 557 return allowedToUseFullscreen(document.frame()) && | 602 return allowedToUseFullscreen(document.frame()) && |
| 558 fullscreenIsSupported(document); | 603 fullscreenIsSupported(document); |
| 559 } | 604 } |
| 560 | 605 |
| 561 void Fullscreen::didEnterFullscreenForElement(Element* element) { | 606 void Fullscreen::didUpdateSize(Element& element) { |
| 562 DCHECK(element); | 607 // StyleAdjuster will set the size so we need to do a style recalc. |
| 563 if (!document()->isActive()) | 608 // Normally changing size means layout so just doing a style recalc is a |
| 564 return; | 609 // bit surprising. |
| 565 | 610 element.setNeedsStyleRecalc( |
| 566 if (m_fullScreenLayoutObject) | 611 LocalStyleChange, |
| 567 m_fullScreenLayoutObject->unwrapLayoutObject(); | 612 StyleChangeReasonForTracing::create(StyleChangeReason::Fullscreen)); |
| 568 | |
| 569 m_currentFullScreenElement = element; | |
| 570 | |
| 571 // Create a placeholder block for a the full-screen element, to keep the page | |
| 572 // from reflowing when the element is removed from the normal flow. Only do | |
| 573 // this for a LayoutBox, as only a box will have a frameRect. The placeholder | |
| 574 // will be created in setFullScreenLayoutObject() during layout. | |
| 575 LayoutObject* layoutObject = m_currentFullScreenElement->layoutObject(); | |
| 576 bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox(); | |
| 577 if (shouldCreatePlaceholder) { | |
| 578 m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect(); | |
| 579 m_savedPlaceholderComputedStyle = | |
| 580 ComputedStyle::clone(layoutObject->styleRef()); | |
| 581 } | |
| 582 | |
| 583 // TODO(alexmos): When |m_forCrossProcessDescendant| is true, some of | |
| 584 // this layout work has already been done in another process, so it should | |
| 585 // not be necessary to repeat it here. | |
| 586 if (m_currentFullScreenElement != document()->documentElement()) | |
| 587 LayoutFullScreen::wrapLayoutObject( | |
| 588 layoutObject, layoutObject ? layoutObject->parent() : 0, document()); | |
| 589 | |
| 590 // When |m_forCrossProcessDescendant| is true, m_currentFullScreenElement | |
| 591 // corresponds to the HTMLFrameOwnerElement for the out-of-process iframe | |
| 592 // that contains the actual fullscreen element. Hence, it must also set | |
| 593 // the ContainsFullScreenElement flag (so that it gains the | |
| 594 // -webkit-full-screen-ancestor style). | |
| 595 if (m_forCrossProcessDescendant) { | |
| 596 DCHECK(m_currentFullScreenElement->isFrameOwnerElement()); | |
| 597 DCHECK(toHTMLFrameOwnerElement(m_currentFullScreenElement) | |
| 598 ->contentFrame() | |
| 599 ->isRemoteFrame()); | |
| 600 m_currentFullScreenElement->setContainsFullScreenElement(true); | |
| 601 } | |
| 602 | |
| 603 m_currentFullScreenElement | |
| 604 ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); | |
| 605 | |
| 606 document()->styleEngine().ensureUAStyleForFullscreen(); | |
| 607 m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); | |
| 608 | |
| 609 // FIXME: This should not call updateStyleAndLayoutTree. | |
| 610 document()->updateStyleAndLayoutTree(); | |
| 611 | |
| 612 m_currentFullScreenElement->didBecomeFullscreenElement(); | |
| 613 | |
| 614 if (document()->frame()) | |
| 615 document()->frame()->eventHandler().scheduleHoverStateUpdate(); | |
| 616 | |
| 617 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); | |
| 618 } | 613 } |
| 619 | 614 |
| 620 void Fullscreen::didExitFullscreen() { | 615 void Fullscreen::elementRemoved(Element& node) { |
| 621 if (!m_currentFullScreenElement) | 616 // Whenever the removing steps run with an oldNode, run these steps: |
| 622 return; | |
| 623 | 617 |
| 624 if (!document()->isActive()) | 618 // 1. Let |nodes| be |oldNode|'s inclusive descendants that have their |
| 625 return; | 619 // fullscreen flag set, in tree order. |
| 626 | 620 |
| 627 m_currentFullScreenElement->willStopBeingFullscreenElement(); | 621 // 2. For each |node| in |nodes|, run these substeps: |
| 628 | 622 |
| 629 if (m_forCrossProcessDescendant) | 623 // Note: The iteration of descendants is done in |
| 630 m_currentFullScreenElement->setContainsFullScreenElement(false); | 624 // ContainerNode::notifyNodeRemoved, and Element::removedFrom calls |
| 625 // Fullscreen::elementRemoved only for fullscreen elements. |
| 626 DCHECK(node.isInTopLayer()); |
| 627 DCHECK(node.isFullscreen()); |
| 631 | 628 |
| 632 m_currentFullScreenElement | 629 // 2.1. If |node| is its node document's fullscreen element, exit fullscreen |
| 633 ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); | 630 // that document. |
| 634 | 631 if (&node == fullscreenElement(node.document())) { |
| 635 if (m_fullScreenLayoutObject) | 632 exitFullscreen(node.document()); |
| 636 LayoutFullScreenItem(m_fullScreenLayoutObject).unwrapLayoutObject(); | |
| 637 | |
| 638 document()->styleEngine().ensureUAStyleForFullscreen(); | |
| 639 m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); | |
| 640 m_currentFullScreenElement = nullptr; | |
| 641 | |
| 642 if (document()->frame()) | |
| 643 document()->frame()->eventHandler().scheduleHoverStateUpdate(); | |
| 644 | |
| 645 // When fullyExitFullscreen is called, we call exitFullscreen on the | |
| 646 // topDocument(). That means that the events will be queued there. So if we | |
| 647 // have no events here, start the timer on the exiting document. | |
| 648 Document* exitingDocument = document(); | |
| 649 if (m_eventQueue.isEmpty()) | |
| 650 exitingDocument = &topmostLocalAncestor(*document()); | |
| 651 DCHECK(exitingDocument); | |
| 652 from(*exitingDocument).m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); | |
| 653 | |
| 654 m_forCrossProcessDescendant = false; | |
| 655 } | |
| 656 | |
| 657 void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) { | |
| 658 if (layoutObject == m_fullScreenLayoutObject) | |
| 659 return; | |
| 660 | |
| 661 if (layoutObject && m_savedPlaceholderComputedStyle) { | |
| 662 layoutObject->createPlaceholder(m_savedPlaceholderComputedStyle.release(), | |
| 663 m_savedPlaceholderFrameRect); | |
| 664 } else if (layoutObject && m_fullScreenLayoutObject && | |
| 665 m_fullScreenLayoutObject->placeholder()) { | |
| 666 LayoutBlockFlow* placeholder = m_fullScreenLayoutObject->placeholder(); | |
| 667 layoutObject->createPlaceholder( | |
| 668 ComputedStyle::clone(placeholder->styleRef()), | |
| 669 placeholder->frameRect()); | |
| 670 } | |
| 671 | |
| 672 if (m_fullScreenLayoutObject) | |
| 673 m_fullScreenLayoutObject->unwrapLayoutObject(); | |
| 674 DCHECK(!m_fullScreenLayoutObject); | |
| 675 | |
| 676 m_fullScreenLayoutObject = layoutObject; | |
| 677 } | |
| 678 | |
| 679 void Fullscreen::fullScreenLayoutObjectDestroyed() { | |
| 680 m_fullScreenLayoutObject = nullptr; | |
| 681 } | |
| 682 | |
| 683 void Fullscreen::enqueueChangeEvent(Document& document, | |
| 684 RequestType requestType) { | |
| 685 Event* event; | |
| 686 if (requestType == UnprefixedRequest) { | |
| 687 event = createEvent(EventTypeNames::fullscreenchange, document); | |
| 688 } else { | |
| 689 DCHECK(document.hasFullscreenSupplement()); | |
| 690 Fullscreen& fullscreen = from(document); | |
| 691 EventTarget* target = fullscreen.fullscreenElement(); | |
| 692 if (!target) | |
| 693 target = fullscreen.currentFullScreenElement(); | |
| 694 if (!target) | |
| 695 target = &document; | |
| 696 event = createEvent(EventTypeNames::webkitfullscreenchange, *target); | |
| 697 } | |
| 698 m_eventQueue.append(event); | |
| 699 // NOTE: The timer is started in | |
| 700 // didEnterFullscreenForElement/didExitFullscreen. | |
| 701 } | |
| 702 | |
| 703 void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType) { | |
| 704 Event* event; | |
| 705 if (requestType == UnprefixedRequest) | |
| 706 event = createEvent(EventTypeNames::fullscreenerror, element.document()); | |
| 707 else | |
| 708 event = createEvent(EventTypeNames::webkitfullscreenerror, element); | |
| 709 m_eventQueue.append(event); | |
| 710 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); | |
| 711 } | |
| 712 | |
| 713 void Fullscreen::eventQueueTimerFired(TimerBase*) { | |
| 714 HeapDeque<Member<Event>> eventQueue; | |
| 715 m_eventQueue.swap(eventQueue); | |
| 716 | |
| 717 while (!eventQueue.isEmpty()) { | |
| 718 Event* event = eventQueue.takeFirst(); | |
| 719 Node* target = event->target()->toNode(); | |
| 720 | |
| 721 // If the element was removed from our tree, also message the | |
| 722 // documentElement. | |
| 723 if (!target->isConnected() && document()->documentElement()) { | |
| 724 DCHECK(isPrefixed(event->type())); | |
| 725 eventQueue.append( | |
| 726 createEvent(event->type(), *document()->documentElement())); | |
| 727 } | |
| 728 | |
| 729 target->dispatchEvent(event); | |
| 730 } | |
| 731 } | |
| 732 | |
| 733 void Fullscreen::elementRemoved(Element& oldNode) { | |
| 734 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its | |
| 735 // node document's fullscreen element stack, run these steps: | |
| 736 | |
| 737 // 1. If |oldNode| is at the top of its node document's fullscreen element | |
| 738 // stack, act as if the exitFullscreen() method was invoked on that document. | |
| 739 if (fullscreenElement() == &oldNode) { | |
| 740 exitFullscreen(oldNode.document()); | |
| 741 return; | 633 return; |
| 742 } | 634 } |
| 743 | 635 |
| 744 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element | 636 // 2.2. Otherwise, unfullscreen |node| within its node document. |
| 745 // stack. | 637 unfullscreen(node); |
| 746 for (size_t i = 0; i < m_fullscreenElementStack.size(); ++i) { | |
| 747 if (m_fullscreenElementStack[i].first.get() == &oldNode) { | |
| 748 m_fullscreenElementStack.remove(i); | |
| 749 return; | |
| 750 } | |
| 751 } | |
| 752 | |
| 753 // NOTE: |oldNode| was not in the fullscreen element stack. | |
| 754 } | |
| 755 | |
| 756 void Fullscreen::clearFullscreenElementStack() { | |
| 757 m_fullscreenElementStack.clear(); | |
| 758 } | |
| 759 | |
| 760 void Fullscreen::popFullscreenElementStack() { | |
| 761 if (m_fullscreenElementStack.isEmpty()) | |
| 762 return; | |
| 763 | |
| 764 m_fullscreenElementStack.removeLast(); | |
| 765 } | |
| 766 | |
| 767 void Fullscreen::pushFullscreenElementStack(Element& element, | |
| 768 RequestType requestType) { | |
| 769 m_fullscreenElementStack.append(std::make_pair(&element, requestType)); | |
| 770 } | |
| 771 | |
| 772 DEFINE_TRACE(Fullscreen) { | |
| 773 visitor->trace(m_currentFullScreenElement); | |
| 774 visitor->trace(m_fullscreenElementStack); | |
| 775 visitor->trace(m_eventQueue); | |
| 776 Supplement<Document>::trace(visitor); | |
| 777 ContextLifecycleObserver::trace(visitor); | |
| 778 } | 638 } |
| 779 | 639 |
| 780 } // namespace blink | 640 } // namespace blink |
| OLD | NEW |