1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * (C) 2006 Alexey Proskuryakov (ap@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
isAttributeOnAllOwners(const WebCore::QualifiedName & attribute,const WebCore::QualifiedName & prefixedAttribute,const HTMLFrameOwnerElement * owner)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
supplementName()58 const char* FullscreenElementStack::supplementName()
59 {
60 return "FullscreenElementStack";
61 }
62
from(Document * document)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
fromIfExistsSlow(Document * document)74 FullscreenElementStack* FullscreenElementStack::fromIfExistsSlow(Document* document)
75 {
76 return static_cast<FullscreenElementStack*>(DocumentSupplement::from(document, supplementName()));
77 }
78
fullscreenElementFrom(Document * document)79 Element* FullscreenElementStack::fullscreenElementFrom(Document* document)
80 {
81 if (FullscreenElementStack* found = fromIfExists(document))
82 return found->webkitFullscreenElement();
83 return 0;
84 }
85
currentFullScreenElementFrom(Document * document)86 Element* FullscreenElementStack::currentFullScreenElementFrom(Document* document)
87 {
88 if (FullscreenElementStack* found = fromIfExists(document))
89 return found->webkitCurrentFullScreenElement();
90 return 0;
91 }
92
isFullScreen(Document * document)93 bool FullscreenElementStack::isFullScreen(Document* document)
94 {
95 if (FullscreenElementStack* found = fromIfExists(document))
96 return found->webkitIsFullScreen();
97 return false;
98 }
99
FullscreenElementStack(Document * document)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
~FullscreenElementStack()109 FullscreenElementStack::~FullscreenElementStack()
110 {
111 }
112
document()113 inline Document* FullscreenElementStack::document()
114 {
115 return lifecycleContext();
116 }
117
documentWasDetached()118 void FullscreenElementStack::documentWasDetached()
119 {
120 m_fullScreenChangeEventTargetQueue.clear();
121 m_fullScreenErrorEventTargetQueue.clear();
122
123 if (m_fullScreenRenderer)
124 setFullScreenRenderer(0);
125 }
126
documentWasDisposed()127 void FullscreenElementStack::documentWasDisposed()
128 {
129 m_fullScreenElement = 0;
130 m_fullScreenElementStack.clear();
131 }
132
fullScreenIsAllowedForElement(Element * element) const133 bool FullscreenElementStack::fullScreenIsAllowedForElement(Element* element) const
134 {
135 ASSERT(element);
136 return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, element->document().ownerElement());
137 }
138
requestFullScreenForElement(Element * element,unsigned short flags,FullScreenCheckType checkType)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
webkitCancelFullScreen()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
webkitExitFullscreen()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
webkitFullscreenEnabled(Document * document)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
webkitWillEnterFullScreenForElement(Element * element)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
webkitDidEnterFullScreenForElement(Element *)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
webkitWillExitFullScreenForElement(Element *)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
webkitDidExitFullScreenForElement(Element *)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
setFullScreenRenderer(RenderFullScreen * renderer)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
fullScreenRendererDestroyed()459 void FullscreenElementStack::fullScreenRendererDestroyed()
460 {
461 m_fullScreenRenderer = 0;
462 }
463
fullScreenChangeDelayTimerFired(Timer<FullscreenElementStack> *)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
fullScreenElementRemoved()508 void FullscreenElementStack::fullScreenElementRemoved()
509 {
510 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
511 webkitCancelFullScreen();
512 }
513
removeFullScreenElementOfSubtree(Node * node,bool amongChildrenOnly)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
clearFullscreenElementStack()533 void FullscreenElementStack::clearFullscreenElementStack()
534 {
535 m_fullScreenElementStack.clear();
536 }
537
popFullscreenElementStack()538 void FullscreenElementStack::popFullscreenElementStack()
539 {
540 if (m_fullScreenElementStack.isEmpty())
541 return;
542
543 m_fullScreenElementStack.removeLast();
544 }
545
pushFullscreenElementStack(Element * element)546 void FullscreenElementStack::pushFullscreenElementStack(Element* element)
547 {
548 m_fullScreenElementStack.append(element);
549 }
550
addDocumentToFullScreenChangeEventQueue(Document * doc)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