Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(814)

Side by Side Diff: third_party/WebKit/Source/core/dom/Fullscreen.cpp

Issue 2573773002: Sync requestFullscreen() and exitFullscreen() algorithms with the spec (Closed)
Patch Set: address feedback Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/dom/Fullscreen.h ('k') | third_party/WebKit/Source/core/dom/LayoutTreeBuilder.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698