1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 5 * (C) 2006 Alexey Proskuryakov (ap (at) webkit.org) 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved. 7 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 8 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) 9 * Copyright (C) 2013 Google Inc. All rights reserved. 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Library General Public 13 * License as published by the Free Software Foundation; either 14 * version 2 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Library General Public License for more details. 20 * 21 * You should have received a copy of the GNU Library General Public License 22 * along with this library; see the file COPYING.LIB. If not, write to 23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 24 * Boston, MA 02110-1301, USA. 25 * 26 */ 27 28 #include "config.h" 29 #include "core/dom/Fullscreen.h" 30 31 #include "core/HTMLNames.h" 32 #include "core/dom/Document.h" 33 #include "core/events/Event.h" 34 #include "core/frame/FrameHost.h" 35 #include "core/frame/LocalFrame.h" 36 #include "core/frame/Settings.h" 37 #include "core/frame/UseCounter.h" 38 #include "core/html/HTMLIFrameElement.h" 39 #include "core/html/HTMLMediaElement.h" 40 #include "core/page/Chrome.h" 41 #include "core/page/ChromeClient.h" 42 #include "core/page/EventHandler.h" 43 #include "core/rendering/RenderFullScreen.h" 44 #include "platform/UserGestureIndicator.h" 45 46 namespace blink { 47 48 using namespace HTMLNames; 49 50 static bool fullscreenIsAllowedForAllOwners(const Document& document) 51 { 52 for (const Element* owner = document.ownerElement(); owner; owner = owner->document().ownerElement()) { 53 if (!isHTMLIFrameElement(owner)) 54 return false; 55 if (!owner->hasAttribute(allowfullscreenAttr)) 56 return false; 57 } 58 return true; 59 } 60 61 static bool fullscreenIsSupported(const Document& document) 62 { 63 // Fullscreen is supported if there is no previously-established user preference, 64 // security risk, or platform limitation. 65 return !document.settings() || document.settings()->fullscreenSupported(); 66 } 67 68 static bool fullscreenIsSupported(const Document& document, const Element& element) 69 { 70 if (!document.settings() || (document.settings()->disallowFullscreenForNonMediaElements() && !isHTMLMediaElement(element))) 71 return false; 72 return fullscreenIsSupported(document); 73 } 74 75 static bool fullscreenElementReady(const Element& element, Fullscreen::RequestType requestType) 76 { 77 // A fullscreen element ready check for an element |element| returns true if all of the 78 // following are true, and false otherwise: 79 80 // |element| is in a document. 81 if (!element.inDocument()) 82 return false; 83 84 // |element|'s node document's fullscreen enabled flag is set. 85 if (!fullscreenIsAllowedForAllOwners(element.document())) { 86 if (requestType == Fullscreen::PrefixedVideoRequest) 87 UseCounter::count(element.document(), UseCounter::VideoFullscreenAllowedExemption); 88 else 89 return false; 90 } 91 92 // |element|'s node document's fullscreen element stack is either empty or its top element is an 93 // inclusive ancestor of |element|. 94 if (const Element* topElement = Fullscreen::fullscreenElementFrom(element.document())) { 95 if (!topElement->contains(&element)) 96 return false; 97 } 98 99 // |element| has no ancestor element whose local name is iframe and namespace is the HTML 100 // namespace. 101 if (Traversal<HTMLIFrameElement>::firstAncestor(element)) 102 return false; 103 104 // |element|'s node document's browsing context either has a browsing context container and the 105 // fullscreen element ready check returns true for |element|'s node document's browsing 106 // context's browsing context container, or it has no browsing context container. 107 if (const Element* owner = element.document().ownerElement()) { 108 if (!fullscreenElementReady(*owner, requestType)) 109 return false; 110 } 111 112 return true; 113 } 114 115 static bool isPrefixed(const AtomicString& type) 116 { 117 return type == EventTypeNames::webkitfullscreenchange || type == EventTypeNames::webkitfullscreenerror; 118 } 119 120 static PassRefPtrWillBeRawPtr<Event> createEvent(const AtomicString& type, EventTarget& target) 121 { 122 EventInit initializer; 123 initializer.bubbles = isPrefixed(type); 124 RefPtrWillBeRawPtr<Event> event = Event::create(type, initializer); 125 event->setTarget(&target); 126 return event; 127 } 128 129 const char* Fullscreen::supplementName() 130 { 131 return "Fullscreen"; 132 } 133 134 Fullscreen& Fullscreen::from(Document& document) 135 { 136 Fullscreen* fullscreen = fromIfExists(document); 137 if (!fullscreen) { 138 fullscreen = new Fullscreen(document); 139 DocumentSupplement::provideTo(document, supplementName(), adoptPtrWillBeNoop(fullscreen)); 140 } 141 142 return *fullscreen; 143 } 144 145 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) 146 { 147 return static_cast<Fullscreen*>(DocumentSupplement::from(document, supplementName())); 148 } 149 150 Element* Fullscreen::fullscreenElementFrom(Document& document) 151 { 152 if (Fullscreen* found = fromIfExists(document)) 153 return found->fullscreenElement(); 154 return 0; 155 } 156 157 Element* Fullscreen::currentFullScreenElementFrom(Document& document) 158 { 159 if (Fullscreen* found = fromIfExists(document)) 160 return found->webkitCurrentFullScreenElement(); 161 return 0; 162 } 163 164 bool Fullscreen::isFullScreen(Document& document) 165 { 166 return currentFullScreenElementFrom(document); 167 } 168 169 Fullscreen::Fullscreen(Document& document) 170 : DocumentLifecycleObserver(&document) 171 , m_areKeysEnabledInFullScreen(false) 172 , m_fullScreenRenderer(nullptr) 173 , m_eventQueueTimer(this, &Fullscreen::eventQueueTimerFired) 174 { 175 document.setHasFullscreenSupplement(); 176 } 177 178 Fullscreen::~Fullscreen() 179 { 180 } 181 182 inline Document* Fullscreen::document() 183 { 184 return lifecycleContext(); 185 } 186 187 void Fullscreen::documentWasDetached() 188 { 189 m_eventQueue.clear(); 190 191 if (m_fullScreenRenderer) 192 m_fullScreenRenderer->destroy(); 193 194 #if ENABLE(OILPAN) 195 m_fullScreenElement = nullptr; 196 m_fullScreenElementStack.clear(); 197 #endif 198 199 } 200 201 #if !ENABLE(OILPAN) 202 void Fullscreen::documentWasDisposed() 203 { 204 // NOTE: the context dispose phase is not supported in oilpan. Please 205 // consider using the detach phase instead. 206 m_fullScreenElement = nullptr; 207 m_fullScreenElementStack.clear(); 208 } 209 #endif 210 211 void Fullscreen::requestFullscreen(Element& element, RequestType requestType) 212 { 213 // Ignore this request if the document is not in a live frame. 214 if (!document()->isActive()) 215 return; 216 217 // If |element| is on top of |doc|'s fullscreen element stack, terminate these substeps. 218 if (&element == fullscreenElement()) 219 return; 220 221 do { 222 // 1. If any of the following conditions are true, terminate these steps and queue a task to fire 223 // an event named fullscreenerror with its bubbles attribute set to true on the context object's 224 // node document: 225 226 // The fullscreen element ready check returns false. 227 if (!fullscreenElementReady(element, requestType)) 228 break; 229 230 // This algorithm is not allowed to show a pop-up: 231 // An algorithm is allowed to show a pop-up if, in the task in which the algorithm is running, either: 232 // - an activation behavior is currently being processed whose click event was trusted, or 233 // - the event listener for a trusted click event is being handled. 234 if (!UserGestureIndicator::processingUserGesture()) 235 break; 236 237 // Fullscreen is not supported. 238 if (!fullscreenIsSupported(element.document(), element)) 239 break; 240 241 // 2. Let doc be element's node document. (i.e. "this") 242 Document* currentDoc = document(); 243 244 // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc. 245 Deque<Document*> docs; 246 247 do { 248 docs.prepend(currentDoc); 249 currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0; 250 } while (currentDoc); 251 252 // 4. For each document in docs, run these substeps: 253 Deque<Document*>::iterator current = docs.begin(), following = docs.begin(); 254 255 do { 256 ++following; 257 258 // 1. Let following document be the document after document in docs, or null if there is no 259 // such document. 260 Document* currentDoc = *current; 261 Document* followingDoc = following != docs.end() ? *following : 0; 262 263 // 2. If following document is null, push context object on document's fullscreen element 264 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute 265 // set to true on the document. 266 if (!followingDoc) { 267 from(*currentDoc).pushFullscreenElementStack(element, requestType); 268 enqueueChangeEvent(*currentDoc, requestType); 269 continue; 270 } 271 272 // 3. Otherwise, if document's fullscreen element stack is either empty or its top element 273 // is not following document's browsing context container, 274 Element* topElement = fullscreenElementFrom(*currentDoc); 275 if (!topElement || topElement != followingDoc->ownerElement()) { 276 // ...push following document's browsing context container on document's fullscreen element 277 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute 278 // set to true on document. 279 from(*currentDoc).pushFullscreenElementStack(*followingDoc->ownerElement(), requestType); 280 enqueueChangeEvent(*currentDoc, requestType); 281 continue; 282 } 283 284 // 4. Otherwise, do nothing for this document. It stays the same. 285 } while (++current != docs.end()); 286 287 // 5. Return, and run the remaining steps asynchronously. 288 // 6. Optionally, perform some animation. 289 m_areKeysEnabledInFullScreen = requestType != PrefixedMozillaRequest && requestType != PrefixedVideoRequest; 290 document()->frameHost()->chrome().client().enterFullScreenForElement(&element); 291 292 // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen. 293 return; 294 } while (0); 295 296 enqueueErrorEvent(element, requestType); 297 } 298 299 void Fullscreen::fullyExitFullscreen(Document& document) 300 { 301 // To fully exit fullscreen, run these steps: 302 303 // 1. Let |doc| be the top-level browsing context's document. 304 Document& doc = document.topDocument(); 305 306 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps. 307 if (!fullscreenElementFrom(doc)) 308 return; 309 310 // 3. Remove elements from |doc|'s fullscreen element stack until only the top element is left. 311 size_t stackSize = from(doc).m_fullScreenElementStack.size(); 312 from(doc).m_fullScreenElementStack.remove(0, stackSize - 1); 313 ASSERT(from(doc).m_fullScreenElementStack.size() == 1); 314 315 // 4. Act as if the exitFullscreen() method was invoked on |doc|. 316 from(doc).exitFullscreen(); 317 } 318 319 void Fullscreen::exitFullscreen() 320 { 321 // The exitFullscreen() method must run these steps: 322 323 // 1. Let doc be the context object. (i.e. "this") 324 Document* currentDoc = document(); 325 if (!currentDoc->isActive()) 326 return; 327 328 // 2. If doc's fullscreen element stack is empty, terminate these steps. 329 if (m_fullScreenElementStack.isEmpty()) 330 return; 331 332 // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen 333 // element stack (if any), ordered so that the child of the doc is last and the document furthest 334 // away from the doc is first. 335 WillBeHeapDeque<RefPtrWillBeMember<Document> > descendants; 336 for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) { 337 if (!descendant->isLocalFrame()) 338 continue; 339 ASSERT(toLocalFrame(descendant)->document()); 340 if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) 341 descendants.prepend(toLocalFrame(descendant)->document()); 342 } 343 344 // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a 345 // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant. 346 for (WillBeHeapDeque<RefPtrWillBeMember<Document> >::iterator i = descendants.begin(); i != descendants.end(); ++i) { 347 ASSERT(*i); 348 RequestType requestType = from(**i).m_fullScreenElementStack.last().second; 349 from(**i).clearFullscreenElementStack(); 350 enqueueChangeEvent(**i, requestType); 351 } 352 353 // 5. While doc is not null, run these substeps: 354 Element* newTop = 0; 355 while (currentDoc) { 356 RequestType requestType = from(*currentDoc).m_fullScreenElementStack.last().second; 357 358 // 1. Pop the top element of doc's fullscreen element stack. 359 from(*currentDoc).popFullscreenElementStack(); 360 361 // If doc's fullscreen element stack is non-empty and the element now at the top is either 362 // not in a document or its node document is not doc, repeat this substep. 363 newTop = fullscreenElementFrom(*currentDoc); 364 if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc)) 365 continue; 366 367 // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true 368 // on doc. 369 enqueueChangeEvent(*currentDoc, requestType); 370 371 // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context 372 // container, set doc to that browsing context container's node document. 373 if (!newTop && currentDoc->ownerElement()) { 374 currentDoc = ¤tDoc->ownerElement()->document(); 375 continue; 376 } 377 378 // 4. Otherwise, set doc to null. 379 currentDoc = 0; 380 } 381 382 // 6. Return, and run the remaining steps asynchronously. 383 // 7. Optionally, perform some animation. 384 385 FrameHost* host = document()->frameHost(); 386 387 // Speculative fix for engaget.com/videos per crbug.com/336239. 388 // FIXME: This check is wrong. We ASSERT(document->isActive()) above 389 // so this should be redundant and should be removed! 390 if (!host) 391 return; 392 393 // Only exit out of full screen window mode if there are no remaining elements in the 394 // full screen stack. 395 if (!newTop) { 396 host->chrome().client().exitFullScreenForElement(m_fullScreenElement.get()); 397 return; 398 } 399 400 // Otherwise, notify the chrome of the new full screen element. 401 host->chrome().client().enterFullScreenForElement(newTop); 402 } 403 404 bool Fullscreen::fullscreenEnabled(Document& document) 405 { 406 // 4. The fullscreenEnabled attribute must return true if the context object has its 407 // fullscreen enabled flag set and fullscreen is supported, and false otherwise. 408 409 // Top-level browsing contexts are implied to have their allowFullScreen attribute set. 410 return fullscreenIsAllowedForAllOwners(document) && fullscreenIsSupported(document); 411 } 412 413 void Fullscreen::didEnterFullScreenForElement(Element* element) 414 { 415 ASSERT(element); 416 if (!document()->isActive()) 417 return; 418 419 if (m_fullScreenRenderer) 420 m_fullScreenRenderer->unwrapRenderer(); 421 422 m_fullScreenElement = element; 423 424 // Create a placeholder block for a the full-screen element, to keep the page from reflowing 425 // when the element is removed from the normal flow. Only do this for a RenderBox, as only 426 // a box will have a frameRect. The placeholder will be created in setFullScreenRenderer() 427 // during layout. 428 RenderObject* renderer = m_fullScreenElement->renderer(); 429 bool shouldCreatePlaceholder = renderer && renderer->isBox(); 430 if (shouldCreatePlaceholder) { 431 m_savedPlaceholderFrameRect = toRenderBox(renderer)->frameRect(); 432 m_savedPlaceholderRenderStyle = RenderStyle::clone(renderer->style()); 433 } 434 435 if (m_fullScreenElement != document()->documentElement()) 436 RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : 0, document()); 437 438 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); 439 440 // FIXME: This should not call updateStyleIfNeeded. 441 document()->setNeedsStyleRecalc(SubtreeStyleChange); 442 document()->updateRenderTreeIfNeeded(); 443 444 m_fullScreenElement->didBecomeFullscreenElement(); 445 446 if (document()->frame()) 447 document()->frame()->eventHandler().scheduleHoverStateUpdate(); 448 449 m_eventQueueTimer.startOneShot(0, FROM_HERE); 450 } 451 452 void Fullscreen::didExitFullScreenForElement(Element*) 453 { 454 if (!m_fullScreenElement) 455 return; 456 457 if (!document()->isActive()) 458 return; 459 460 m_fullScreenElement->willStopBeingFullscreenElement(); 461 462 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); 463 464 m_areKeysEnabledInFullScreen = false; 465 466 if (m_fullScreenRenderer) 467 m_fullScreenRenderer->unwrapRenderer(); 468 469 m_fullScreenElement = nullptr; 470 document()->setNeedsStyleRecalc(SubtreeStyleChange); 471 472 if (document()->frame()) 473 document()->frame()->eventHandler().scheduleHoverStateUpdate(); 474 475 // When fullyExitFullscreen is called, we call exitFullscreen on the topDocument(). That means 476 // that the events will be queued there. So if we have no events here, start the timer on the 477 // exiting document. 478 Document* exitingDocument = document(); 479 if (m_eventQueue.isEmpty()) 480 exitingDocument = &document()->topDocument(); 481 ASSERT(exitingDocument); 482 from(*exitingDocument).m_eventQueueTimer.startOneShot(0, FROM_HERE); 483 } 484 485 void Fullscreen::setFullScreenRenderer(RenderFullScreen* renderer) 486 { 487 if (renderer == m_fullScreenRenderer) 488 return; 489 490 if (renderer && m_savedPlaceholderRenderStyle) { 491 renderer->createPlaceholder(m_savedPlaceholderRenderStyle.release(), m_savedPlaceholderFrameRect); 492 } else if (renderer && m_fullScreenRenderer && m_fullScreenRenderer->placeholder()) { 493 RenderBlock* placeholder = m_fullScreenRenderer->placeholder(); 494 renderer->createPlaceholder(RenderStyle::clone(placeholder->style()), placeholder->frameRect()); 495 } 496 497 if (m_fullScreenRenderer) 498 m_fullScreenRenderer->unwrapRenderer(); 499 ASSERT(!m_fullScreenRenderer); 500 501 m_fullScreenRenderer = renderer; 502 } 503 504 void Fullscreen::fullScreenRendererDestroyed() 505 { 506 m_fullScreenRenderer = nullptr; 507 } 508 509 void Fullscreen::enqueueChangeEvent(Document& document, RequestType requestType) 510 { 511 RefPtrWillBeRawPtr<Event> event; 512 if (requestType == UnprefixedRequest) { 513 event = createEvent(EventTypeNames::fullscreenchange, document); 514 } else { 515 ASSERT(document.hasFullscreenSupplement()); 516 Fullscreen& fullscreen = from(document); 517 EventTarget* target = fullscreen.fullscreenElement(); 518 if (!target) 519 target = fullscreen.webkitCurrentFullScreenElement(); 520 if (!target) 521 target = &document; 522 event = createEvent(EventTypeNames::webkitfullscreenchange, *target); 523 } 524 m_eventQueue.append(event); 525 // NOTE: The timer is started in didEnterFullScreenForElement/didExitFullScreenForElement. 526 } 527 528 void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType) 529 { 530 RefPtrWillBeRawPtr<Event> event; 531 if (requestType == UnprefixedRequest) 532 event = createEvent(EventTypeNames::fullscreenerror, element.document()); 533 else 534 event = createEvent(EventTypeNames::webkitfullscreenerror, element); 535 m_eventQueue.append(event); 536 m_eventQueueTimer.startOneShot(0, FROM_HERE); 537 } 538 539 void Fullscreen::eventQueueTimerFired(Timer<Fullscreen>*) 540 { 541 // Since we dispatch events in this function, it's possible that the 542 // document will be detached and GC'd. We protect it here to make sure we 543 // can finish the function successfully. 544 RefPtrWillBeRawPtr<Document> protectDocument(document()); 545 WillBeHeapDeque<RefPtrWillBeMember<Event> > eventQueue; 546 m_eventQueue.swap(eventQueue); 547 548 while (!eventQueue.isEmpty()) { 549 RefPtrWillBeRawPtr<Event> event = eventQueue.takeFirst(); 550 Node* target = event->target()->toNode(); 551 552 // If the element was removed from our tree, also message the documentElement. 553 if (!target->inDocument() && document()->documentElement()) { 554 ASSERT(isPrefixed(event->type())); 555 eventQueue.append(createEvent(event->type(), *document()->documentElement())); 556 } 557 558 target->dispatchEvent(event); 559 } 560 } 561 562 void Fullscreen::elementRemoved(Element& oldNode) 563 { 564 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its node document's 565 // fullscreen element stack, run these steps: 566 567 // 1. If |oldNode| is at the top of its node document's fullscreen element stack, act as if the 568 // exitFullscreen() method was invoked on that document. 569 if (fullscreenElement() == &oldNode) { 570 exitFullscreen(); 571 return; 572 } 573 574 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element stack. 575 for (size_t i = 0; i < m_fullScreenElementStack.size(); ++i) { 576 if (m_fullScreenElementStack[i].first.get() == &oldNode) { 577 m_fullScreenElementStack.remove(i); 578 return; 579 } 580 } 581 582 // NOTE: |oldNode| was not in the fullscreen element stack. 583 } 584 585 void Fullscreen::clearFullscreenElementStack() 586 { 587 m_fullScreenElementStack.clear(); 588 } 589 590 void Fullscreen::popFullscreenElementStack() 591 { 592 if (m_fullScreenElementStack.isEmpty()) 593 return; 594 595 m_fullScreenElementStack.removeLast(); 596 } 597 598 void Fullscreen::pushFullscreenElementStack(Element& element, RequestType requestType) 599 { 600 m_fullScreenElementStack.append(std::make_pair(&element, requestType)); 601 } 602 603 void Fullscreen::trace(Visitor* visitor) 604 { 605 visitor->trace(m_fullScreenElement); 606 visitor->trace(m_fullScreenElementStack); 607 visitor->trace(m_fullScreenRenderer); 608 visitor->trace(m_eventQueue); 609 DocumentSupplement::trace(visitor); 610 } 611 612 } // namespace blink 613