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