| 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 15 matching lines...) Expand all Loading... |
| 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/ConditionalFeatures.h" | 32 #include "bindings/core/v8/ConditionalFeatures.h" |
| 33 #include "core/dom/Document.h" | 33 #include "core/dom/Document.h" |
| 34 #include "core/dom/ElementTraversal.h" | 34 #include "core/dom/ElementTraversal.h" |
| 35 #include "core/dom/StyleEngine.h" | 35 #include "core/dom/StyleEngine.h" |
| 36 #include "core/dom/TaskRunnerHelper.h" |
| 36 #include "core/events/Event.h" | 37 #include "core/events/Event.h" |
| 37 #include "core/frame/FrameView.h" | 38 #include "core/frame/FrameView.h" |
| 38 #include "core/frame/HostsUsingFeatures.h" | 39 #include "core/frame/HostsUsingFeatures.h" |
| 39 #include "core/frame/LocalFrame.h" | 40 #include "core/frame/LocalFrame.h" |
| 40 #include "core/frame/Settings.h" | 41 #include "core/frame/Settings.h" |
| 41 #include "core/frame/UseCounter.h" | 42 #include "core/frame/UseCounter.h" |
| 42 #include "core/html/HTMLIFrameElement.h" | 43 #include "core/html/HTMLIFrameElement.h" |
| 43 #include "core/input/EventHandler.h" | 44 #include "core/input/EventHandler.h" |
| 44 #include "core/inspector/ConsoleMessage.h" | 45 #include "core/inspector/ConsoleMessage.h" |
| 45 #include "core/layout/LayoutBlockFlow.h" | 46 #include "core/layout/LayoutBlockFlow.h" |
| (...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 179 // |element|'s node document's browsing context's browsing context container, | 180 // |element|'s node document's browsing context's browsing context container, |
| 180 // or it has no browsing context container. | 181 // or it has no browsing context container. |
| 181 if (const Element* owner = element.document().localOwner()) { | 182 if (const Element* owner = element.document().localOwner()) { |
| 182 if (!fullscreenElementReady(*owner)) | 183 if (!fullscreenElementReady(*owner)) |
| 183 return false; | 184 return false; |
| 184 } | 185 } |
| 185 | 186 |
| 186 return true; | 187 return true; |
| 187 } | 188 } |
| 188 | 189 |
| 189 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen step 4: | 190 bool isPrefixed(const AtomicString& type) { |
| 190 bool requestFullscreenConditionsMet(Element& pending, Document& document) { | 191 return type == EventTypeNames::webkitfullscreenchange || |
| 191 // |pending|'s namespace is the HTML namespace or |pending| is an SVG svg or | 192 type == EventTypeNames::webkitfullscreenerror; |
| 192 // MathML math element. Note: MathML is not supported. | 193 } |
| 193 if (!pending.isHTMLElement() && !isSVGSVGElement(pending)) | |
| 194 return false; | |
| 195 | 194 |
| 196 // The fullscreen element ready check for |pending| returns false. | 195 Event* createEvent(const AtomicString& type, EventTarget& target) { |
| 197 if (!fullscreenElementReady(pending)) | 196 EventInit initializer; |
| 198 return false; | 197 initializer.setBubbles(isPrefixed(type)); |
| 199 | 198 Event* event = Event::create(type, initializer); |
| 200 // Fullscreen is supported. | 199 event->setTarget(&target); |
| 201 if (!fullscreenIsSupported(document)) | 200 return event; |
| 202 return false; | |
| 203 | |
| 204 // This algorithm is allowed to request fullscreen. | |
| 205 if (!allowedToRequestFullscreen(document)) | |
| 206 return false; | |
| 207 | |
| 208 return true; | |
| 209 } | 201 } |
| 210 | 202 |
| 211 // Walks the frame tree and returns the first local ancestor frame, if any. | 203 // Walks the frame tree and returns the first local ancestor frame, if any. |
| 212 LocalFrame* nextLocalAncestor(Frame& frame) { | 204 LocalFrame* nextLocalAncestor(Frame& frame) { |
| 213 Frame* parent = frame.tree().parent(); | 205 Frame* parent = frame.tree().parent(); |
| 214 if (!parent) | 206 if (!parent) |
| 215 return nullptr; | 207 return nullptr; |
| 216 if (parent->isLocalFrame()) | 208 if (parent->isLocalFrame()) |
| 217 return toLocalFrame(parent); | 209 return toLocalFrame(parent); |
| 218 return nextLocalAncestor(*parent); | 210 return nextLocalAncestor(*parent); |
| 219 } | 211 } |
| 220 | 212 |
| 221 // Walks the document's frame tree and returns the document of the first local | 213 // Walks the document's frame tree and returns the document of the first local |
| 222 // ancestor frame, if any. | 214 // ancestor frame, if any. |
| 223 Document* nextLocalAncestor(Document& document) { | 215 Document* nextLocalAncestor(Document& document) { |
| 224 LocalFrame* frame = document.frame(); | 216 LocalFrame* frame = document.frame(); |
| 225 if (!frame) | 217 if (!frame) |
| 226 return nullptr; | 218 return nullptr; |
| 227 LocalFrame* next = nextLocalAncestor(*frame); | 219 LocalFrame* next = nextLocalAncestor(*document.frame()); |
| 228 if (!next) | 220 if (!next) |
| 229 return nullptr; | 221 return nullptr; |
| 230 DCHECK(next->document()); | 222 DCHECK(next->document()); |
| 231 return next->document(); | 223 return next->document(); |
| 232 } | 224 } |
| 233 | 225 |
| 234 // Helper to walk the ancestor chain and return the Document of the topmost | 226 // Helper to walk the ancestor chain and return the Document of the topmost |
| 235 // local ancestor frame. Note that this is not the same as the topmost frame's | 227 // local ancestor frame. Note that this is not the same as the topmost frame's |
| 236 // Document, which might be unavailable in OOPIF scenarios. For example, with | 228 // Document, which might be unavailable in OOPIF scenarios. For example, with |
| 237 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in | 229 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in |
| 238 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. | 230 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. |
| 239 Document& topmostLocalAncestor(Document& document) { | 231 Document& topmostLocalAncestor(Document& document) { |
| 240 if (Document* next = nextLocalAncestor(document)) | 232 if (Document* next = nextLocalAncestor(document)) |
| 241 return topmostLocalAncestor(*next); | 233 return topmostLocalAncestor(*next); |
| 242 return document; | 234 return document; |
| 243 } | 235 } |
| 244 | 236 |
| 245 // https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen | 237 // Helper to find the browsing context container in |doc| that embeds the |
| 246 HeapVector<Member<Document>> collectDocumentsToUnfullscreen( | 238 // |descendant| Document, possibly through multiple levels of nesting. This |
| 247 Document& doc, | 239 // works even in OOPIF scenarios like A-B-A, where there may be remote frames |
| 248 Fullscreen::ExitType exitType) { | 240 // in between |doc| and |descendant|. |
| 249 DCHECK(Fullscreen::fullscreenElementFrom(doc)); | 241 HTMLFrameOwnerElement* findContainerForDescendant(const Document& doc, |
| 250 | 242 const Document& descendant) { |
| 251 // 1. If |doc|'s top layer consists of more than a single element that has | 243 Frame* frame = descendant.frame(); |
| 252 // its fullscreen flag set, return the empty set. | 244 while (frame->tree().parent() != doc.frame()) |
| 253 // TODO(foolip): See TODO in |fullyExitFullscreen()|. | 245 frame = frame->tree().parent(); |
| 254 if (exitType != Fullscreen::ExitType::Fully && | 246 return toHTMLFrameOwnerElement(frame->owner()); |
| 255 Fullscreen::fullscreenElementStackSizeFrom(doc) > 1) | |
| 256 return HeapVector<Member<Document>>(); | |
| 257 | |
| 258 // 2. Let |docs| be an ordered set consisting of |doc|. | |
| 259 HeapVector<Member<Document>> docs; | |
| 260 docs.push_back(&doc); | |
| 261 | |
| 262 // 3. While |docs|'s last document ... | |
| 263 // | |
| 264 // OOPIF: Skip over remote frames, assuming that they have exactly one element | |
| 265 // in their fullscreen element stacks, thereby erring on the side of exiting | |
| 266 // fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see | |
| 267 // https://crbug.com/617369. | |
| 268 for (Document* document = nextLocalAncestor(doc); document; | |
| 269 document = nextLocalAncestor(*document)) { | |
| 270 // ... has a browsing context container whose node document's top layer | |
| 271 // consists of a single element that has its fullscreen flag set and does | |
| 272 // not have its iframe fullscreen flag set (if any), append that node | |
| 273 // document to |docs|. | |
| 274 // TODO(foolip): Support the iframe fullscreen flag. | |
| 275 // https://crbug.com/644695 | |
| 276 if (Fullscreen::fullscreenElementStackSizeFrom(*document) == 1) | |
| 277 docs.push_back(document); | |
| 278 else | |
| 279 break; | |
| 280 } | |
| 281 | |
| 282 // 4. Return |docs|. | |
| 283 return docs; | |
| 284 } | 247 } |
| 285 | 248 |
| 286 // Creates a non-bubbling event with |document| as its target. | 249 // Fullscreen status affects scroll paint properties through |
| 287 Event* createEvent(const AtomicString& type, Document& document) { | 250 // FrameView::userInputScrollable(). |
| 288 DCHECK(type == EventTypeNames::fullscreenchange || | 251 void setNeedsPaintPropertyUpdate(Document* document) { |
| 289 type == EventTypeNames::fullscreenerror); | 252 if (!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() || |
| 253 RuntimeEnabledFeatures::rootLayerScrollingEnabled()) |
| 254 return; |
| 290 | 255 |
| 291 Event* event = Event::create(type); | 256 if (!document) |
| 292 event->setTarget(&document); | 257 return; |
| 293 return event; | |
| 294 } | |
| 295 | 258 |
| 296 // Creates a bubbling event with |element| as its target. If |element| is not | 259 LocalFrame* frame = document->frame(); |
| 297 // connected to |document|, then |document| is used as the target instead. | 260 if (!frame) |
| 298 Event* createPrefixedEvent(const AtomicString& type, | 261 return; |
| 299 Element& element, | |
| 300 Document& document) { | |
| 301 DCHECK(type == EventTypeNames::webkitfullscreenchange || | |
| 302 type == EventTypeNames::webkitfullscreenerror); | |
| 303 | 262 |
| 304 Event* event = Event::createBubble(type); | 263 if (FrameView* frameView = frame->view()) |
| 305 if (element.isConnected() && element.document() == document) | 264 frameView->setNeedsPaintPropertyUpdate(); |
| 306 event->setTarget(&element); | |
| 307 else | |
| 308 event->setTarget(&document); | |
| 309 return event; | |
| 310 } | |
| 311 | |
| 312 Event* createChangeEvent(Document& document, | |
| 313 Element& element, | |
| 314 Fullscreen::RequestType requestType) { | |
| 315 if (requestType == Fullscreen::RequestType::Unprefixed) | |
| 316 return createEvent(EventTypeNames::fullscreenchange, document); | |
| 317 return createPrefixedEvent(EventTypeNames::webkitfullscreenchange, element, | |
| 318 document); | |
| 319 } | |
| 320 | |
| 321 Event* createErrorEvent(Document& document, | |
| 322 Element& element, | |
| 323 Fullscreen::RequestType requestType) { | |
| 324 if (requestType == Fullscreen::RequestType::Unprefixed) | |
| 325 return createEvent(EventTypeNames::fullscreenerror, document); | |
| 326 return createPrefixedEvent(EventTypeNames::webkitfullscreenerror, element, | |
| 327 document); | |
| 328 } | |
| 329 | |
| 330 void dispatchEvents(const HeapVector<Member<Event>>& events) { | |
| 331 for (Event* event : events) | |
| 332 event->target()->dispatchEvent(event); | |
| 333 } | 265 } |
| 334 | 266 |
| 335 } // anonymous namespace | 267 } // anonymous namespace |
| 336 | 268 |
| 337 const char* Fullscreen::supplementName() { | 269 const char* Fullscreen::supplementName() { |
| 338 return "Fullscreen"; | 270 return "Fullscreen"; |
| 339 } | 271 } |
| 340 | 272 |
| 341 Fullscreen& Fullscreen::from(Document& document) { | 273 Fullscreen& Fullscreen::from(Document& document) { |
| 342 Fullscreen* fullscreen = fromIfExists(document); | 274 Fullscreen* fullscreen = fromIfExists(document); |
| 343 if (!fullscreen) { | 275 if (!fullscreen) { |
| 344 fullscreen = new Fullscreen(document); | 276 fullscreen = new Fullscreen(document); |
| 345 Supplement<Document>::provideTo(document, supplementName(), fullscreen); | 277 Supplement<Document>::provideTo(document, supplementName(), fullscreen); |
| 346 } | 278 } |
| 347 | 279 |
| 348 return *fullscreen; | 280 return *fullscreen; |
| 349 } | 281 } |
| 350 | 282 |
| 351 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) { | 283 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) { |
| 352 return static_cast<Fullscreen*>( | 284 return static_cast<Fullscreen*>( |
| 353 Supplement<Document>::from(document, supplementName())); | 285 Supplement<Document>::from(document, supplementName())); |
| 354 } | 286 } |
| 355 | 287 |
| 356 Element* Fullscreen::fullscreenElementFrom(Document& document) { | 288 Element* Fullscreen::fullscreenElementFrom(Document& document) { |
| 357 if (Fullscreen* found = fromIfExists(document)) | 289 if (Fullscreen* found = fromIfExists(document)) |
| 358 return found->fullscreenElement(); | 290 return found->fullscreenElement(); |
| 359 return nullptr; | 291 return nullptr; |
| 360 } | 292 } |
| 361 | 293 |
| 362 size_t Fullscreen::fullscreenElementStackSizeFrom(Document& document) { | |
| 363 if (Fullscreen* found = fromIfExists(document)) | |
| 364 return found->m_fullscreenElementStack.size(); | |
| 365 return 0; | |
| 366 } | |
| 367 | |
| 368 Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) { | 294 Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) { |
| 369 Element* element = fullscreenElementFrom(scope.document()); | 295 Element* element = fullscreenElementFrom(scope.document()); |
| 370 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | 296 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
| 371 return element; | 297 return element; |
| 372 | 298 |
| 373 // TODO(kochi): Once V0 code is removed, we can use the same logic for | 299 // TODO(kochi): Once V0 code is removed, we can use the same logic for |
| 374 // Document and ShadowRoot. | 300 // Document and ShadowRoot. |
| 375 if (!scope.rootNode().isShadowRoot()) { | 301 if (!scope.rootNode().isShadowRoot()) { |
| 376 // For Shadow DOM V0 compatibility: We allow returning an element in V0 | 302 // For Shadow DOM V0 compatibility: We allow returning an element in V0 |
| 377 // shadow tree, even though it leaks the Shadow DOM. | 303 // shadow tree, even though it leaks the Shadow DOM. |
| 378 if (element->isInV0ShadowTree()) { | 304 if (element->isInV0ShadowTree()) { |
| 379 UseCounter::count(scope.document(), | 305 UseCounter::count(scope.document(), |
| 380 UseCounter::DocumentFullscreenElementInV0Shadow); | 306 UseCounter::DocumentFullscreenElementInV0Shadow); |
| 381 return element; | 307 return element; |
| 382 } | 308 } |
| 383 } else if (!toShadowRoot(scope.rootNode()).isV1()) { | 309 } else if (!toShadowRoot(scope.rootNode()).isV1()) { |
| 384 return nullptr; | 310 return nullptr; |
| 385 } | 311 } |
| 386 return scope.adjustedElement(*element); | 312 return scope.adjustedElement(*element); |
| 387 } | 313 } |
| 388 | 314 |
| 315 Element* Fullscreen::currentFullScreenElementFrom(Document& document) { |
| 316 if (Fullscreen* found = fromIfExists(document)) |
| 317 return found->currentFullScreenElement(); |
| 318 return nullptr; |
| 319 } |
| 320 |
| 321 Element* Fullscreen::currentFullScreenElementForBindingFrom( |
| 322 Document& document) { |
| 323 Element* element = currentFullScreenElementFrom(document); |
| 324 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
| 325 return element; |
| 326 |
| 327 // For Shadow DOM V0 compatibility: We allow returning an element in V0 shadow |
| 328 // tree, even though it leaks the Shadow DOM. |
| 329 if (element->isInV0ShadowTree()) { |
| 330 UseCounter::count(document, |
| 331 UseCounter::DocumentFullscreenElementInV0Shadow); |
| 332 return element; |
| 333 } |
| 334 return document.adjustedElement(*element); |
| 335 } |
| 336 |
| 389 Fullscreen::Fullscreen(Document& document) | 337 Fullscreen::Fullscreen(Document& document) |
| 390 : Supplement<Document>(document), | 338 : Supplement<Document>(document), |
| 391 ContextLifecycleObserver(&document), | 339 ContextLifecycleObserver(&document), |
| 392 m_fullScreenLayoutObject(nullptr) { | 340 m_fullScreenLayoutObject(nullptr), |
| 341 m_eventQueueTimer(TaskRunnerHelper::get(TaskType::Unthrottled, &document), |
| 342 this, |
| 343 &Fullscreen::eventQueueTimerFired), |
| 344 m_forCrossProcessDescendant(false) { |
| 393 document.setHasFullscreenSupplement(); | 345 document.setHasFullscreenSupplement(); |
| 394 } | 346 } |
| 395 | 347 |
| 396 Fullscreen::~Fullscreen() {} | 348 Fullscreen::~Fullscreen() {} |
| 397 | 349 |
| 398 Document* Fullscreen::document() { | 350 inline Document* Fullscreen::document() { |
| 399 return toDocument(lifecycleContext()); | 351 return toDocument(lifecycleContext()); |
| 400 } | 352 } |
| 401 | 353 |
| 402 void Fullscreen::contextDestroyed(ExecutionContext*) { | 354 void Fullscreen::contextDestroyed(ExecutionContext*) { |
| 355 m_eventQueue.clear(); |
| 356 |
| 403 if (m_fullScreenLayoutObject) | 357 if (m_fullScreenLayoutObject) |
| 404 m_fullScreenLayoutObject->destroy(); | 358 m_fullScreenLayoutObject->destroy(); |
| 405 | 359 |
| 406 m_pendingRequests.clear(); | 360 m_currentFullScreenElement = nullptr; |
| 407 m_fullscreenElementStack.clear(); | 361 m_fullscreenElementStack.clear(); |
| 408 } | 362 } |
| 409 | 363 |
| 410 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen | 364 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen |
| 411 void Fullscreen::requestFullscreen(Element& pending) { | 365 void Fullscreen::requestFullscreen(Element& element) { |
| 412 // TODO(foolip): Make RequestType::Unprefixed the default when the unprefixed | 366 // TODO(foolip): Make RequestType::Unprefixed the default when the unprefixed |
| 413 // API is enabled. https://crbug.com/383813 | 367 // API is enabled. https://crbug.com/383813 |
| 414 requestFullscreen(pending, RequestType::Prefixed); | 368 requestFullscreen(element, RequestType::Prefixed, false); |
| 415 } | 369 } |
| 416 | 370 |
| 417 void Fullscreen::requestFullscreen(Element& pending, RequestType requestType) { | 371 void Fullscreen::requestFullscreen(Element& element, |
| 418 Document& document = pending.document(); | 372 RequestType requestType, |
| 419 | 373 bool forCrossProcessDescendant) { |
| 420 // Ignore this call if the document is not in a live frame. | 374 Document& document = element.document(); |
| 421 if (!document.isActive() || !document.frame()) | |
| 422 return; | |
| 423 | |
| 424 bool forCrossProcessDescendant = | |
| 425 requestType == RequestType::PrefixedForCrossProcessDescendant; | |
| 426 | 375 |
| 427 // Use counters only need to be incremented in the process of the actual | 376 // Use counters only need to be incremented in the process of the actual |
| 428 // fullscreen element. | 377 // fullscreen element. |
| 429 if (!forCrossProcessDescendant) { | 378 if (!forCrossProcessDescendant) { |
| 430 if (document.isSecureContext()) { | 379 if (document.isSecureContext()) { |
| 431 UseCounter::count(document, UseCounter::FullscreenSecureOrigin); | 380 UseCounter::count(document, UseCounter::FullscreenSecureOrigin); |
| 432 } else { | 381 } else { |
| 433 UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); | 382 UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); |
| 434 HostsUsingFeatures::countAnyWorld( | 383 HostsUsingFeatures::countAnyWorld( |
| 435 document, HostsUsingFeatures::Feature::FullscreenInsecureHost); | 384 document, HostsUsingFeatures::Feature::FullscreenInsecureHost); |
| 436 } | 385 } |
| 437 } | 386 } |
| 438 | 387 |
| 439 // 1. Let |pending| be the context object. | 388 // Ignore this call if the document is not in a live frame. |
| 440 | 389 if (!document.isActive() || !document.frame()) |
| 441 // 2. Let |error| be false. | 390 return; |
| 442 bool error = false; | 391 |
| 443 | 392 // If |element| is on top of |doc|'s fullscreen element stack, terminate these |
| 444 // 3. Let |promise| be a new promise. | 393 // substeps. |
| 445 // TODO(foolip): Promises. https://crbug.com/644637 | 394 if (&element == fullscreenElementFrom(document)) |
| 446 | 395 return; |
| 447 // 4. If any of the following conditions are false, then set |error| to true: | 396 |
| 448 // | 397 do { |
| 449 // OOPIF: If |requestFullscreen()| was already called in a descendant frame | 398 // 1. If any of the following conditions are false, then terminate these |
| 450 // and passed the checks, do not check again here. | 399 // steps and queue a task to fire an event named fullscreenerror with its |
| 451 if (!forCrossProcessDescendant && | 400 // bubbles attribute set to true on the context object's node document: |
| 452 !requestFullscreenConditionsMet(pending, document)) | 401 |
| 453 error = true; | 402 // |element|'s namespace is the HTML namespace or |element| is an SVG |
| 454 | 403 // svg or MathML math element. |
| 455 // 5. Return |promise|, and run the remaining steps in parallel. | 404 // Note: MathML is not supported. |
| 456 // TODO(foolip): Promises. https://crbug.com/644637 | 405 if (!element.isHTMLElement() && !isSVGSVGElement(element)) |
| 457 | 406 break; |
| 458 // 6. If |error| is false: Resize pending's top-level browsing context's | 407 |
| 459 // document's viewport's dimensions to match the dimensions of the screen of | 408 // The fullscreen element ready check for |element| returns true. |
| 460 // the output device. Optionally display a message how the end user can | 409 if (!fullscreenElementReady(element)) |
| 461 // revert this. | 410 break; |
| 462 if (!error) { | 411 |
| 463 from(document).m_pendingRequests.push_back( | 412 // Fullscreen is supported. |
| 464 std::make_pair(&pending, requestType)); | 413 if (!fullscreenIsSupported(document)) |
| 465 LocalFrame& frame = *document.frame(); | 414 break; |
| 466 frame.chromeClient().enterFullscreen(frame); | 415 |
| 467 } else { | 416 // This algorithm is allowed to request fullscreen. |
| 468 enqueueTaskForRequest(document, pending, requestType, true); | 417 // OOPIF: If |forCrossProcessDescendant| is true, requestFullscreen was |
| 469 } | 418 // already called on a descendant element in another process, and |
| 470 } | 419 // getting here means that it was already allowed to request fullscreen. |
| 471 | 420 if (!forCrossProcessDescendant && !allowedToRequestFullscreen(document)) |
| 472 void Fullscreen::didEnterFullscreen() { | 421 break; |
| 473 if (!document()) | 422 |
| 474 return; | 423 // 2. Let doc be element's node document. (i.e. "this") |
| 475 | 424 |
| 476 ElementStack requests; | 425 // 3. Let docs be all doc's ancestor browsing context's documents (if any) |
| 477 requests.swap(m_pendingRequests); | 426 // and doc. |
| 478 for (const ElementStackEntry& request : requests) | 427 // |
| 479 enqueueTaskForRequest(*document(), *request.first, request.second, false); | 428 // For OOPIF scenarios, |docs| will only contain documents for local |
| 480 } | 429 // ancestors, and remote ancestors will be processed in their |
| 481 | 430 // respective processes. This preserves the spec's event firing order |
| 482 void Fullscreen::enqueueTaskForRequest(Document& document, | 431 // for local ancestors, but not for remote ancestors. However, that |
| 483 Element& pending, | 432 // difference shouldn't be observable in practice: a fullscreenchange |
| 484 RequestType requestType, | 433 // event handler would need to postMessage a frame in another renderer |
| 485 bool error) { | 434 // process, where the message should be queued up and processed after |
| 486 // 7. As part of the next animation frame task, run these substeps: | 435 // the IPC that dispatches fullscreenchange. |
| 487 document.enqueueAnimationFrameTask( | 436 HeapDeque<Member<Document>> docs; |
| 488 WTF::bind(&runTaskForRequest, wrapPersistent(&document), | 437 for (Document* doc = &document; doc; doc = nextLocalAncestor(*doc)) |
| 489 wrapPersistent(&pending), requestType, error)); | 438 docs.prepend(doc); |
| 490 } | 439 |
| 491 | 440 // 4. For each document in docs, run these substeps: |
| 492 void Fullscreen::runTaskForRequest(Document* document, | 441 HeapDeque<Member<Document>>::iterator current = docs.begin(), |
| 493 Element* element, | 442 following = docs.begin(); |
| 494 RequestType requestType, | 443 |
| 495 bool error) { | 444 do { |
| 496 DCHECK(document); | 445 ++following; |
| 497 DCHECK(document->isActive()); | 446 |
| 498 DCHECK(document->frame()); | 447 // 1. Let following document be the document after document in docs, or |
| 499 DCHECK(element); | 448 // null if there is no such document. |
| 500 | 449 Document* currentDoc = *current; |
| 501 Document& pendingDoc = *document; | 450 Document* followingDoc = following != docs.end() ? *following : nullptr; |
| 502 Element& pending = *element; | 451 |
| 503 | 452 // 2. If following document is null, push context object on document's |
| 504 // TODO(foolip): Spec something like: If |pending|'s node document is not | 453 // fullscreen element stack, and queue a task to fire an event named |
| 505 // |pendingDoc|, then set |error| to true. | 454 // fullscreenchange with its bubbles attribute set to true on the |
| 506 // https://github.com/whatwg/fullscreen/issues/33 | 455 // document. |
| 507 if (pending.document() != pendingDoc) | 456 if (!followingDoc) { |
| 508 error = true; | 457 from(*currentDoc).pushFullscreenElementStack(element, requestType); |
| 509 | 458 from(document).enqueueChangeEvent(*currentDoc, requestType); |
| 510 // 7.1. If either |error| is true or the fullscreen element ready check for | 459 continue; |
| 511 // |pending| returns false, fire an event named fullscreenerror on | 460 } |
| 512 // |pending|'s node document, reject |promise| with a TypeError exception, | 461 |
| 513 // and terminate these steps. | 462 // 3. Otherwise, if document's fullscreen element stack is either empty or |
| 514 // TODO(foolip): Promises. https://crbug.com/644637 | 463 // its top element is not following document's browsing context container, |
| 515 if (error || !fullscreenElementReady(pending)) { | 464 Element* topElement = fullscreenElementFrom(*currentDoc); |
| 516 Event* event = createErrorEvent(pendingDoc, pending, requestType); | 465 HTMLFrameOwnerElement* followingOwner = |
| 517 event->target()->dispatchEvent(event); | 466 findContainerForDescendant(*currentDoc, *followingDoc); |
| 518 return; | 467 if (!topElement || topElement != followingOwner) { |
| 519 } | 468 // ...push following document's browsing context container on document's |
| 520 | 469 // fullscreen element stack, and queue a task to fire an event named |
| 521 // 7.2. Let |fullscreenElements| be an ordered set initially consisting of | 470 // fullscreenchange with its bubbles attribute set to true on document. |
| 522 // |pending|. | 471 from(*currentDoc) |
| 523 HeapDeque<Member<Element>> fullscreenElements; | 472 .pushFullscreenElementStack(*followingOwner, requestType); |
| 524 fullscreenElements.append(pending); | 473 from(document).enqueueChangeEvent(*currentDoc, requestType); |
| 525 | 474 continue; |
| 526 // 7.3. While the first element in |fullscreenElements| is in a nested | 475 } |
| 527 // browsing context, prepend its browsing context container to | 476 |
| 528 // |fullscreenElements|. | 477 // 4. Otherwise, do nothing for this document. It stays the same. |
| 529 // | 478 } while (++current != docs.end()); |
| 530 // OOPIF: |fullscreenElements| will only contain elements for local ancestors, | 479 |
| 531 // and remote ancestors will be processed in their respective processes. This | 480 from(document).m_forCrossProcessDescendant = forCrossProcessDescendant; |
| 532 // preserves the spec's event firing order for local ancestors, but not for | 481 |
| 533 // remote ancestors. However, that difference shouldn't be observable in | 482 // 5. Return, and run the remaining steps asynchronously. |
| 534 // practice: a fullscreenchange event handler would need to postMessage a | 483 // 6. Optionally, perform some animation. |
| 535 // frame in another renderer process, where the message should be queued up | 484 from(document).m_pendingFullscreenElement = &element; |
| 536 // and processed after the IPC that dispatches fullscreenchange. | 485 document.frame()->chromeClient().enterFullscreen(*document.frame()); |
| 537 for (Frame* frame = pending.document().frame(); frame; | 486 |
| 538 frame = frame->tree().parent()) { | 487 // 7. Optionally, display a message indicating how the user can exit |
| 539 if (!frame->owner() || !frame->owner()->isLocal()) | 488 // displaying the context object fullscreen. |
| 540 continue; | 489 return; |
| 541 Element* element = toHTMLFrameOwnerElement(frame->owner()); | 490 } while (false); |
| 542 fullscreenElements.prepend(element); | 491 |
| 543 } | 492 from(document).enqueueErrorEvent(element, requestType); |
| 544 | |
| 545 // 7.4. Let |eventDocs| be an empty list. | |
| 546 // Note: For prefixed requests, the event target is an element, so instead | |
| 547 // let |events| be a list of events to dispatch. | |
| 548 HeapVector<Member<Event>> events; | |
| 549 | |
| 550 // 7.5. For each |element| in |fullscreenElements|, in order, run these | |
| 551 // subsubsteps: | |
| 552 for (Element* element : fullscreenElements) { | |
| 553 // 7.5.1. Let |doc| be |element|'s node document. | |
| 554 Document& doc = element->document(); | |
| 555 | |
| 556 // 7.5.2. If |element| is |doc|'s fullscreen element, terminate these | |
| 557 // subsubsteps. | |
| 558 if (element == fullscreenElementFrom(doc)) | |
| 559 continue; | |
| 560 | |
| 561 // 7.5.3. Otherwise, append |doc| to |eventDocs|. | |
| 562 events.push_back(createChangeEvent(doc, *element, requestType)); | |
| 563 | |
| 564 // 7.5.4. If |element| is |pending| and |pending| is an iframe element, | |
| 565 // set |element|'s iframe fullscreen flag. | |
| 566 // TODO(foolip): Support the iframe fullscreen flag. | |
| 567 // https://crbug.com/644695 | |
| 568 | |
| 569 // 7.5.5. Fullscreen |element| within |doc|. | |
| 570 // TODO(foolip): Merge fullscreen element stack into top layer. | |
| 571 // https://crbug.com/627790 | |
| 572 from(doc).pushFullscreenElementStack(*element, requestType); | |
| 573 } | |
| 574 | |
| 575 // 7.6. For each |doc| in |eventDocs|, in order, fire an event named | |
| 576 // fullscreenchange on |doc|. | |
| 577 dispatchEvents(events); | |
| 578 | |
| 579 // 7.7. Fulfill |promise| with undefined. | |
| 580 // TODO(foolip): Promises. https://crbug.com/644637 | |
| 581 } | 493 } |
| 582 | 494 |
| 583 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen | 495 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen |
| 584 void Fullscreen::fullyExitFullscreen(Document& document) { | 496 void Fullscreen::fullyExitFullscreen(Document& document) { |
| 585 // 1. If |document|'s fullscreen element is null, terminate these steps. | 497 // To fully exit fullscreen, run these steps: |
| 586 | 498 |
| 587 // 2. Unfullscreen elements whose fullscreen flag is set, within | 499 // 1. Let |doc| be the top-level browsing context's document. |
| 588 // |document|'s top layer, except for |document|'s fullscreen element. | 500 // |
| 589 | 501 // Since the top-level browsing context's document might be unavailable in |
| 590 // 3. Exit fullscreen |document|. | 502 // OOPIF scenarios (i.e., when the top frame is remote), this actually uses |
| 591 | 503 // the Document of the topmost local ancestor frame. Without OOPIF, this |
| 592 // TODO(foolip): Change the spec. To remove elements from |document|'s top | 504 // will be the top frame's document. With OOPIF, each renderer process for |
| 593 // layer as in step 2 could leave descendant frames in fullscreen. It may work | 505 // the current page will separately call fullyExitFullscreen to cover all |
| 594 // to give the "exit fullscreen" algorithm a |fully| flag that's used in the | 506 // local frames in each process. |
| 595 // animation frame task after exit. Here, retain the old behavior of fully | 507 Document& doc = topmostLocalAncestor(document); |
| 596 // exiting fullscreen for the topmost local ancestor: | 508 |
| 597 exitFullscreen(topmostLocalAncestor(document), ExitType::Fully); | 509 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps. |
| 510 if (!fullscreenElementFrom(doc)) |
| 511 return; |
| 512 |
| 513 // 3. Remove elements from |doc|'s fullscreen element stack until only the top |
| 514 // element is left. |
| 515 size_t stackSize = from(doc).m_fullscreenElementStack.size(); |
| 516 from(doc).m_fullscreenElementStack.remove(0, stackSize - 1); |
| 517 DCHECK_EQ(from(doc).m_fullscreenElementStack.size(), 1u); |
| 518 |
| 519 // 4. Act as if the exitFullscreen() method was invoked on |doc|. |
| 520 exitFullscreen(doc); |
| 598 } | 521 } |
| 599 | 522 |
| 600 // https://fullscreen.spec.whatwg.org/#exit-fullscreen | 523 // https://fullscreen.spec.whatwg.org/#exit-fullscreen |
| 601 void Fullscreen::exitFullscreen(Document& doc, ExitType exitType) { | 524 void Fullscreen::exitFullscreen(Document& document) { |
| 602 if (!doc.isActive() || !doc.frame()) | 525 // The exitFullscreen() method must run these steps: |
| 603 return; | 526 |
| 604 | 527 // Ignore this call if the document is not in a live frame. |
| 605 // 1. Let |promise| be a new promise. | 528 if (!document.isActive() || !document.frame()) |
| 606 // 2. If |doc|'s fullscreen element is null, reject |promise| with a | 529 return; |
| 607 // TypeError exception, and return |promise|. | 530 |
| 608 // TODO(foolip): Promises. https://crbug.com/644637 | 531 // 1. Let doc be the context object. (i.e. "this") |
| 609 if (!fullscreenElementFrom(doc)) | 532 // 2. If doc's fullscreen element stack is empty, terminate these steps. |
| 610 return; | 533 if (!fullscreenElementFrom(document)) |
| 611 | 534 return; |
| 612 // 3. Let |resize| be false. | 535 |
| 613 bool resize = false; | 536 // 3. Let descendants be all the doc's descendant browsing context's documents |
| 614 | 537 // with a non-empty fullscreen element stack (if any), ordered so that the |
| 615 // 4. Let |docs| be the result of collecting documents to unfullscreen given | 538 // child of the doc is last and the document furthest away from the doc is |
| 616 // |doc|. | 539 // first. |
| 617 HeapVector<Member<Document>> docs = | 540 HeapDeque<Member<Document>> descendants; |
| 618 collectDocumentsToUnfullscreen(doc, exitType); | 541 for (Frame* descendant = document.frame()->tree().traverseNext(); descendant; |
| 619 | 542 descendant = descendant->tree().traverseNext()) { |
| 620 // 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document. | |
| 621 // | |
| 622 // OOPIF: Let |topLevelDoc| be the topmost local ancestor instead. If the main | |
| 623 // frame is in another process, we will still fully exit fullscreen even | |
| 624 // though that's wrong if the main frame was in nested fullscreen. | |
| 625 // TODO(alexmos): Deal with nested fullscreen cases, see | |
| 626 // https://crbug.com/617369. | |
| 627 Document& topLevelDoc = topmostLocalAncestor(doc); | |
| 628 | |
| 629 // 6. If |topLevelDoc| is in |docs|, set |resize| to true. | |
| 630 if (!docs.isEmpty() && docs.back() == &topLevelDoc) | |
| 631 resize = true; | |
| 632 | |
| 633 // 7. Return |promise|, and run the remaining steps in parallel. | |
| 634 // TODO(foolip): Promises. https://crbug.com/644637 | |
| 635 | |
| 636 // Note: |ExitType::Fully| is only used together with the topmost local | |
| 637 // ancestor in |fullyExitFullscreen()|, and so implies that |resize| is true. | |
| 638 // This would change if matching the spec for "fully exit fullscreen". | |
| 639 if (exitType == ExitType::Fully) | |
| 640 DCHECK(resize); | |
| 641 | |
| 642 // 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal" | |
| 643 // dimensions. | |
| 644 if (resize) { | |
| 645 LocalFrame& frame = *doc.frame(); | |
| 646 frame.chromeClient().exitFullscreen(frame); | |
| 647 } else { | |
| 648 enqueueTaskForExit(doc, exitType); | |
| 649 } | |
| 650 } | |
| 651 | |
| 652 void Fullscreen::didExitFullscreen() { | |
| 653 if (!document()) | |
| 654 return; | |
| 655 | |
| 656 DCHECK_EQ(document(), &topmostLocalAncestor(*document())); | |
| 657 | |
| 658 enqueueTaskForExit(*document(), ExitType::Fully); | |
| 659 } | |
| 660 | |
| 661 void Fullscreen::enqueueTaskForExit(Document& document, ExitType exitType) { | |
| 662 // 9. As part of the next animation frame task, run these substeps: | |
| 663 document.enqueueAnimationFrameTask( | |
| 664 WTF::bind(&runTaskForExit, wrapPersistent(&document), exitType)); | |
| 665 } | |
| 666 | |
| 667 void Fullscreen::runTaskForExit(Document* document, ExitType exitType) { | |
| 668 DCHECK(document); | |
| 669 DCHECK(document->isActive()); | |
| 670 DCHECK(document->frame()); | |
| 671 | |
| 672 Document& doc = *document; | |
| 673 | |
| 674 if (!fullscreenElementFrom(doc)) | |
| 675 return; | |
| 676 | |
| 677 // 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen | |
| 678 // given |doc|. | |
| 679 | |
| 680 // 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully | |
| 681 // exit fullscreen |topLevelDoc|, reject promise with a TypeError exception, | |
| 682 // and terminate these steps. | |
| 683 | |
| 684 // TODO(foolip): See TODO in |fullyExitFullscreen()|. Instead of using "fully | |
| 685 // exit fullscreen" in step 9.2 (which is async), give "exit fullscreen" a | |
| 686 // |fully| flag which is always true if |resize| was true. | |
| 687 | |
| 688 HeapVector<Member<Document>> exitDocs = | |
| 689 collectDocumentsToUnfullscreen(doc, exitType); | |
| 690 | |
| 691 // 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|. | |
| 692 if (exitDocs.isEmpty()) | |
| 693 exitDocs.push_back(&doc); | |
| 694 | |
| 695 // 9.4. If |exitDocs|'s last document has a browsing context container, | |
| 696 // append that browsing context container's node document to |exitDocs|. | |
| 697 // | |
| 698 // OOPIF: Skip over remote frames, assuming that they have exactly one element | |
| 699 // in their fullscreen element stacks, thereby erring on the side of exiting | |
| 700 // fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see | |
| 701 // https://crbug.com/617369. | |
| 702 if (Document* document = nextLocalAncestor(*exitDocs.back())) | |
| 703 exitDocs.push_back(document); | |
| 704 | |
| 705 // 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s | |
| 706 // descendant browsing contexts' documents whose fullscreen element is | |
| 707 // non-null, if any, in *reverse* tree order. | |
| 708 HeapDeque<Member<Document>> descendantDocs; | |
| 709 for (Frame* descendant = doc.frame()->tree().firstChild(); descendant; | |
| 710 descendant = descendant->tree().traverseNext(doc.frame())) { | |
| 711 if (!descendant->isLocalFrame()) | 543 if (!descendant->isLocalFrame()) |
| 712 continue; | 544 continue; |
| 713 DCHECK(toLocalFrame(descendant)->document()); | 545 DCHECK(toLocalFrame(descendant)->document()); |
| 714 if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) | 546 if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) |
| 715 descendantDocs.prepend(toLocalFrame(descendant)->document()); | 547 descendants.prepend(toLocalFrame(descendant)->document()); |
| 716 } | 548 } |
| 717 | 549 |
| 718 // Note: For prefixed requests, the event target is an element, so let | 550 // 4. For each descendant in descendants, empty descendant's fullscreen |
| 719 // |events| be a list of events to dispatch. | 551 // element stack, and queue a task to fire an event named fullscreenchange |
| 720 HeapVector<Member<Event>> events; | 552 // with its bubbles attribute set to true on descendant. |
| 721 | 553 for (auto& descendant : descendants) { |
| 722 // 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen | 554 DCHECK(descendant); |
| 723 // |descendantDoc|. | 555 RequestType requestType = |
| 724 for (Document* descendantDoc : descendantDocs) { | 556 from(*descendant).m_fullscreenElementStack.back().second; |
| 725 Fullscreen& fullscreen = from(*descendantDoc); | 557 from(*descendant).clearFullscreenElementStack(); |
| 726 ElementStack& stack = fullscreen.m_fullscreenElementStack; | 558 from(document).enqueueChangeEvent(*descendant, requestType); |
| 727 DCHECK(!stack.isEmpty()); | 559 } |
| 728 events.push_back(createChangeEvent(*descendantDoc, *stack.back().first, | 560 |
| 729 stack.back().second)); | 561 // 5. While doc is not null, run these substeps: |
| 730 while (!stack.isEmpty()) | 562 Element* newTop = nullptr; |
| 731 fullscreen.popFullscreenElementStack(); | 563 for (Document* currentDoc = &document; currentDoc;) { |
| 732 } | 564 RequestType requestType = |
| 733 | 565 from(*currentDoc).m_fullscreenElementStack.back().second; |
| 734 // 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s | 566 |
| 735 // fullscreen element. | 567 // 1. Pop the top element of doc's fullscreen element stack. |
| 736 for (Document* exitDoc : exitDocs) { | 568 from(*currentDoc).popFullscreenElementStack(); |
| 737 Fullscreen& fullscreen = from(*exitDoc); | 569 |
| 738 ElementStack& stack = fullscreen.m_fullscreenElementStack; | 570 // If doc's fullscreen element stack is non-empty and the element now at |
| 739 DCHECK(!stack.isEmpty()); | 571 // the top is either not in a document or its node document is not doc, |
| 740 events.push_back( | 572 // repeat this substep. |
| 741 createChangeEvent(*exitDoc, *stack.back().first, stack.back().second)); | 573 newTop = fullscreenElementFrom(*currentDoc); |
| 742 fullscreen.popFullscreenElementStack(); | 574 if (newTop && (!newTop->isConnected() || newTop->document() != currentDoc)) |
| 743 | 575 continue; |
| 744 // TODO(foolip): See TODO in |fullyExitFullscreen()|. | 576 |
| 745 if (exitDoc == &doc && exitType == ExitType::Fully) { | 577 // 2. Queue a task to fire an event named fullscreenchange with its bubbles |
| 746 while (!stack.isEmpty()) | 578 // attribute set to true on doc. |
| 747 fullscreen.popFullscreenElementStack(); | 579 from(document).enqueueChangeEvent(*currentDoc, requestType); |
| 580 |
| 581 // 3. If doc's fullscreen element stack is empty and doc's browsing context |
| 582 // has a browsing context container, set doc to that browsing context |
| 583 // container's node document. |
| 584 // |
| 585 // OOPIF: If browsing context container's document is in another |
| 586 // process, keep moving up the ancestor chain and looking for a |
| 587 // browsing context container with a local document. |
| 588 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 589 // https://crbug.com/617369. |
| 590 if (!newTop) { |
| 591 currentDoc = nextLocalAncestor(*currentDoc); |
| 592 continue; |
| 748 } | 593 } |
| 749 } | 594 |
| 750 | 595 // 4. Otherwise, set doc to null. |
| 751 // 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an | 596 currentDoc = nullptr; |
| 752 // event named fullscreenchange on |descendantDoc|. | 597 } |
| 753 // 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named | 598 |
| 754 // fullscreenchange on |exitDoc|. | 599 // 6. Return, and run the remaining steps asynchronously. |
| 755 dispatchEvents(events); | 600 // 7. Optionally, perform some animation. |
| 756 | 601 |
| 757 // 9.10. Fulfill |promise| with undefined. | 602 // Only exit fullscreen mode if the fullscreen element stack is empty. |
| 758 // TODO(foolip): Promises. https://crbug.com/644637 | 603 if (!newTop) { |
| 604 document.frame()->chromeClient().exitFullscreen(*document.frame()); |
| 605 return; |
| 606 } |
| 607 |
| 608 // Otherwise, enter fullscreen for the fullscreen element stack's top element. |
| 609 from(document).m_pendingFullscreenElement = newTop; |
| 610 from(document).didEnterFullscreen(); |
| 759 } | 611 } |
| 760 | 612 |
| 761 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled | 613 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled |
| 762 bool Fullscreen::fullscreenEnabled(Document& document) { | 614 bool Fullscreen::fullscreenEnabled(Document& document) { |
| 763 // The fullscreenEnabled attribute's getter must return true if the context | 615 // The fullscreenEnabled attribute's getter must return true if the context |
| 764 // object is allowed to use the feature indicated by attribute name | 616 // object is allowed to use the feature indicated by attribute name |
| 765 // allowfullscreen and fullscreen is supported, and false otherwise. | 617 // allowfullscreen and fullscreen is supported, and false otherwise. |
| 766 return allowedToUseFullscreen(document.frame()) && | 618 return allowedToUseFullscreen(document.frame()) && |
| 767 fullscreenIsSupported(document); | 619 fullscreenIsSupported(document); |
| 768 } | 620 } |
| 769 | 621 |
| 622 void Fullscreen::didEnterFullscreen() { |
| 623 if (!document()->isActive() || !document()->frame()) |
| 624 return; |
| 625 |
| 626 // Start the timer for events enqueued by |requestFullscreen()|. The hover |
| 627 // state update is scheduled first so that it's done when the events fire. |
| 628 document()->frame()->eventHandler().scheduleHoverStateUpdate(); |
| 629 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); |
| 630 |
| 631 Element* element = m_pendingFullscreenElement.release(); |
| 632 if (!element) |
| 633 return; |
| 634 |
| 635 if (m_currentFullScreenElement == element) |
| 636 return; |
| 637 |
| 638 if (!element->isConnected() || &element->document() != document()) { |
| 639 // The element was removed or has moved to another document since the |
| 640 // |requestFullscreen()| call. Exit fullscreen again to recover. |
| 641 // TODO(foolip): Fire a fullscreenerror event. This is currently difficult |
| 642 // because the fullscreenchange event has already been enqueued and possibly |
| 643 // even fired. https://crbug.com/402376 |
| 644 LocalFrame& frame = *document()->frame(); |
| 645 frame.chromeClient().exitFullscreen(frame); |
| 646 return; |
| 647 } |
| 648 |
| 649 if (m_fullScreenLayoutObject) |
| 650 m_fullScreenLayoutObject->unwrapLayoutObject(); |
| 651 |
| 652 Element* previousElement = m_currentFullScreenElement; |
| 653 m_currentFullScreenElement = element; |
| 654 |
| 655 // Create a placeholder block for a the full-screen element, to keep the page |
| 656 // from reflowing when the element is removed from the normal flow. Only do |
| 657 // this for a LayoutBox, as only a box will have a frameRect. The placeholder |
| 658 // will be created in setFullScreenLayoutObject() during layout. |
| 659 LayoutObject* layoutObject = m_currentFullScreenElement->layoutObject(); |
| 660 bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox(); |
| 661 if (shouldCreatePlaceholder) { |
| 662 m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect(); |
| 663 m_savedPlaceholderComputedStyle = |
| 664 ComputedStyle::clone(layoutObject->styleRef()); |
| 665 } |
| 666 |
| 667 // TODO(alexmos): When |m_forCrossProcessDescendant| is true, some of |
| 668 // this layout work has already been done in another process, so it should |
| 669 // not be necessary to repeat it here. |
| 670 if (m_currentFullScreenElement != document()->documentElement()) { |
| 671 LayoutFullScreen::wrapLayoutObject( |
| 672 layoutObject, layoutObject ? layoutObject->parent() : 0, document()); |
| 673 } |
| 674 |
| 675 // When |m_forCrossProcessDescendant| is true, m_currentFullScreenElement |
| 676 // corresponds to the HTMLFrameOwnerElement for the out-of-process iframe |
| 677 // that contains the actual fullscreen element. Hence, it must also set |
| 678 // the ContainsFullScreenElement flag (so that it gains the |
| 679 // -webkit-full-screen-ancestor style). |
| 680 if (m_forCrossProcessDescendant) { |
| 681 DCHECK(m_currentFullScreenElement->isFrameOwnerElement()); |
| 682 DCHECK(toHTMLFrameOwnerElement(m_currentFullScreenElement) |
| 683 ->contentFrame() |
| 684 ->isRemoteFrame()); |
| 685 m_currentFullScreenElement->setContainsFullScreenElement(true); |
| 686 } |
| 687 |
| 688 m_currentFullScreenElement |
| 689 ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); |
| 690 |
| 691 document()->styleEngine().ensureUAStyleForFullscreen(); |
| 692 m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); |
| 693 |
| 694 // FIXME: This should not call updateStyleAndLayoutTree. |
| 695 document()->updateStyleAndLayoutTree(); |
| 696 |
| 697 document()->frame()->chromeClient().fullscreenElementChanged(previousElement, |
| 698 element); |
| 699 } |
| 700 |
| 701 void Fullscreen::didExitFullscreen() { |
| 702 if (!document()->isActive() || !document()->frame()) |
| 703 return; |
| 704 |
| 705 // Start the timer for events enqueued by |exitFullscreen()|. The hover state |
| 706 // update is scheduled first so that it's done when the events fire. |
| 707 document()->frame()->eventHandler().scheduleHoverStateUpdate(); |
| 708 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); |
| 709 |
| 710 // If fullscreen was canceled by the browser, e.g. if the user pressed Esc, |
| 711 // then |exitFullscreen()| was never called. Let |fullyExitFullscreen()| clear |
| 712 // the fullscreen element stack and fire any events as necessary. |
| 713 // TODO(foolip): Remove this when state changes and events are synchronized |
| 714 // with animation frames. https://crbug.com/402376 |
| 715 fullyExitFullscreen(*document()); |
| 716 |
| 717 if (!m_currentFullScreenElement) |
| 718 return; |
| 719 |
| 720 if (m_forCrossProcessDescendant) |
| 721 m_currentFullScreenElement->setContainsFullScreenElement(false); |
| 722 |
| 723 m_currentFullScreenElement |
| 724 ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); |
| 725 |
| 726 if (m_fullScreenLayoutObject) |
| 727 LayoutFullScreenItem(m_fullScreenLayoutObject).unwrapLayoutObject(); |
| 728 |
| 729 document()->styleEngine().ensureUAStyleForFullscreen(); |
| 730 m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); |
| 731 Element* previousElement = m_currentFullScreenElement; |
| 732 m_currentFullScreenElement = nullptr; |
| 733 |
| 734 m_forCrossProcessDescendant = false; |
| 735 |
| 736 document()->frame()->chromeClient().fullscreenElementChanged(previousElement, |
| 737 nullptr); |
| 738 } |
| 739 |
| 770 void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) { | 740 void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) { |
| 771 if (layoutObject == m_fullScreenLayoutObject) | 741 if (layoutObject == m_fullScreenLayoutObject) |
| 772 return; | 742 return; |
| 773 | 743 |
| 774 if (layoutObject && m_savedPlaceholderComputedStyle) { | 744 if (layoutObject && m_savedPlaceholderComputedStyle) { |
| 775 layoutObject->createPlaceholder(std::move(m_savedPlaceholderComputedStyle), | 745 layoutObject->createPlaceholder(std::move(m_savedPlaceholderComputedStyle), |
| 776 m_savedPlaceholderFrameRect); | 746 m_savedPlaceholderFrameRect); |
| 777 } else if (layoutObject && m_fullScreenLayoutObject && | 747 } else if (layoutObject && m_fullScreenLayoutObject && |
| 778 m_fullScreenLayoutObject->placeholder()) { | 748 m_fullScreenLayoutObject->placeholder()) { |
| 779 LayoutBlockFlow* placeholder = m_fullScreenLayoutObject->placeholder(); | 749 LayoutBlockFlow* placeholder = m_fullScreenLayoutObject->placeholder(); |
| 780 layoutObject->createPlaceholder( | 750 layoutObject->createPlaceholder( |
| 781 ComputedStyle::clone(placeholder->styleRef()), | 751 ComputedStyle::clone(placeholder->styleRef()), |
| 782 placeholder->frameRect()); | 752 placeholder->frameRect()); |
| 783 } | 753 } |
| 784 | 754 |
| 785 if (m_fullScreenLayoutObject) | 755 if (m_fullScreenLayoutObject) |
| 786 m_fullScreenLayoutObject->unwrapLayoutObject(); | 756 m_fullScreenLayoutObject->unwrapLayoutObject(); |
| 787 DCHECK(!m_fullScreenLayoutObject); | 757 DCHECK(!m_fullScreenLayoutObject); |
| 788 | 758 |
| 789 m_fullScreenLayoutObject = layoutObject; | 759 m_fullScreenLayoutObject = layoutObject; |
| 790 } | 760 } |
| 791 | 761 |
| 792 void Fullscreen::fullScreenLayoutObjectDestroyed() { | 762 void Fullscreen::fullScreenLayoutObjectDestroyed() { |
| 793 m_fullScreenLayoutObject = nullptr; | 763 m_fullScreenLayoutObject = nullptr; |
| 794 } | 764 } |
| 795 | 765 |
| 766 void Fullscreen::enqueueChangeEvent(Document& document, |
| 767 RequestType requestType) { |
| 768 Event* event; |
| 769 if (requestType == RequestType::Unprefixed) { |
| 770 event = createEvent(EventTypeNames::fullscreenchange, document); |
| 771 } else { |
| 772 DCHECK(document.hasFullscreenSupplement()); |
| 773 Fullscreen& fullscreen = from(document); |
| 774 EventTarget* target = fullscreen.fullscreenElement(); |
| 775 if (!target) |
| 776 target = fullscreen.currentFullScreenElement(); |
| 777 if (!target) |
| 778 target = &document; |
| 779 event = createEvent(EventTypeNames::webkitfullscreenchange, *target); |
| 780 } |
| 781 m_eventQueue.append(event); |
| 782 // NOTE: The timer is started in didEnterFullscreen/didExitFullscreen. |
| 783 } |
| 784 |
| 785 void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType) { |
| 786 Event* event; |
| 787 if (requestType == RequestType::Unprefixed) |
| 788 event = createEvent(EventTypeNames::fullscreenerror, element.document()); |
| 789 else |
| 790 event = createEvent(EventTypeNames::webkitfullscreenerror, element); |
| 791 m_eventQueue.append(event); |
| 792 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); |
| 793 } |
| 794 |
| 795 void Fullscreen::eventQueueTimerFired(TimerBase*) { |
| 796 HeapDeque<Member<Event>> eventQueue; |
| 797 m_eventQueue.swap(eventQueue); |
| 798 |
| 799 while (!eventQueue.isEmpty()) { |
| 800 Event* event = eventQueue.takeFirst(); |
| 801 Node* target = event->target()->toNode(); |
| 802 |
| 803 // If the element was removed from our tree, also message the |
| 804 // documentElement. |
| 805 if (!target->isConnected() && document()->documentElement()) { |
| 806 DCHECK(isPrefixed(event->type())); |
| 807 eventQueue.append( |
| 808 createEvent(event->type(), *document()->documentElement())); |
| 809 } |
| 810 |
| 811 target->dispatchEvent(event); |
| 812 } |
| 813 } |
| 814 |
| 796 void Fullscreen::elementRemoved(Element& oldNode) { | 815 void Fullscreen::elementRemoved(Element& oldNode) { |
| 797 DCHECK_EQ(document(), &oldNode.document()); | |
| 798 | |
| 799 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its | 816 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its |
| 800 // node document's fullscreen element stack, run these steps: | 817 // node document's fullscreen element stack, run these steps: |
| 801 | 818 |
| 802 // 1. If |oldNode| is at the top of its node document's fullscreen element | 819 // 1. If |oldNode| is at the top of its node document's fullscreen element |
| 803 // stack, act as if the exitFullscreen() method was invoked on that document. | 820 // stack, act as if the exitFullscreen() method was invoked on that document. |
| 804 if (fullscreenElement() == &oldNode) { | 821 if (fullscreenElement() == &oldNode) { |
| 805 exitFullscreen(oldNode.document()); | 822 exitFullscreen(oldNode.document()); |
| 806 return; | 823 return; |
| 807 } | 824 } |
| 808 | 825 |
| 809 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element | 826 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element |
| 810 // stack. | 827 // stack. |
| 811 for (size_t i = 0; i < m_fullscreenElementStack.size(); ++i) { | 828 for (size_t i = 0; i < m_fullscreenElementStack.size(); ++i) { |
| 812 if (m_fullscreenElementStack[i].first.get() == &oldNode) { | 829 if (m_fullscreenElementStack[i].first.get() == &oldNode) { |
| 813 m_fullscreenElementStack.remove(i); | 830 m_fullscreenElementStack.remove(i); |
| 814 return; | 831 return; |
| 815 } | 832 } |
| 816 } | 833 } |
| 817 | 834 |
| 818 // NOTE: |oldNode| was not in the fullscreen element stack. | 835 // NOTE: |oldNode| was not in the fullscreen element stack. |
| 819 } | 836 } |
| 820 | 837 |
| 838 void Fullscreen::clearFullscreenElementStack() { |
| 839 if (m_fullscreenElementStack.isEmpty()) |
| 840 return; |
| 841 |
| 842 m_fullscreenElementStack.clear(); |
| 843 |
| 844 setNeedsPaintPropertyUpdate(document()); |
| 845 } |
| 846 |
| 821 void Fullscreen::popFullscreenElementStack() { | 847 void Fullscreen::popFullscreenElementStack() { |
| 822 DCHECK(!m_fullscreenElementStack.isEmpty()); | 848 if (m_fullscreenElementStack.isEmpty()) |
| 849 return; |
| 823 | 850 |
| 824 Element* previousElement = fullscreenElement(); | |
| 825 m_fullscreenElementStack.pop_back(); | 851 m_fullscreenElementStack.pop_back(); |
| 826 | 852 |
| 827 // Note: |requestType| is only used if |fullscreenElement()| is non-null. | 853 setNeedsPaintPropertyUpdate(document()); |
| 828 RequestType requestType = m_fullscreenElementStack.isEmpty() | |
| 829 ? RequestType::Unprefixed | |
| 830 : m_fullscreenElementStack.back().second; | |
| 831 fullscreenElementChanged(previousElement, fullscreenElement(), requestType); | |
| 832 } | 854 } |
| 833 | 855 |
| 834 void Fullscreen::pushFullscreenElementStack(Element& element, | 856 void Fullscreen::pushFullscreenElementStack(Element& element, |
| 835 RequestType requestType) { | 857 RequestType requestType) { |
| 836 Element* previousElement = fullscreenElement(); | |
| 837 m_fullscreenElementStack.push_back(std::make_pair(&element, requestType)); | 858 m_fullscreenElementStack.push_back(std::make_pair(&element, requestType)); |
| 838 | 859 |
| 839 fullscreenElementChanged(previousElement, &element, requestType); | 860 setNeedsPaintPropertyUpdate(document()); |
| 840 } | |
| 841 | |
| 842 void Fullscreen::fullscreenElementChanged(Element* fromElement, | |
| 843 Element* toElement, | |
| 844 RequestType toRequestType) { | |
| 845 DCHECK_NE(fromElement, toElement); | |
| 846 | |
| 847 if (!document()) | |
| 848 return; | |
| 849 | |
| 850 document()->styleEngine().ensureUAStyleForFullscreen(); | |
| 851 | |
| 852 if (m_fullScreenLayoutObject) | |
| 853 m_fullScreenLayoutObject->unwrapLayoutObject(); | |
| 854 DCHECK(!m_fullScreenLayoutObject); | |
| 855 | |
| 856 if (fromElement) { | |
| 857 DCHECK_NE(fromElement, fullscreenElement()); | |
| 858 | |
| 859 fromElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); | |
| 860 | |
| 861 fromElement->setContainsFullScreenElement(false); | |
| 862 fromElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries( | |
| 863 false); | |
| 864 } | |
| 865 | |
| 866 if (toElement) { | |
| 867 DCHECK_EQ(toElement, fullscreenElement()); | |
| 868 | |
| 869 toElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); | |
| 870 | |
| 871 // OOPIF: For RequestType::PrefixedForCrossProcessDescendant, |toElement| is | |
| 872 // the iframe element for the out-of-process frame that contains the | |
| 873 // fullscreen element. Hence, it must match :-webkit-full-screen-ancestor. | |
| 874 if (toRequestType == RequestType::PrefixedForCrossProcessDescendant) { | |
| 875 DCHECK(isHTMLIFrameElement(toElement)); | |
| 876 toElement->setContainsFullScreenElement(true); | |
| 877 } | |
| 878 toElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries( | |
| 879 true); | |
| 880 | |
| 881 // Create a placeholder block for the fullscreen element, to keep the page | |
| 882 // from reflowing when the element is removed from the normal flow. Only do | |
| 883 // this for a LayoutBox, as only a box will have a frameRect. The | |
| 884 // placeholder will be created in setFullScreenLayoutObject() during layout. | |
| 885 LayoutObject* layoutObject = toElement->layoutObject(); | |
| 886 bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox(); | |
| 887 if (shouldCreatePlaceholder) { | |
| 888 m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect(); | |
| 889 m_savedPlaceholderComputedStyle = | |
| 890 ComputedStyle::clone(layoutObject->styleRef()); | |
| 891 } | |
| 892 | |
| 893 if (toElement != document()->documentElement()) { | |
| 894 LayoutFullScreen::wrapLayoutObject( | |
| 895 layoutObject, layoutObject ? layoutObject->parent() : 0, document()); | |
| 896 } | |
| 897 } | |
| 898 | |
| 899 if (LocalFrame* frame = document()->frame()) { | |
| 900 // TODO(foolip): Synchronize hover state changes with animation frames. | |
| 901 // https://crbug.com/668758 | |
| 902 frame->eventHandler().scheduleHoverStateUpdate(); | |
| 903 frame->chromeClient().fullscreenElementChanged(fromElement, toElement); | |
| 904 | |
| 905 if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() && | |
| 906 !RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { | |
| 907 // Fullscreen status affects scroll paint properties through | |
| 908 // FrameView::userInputScrollable(). | |
| 909 if (FrameView* frameView = frame->view()) | |
| 910 frameView->setNeedsPaintPropertyUpdate(); | |
| 911 } | |
| 912 } | |
| 913 | |
| 914 // TODO(foolip): This should not call updateStyleAndLayoutTree. | |
| 915 document()->updateStyleAndLayoutTree(); | |
| 916 } | 861 } |
| 917 | 862 |
| 918 DEFINE_TRACE(Fullscreen) { | 863 DEFINE_TRACE(Fullscreen) { |
| 919 visitor->trace(m_pendingRequests); | 864 visitor->trace(m_pendingFullscreenElement); |
| 920 visitor->trace(m_fullscreenElementStack); | 865 visitor->trace(m_fullscreenElementStack); |
| 866 visitor->trace(m_currentFullScreenElement); |
| 867 visitor->trace(m_eventQueue); |
| 921 Supplement<Document>::trace(visitor); | 868 Supplement<Document>::trace(visitor); |
| 922 ContextLifecycleObserver::trace(visitor); | 869 ContextLifecycleObserver::trace(visitor); |
| 923 } | 870 } |
| 924 | 871 |
| 925 } // namespace blink | 872 } // namespace blink |
| OLD | NEW |