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