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/FullscreenElementStack.h" 30 31 #include "HTMLNames.h" 32 #include "core/dom/Document.h" 33 #include "core/events/Event.h" 34 #include "core/html/HTMLFrameOwnerElement.h" 35 #include "core/page/Chrome.h" 36 #include "core/page/ChromeClient.h" 37 #include "core/frame/Frame.h" 38 #include "core/page/Page.h" 39 #include "core/frame/Settings.h" 40 #include "core/rendering/RenderFullScreen.h" 41 #include "platform/UserGestureIndicator.h" 42 43 namespace WebCore { 44 45 using namespace HTMLNames; 46 47 static bool isAttributeOnAllOwners(const WebCore::QualifiedName& attribute, const WebCore::QualifiedName& prefixedAttribute, const HTMLFrameOwnerElement* owner) 48 { 49 if (!owner) 50 return true; 51 do { 52 if (!(owner->hasAttribute(attribute) || owner->hasAttribute(prefixedAttribute))) 53 return false; 54 } while ((owner = owner->document().ownerElement())); 55 return true; 56 } 57 58 const char* FullscreenElementStack::supplementName() 59 { 60 return "FullscreenElementStack"; 61 } 62 63 FullscreenElementStack* FullscreenElementStack::from(Document* document) 64 { 65 FullscreenElementStack* fullscreen = fromIfExists(document); 66 if (!fullscreen) { 67 fullscreen = new FullscreenElementStack(document); 68 DocumentSupplement::provideTo(document, supplementName(), adoptPtr(fullscreen)); 69 } 70 71 return fullscreen; 72 } 73 74 FullscreenElementStack* FullscreenElementStack::fromIfExistsSlow(Document* document) 75 { 76 return static_cast<FullscreenElementStack*>(DocumentSupplement::from(document, supplementName())); 77 } 78 79 Element* FullscreenElementStack::fullscreenElementFrom(Document* document) 80 { 81 if (FullscreenElementStack* found = fromIfExists(document)) 82 return found->webkitFullscreenElement(); 83 return 0; 84 } 85 86 Element* FullscreenElementStack::currentFullScreenElementFrom(Document* document) 87 { 88 if (FullscreenElementStack* found = fromIfExists(document)) 89 return found->webkitCurrentFullScreenElement(); 90 return 0; 91 } 92 93 bool FullscreenElementStack::isFullScreen(Document* document) 94 { 95 if (FullscreenElementStack* found = fromIfExists(document)) 96 return found->webkitIsFullScreen(); 97 return false; 98 } 99 100 FullscreenElementStack::FullscreenElementStack(Document* document) 101 : DocumentLifecycleObserver(document) 102 , m_areKeysEnabledInFullScreen(false) 103 , m_fullScreenRenderer(0) 104 , m_fullScreenChangeDelayTimer(this, &FullscreenElementStack::fullScreenChangeDelayTimerFired) 105 { 106 document->setHasFullscreenElementStack(); 107 } 108 109 FullscreenElementStack::~FullscreenElementStack() 110 { 111 } 112 113 inline Document* FullscreenElementStack::document() 114 { 115 return lifecycleContext(); 116 } 117 118 void FullscreenElementStack::documentWasDetached() 119 { 120 m_fullScreenChangeEventTargetQueue.clear(); 121 m_fullScreenErrorEventTargetQueue.clear(); 122 123 if (m_fullScreenRenderer) 124 setFullScreenRenderer(0); 125 } 126 127 void FullscreenElementStack::documentWasDisposed() 128 { 129 m_fullScreenElement = 0; 130 m_fullScreenElementStack.clear(); 131 } 132 133 bool FullscreenElementStack::fullScreenIsAllowedForElement(Element* element) const 134 { 135 ASSERT(element); 136 return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, element->document().ownerElement()); 137 } 138 139 void FullscreenElementStack::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType) 140 { 141 // The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements 142 // for full screen mode, and do not have the concept of a full screen element stack. 143 bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST); 144 145 do { 146 if (!element) 147 element = document()->documentElement(); 148 149 // 1. If any of the following conditions are true, terminate these steps and queue a task to fire 150 // an event named fullscreenerror with its bubbles attribute set to true on the context object's 151 // node document: 152 153 // The context object is not in a document. 154 if (!element->inDocument()) 155 break; 156 157 // The context object's node document, or an ancestor browsing context's document does not have 158 // the fullscreen enabled flag set. 159 if (checkType == EnforceIFrameAllowFullScreenRequirement && !fullScreenIsAllowedForElement(element)) 160 break; 161 162 // The context object's node document fullscreen element stack is not empty and its top element 163 // is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was 164 // made via the legacy Mozilla-style API.) 165 if (!m_fullScreenElementStack.isEmpty() && !inLegacyMozillaMode) { 166 Element* lastElementOnStack = m_fullScreenElementStack.last().get(); 167 if (lastElementOnStack == element || !lastElementOnStack->contains(element)) 168 break; 169 } 170 171 // A descendant browsing context's document has a non-empty fullscreen element stack. 172 bool descendentHasNonEmptyStack = false; 173 for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) { 174 if (fullscreenElementFrom(descendant->document())) { 175 descendentHasNonEmptyStack = true; 176 break; 177 } 178 } 179 if (descendentHasNonEmptyStack && !inLegacyMozillaMode) 180 break; 181 182 // This algorithm is not allowed to show a pop-up: 183 // An algorithm is allowed to show a pop-up if, in the task in which the algorithm is running, either: 184 // - an activation behavior is currently being processed whose click event was trusted, or 185 // - the event listener for a trusted click event is being handled. 186 // FIXME: Does this need to null-check settings()? 187 if (!UserGestureIndicator::processingUserGesture() && (!element->isMediaElement() || document()->settings()->mediaFullscreenRequiresUserGesture())) 188 break; 189 190 // There is a previously-established user preference, security risk, or platform limitation. 191 if (!document()->settings() || !document()->settings()->fullScreenEnabled()) 192 break; 193 194 // 2. Let doc be element's node document. (i.e. "this") 195 Document* currentDoc = document(); 196 197 // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc. 198 Deque<Document*> docs; 199 200 do { 201 docs.prepend(currentDoc); 202 currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0; 203 } while (currentDoc); 204 205 // 4. For each document in docs, run these substeps: 206 Deque<Document*>::iterator current = docs.begin(), following = docs.begin(); 207 208 do { 209 ++following; 210 211 // 1. Let following document be the document after document in docs, or null if there is no 212 // such document. 213 Document* currentDoc = *current; 214 Document* followingDoc = following != docs.end() ? *following : 0; 215 216 // 2. If following document is null, push context object on document's fullscreen element 217 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute 218 // set to true on the document. 219 if (!followingDoc) { 220 from(currentDoc)->pushFullscreenElementStack(element); 221 addDocumentToFullScreenChangeEventQueue(currentDoc); 222 continue; 223 } 224 225 // 3. Otherwise, if document's fullscreen element stack is either empty or its top element 226 // is not following document's browsing context container, 227 Element* topElement = fullscreenElementFrom(currentDoc); 228 if (!topElement || topElement != followingDoc->ownerElement()) { 229 // ...push following document's browsing context container on document's fullscreen element 230 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute 231 // set to true on document. 232 from(currentDoc)->pushFullscreenElementStack(followingDoc->ownerElement()); 233 addDocumentToFullScreenChangeEventQueue(currentDoc); 234 continue; 235 } 236 237 // 4. Otherwise, do nothing for this document. It stays the same. 238 } while (++current != docs.end()); 239 240 // 5. Return, and run the remaining steps asynchronously. 241 // 6. Optionally, perform some animation. 242 m_areKeysEnabledInFullScreen = flags & Element::ALLOW_KEYBOARD_INPUT; 243 document()->page()->chrome().client().enterFullScreenForElement(element); 244 245 // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen. 246 return; 247 } while (0); 248 249 m_fullScreenErrorEventTargetQueue.append(element ? element : document()->documentElement()); 250 m_fullScreenChangeDelayTimer.startOneShot(0); 251 } 252 253 void FullscreenElementStack::webkitCancelFullScreen() 254 { 255 // The Mozilla "cancelFullScreen()" API behaves like the W3C "fully exit fullscreen" behavior, which 256 // is defined as: 257 // "To fully exit fullscreen act as if the exitFullscreen() method was invoked on the top-level browsing 258 // context's document and subsequently empty that document's fullscreen element stack." 259 if (!fullscreenElementFrom(document()->topDocument())) 260 return; 261 262 // To achieve that aim, remove all the elements from the top document's stack except for the first before 263 // calling webkitExitFullscreen(): 264 Vector<RefPtr<Element> > replacementFullscreenElementStack; 265 replacementFullscreenElementStack.append(fullscreenElementFrom(document()->topDocument())); 266 FullscreenElementStack* topFullscreenElementStack = from(document()->topDocument()); 267 topFullscreenElementStack->m_fullScreenElementStack.swap(replacementFullscreenElementStack); 268 topFullscreenElementStack->webkitExitFullscreen(); 269 } 270 271 void FullscreenElementStack::webkitExitFullscreen() 272 { 273 // The exitFullscreen() method must run these steps: 274 275 // 1. Let doc be the context object. (i.e. "this") 276 Document* currentDoc = document(); 277 278 // 2. If doc's fullscreen element stack is empty, terminate these steps. 279 if (m_fullScreenElementStack.isEmpty()) 280 return; 281 282 // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen 283 // element stack (if any), ordered so that the child of the doc is last and the document furthest 284 // away from the doc is first. 285 Deque<RefPtr<Document> > descendants; 286 for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) { 287 if (fullscreenElementFrom(descendant->document())) 288 descendants.prepend(descendant->document()); 289 } 290 291 // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a 292 // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant. 293 for (Deque<RefPtr<Document> >::iterator i = descendants.begin(); i != descendants.end(); ++i) { 294 from(i->get())->clearFullscreenElementStack(); 295 addDocumentToFullScreenChangeEventQueue(i->get()); 296 } 297 298 // 5. While doc is not null, run these substeps: 299 Element* newTop = 0; 300 while (currentDoc) { 301 // 1. Pop the top element of doc's fullscreen element stack. 302 from(currentDoc)->popFullscreenElementStack(); 303 304 // If doc's fullscreen element stack is non-empty and the element now at the top is either 305 // not in a document or its node document is not doc, repeat this substep. 306 newTop = fullscreenElementFrom(currentDoc); 307 if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc)) 308 continue; 309 310 // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true 311 // on doc. 312 addDocumentToFullScreenChangeEventQueue(currentDoc); 313 314 // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context 315 // container, set doc to that browsing context container's node document. 316 if (!newTop && currentDoc->ownerElement()) { 317 currentDoc = ¤tDoc->ownerElement()->document(); 318 continue; 319 } 320 321 // 4. Otherwise, set doc to null. 322 currentDoc = 0; 323 } 324 325 // 6. Return, and run the remaining steps asynchronously. 326 // 7. Optionally, perform some animation. 327 328 if (!document()->page()) 329 return; 330 331 // Only exit out of full screen window mode if there are no remaining elements in the 332 // full screen stack. 333 if (!newTop) { 334 document()->page()->chrome().client().exitFullScreenForElement(m_fullScreenElement.get()); 335 return; 336 } 337 338 // Otherwise, notify the chrome of the new full screen element. 339 document()->page()->chrome().client().enterFullScreenForElement(newTop); 340 } 341 342 bool FullscreenElementStack::webkitFullscreenEnabled(Document* document) 343 { 344 // 4. The fullscreenEnabled attribute must return true if the context object and all ancestor 345 // browsing context's documents have their fullscreen enabled flag set, or false otherwise. 346 347 // Top-level browsing contexts are implied to have their allowFullScreen attribute set. 348 return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, document->ownerElement()); 349 350 } 351 352 void FullscreenElementStack::webkitWillEnterFullScreenForElement(Element* element) 353 { 354 if (!document()->isActive()) 355 return; 356 357 ASSERT(element); 358 359 // Protect against being called after the document has been removed from the page. 360 if (!document()->settings()) 361 return; 362 363 ASSERT(document()->settings()->fullScreenEnabled()); 364 365 if (m_fullScreenRenderer) 366 m_fullScreenRenderer->unwrapRenderer(); 367 368 m_fullScreenElement = element; 369 370 // Create a placeholder block for a the full-screen element, to keep the page from reflowing 371 // when the element is removed from the normal flow. Only do this for a RenderBox, as only 372 // a box will have a frameRect. The placeholder will be created in setFullScreenRenderer() 373 // during layout. 374 RenderObject* renderer = m_fullScreenElement->renderer(); 375 bool shouldCreatePlaceholder = renderer && renderer->isBox(); 376 if (shouldCreatePlaceholder) { 377 m_savedPlaceholderFrameRect = toRenderBox(renderer)->frameRect(); 378 m_savedPlaceholderRenderStyle = RenderStyle::clone(renderer->style()); 379 } 380 381 if (m_fullScreenElement != document()->documentElement()) 382 RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : 0, document()); 383 384 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); 385 386 document()->recalcStyle(Force); 387 } 388 389 void FullscreenElementStack::webkitDidEnterFullScreenForElement(Element*) 390 { 391 if (!m_fullScreenElement) 392 return; 393 394 if (!document()->isActive()) 395 return; 396 397 m_fullScreenElement->didBecomeFullscreenElement(); 398 399 m_fullScreenChangeDelayTimer.startOneShot(0); 400 } 401 402 void FullscreenElementStack::webkitWillExitFullScreenForElement(Element*) 403 { 404 if (!m_fullScreenElement) 405 return; 406 407 if (!document()->isActive()) 408 return; 409 410 m_fullScreenElement->willStopBeingFullscreenElement(); 411 } 412 413 void FullscreenElementStack::webkitDidExitFullScreenForElement(Element*) 414 { 415 if (!m_fullScreenElement) 416 return; 417 418 if (!document()->isActive()) 419 return; 420 421 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); 422 423 m_areKeysEnabledInFullScreen = false; 424 425 if (m_fullScreenRenderer) 426 m_fullScreenRenderer->unwrapRenderer(); 427 428 m_fullScreenElement = 0; 429 document()->setNeedsStyleRecalc(); 430 431 // When webkitCancelFullScreen is called, we call webkitExitFullScreen on the topDocument(). That 432 // means that the events will be queued there. So if we have no events here, start the timer on 433 // the exiting document. 434 Document* exitingDocument = document(); 435 if (m_fullScreenChangeEventTargetQueue.isEmpty() && m_fullScreenErrorEventTargetQueue.isEmpty()) 436 exitingDocument = document()->topDocument(); 437 from(exitingDocument)->m_fullScreenChangeDelayTimer.startOneShot(0); 438 } 439 440 void FullscreenElementStack::setFullScreenRenderer(RenderFullScreen* renderer) 441 { 442 if (renderer == m_fullScreenRenderer) 443 return; 444 445 if (renderer && m_savedPlaceholderRenderStyle) { 446 renderer->createPlaceholder(m_savedPlaceholderRenderStyle.release(), m_savedPlaceholderFrameRect); 447 } else if (renderer && m_fullScreenRenderer && m_fullScreenRenderer->placeholder()) { 448 RenderBlock* placeholder = m_fullScreenRenderer->placeholder(); 449 renderer->createPlaceholder(RenderStyle::clone(placeholder->style()), placeholder->frameRect()); 450 } 451 452 if (m_fullScreenRenderer) 453 m_fullScreenRenderer->destroy(); 454 ASSERT(!m_fullScreenRenderer); 455 456 m_fullScreenRenderer = renderer; 457 } 458 459 void FullscreenElementStack::fullScreenRendererDestroyed() 460 { 461 m_fullScreenRenderer = 0; 462 } 463 464 void FullscreenElementStack::fullScreenChangeDelayTimerFired(Timer<FullscreenElementStack>*) 465 { 466 // Since we dispatch events in this function, it's possible that the 467 // document will be detached and GC'd. We protect it here to make sure we 468 // can finish the function successfully. 469 RefPtr<Document> protectDocument(document()); 470 Deque<RefPtr<Node> > changeQueue; 471 m_fullScreenChangeEventTargetQueue.swap(changeQueue); 472 Deque<RefPtr<Node> > errorQueue; 473 m_fullScreenErrorEventTargetQueue.swap(errorQueue); 474 475 while (!changeQueue.isEmpty()) { 476 RefPtr<Node> node = changeQueue.takeFirst(); 477 if (!node) 478 node = document()->documentElement(); 479 // The dispatchEvent below may have blown away our documentElement. 480 if (!node) 481 continue; 482 483 // If the element was removed from our tree, also message the documentElement. Since we may 484 // have a document hierarchy, check that node isn't in another document. 485 if (!document()->contains(node.get()) && !node->inDocument()) 486 changeQueue.append(document()->documentElement()); 487 488 node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenchange)); 489 } 490 491 while (!errorQueue.isEmpty()) { 492 RefPtr<Node> node = errorQueue.takeFirst(); 493 if (!node) 494 node = document()->documentElement(); 495 // The dispatchEvent below may have blown away our documentElement. 496 if (!node) 497 continue; 498 499 // If the element was removed from our tree, also message the documentElement. Since we may 500 // have a document hierarchy, check that node isn't in another document. 501 if (!document()->contains(node.get()) && !node->inDocument()) 502 errorQueue.append(document()->documentElement()); 503 504 node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenerror)); 505 } 506 } 507 508 void FullscreenElementStack::fullScreenElementRemoved() 509 { 510 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); 511 webkitCancelFullScreen(); 512 } 513 514 void FullscreenElementStack::removeFullScreenElementOfSubtree(Node* node, bool amongChildrenOnly) 515 { 516 if (!m_fullScreenElement) 517 return; 518 519 // If the node isn't in a document it can't have a fullscreen'd child. 520 if (!node->inDocument()) 521 return; 522 523 bool elementInSubtree = false; 524 if (amongChildrenOnly) 525 elementInSubtree = m_fullScreenElement->isDescendantOf(node); 526 else 527 elementInSubtree = (m_fullScreenElement == node) || m_fullScreenElement->isDescendantOf(node); 528 529 if (elementInSubtree) 530 fullScreenElementRemoved(); 531 } 532 533 void FullscreenElementStack::clearFullscreenElementStack() 534 { 535 m_fullScreenElementStack.clear(); 536 } 537 538 void FullscreenElementStack::popFullscreenElementStack() 539 { 540 if (m_fullScreenElementStack.isEmpty()) 541 return; 542 543 m_fullScreenElementStack.removeLast(); 544 } 545 546 void FullscreenElementStack::pushFullscreenElementStack(Element* element) 547 { 548 m_fullScreenElementStack.append(element); 549 } 550 551 void FullscreenElementStack::addDocumentToFullScreenChangeEventQueue(Document* doc) 552 { 553 ASSERT(doc); 554 555 Node* target = 0; 556 if (FullscreenElementStack* fullscreen = fromIfExists(doc)) { 557 target = fullscreen->webkitFullscreenElement(); 558 if (!target) 559 target = fullscreen->webkitCurrentFullScreenElement(); 560 } 561 562 if (!target) 563 target = doc; 564 m_fullScreenChangeEventTargetQueue.append(target); 565 } 566 567 } // namespace WebCore 568