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