| 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 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 155 // |element|'s node document's browsing context's browsing context container, | 155 // |element|'s node document's browsing context's browsing context container, |
| 156 // or it has no browsing context container. | 156 // or it has no browsing context container. |
| 157 if (const Element* owner = element.GetDocument().LocalOwner()) { | 157 if (const Element* owner = element.GetDocument().LocalOwner()) { |
| 158 if (!FullscreenElementReady(*owner)) | 158 if (!FullscreenElementReady(*owner)) |
| 159 return false; | 159 return false; |
| 160 } | 160 } |
| 161 | 161 |
| 162 return true; | 162 return true; |
| 163 } | 163 } |
| 164 | 164 |
| 165 bool IsPrefixed(const AtomicString& type) { | 165 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen step 4: |
| 166 return type == EventTypeNames::webkitfullscreenchange || | 166 bool RequestFullscreenConditionsMet(Element& pending, Document& document) { |
| 167 type == EventTypeNames::webkitfullscreenerror; | 167 // |pending|'s namespace is the HTML namespace or |pending| is an SVG svg or |
| 168 } | 168 // MathML math element. Note: MathML is not supported. |
| 169 if (!pending.IsHTMLElement() && !isSVGSVGElement(pending)) |
| 170 return false; |
| 169 | 171 |
| 170 Event* CreateEvent(const AtomicString& type, EventTarget& target) { | 172 // The fullscreen element ready check for |pending| returns false. |
| 171 EventInit initializer; | 173 if (!FullscreenElementReady(pending)) |
| 172 initializer.setBubbles(IsPrefixed(type)); | 174 return false; |
| 173 Event* event = Event::Create(type, initializer); | 175 |
| 174 event->SetTarget(&target); | 176 // Fullscreen is supported. |
| 175 return event; | 177 if (!FullscreenIsSupported(document)) |
| 178 return false; |
| 179 |
| 180 // This algorithm is allowed to request fullscreen. |
| 181 if (!AllowedToRequestFullscreen(document)) |
| 182 return false; |
| 183 |
| 184 return true; |
| 176 } | 185 } |
| 177 | 186 |
| 178 // Walks the frame tree and returns the first local ancestor frame, if any. | 187 // Walks the frame tree and returns the first local ancestor frame, if any. |
| 179 LocalFrame* NextLocalAncestor(Frame& frame) { | 188 LocalFrame* NextLocalAncestor(Frame& frame) { |
| 180 Frame* parent = frame.Tree().Parent(); | 189 Frame* parent = frame.Tree().Parent(); |
| 181 if (!parent) | 190 if (!parent) |
| 182 return nullptr; | 191 return nullptr; |
| 183 if (parent->IsLocalFrame()) | 192 if (parent->IsLocalFrame()) |
| 184 return ToLocalFrame(parent); | 193 return ToLocalFrame(parent); |
| 185 return NextLocalAncestor(*parent); | 194 return NextLocalAncestor(*parent); |
| 186 } | 195 } |
| 187 | 196 |
| 188 // Walks the document's frame tree and returns the document of the first local | 197 // Walks the document's frame tree and returns the document of the first local |
| 189 // ancestor frame, if any. | 198 // ancestor frame, if any. |
| 190 Document* NextLocalAncestor(Document& document) { | 199 Document* NextLocalAncestor(Document& document) { |
| 191 LocalFrame* frame = document.GetFrame(); | 200 LocalFrame* frame = document.GetFrame(); |
| 192 if (!frame) | 201 if (!frame) |
| 193 return nullptr; | 202 return nullptr; |
| 194 LocalFrame* next = NextLocalAncestor(*document.GetFrame()); | 203 LocalFrame* next = NextLocalAncestor(*frame); |
| 195 if (!next) | 204 if (!next) |
| 196 return nullptr; | 205 return nullptr; |
| 197 DCHECK(next->GetDocument()); | 206 DCHECK(next->GetDocument()); |
| 198 return next->GetDocument(); | 207 return next->GetDocument(); |
| 199 } | 208 } |
| 200 | 209 |
| 201 // Helper to walk the ancestor chain and return the Document of the topmost | 210 // Helper to walk the ancestor chain and return the Document of the topmost |
| 202 // local ancestor frame. Note that this is not the same as the topmost frame's | 211 // local ancestor frame. Note that this is not the same as the topmost frame's |
| 203 // Document, which might be unavailable in OOPIF scenarios. For example, with | 212 // Document, which might be unavailable in OOPIF scenarios. For example, with |
| 204 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in | 213 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in |
| 205 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. | 214 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. |
| 206 Document& TopmostLocalAncestor(Document& document) { | 215 Document& TopmostLocalAncestor(Document& document) { |
| 207 if (Document* next = NextLocalAncestor(document)) | 216 if (Document* next = NextLocalAncestor(document)) |
| 208 return TopmostLocalAncestor(*next); | 217 return TopmostLocalAncestor(*next); |
| 209 return document; | 218 return document; |
| 210 } | 219 } |
| 211 | 220 |
| 212 // Helper to find the browsing context container in |doc| that embeds the | 221 // https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen |
| 213 // |descendant| Document, possibly through multiple levels of nesting. This | 222 HeapVector<Member<Document>> CollectDocumentsToUnfullscreen( |
| 214 // works even in OOPIF scenarios like A-B-A, where there may be remote frames | 223 Document& doc, |
| 215 // in between |doc| and |descendant|. | 224 Fullscreen::ExitType exit_type) { |
| 216 HTMLFrameOwnerElement* FindContainerForDescendant(const Document& doc, | 225 DCHECK(Fullscreen::FullscreenElementFrom(doc)); |
| 217 const Document& descendant) { | 226 |
| 218 Frame* frame = descendant.GetFrame(); | 227 // 1. If |doc|'s top layer consists of more than a single element that has |
| 219 while (frame->Tree().Parent() != doc.GetFrame()) | 228 // its fullscreen flag set, return the empty set. |
| 220 frame = frame->Tree().Parent(); | 229 // TODO(foolip): See TODO in |fullyExitFullscreen()|. |
| 221 return ToHTMLFrameOwnerElement(frame->Owner()); | 230 if (exit_type != Fullscreen::ExitType::kFully && |
| 231 Fullscreen::FullscreenElementStackSizeFrom(doc) > 1) |
| 232 return HeapVector<Member<Document>>(); |
| 233 |
| 234 // 2. Let |docs| be an ordered set consisting of |doc|. |
| 235 HeapVector<Member<Document>> docs; |
| 236 docs.push_back(&doc); |
| 237 |
| 238 // 3. While |docs|'s last document ... |
| 239 // |
| 240 // OOPIF: Skip over remote frames, assuming that they have exactly one element |
| 241 // in their fullscreen element stacks, thereby erring on the side of exiting |
| 242 // fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see |
| 243 // https://crbug.com/617369. |
| 244 for (Document* document = NextLocalAncestor(doc); document; |
| 245 document = NextLocalAncestor(*document)) { |
| 246 // ... has a browsing context container whose node document's top layer |
| 247 // consists of a single element that has its fullscreen flag set and does |
| 248 // not have its iframe fullscreen flag set (if any), append that node |
| 249 // document to |docs|. |
| 250 // TODO(foolip): Support the iframe fullscreen flag. |
| 251 // https://crbug.com/644695 |
| 252 if (Fullscreen::FullscreenElementStackSizeFrom(*document) == 1) |
| 253 docs.push_back(document); |
| 254 else |
| 255 break; |
| 256 } |
| 257 |
| 258 // 4. Return |docs|. |
| 259 return docs; |
| 222 } | 260 } |
| 223 | 261 |
| 224 // Fullscreen status affects scroll paint properties through | 262 // Creates a non-bubbling event with |document| as its target. |
| 225 // LocalFrameView::userInputScrollable(). | 263 Event* CreateEvent(const AtomicString& type, Document& document) { |
| 226 void SetNeedsPaintPropertyUpdate(Document* document) { | 264 DCHECK(type == EventTypeNames::fullscreenchange || |
| 227 if (!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() || | 265 type == EventTypeNames::fullscreenerror); |
| 228 RuntimeEnabledFeatures::rootLayerScrollingEnabled()) | |
| 229 return; | |
| 230 | 266 |
| 231 if (!document) | 267 Event* event = Event::Create(type); |
| 232 return; | 268 event->SetTarget(&document); |
| 269 return event; |
| 270 } |
| 233 | 271 |
| 234 LocalFrame* frame = document->GetFrame(); | 272 // Creates a bubbling event with |element| as its target. If |element| is not |
| 235 if (!frame) | 273 // connected to |document|, then |document| is used as the target instead. |
| 236 return; | 274 Event* CreatePrefixedEvent(const AtomicString& type, |
| 275 Element& element, |
| 276 Document& document) { |
| 277 DCHECK(type == EventTypeNames::webkitfullscreenchange || |
| 278 type == EventTypeNames::webkitfullscreenerror); |
| 237 | 279 |
| 238 if (LocalFrameView* frame_view = frame->View()) | 280 Event* event = Event::CreateBubble(type); |
| 239 frame_view->SetNeedsPaintPropertyUpdate(); | 281 if (element.isConnected() && element.GetDocument() == document) |
| 282 event->SetTarget(&element); |
| 283 else |
| 284 event->SetTarget(&document); |
| 285 return event; |
| 286 } |
| 287 |
| 288 Event* CreateChangeEvent(Document& document, |
| 289 Element& element, |
| 290 Fullscreen::RequestType request_type) { |
| 291 if (request_type == Fullscreen::RequestType::kUnprefixed) |
| 292 return CreateEvent(EventTypeNames::fullscreenchange, document); |
| 293 return CreatePrefixedEvent(EventTypeNames::webkitfullscreenchange, element, |
| 294 document); |
| 295 } |
| 296 |
| 297 Event* CreateErrorEvent(Document& document, |
| 298 Element& element, |
| 299 Fullscreen::RequestType request_type) { |
| 300 if (request_type == Fullscreen::RequestType::kUnprefixed) |
| 301 return CreateEvent(EventTypeNames::fullscreenerror, document); |
| 302 return CreatePrefixedEvent(EventTypeNames::webkitfullscreenerror, element, |
| 303 document); |
| 304 } |
| 305 |
| 306 void DispatchEvents(const HeapVector<Member<Event>>& events) { |
| 307 for (Event* event : events) |
| 308 event->target()->DispatchEvent(event); |
| 240 } | 309 } |
| 241 | 310 |
| 242 } // anonymous namespace | 311 } // anonymous namespace |
| 243 | 312 |
| 244 const char* Fullscreen::SupplementName() { | 313 const char* Fullscreen::SupplementName() { |
| 245 return "Fullscreen"; | 314 return "Fullscreen"; |
| 246 } | 315 } |
| 247 | 316 |
| 248 Fullscreen& Fullscreen::From(Document& document) { | 317 Fullscreen& Fullscreen::From(Document& document) { |
| 249 Fullscreen* fullscreen = FromIfExists(document); | 318 Fullscreen* fullscreen = FromIfExists(document); |
| 250 if (!fullscreen) { | 319 if (!fullscreen) { |
| 251 fullscreen = new Fullscreen(document); | 320 fullscreen = new Fullscreen(document); |
| 252 Supplement<Document>::ProvideTo(document, SupplementName(), fullscreen); | 321 Supplement<Document>::ProvideTo(document, SupplementName(), fullscreen); |
| 253 } | 322 } |
| 254 | 323 |
| 255 return *fullscreen; | 324 return *fullscreen; |
| 256 } | 325 } |
| 257 | 326 |
| 258 Fullscreen* Fullscreen::FromIfExistsSlow(Document& document) { | 327 Fullscreen* Fullscreen::FromIfExistsSlow(Document& document) { |
| 259 return static_cast<Fullscreen*>( | 328 return static_cast<Fullscreen*>( |
| 260 Supplement<Document>::From(document, SupplementName())); | 329 Supplement<Document>::From(document, SupplementName())); |
| 261 } | 330 } |
| 262 | 331 |
| 263 Element* Fullscreen::FullscreenElementFrom(Document& document) { | 332 Element* Fullscreen::FullscreenElementFrom(Document& document) { |
| 264 if (Fullscreen* found = FromIfExists(document)) | 333 if (Fullscreen* found = FromIfExists(document)) |
| 265 return found->FullscreenElement(); | 334 return found->FullscreenElement(); |
| 266 return nullptr; | 335 return nullptr; |
| 267 } | 336 } |
| 268 | 337 |
| 338 size_t Fullscreen::FullscreenElementStackSizeFrom(Document& document) { |
| 339 if (Fullscreen* found = FromIfExists(document)) |
| 340 return found->fullscreen_element_stack_.size(); |
| 341 return 0; |
| 342 } |
| 343 |
| 269 Element* Fullscreen::FullscreenElementForBindingFrom(TreeScope& scope) { | 344 Element* Fullscreen::FullscreenElementForBindingFrom(TreeScope& scope) { |
| 270 Element* element = FullscreenElementFrom(scope.GetDocument()); | 345 Element* element = FullscreenElementFrom(scope.GetDocument()); |
| 271 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | 346 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
| 272 return element; | 347 return element; |
| 273 | 348 |
| 274 // TODO(kochi): Once V0 code is removed, we can use the same logic for | 349 // TODO(kochi): Once V0 code is removed, we can use the same logic for |
| 275 // Document and ShadowRoot. | 350 // Document and ShadowRoot. |
| 276 if (!scope.RootNode().IsShadowRoot()) { | 351 if (!scope.RootNode().IsShadowRoot()) { |
| 277 // For Shadow DOM V0 compatibility: We allow returning an element in V0 | 352 // For Shadow DOM V0 compatibility: We allow returning an element in V0 |
| 278 // shadow tree, even though it leaks the Shadow DOM. | 353 // shadow tree, even though it leaks the Shadow DOM. |
| 279 if (element->IsInV0ShadowTree()) { | 354 if (element->IsInV0ShadowTree()) { |
| 280 UseCounter::Count(scope.GetDocument(), | 355 UseCounter::Count(scope.GetDocument(), |
| 281 UseCounter::kDocumentFullscreenElementInV0Shadow); | 356 UseCounter::kDocumentFullscreenElementInV0Shadow); |
| 282 return element; | 357 return element; |
| 283 } | 358 } |
| 284 } else if (!ToShadowRoot(scope.RootNode()).IsV1()) { | 359 } else if (!ToShadowRoot(scope.RootNode()).IsV1()) { |
| 285 return nullptr; | 360 return nullptr; |
| 286 } | 361 } |
| 287 return scope.AdjustedElement(*element); | 362 return scope.AdjustedElement(*element); |
| 288 } | 363 } |
| 289 | 364 |
| 290 Element* Fullscreen::CurrentFullScreenElementFrom(Document& document) { | |
| 291 if (Fullscreen* found = FromIfExists(document)) | |
| 292 return found->CurrentFullScreenElement(); | |
| 293 return nullptr; | |
| 294 } | |
| 295 | |
| 296 Element* Fullscreen::CurrentFullScreenElementForBindingFrom( | |
| 297 Document& document) { | |
| 298 Element* element = CurrentFullScreenElementFrom(document); | |
| 299 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | |
| 300 return element; | |
| 301 | |
| 302 // For Shadow DOM V0 compatibility: We allow returning an element in V0 shadow | |
| 303 // tree, even though it leaks the Shadow DOM. | |
| 304 if (element->IsInV0ShadowTree()) { | |
| 305 UseCounter::Count(document, | |
| 306 UseCounter::kDocumentFullscreenElementInV0Shadow); | |
| 307 return element; | |
| 308 } | |
| 309 return document.AdjustedElement(*element); | |
| 310 } | |
| 311 | |
| 312 bool Fullscreen::IsInFullscreenElementStack(const Element& element) { | 365 bool Fullscreen::IsInFullscreenElementStack(const Element& element) { |
| 313 const Fullscreen* found = FromIfExists(element.GetDocument()); | 366 const Fullscreen* found = FromIfExists(element.GetDocument()); |
| 314 if (!found) | 367 if (!found) |
| 315 return false; | 368 return false; |
| 316 for (size_t i = 0; i < found->fullscreen_element_stack_.size(); ++i) { | 369 for (size_t i = 0; i < found->fullscreen_element_stack_.size(); ++i) { |
| 317 if (found->fullscreen_element_stack_[i].first.Get() == &element) | 370 if (found->fullscreen_element_stack_[i].first.Get() == &element) |
| 318 return true; | 371 return true; |
| 319 } | 372 } |
| 320 return false; | 373 return false; |
| 321 } | 374 } |
| 322 | 375 |
| 323 Fullscreen::Fullscreen(Document& document) | 376 Fullscreen::Fullscreen(Document& document) |
| 324 : Supplement<Document>(document), | 377 : Supplement<Document>(document), |
| 325 ContextLifecycleObserver(&document), | 378 ContextLifecycleObserver(&document), |
| 326 full_screen_layout_object_(nullptr), | 379 full_screen_layout_object_(nullptr) { |
| 327 event_queue_timer_( | |
| 328 TaskRunnerHelper::Get(TaskType::kUnthrottled, &document), | |
| 329 this, | |
| 330 &Fullscreen::EventQueueTimerFired), | |
| 331 for_cross_process_descendant_(false) { | |
| 332 document.SetHasFullscreenSupplement(); | 380 document.SetHasFullscreenSupplement(); |
| 333 } | 381 } |
| 334 | 382 |
| 335 Fullscreen::~Fullscreen() {} | 383 Fullscreen::~Fullscreen() {} |
| 336 | 384 |
| 337 inline Document* Fullscreen::GetDocument() { | 385 Document* Fullscreen::GetDocument() { |
| 338 return ToDocument(LifecycleContext()); | 386 return ToDocument(LifecycleContext()); |
| 339 } | 387 } |
| 340 | 388 |
| 341 void Fullscreen::ContextDestroyed(ExecutionContext*) { | 389 void Fullscreen::ContextDestroyed(ExecutionContext*) { |
| 342 event_queue_.clear(); | |
| 343 | |
| 344 if (full_screen_layout_object_) | 390 if (full_screen_layout_object_) |
| 345 full_screen_layout_object_->Destroy(); | 391 full_screen_layout_object_->Destroy(); |
| 346 | 392 |
| 347 current_full_screen_element_ = nullptr; | 393 pending_requests_.clear(); |
| 348 fullscreen_element_stack_.clear(); | 394 fullscreen_element_stack_.clear(); |
| 349 } | 395 } |
| 350 | 396 |
| 351 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen | 397 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen |
| 352 void Fullscreen::RequestFullscreen(Element& element) { | 398 void Fullscreen::RequestFullscreen(Element& pending) { |
| 353 // TODO(foolip): Make RequestType::Unprefixed the default when the unprefixed | 399 // TODO(foolip): Make RequestType::Unprefixed the default when the unprefixed |
| 354 // API is enabled. https://crbug.com/383813 | 400 // API is enabled. https://crbug.com/383813 |
| 355 RequestFullscreen(element, RequestType::kPrefixed, false); | 401 RequestFullscreen(pending, RequestType::kPrefixed); |
| 356 } | 402 } |
| 357 | 403 |
| 358 void Fullscreen::RequestFullscreen(Element& element, | 404 void Fullscreen::RequestFullscreen(Element& pending, RequestType request_type) { |
| 359 RequestType request_type, | 405 Document& document = pending.GetDocument(); |
| 360 bool for_cross_process_descendant) { | 406 |
| 361 Document& document = element.GetDocument(); | 407 // Ignore this call if the document is not in a live frame. |
| 408 if (!document.IsActive() || !document.GetFrame()) |
| 409 return; |
| 410 |
| 411 bool for_cross_process_descendant = |
| 412 request_type == RequestType::kPrefixedForCrossProcessDescendant; |
| 362 | 413 |
| 363 // Use counters only need to be incremented in the process of the actual | 414 // Use counters only need to be incremented in the process of the actual |
| 364 // fullscreen element. | 415 // fullscreen element. |
| 365 if (!for_cross_process_descendant) { | 416 if (!for_cross_process_descendant) { |
| 366 if (document.IsSecureContext()) { | 417 if (document.IsSecureContext()) { |
| 367 UseCounter::Count(document, UseCounter::kFullscreenSecureOrigin); | 418 UseCounter::Count(document, UseCounter::kFullscreenSecureOrigin); |
| 368 } else { | 419 } else { |
| 369 UseCounter::Count(document, UseCounter::kFullscreenInsecureOrigin); | 420 UseCounter::Count(document, UseCounter::kFullscreenInsecureOrigin); |
| 370 HostsUsingFeatures::CountAnyWorld( | 421 HostsUsingFeatures::CountAnyWorld( |
| 371 document, HostsUsingFeatures::Feature::kFullscreenInsecureHost); | 422 document, HostsUsingFeatures::Feature::kFullscreenInsecureHost); |
| 372 } | 423 } |
| 373 } | 424 } |
| 374 | 425 |
| 375 // Ignore this call if the document is not in a live frame. | 426 // 1. Let |pending| be the context object. |
| 376 if (!document.IsActive() || !document.GetFrame()) | 427 |
| 377 return; | 428 // 2. Let |error| be false. |
| 378 | 429 bool error = false; |
| 379 // If |element| is on top of |doc|'s fullscreen element stack, terminate these | 430 |
| 380 // substeps. | 431 // 3. Let |promise| be a new promise. |
| 381 if (&element == FullscreenElementFrom(document)) | 432 // TODO(foolip): Promises. https://crbug.com/644637 |
| 382 return; | 433 |
| 383 | 434 // 4. If any of the following conditions are false, then set |error| to true: |
| 384 do { | 435 // |
| 385 // 1. If any of the following conditions are false, then terminate these | 436 // OOPIF: If |requestFullscreen()| was already called in a descendant frame |
| 386 // steps and queue a task to fire an event named fullscreenerror with its | 437 // and passed the checks, do not check again here. |
| 387 // bubbles attribute set to true on the context object's node document: | 438 if (!for_cross_process_descendant && |
| 388 | 439 !RequestFullscreenConditionsMet(pending, document)) |
| 389 // |element|'s namespace is the HTML namespace or |element| is an SVG | 440 error = true; |
| 390 // svg or MathML math element. | 441 |
| 391 // Note: MathML is not supported. | 442 // 5. Return |promise|, and run the remaining steps in parallel. |
| 392 if (!element.IsHTMLElement() && !isSVGSVGElement(element)) | 443 // TODO(foolip): Promises. https://crbug.com/644637 |
| 393 break; | 444 |
| 445 // 6. If |error| is false: Resize pending's top-level browsing context's |
| 446 // document's viewport's dimensions to match the dimensions of the screen of |
| 447 // the output device. Optionally display a message how the end user can |
| 448 // revert this. |
| 449 if (!error) { |
| 450 if (From(document).pending_requests_.size()) { |
| 451 UseCounter::Count(document, |
| 452 UseCounter::kFullscreenRequestWithPendingElement); |
| 453 } |
| 394 | 454 |
| 395 // TODO(foolip): In order to reinstate the hierarchy restrictions in the | 455 // TODO(foolip): In order to reinstate the hierarchy restrictions in the |
| 396 // spec, something has to prevent dialog elements from moving within top | 456 // spec, something has to prevent dialog elements from moving within top |
| 397 // layer. Either disallowing fullscreen for dialog elements entirely or just | 457 // layer. Either disallowing fullscreen for dialog elements entirely or just |
| 398 // preventing dialog elements from simultaneously being fullscreen and modal | 458 // preventing dialog elements from simultaneously being fullscreen and modal |
| 399 // are good candidates. See https://github.com/whatwg/fullscreen/pull/91 | 459 // are good candidates. See https://github.com/whatwg/fullscreen/pull/91 |
| 400 if (isHTMLDialogElement(element)) { | 460 if (isHTMLDialogElement(pending)) { |
| 401 UseCounter::Count(document, | 461 UseCounter::Count(document, |
| 402 UseCounter::kRequestFullscreenForDialogElement); | 462 UseCounter::kRequestFullscreenForDialogElement); |
| 403 if (element.IsInTopLayer()) { | 463 if (pending.IsInTopLayer()) { |
| 404 UseCounter::Count( | 464 UseCounter::Count( |
| 405 document, UseCounter::kRequestFullscreenForDialogElementInTopLayer); | 465 document, UseCounter::kRequestFullscreenForDialogElementInTopLayer); |
| 406 } | 466 } |
| 407 } | 467 } |
| 408 | 468 |
| 409 // The fullscreen element ready check for |element| returns true. | 469 From(document).pending_requests_.push_back( |
| 410 if (!FullscreenElementReady(element)) | 470 std::make_pair(&pending, request_type)); |
| 411 break; | 471 LocalFrame& frame = *document.GetFrame(); |
| 412 | 472 frame.GetChromeClient().EnterFullscreen(frame); |
| 413 // Fullscreen is supported. | 473 } else { |
| 414 if (!FullscreenIsSupported(document)) | 474 EnqueueTaskForRequest(document, pending, request_type, true); |
| 415 break; | 475 } |
| 416 | 476 } |
| 417 // This algorithm is allowed to request fullscreen. | 477 |
| 418 // OOPIF: If |forCrossProcessDescendant| is true, requestFullscreen was | 478 void Fullscreen::DidEnterFullscreen() { |
| 419 // already called on a descendant element in another process, and | 479 if (!GetDocument()) |
| 420 // getting here means that it was already allowed to request fullscreen. | 480 return; |
| 421 if (!for_cross_process_descendant && !AllowedToRequestFullscreen(document)) | 481 |
| 422 break; | 482 ElementStack requests; |
| 423 | 483 requests.swap(pending_requests_); |
| 424 // 2. Let doc be element's node document. (i.e. "this") | 484 for (const ElementStackEntry& request : requests) { |
| 425 | 485 EnqueueTaskForRequest(*GetDocument(), *request.first, request.second, |
| 426 // 3. Let docs be all doc's ancestor browsing context's documents (if any) | 486 false); |
| 427 // and doc. | 487 } |
| 428 // | 488 } |
| 429 // For OOPIF scenarios, |docs| will only contain documents for local | 489 |
| 430 // ancestors, and remote ancestors will be processed in their | 490 void Fullscreen::EnqueueTaskForRequest(Document& document, |
| 431 // respective processes. This preserves the spec's event firing order | 491 Element& pending, |
| 432 // for local ancestors, but not for remote ancestors. However, that | 492 RequestType request_type, |
| 433 // difference shouldn't be observable in practice: a fullscreenchange | 493 bool error) { |
| 434 // event handler would need to postMessage a frame in another renderer | 494 // 7. As part of the next animation frame task, run these substeps: |
| 435 // process, where the message should be queued up and processed after | 495 document.EnqueueAnimationFrameTask( |
| 436 // the IPC that dispatches fullscreenchange. | 496 WTF::Bind(&RunTaskForRequest, WrapPersistent(&document), |
| 437 HeapDeque<Member<Document>> docs; | 497 WrapPersistent(&pending), request_type, error)); |
| 438 for (Document* doc = &document; doc; doc = NextLocalAncestor(*doc)) | 498 } |
| 439 docs.push_front(doc); | 499 |
| 440 | 500 void Fullscreen::RunTaskForRequest(Document* document, |
| 441 // 4. For each document in docs, run these substeps: | 501 Element* element, |
| 442 HeapDeque<Member<Document>>::iterator current = docs.begin(), | 502 RequestType request_type, |
| 443 following = docs.begin(); | 503 bool error) { |
| 444 | 504 DCHECK(document); |
| 445 do { | 505 DCHECK(document->IsActive()); |
| 446 ++following; | 506 DCHECK(document->GetFrame()); |
| 447 | 507 DCHECK(element); |
| 448 // 1. Let following document be the document after document in docs, or | 508 |
| 449 // null if there is no such document. | 509 Document& pending_doc = *document; |
| 450 Document* current_doc = *current; | 510 Element& pending = *element; |
| 451 Document* following_doc = following != docs.end() ? *following : nullptr; | 511 |
| 452 | 512 // TODO(foolip): Spec something like: If |pending|'s node document is not |
| 453 // 2. If following document is null, push context object on document's | 513 // |pendingDoc|, then set |error| to true. |
| 454 // fullscreen element stack, and queue a task to fire an event named | 514 // https://github.com/whatwg/fullscreen/issues/33 |
| 455 // fullscreenchange with its bubbles attribute set to true on the | 515 if (pending.GetDocument() != pending_doc) |
| 456 // document. | 516 error = true; |
| 457 if (!following_doc) { | 517 |
| 458 From(*current_doc).PushFullscreenElementStack(element, request_type); | 518 // 7.1. If either |error| is true or the fullscreen element ready check for |
| 459 From(document).EnqueueChangeEvent(*current_doc, request_type); | 519 // |pending| returns false, fire an event named fullscreenerror on |
| 460 continue; | 520 // |pending|'s node document, reject |promise| with a TypeError exception, |
| 461 } | 521 // and terminate these steps. |
| 462 | 522 // TODO(foolip): Promises. https://crbug.com/644637 |
| 463 // 3. Otherwise, if document's fullscreen element stack is either empty or | 523 if (error || !FullscreenElementReady(pending)) { |
| 464 // its top element is not following document's browsing context container, | 524 Event* event = CreateErrorEvent(pending_doc, pending, request_type); |
| 465 Element* top_element = FullscreenElementFrom(*current_doc); | 525 event->target()->DispatchEvent(event); |
| 466 HTMLFrameOwnerElement* following_owner = | 526 return; |
| 467 FindContainerForDescendant(*current_doc, *following_doc); | 527 } |
| 468 if (!top_element || top_element != following_owner) { | 528 |
| 469 // ...push following document's browsing context container on document's | 529 // 7.2. Let |fullscreenElements| be an ordered set initially consisting of |
| 470 // fullscreen element stack, and queue a task to fire an event named | 530 // |pending|. |
| 471 // fullscreenchange with its bubbles attribute set to true on document. | 531 HeapDeque<Member<Element>> fullscreen_elements; |
| 472 From(*current_doc) | 532 fullscreen_elements.push_back(pending); |
| 473 .PushFullscreenElementStack(*following_owner, request_type); | 533 |
| 474 From(document).EnqueueChangeEvent(*current_doc, request_type); | 534 // 7.3. While the first element in |fullscreenElements| is in a nested |
| 475 continue; | 535 // browsing context, prepend its browsing context container to |
| 476 } | 536 // |fullscreenElements|. |
| 477 | 537 // |
| 478 // 4. Otherwise, do nothing for this document. It stays the same. | 538 // OOPIF: |fullscreenElements| will only contain elements for local ancestors, |
| 479 } while (++current != docs.end()); | 539 // and remote ancestors will be processed in their respective processes. This |
| 480 | 540 // preserves the spec's event firing order for local ancestors, but not for |
| 481 From(document).for_cross_process_descendant_ = for_cross_process_descendant; | 541 // remote ancestors. However, that difference shouldn't be observable in |
| 482 | 542 // practice: a fullscreenchange event handler would need to postMessage a |
| 483 // 5. Return, and run the remaining steps asynchronously. | 543 // frame in another renderer process, where the message should be queued up |
| 484 // 6. Optionally, perform some animation. | 544 // and processed after the IPC that dispatches fullscreenchange. |
| 485 if (From(document).pending_fullscreen_element_) { | 545 for (Frame* frame = pending.GetDocument().GetFrame(); frame; |
| 486 UseCounter::Count(document, | 546 frame = frame->Tree().Parent()) { |
| 487 UseCounter::kFullscreenRequestWithPendingElement); | 547 if (!frame->Owner() || !frame->Owner()->IsLocal()) |
| 488 } | 548 continue; |
| 489 From(document).pending_fullscreen_element_ = &element; | 549 Element* element = ToHTMLFrameOwnerElement(frame->Owner()); |
| 490 document.GetFrame()->GetChromeClient().EnterFullscreen( | 550 fullscreen_elements.push_front(element); |
| 491 *document.GetFrame()); | 551 } |
| 492 | 552 |
| 493 // 7. Optionally, display a message indicating how the user can exit | 553 // 7.4. Let |eventDocs| be an empty list. |
| 494 // displaying the context object fullscreen. | 554 // Note: For prefixed requests, the event target is an element, so instead |
| 495 return; | 555 // let |events| be a list of events to dispatch. |
| 496 } while (false); | 556 HeapVector<Member<Event>> events; |
| 497 | 557 |
| 498 From(document).EnqueueErrorEvent(element, request_type); | 558 // 7.5. For each |element| in |fullscreenElements|, in order, run these |
| 559 // subsubsteps: |
| 560 for (Element* element : fullscreen_elements) { |
| 561 // 7.5.1. Let |doc| be |element|'s node document. |
| 562 Document& doc = element->GetDocument(); |
| 563 |
| 564 // 7.5.2. If |element| is |doc|'s fullscreen element, terminate these |
| 565 // subsubsteps. |
| 566 if (element == FullscreenElementFrom(doc)) |
| 567 continue; |
| 568 |
| 569 // 7.5.3. Otherwise, append |doc| to |eventDocs|. |
| 570 events.push_back(CreateChangeEvent(doc, *element, request_type)); |
| 571 |
| 572 // 7.5.4. If |element| is |pending| and |pending| is an iframe element, |
| 573 // set |element|'s iframe fullscreen flag. |
| 574 // TODO(foolip): Support the iframe fullscreen flag. |
| 575 // https://crbug.com/644695 |
| 576 |
| 577 // 7.5.5. Fullscreen |element| within |doc|. |
| 578 // TODO(foolip): Merge fullscreen element stack into top layer. |
| 579 // https://crbug.com/627790 |
| 580 From(doc).PushFullscreenElementStack(*element, request_type); |
| 581 } |
| 582 |
| 583 // 7.6. For each |doc| in |eventDocs|, in order, fire an event named |
| 584 // fullscreenchange on |doc|. |
| 585 DispatchEvents(events); |
| 586 |
| 587 // 7.7. Fulfill |promise| with undefined. |
| 588 // TODO(foolip): Promises. https://crbug.com/644637 |
| 499 } | 589 } |
| 500 | 590 |
| 501 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen | 591 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen |
| 502 void Fullscreen::FullyExitFullscreen(Document& document) { | 592 void Fullscreen::FullyExitFullscreen(Document& document) { |
| 503 // To fully exit fullscreen, run these steps: | 593 // 1. If |document|'s fullscreen element is null, terminate these steps. |
| 504 | 594 |
| 505 // 1. Let |doc| be the top-level browsing context's document. | 595 // 2. Unfullscreen elements whose fullscreen flag is set, within |
| 596 // |document|'s top layer, except for |document|'s fullscreen element. |
| 597 |
| 598 // 3. Exit fullscreen |document|. |
| 599 |
| 600 // TODO(foolip): Change the spec. To remove elements from |document|'s top |
| 601 // layer as in step 2 could leave descendant frames in fullscreen. It may work |
| 602 // to give the "exit fullscreen" algorithm a |fully| flag that's used in the |
| 603 // animation frame task after exit. Here, retain the old behavior of fully |
| 604 // exiting fullscreen for the topmost local ancestor: |
| 605 ExitFullscreen(TopmostLocalAncestor(document), ExitType::kFully); |
| 606 } |
| 607 |
| 608 // https://fullscreen.spec.whatwg.org/#exit-fullscreen |
| 609 void Fullscreen::ExitFullscreen(Document& doc, ExitType exit_type) { |
| 610 if (!doc.IsActive() || !doc.GetFrame()) |
| 611 return; |
| 612 |
| 613 // 1. Let |promise| be a new promise. |
| 614 // 2. If |doc|'s fullscreen element is null, reject |promise| with a |
| 615 // TypeError exception, and return |promise|. |
| 616 // TODO(foolip): Promises. https://crbug.com/644637 |
| 617 if (!FullscreenElementFrom(doc)) |
| 618 return; |
| 619 |
| 620 // 3. Let |resize| be false. |
| 621 bool resize = false; |
| 622 |
| 623 // 4. Let |docs| be the result of collecting documents to unfullscreen given |
| 624 // |doc|. |
| 625 HeapVector<Member<Document>> docs = |
| 626 CollectDocumentsToUnfullscreen(doc, exit_type); |
| 627 |
| 628 // 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document. |
| 506 // | 629 // |
| 507 // Since the top-level browsing context's document might be unavailable in | 630 // OOPIF: Let |topLevelDoc| be the topmost local ancestor instead. If the main |
| 508 // OOPIF scenarios (i.e., when the top frame is remote), this actually uses | 631 // frame is in another process, we will still fully exit fullscreen even |
| 509 // the Document of the topmost local ancestor frame. Without OOPIF, this | 632 // though that's wrong if the main frame was in nested fullscreen. |
| 510 // will be the top frame's document. With OOPIF, each renderer process for | 633 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 511 // the current page will separately call fullyExitFullscreen to cover all | 634 // https://crbug.com/617369. |
| 512 // local frames in each process. | 635 Document& top_level_doc = TopmostLocalAncestor(doc); |
| 513 Document& doc = TopmostLocalAncestor(document); | 636 |
| 514 | 637 // 6. If |topLevelDoc| is in |docs|, set |resize| to true. |
| 515 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps. | 638 if (!docs.IsEmpty() && docs.back() == &top_level_doc) |
| 639 resize = true; |
| 640 |
| 641 // 7. Return |promise|, and run the remaining steps in parallel. |
| 642 // TODO(foolip): Promises. https://crbug.com/644637 |
| 643 |
| 644 // Note: |ExitType::Fully| is only used together with the topmost local |
| 645 // ancestor in |fullyExitFullscreen()|, and so implies that |resize| is true. |
| 646 // This would change if matching the spec for "fully exit fullscreen". |
| 647 if (exit_type == ExitType::kFully) |
| 648 DCHECK(resize); |
| 649 |
| 650 // 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal" |
| 651 // dimensions. |
| 652 if (resize) { |
| 653 LocalFrame& frame = *doc.GetFrame(); |
| 654 frame.GetChromeClient().ExitFullscreen(frame); |
| 655 } else { |
| 656 EnqueueTaskForExit(doc, exit_type); |
| 657 } |
| 658 } |
| 659 |
| 660 void Fullscreen::DidExitFullscreen() { |
| 661 if (!GetDocument()) |
| 662 return; |
| 663 |
| 664 DCHECK_EQ(GetDocument(), &TopmostLocalAncestor(*GetDocument())); |
| 665 |
| 666 EnqueueTaskForExit(*GetDocument(), ExitType::kFully); |
| 667 } |
| 668 |
| 669 void Fullscreen::EnqueueTaskForExit(Document& document, ExitType exit_type) { |
| 670 // 9. As part of the next animation frame task, run these substeps: |
| 671 document.EnqueueAnimationFrameTask( |
| 672 WTF::Bind(&RunTaskForExit, WrapPersistent(&document), exit_type)); |
| 673 } |
| 674 |
| 675 void Fullscreen::RunTaskForExit(Document* document, ExitType exit_type) { |
| 676 DCHECK(document); |
| 677 DCHECK(document->IsActive()); |
| 678 DCHECK(document->GetFrame()); |
| 679 |
| 680 Document& doc = *document; |
| 681 |
| 516 if (!FullscreenElementFrom(doc)) | 682 if (!FullscreenElementFrom(doc)) |
| 517 return; | 683 return; |
| 518 | 684 |
| 519 // 3. Remove elements from |doc|'s fullscreen element stack until only the top | 685 // 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen |
| 520 // element is left. | 686 // given |doc|. |
| 521 size_t stack_size = From(doc).fullscreen_element_stack_.size(); | 687 |
| 522 From(doc).fullscreen_element_stack_.erase(0, stack_size - 1); | 688 // 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully |
| 523 DCHECK_EQ(From(doc).fullscreen_element_stack_.size(), 1u); | 689 // exit fullscreen |topLevelDoc|, reject promise with a TypeError exception, |
| 524 | 690 // and terminate these steps. |
| 525 // 4. Act as if the exitFullscreen() method was invoked on |doc|. | 691 |
| 526 ExitFullscreen(doc); | 692 // TODO(foolip): See TODO in |fullyExitFullscreen()|. Instead of using "fully |
| 527 } | 693 // exit fullscreen" in step 9.2 (which is async), give "exit fullscreen" a |
| 528 | 694 // |fully| flag which is always true if |resize| was true. |
| 529 // https://fullscreen.spec.whatwg.org/#exit-fullscreen | 695 |
| 530 void Fullscreen::ExitFullscreen(Document& document) { | 696 HeapVector<Member<Document>> exit_docs = |
| 531 // The exitFullscreen() method must run these steps: | 697 CollectDocumentsToUnfullscreen(doc, exit_type); |
| 532 | 698 |
| 533 // Ignore this call if the document is not in a live frame. | 699 // 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|. |
| 534 if (!document.IsActive() || !document.GetFrame()) | 700 if (exit_docs.IsEmpty()) |
| 535 return; | 701 exit_docs.push_back(&doc); |
| 536 | 702 |
| 537 // 1. Let doc be the context object. (i.e. "this") | 703 // 9.4. If |exitDocs|'s last document has a browsing context container, |
| 538 // 2. If doc's fullscreen element stack is empty, terminate these steps. | 704 // append that browsing context container's node document to |exitDocs|. |
| 539 if (!FullscreenElementFrom(document)) | 705 // |
| 540 return; | 706 // OOPIF: Skip over remote frames, assuming that they have exactly one element |
| 541 | 707 // in their fullscreen element stacks, thereby erring on the side of exiting |
| 542 // 3. Let descendants be all the doc's descendant browsing context's documents | 708 // fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see |
| 543 // with a non-empty fullscreen element stack (if any), ordered so that the | 709 // https://crbug.com/617369. |
| 544 // child of the doc is last and the document furthest away from the doc is | 710 if (Document* document = NextLocalAncestor(*exit_docs.back())) |
| 545 // first. | 711 exit_docs.push_back(document); |
| 546 HeapDeque<Member<Document>> descendants; | 712 |
| 547 for (Frame* descendant = document.GetFrame()->Tree().TraverseNext(); | 713 // 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s |
| 548 descendant; descendant = descendant->Tree().TraverseNext()) { | 714 // descendant browsing contexts' documents whose fullscreen element is |
| 715 // non-null, if any, in *reverse* tree order. |
| 716 HeapDeque<Member<Document>> descendant_docs; |
| 717 for (Frame* descendant = doc.GetFrame()->Tree().FirstChild(); descendant; |
| 718 descendant = descendant->Tree().TraverseNext(doc.GetFrame())) { |
| 549 if (!descendant->IsLocalFrame()) | 719 if (!descendant->IsLocalFrame()) |
| 550 continue; | 720 continue; |
| 551 DCHECK(ToLocalFrame(descendant)->GetDocument()); | 721 DCHECK(ToLocalFrame(descendant)->GetDocument()); |
| 552 if (FullscreenElementFrom(*ToLocalFrame(descendant)->GetDocument())) | 722 if (FullscreenElementFrom(*ToLocalFrame(descendant)->GetDocument())) |
| 553 descendants.push_front(ToLocalFrame(descendant)->GetDocument()); | 723 descendant_docs.push_front(ToLocalFrame(descendant)->GetDocument()); |
| 554 } | 724 } |
| 555 | 725 |
| 556 // 4. For each descendant in descendants, empty descendant's fullscreen | 726 // Note: For prefixed requests, the event target is an element, so let |
| 557 // element stack, and queue a task to fire an event named fullscreenchange | 727 // |events| be a list of events to dispatch. |
| 558 // with its bubbles attribute set to true on descendant. | 728 HeapVector<Member<Event>> events; |
| 559 for (auto& descendant : descendants) { | 729 |
| 560 DCHECK(descendant); | 730 // 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen |
| 561 RequestType request_type = | 731 // |descendantDoc|. |
| 562 From(*descendant).fullscreen_element_stack_.back().second; | 732 for (Document* descendant_doc : descendant_docs) { |
| 563 From(*descendant).ClearFullscreenElementStack(); | 733 Fullscreen& fullscreen = From(*descendant_doc); |
| 564 From(document).EnqueueChangeEvent(*descendant, request_type); | 734 ElementStack& stack = fullscreen.fullscreen_element_stack_; |
| 565 } | 735 DCHECK(!stack.IsEmpty()); |
| 566 | 736 events.push_back(CreateChangeEvent(*descendant_doc, *stack.back().first, |
| 567 // 5. While doc is not null, run these substeps: | 737 stack.back().second)); |
| 568 Element* new_top = nullptr; | 738 while (!stack.IsEmpty()) |
| 569 for (Document* current_doc = &document; current_doc;) { | 739 fullscreen.PopFullscreenElementStack(); |
| 570 RequestType request_type = | 740 } |
| 571 From(*current_doc).fullscreen_element_stack_.back().second; | 741 |
| 572 | 742 // 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s |
| 573 // 1. Pop the top element of doc's fullscreen element stack. | 743 // fullscreen element. |
| 574 From(*current_doc).PopFullscreenElementStack(); | 744 for (Document* exit_doc : exit_docs) { |
| 575 | 745 Fullscreen& fullscreen = From(*exit_doc); |
| 576 // If doc's fullscreen element stack is non-empty and the element now at | 746 ElementStack& stack = fullscreen.fullscreen_element_stack_; |
| 577 // the top is either not in a document or its node document is not doc, | 747 DCHECK(!stack.IsEmpty()); |
| 578 // repeat this substep. | 748 events.push_back( |
| 579 new_top = FullscreenElementFrom(*current_doc); | 749 CreateChangeEvent(*exit_doc, *stack.back().first, stack.back().second)); |
| 580 if (new_top && | 750 fullscreen.PopFullscreenElementStack(); |
| 581 (!new_top->isConnected() || new_top->GetDocument() != current_doc)) | 751 |
| 582 continue; | 752 // TODO(foolip): See TODO in |fullyExitFullscreen()|. |
| 583 | 753 if (exit_doc == &doc && exit_type == ExitType::kFully) { |
| 584 // 2. Queue a task to fire an event named fullscreenchange with its bubbles | 754 while (!stack.IsEmpty()) |
| 585 // attribute set to true on doc. | 755 fullscreen.PopFullscreenElementStack(); |
| 586 From(document).EnqueueChangeEvent(*current_doc, request_type); | |
| 587 | |
| 588 // 3. If doc's fullscreen element stack is empty and doc's browsing context | |
| 589 // has a browsing context container, set doc to that browsing context | |
| 590 // container's node document. | |
| 591 // | |
| 592 // OOPIF: If browsing context container's document is in another | |
| 593 // process, keep moving up the ancestor chain and looking for a | |
| 594 // browsing context container with a local document. | |
| 595 // TODO(alexmos): Deal with nested fullscreen cases, see | |
| 596 // https://crbug.com/617369. | |
| 597 if (!new_top) { | |
| 598 current_doc = NextLocalAncestor(*current_doc); | |
| 599 continue; | |
| 600 } | 756 } |
| 601 | 757 } |
| 602 // 4. Otherwise, set doc to null. | 758 |
| 603 current_doc = nullptr; | 759 // 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an |
| 604 } | 760 // event named fullscreenchange on |descendantDoc|. |
| 605 | 761 // 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named |
| 606 // 6. Return, and run the remaining steps asynchronously. | 762 // fullscreenchange on |exitDoc|. |
| 607 // 7. Optionally, perform some animation. | 763 DispatchEvents(events); |
| 608 | 764 |
| 609 // Only exit fullscreen mode if the fullscreen element stack is empty. | 765 // 9.10. Fulfill |promise| with undefined. |
| 610 if (!new_top) { | 766 // TODO(foolip): Promises. https://crbug.com/644637 |
| 611 document.GetFrame()->GetChromeClient().ExitFullscreen(*document.GetFrame()); | |
| 612 return; | |
| 613 } | |
| 614 | |
| 615 // Otherwise, enter fullscreen for the fullscreen element stack's top element. | |
| 616 From(document).pending_fullscreen_element_ = new_top; | |
| 617 From(document).DidEnterFullscreen(); | |
| 618 } | 767 } |
| 619 | 768 |
| 620 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled | 769 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled |
| 621 bool Fullscreen::FullscreenEnabled(Document& document) { | 770 bool Fullscreen::FullscreenEnabled(Document& document) { |
| 622 // The fullscreenEnabled attribute's getter must return true if the context | 771 // The fullscreenEnabled attribute's getter must return true if the context |
| 623 // object is allowed to use the feature indicated by attribute name | 772 // object is allowed to use the feature indicated by attribute name |
| 624 // allowfullscreen and fullscreen is supported, and false otherwise. | 773 // allowfullscreen and fullscreen is supported, and false otherwise. |
| 625 return AllowedToUseFullscreen(document.GetFrame()) && | 774 return AllowedToUseFullscreen(document.GetFrame()) && |
| 626 FullscreenIsSupported(document); | 775 FullscreenIsSupported(document); |
| 627 } | 776 } |
| 628 | 777 |
| 629 void Fullscreen::DidEnterFullscreen() { | |
| 630 if (!GetDocument()->IsActive() || !GetDocument()->GetFrame()) | |
| 631 return; | |
| 632 | |
| 633 // Start the timer for events enqueued by |requestFullscreen()|. The hover | |
| 634 // state update is scheduled first so that it's done when the events fire. | |
| 635 GetDocument()->GetFrame()->GetEventHandler().ScheduleHoverStateUpdate(); | |
| 636 event_queue_timer_.StartOneShot(0, BLINK_FROM_HERE); | |
| 637 | |
| 638 Element* element = pending_fullscreen_element_.Release(); | |
| 639 if (!element) | |
| 640 return; | |
| 641 | |
| 642 if (current_full_screen_element_ == element) | |
| 643 return; | |
| 644 | |
| 645 if (!element->isConnected() || &element->GetDocument() != GetDocument()) { | |
| 646 // The element was removed or has moved to another document since the | |
| 647 // |requestFullscreen()| call. Exit fullscreen again to recover. | |
| 648 // TODO(foolip): Fire a fullscreenerror event. This is currently difficult | |
| 649 // because the fullscreenchange event has already been enqueued and possibly | |
| 650 // even fired. https://crbug.com/402376 | |
| 651 LocalFrame& frame = *GetDocument()->GetFrame(); | |
| 652 frame.GetChromeClient().ExitFullscreen(frame); | |
| 653 return; | |
| 654 } | |
| 655 | |
| 656 if (full_screen_layout_object_) | |
| 657 full_screen_layout_object_->UnwrapLayoutObject(); | |
| 658 | |
| 659 Element* previous_element = current_full_screen_element_; | |
| 660 current_full_screen_element_ = element; | |
| 661 | |
| 662 // Create a placeholder block for a the full-screen element, to keep the page | |
| 663 // from reflowing when the element is removed from the normal flow. Only do | |
| 664 // this for a LayoutBox, as only a box will have a frameRect. The placeholder | |
| 665 // will be created in setFullScreenLayoutObject() during layout. | |
| 666 LayoutObject* layout_object = current_full_screen_element_->GetLayoutObject(); | |
| 667 bool should_create_placeholder = layout_object && layout_object->IsBox(); | |
| 668 if (should_create_placeholder) { | |
| 669 saved_placeholder_frame_rect_ = ToLayoutBox(layout_object)->FrameRect(); | |
| 670 saved_placeholder_computed_style_ = | |
| 671 ComputedStyle::Clone(layout_object->StyleRef()); | |
| 672 } | |
| 673 | |
| 674 // TODO(alexmos): When |m_forCrossProcessDescendant| is true, some of | |
| 675 // this layout work has already been done in another process, so it should | |
| 676 // not be necessary to repeat it here. | |
| 677 if (current_full_screen_element_ != GetDocument()->documentElement()) { | |
| 678 LayoutFullScreen::WrapLayoutObject( | |
| 679 layout_object, layout_object ? layout_object->Parent() : 0, | |
| 680 GetDocument()); | |
| 681 } | |
| 682 | |
| 683 // When |m_forCrossProcessDescendant| is true, m_currentFullScreenElement | |
| 684 // corresponds to the HTMLFrameOwnerElement for the out-of-process iframe | |
| 685 // that contains the actual fullscreen element. Hence, it must also set | |
| 686 // the ContainsFullScreenElement flag (so that it gains the | |
| 687 // -webkit-full-screen-ancestor style). | |
| 688 if (for_cross_process_descendant_) { | |
| 689 DCHECK(current_full_screen_element_->IsFrameOwnerElement()); | |
| 690 DCHECK(ToHTMLFrameOwnerElement(current_full_screen_element_) | |
| 691 ->ContentFrame() | |
| 692 ->IsRemoteFrame()); | |
| 693 current_full_screen_element_->SetContainsFullScreenElement(true); | |
| 694 } | |
| 695 | |
| 696 current_full_screen_element_ | |
| 697 ->SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); | |
| 698 | |
| 699 GetDocument()->GetStyleEngine().EnsureUAStyleForFullscreen(); | |
| 700 current_full_screen_element_->PseudoStateChanged( | |
| 701 CSSSelector::kPseudoFullScreen); | |
| 702 | |
| 703 // FIXME: This should not call updateStyleAndLayoutTree. | |
| 704 GetDocument()->UpdateStyleAndLayoutTree(); | |
| 705 | |
| 706 GetDocument()->GetFrame()->GetChromeClient().FullscreenElementChanged( | |
| 707 previous_element, element); | |
| 708 } | |
| 709 | |
| 710 void Fullscreen::DidExitFullscreen() { | |
| 711 if (!GetDocument()->IsActive() || !GetDocument()->GetFrame()) | |
| 712 return; | |
| 713 | |
| 714 // Start the timer for events enqueued by |exitFullscreen()|. The hover state | |
| 715 // update is scheduled first so that it's done when the events fire. | |
| 716 GetDocument()->GetFrame()->GetEventHandler().ScheduleHoverStateUpdate(); | |
| 717 event_queue_timer_.StartOneShot(0, BLINK_FROM_HERE); | |
| 718 | |
| 719 // If fullscreen was canceled by the browser, e.g. if the user pressed Esc, | |
| 720 // then |exitFullscreen()| was never called. Let |fullyExitFullscreen()| clear | |
| 721 // the fullscreen element stack and fire any events as necessary. | |
| 722 // TODO(foolip): Remove this when state changes and events are synchronized | |
| 723 // with animation frames. https://crbug.com/402376 | |
| 724 FullyExitFullscreen(*GetDocument()); | |
| 725 | |
| 726 if (!current_full_screen_element_) | |
| 727 return; | |
| 728 | |
| 729 if (for_cross_process_descendant_) | |
| 730 current_full_screen_element_->SetContainsFullScreenElement(false); | |
| 731 | |
| 732 current_full_screen_element_ | |
| 733 ->SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); | |
| 734 | |
| 735 if (full_screen_layout_object_) | |
| 736 LayoutFullScreenItem(full_screen_layout_object_).UnwrapLayoutObject(); | |
| 737 | |
| 738 GetDocument()->GetStyleEngine().EnsureUAStyleForFullscreen(); | |
| 739 current_full_screen_element_->PseudoStateChanged( | |
| 740 CSSSelector::kPseudoFullScreen); | |
| 741 Element* previous_element = current_full_screen_element_; | |
| 742 current_full_screen_element_ = nullptr; | |
| 743 | |
| 744 for_cross_process_descendant_ = false; | |
| 745 | |
| 746 GetDocument()->GetFrame()->GetChromeClient().FullscreenElementChanged( | |
| 747 previous_element, nullptr); | |
| 748 } | |
| 749 | |
| 750 void Fullscreen::SetFullScreenLayoutObject(LayoutFullScreen* layout_object) { | 778 void Fullscreen::SetFullScreenLayoutObject(LayoutFullScreen* layout_object) { |
| 751 if (layout_object == full_screen_layout_object_) | 779 if (layout_object == full_screen_layout_object_) |
| 752 return; | 780 return; |
| 753 | 781 |
| 754 if (layout_object && saved_placeholder_computed_style_) { | 782 if (layout_object && saved_placeholder_computed_style_) { |
| 755 layout_object->CreatePlaceholder( | 783 layout_object->CreatePlaceholder( |
| 756 std::move(saved_placeholder_computed_style_), | 784 std::move(saved_placeholder_computed_style_), |
| 757 saved_placeholder_frame_rect_); | 785 saved_placeholder_frame_rect_); |
| 758 } else if (layout_object && full_screen_layout_object_ && | 786 } else if (layout_object && full_screen_layout_object_ && |
| 759 full_screen_layout_object_->Placeholder()) { | 787 full_screen_layout_object_->Placeholder()) { |
| 760 LayoutBlockFlow* placeholder = full_screen_layout_object_->Placeholder(); | 788 LayoutBlockFlow* placeholder = full_screen_layout_object_->Placeholder(); |
| 761 layout_object->CreatePlaceholder( | 789 layout_object->CreatePlaceholder( |
| 762 ComputedStyle::Clone(placeholder->StyleRef()), | 790 ComputedStyle::Clone(placeholder->StyleRef()), |
| 763 placeholder->FrameRect()); | 791 placeholder->FrameRect()); |
| 764 } | 792 } |
| 765 | 793 |
| 766 if (full_screen_layout_object_) | 794 if (full_screen_layout_object_) |
| 767 full_screen_layout_object_->UnwrapLayoutObject(); | 795 full_screen_layout_object_->UnwrapLayoutObject(); |
| 768 DCHECK(!full_screen_layout_object_); | 796 DCHECK(!full_screen_layout_object_); |
| 769 | 797 |
| 770 full_screen_layout_object_ = layout_object; | 798 full_screen_layout_object_ = layout_object; |
| 771 } | 799 } |
| 772 | 800 |
| 773 void Fullscreen::FullScreenLayoutObjectDestroyed() { | 801 void Fullscreen::FullScreenLayoutObjectDestroyed() { |
| 774 full_screen_layout_object_ = nullptr; | 802 full_screen_layout_object_ = nullptr; |
| 775 } | 803 } |
| 776 | 804 |
| 777 void Fullscreen::EnqueueChangeEvent(Document& document, | 805 void Fullscreen::ElementRemoved(Element& node) { |
| 778 RequestType request_type) { | 806 DCHECK_EQ(GetDocument(), &node.GetDocument()); |
| 779 Event* event; | |
| 780 if (request_type == RequestType::kUnprefixed) { | |
| 781 event = CreateEvent(EventTypeNames::fullscreenchange, document); | |
| 782 } else { | |
| 783 DCHECK(document.HasFullscreenSupplement()); | |
| 784 Fullscreen& fullscreen = From(document); | |
| 785 EventTarget* target = fullscreen.FullscreenElement(); | |
| 786 if (!target) | |
| 787 target = fullscreen.CurrentFullScreenElement(); | |
| 788 if (!target) | |
| 789 target = &document; | |
| 790 event = CreateEvent(EventTypeNames::webkitfullscreenchange, *target); | |
| 791 } | |
| 792 event_queue_.push_back(event); | |
| 793 // NOTE: The timer is started in didEnterFullscreen/didExitFullscreen. | |
| 794 } | |
| 795 | 807 |
| 796 void Fullscreen::EnqueueErrorEvent(Element& element, RequestType request_type) { | |
| 797 Event* event; | |
| 798 if (request_type == RequestType::kUnprefixed) | |
| 799 event = CreateEvent(EventTypeNames::fullscreenerror, element.GetDocument()); | |
| 800 else | |
| 801 event = CreateEvent(EventTypeNames::webkitfullscreenerror, element); | |
| 802 event_queue_.push_back(event); | |
| 803 event_queue_timer_.StartOneShot(0, BLINK_FROM_HERE); | |
| 804 } | |
| 805 | |
| 806 void Fullscreen::EventQueueTimerFired(TimerBase*) { | |
| 807 HeapDeque<Member<Event>> event_queue; | |
| 808 event_queue_.Swap(event_queue); | |
| 809 | |
| 810 while (!event_queue.IsEmpty()) { | |
| 811 Event* event = event_queue.TakeFirst(); | |
| 812 Node* target = event->target()->ToNode(); | |
| 813 | |
| 814 // If the element was removed from our tree, also message the | |
| 815 // documentElement. | |
| 816 if (!target->isConnected() && GetDocument()->documentElement()) { | |
| 817 DCHECK(IsPrefixed(event->type())); | |
| 818 event_queue.push_back( | |
| 819 CreateEvent(event->type(), *GetDocument()->documentElement())); | |
| 820 } | |
| 821 | |
| 822 target->DispatchEvent(event); | |
| 823 } | |
| 824 } | |
| 825 | |
| 826 void Fullscreen::ElementRemoved(Element& node) { | |
| 827 // |Fullscreen::ElementRemoved| is called for each removed element, so only | 808 // |Fullscreen::ElementRemoved| is called for each removed element, so only |
| 828 // the body of the spec "removing steps" loop appears here: | 809 // the body of the spec "removing steps" loop appears here: |
| 829 | 810 |
| 830 // 2.1. If |node| is its node document's fullscreen element, exit fullscreen | 811 // 2.1. If |node| is its node document's fullscreen element, exit fullscreen |
| 831 // that document. | 812 // that document. |
| 832 if (FullscreenElement() == &node) { | 813 if (FullscreenElement() == &node) { |
| 833 ExitFullscreen(node.GetDocument()); | 814 ExitFullscreen(node.GetDocument()); |
| 834 return; | 815 return; |
| 835 } | 816 } |
| 836 | 817 |
| 837 // 2.2. Otherwise, unfullscreen |node| within its node document. | 818 // 2.2. Otherwise, unfullscreen |node| within its node document. |
| 838 for (size_t i = 0; i < fullscreen_element_stack_.size(); ++i) { | 819 for (size_t i = 0; i < fullscreen_element_stack_.size(); ++i) { |
| 839 if (fullscreen_element_stack_[i].first.Get() == &node) { | 820 if (fullscreen_element_stack_[i].first.Get() == &node) { |
| 840 fullscreen_element_stack_.erase(i); | 821 fullscreen_element_stack_.erase(i); |
| 841 return; | 822 return; |
| 842 } | 823 } |
| 843 } | 824 } |
| 844 | 825 |
| 845 // Note: |node| was not in the fullscreen element stack. | 826 // Note: |node| was not in the fullscreen element stack. |
| 846 } | 827 } |
| 847 | 828 |
| 848 void Fullscreen::ClearFullscreenElementStack() { | 829 void Fullscreen::PopFullscreenElementStack() { |
| 849 if (fullscreen_element_stack_.IsEmpty()) | 830 DCHECK(!fullscreen_element_stack_.IsEmpty()); |
| 850 return; | |
| 851 | 831 |
| 852 fullscreen_element_stack_.clear(); | 832 Element* previous_element = FullscreenElement(); |
| 853 | |
| 854 SetNeedsPaintPropertyUpdate(GetDocument()); | |
| 855 } | |
| 856 | |
| 857 void Fullscreen::PopFullscreenElementStack() { | |
| 858 if (fullscreen_element_stack_.IsEmpty()) | |
| 859 return; | |
| 860 | |
| 861 fullscreen_element_stack_.pop_back(); | 833 fullscreen_element_stack_.pop_back(); |
| 862 | 834 |
| 863 SetNeedsPaintPropertyUpdate(GetDocument()); | 835 // Note: |requestType| is only used if |fullscreenElement()| is non-null. |
| 836 RequestType request_type = fullscreen_element_stack_.IsEmpty() |
| 837 ? RequestType::kUnprefixed |
| 838 : fullscreen_element_stack_.back().second; |
| 839 FullscreenElementChanged(previous_element, FullscreenElement(), request_type); |
| 864 } | 840 } |
| 865 | 841 |
| 866 void Fullscreen::PushFullscreenElementStack(Element& element, | 842 void Fullscreen::PushFullscreenElementStack(Element& element, |
| 867 RequestType request_type) { | 843 RequestType request_type) { |
| 844 Element* previous_element = FullscreenElement(); |
| 868 fullscreen_element_stack_.push_back(std::make_pair(&element, request_type)); | 845 fullscreen_element_stack_.push_back(std::make_pair(&element, request_type)); |
| 869 | 846 |
| 870 SetNeedsPaintPropertyUpdate(GetDocument()); | 847 FullscreenElementChanged(previous_element, &element, request_type); |
| 848 } |
| 849 |
| 850 void Fullscreen::FullscreenElementChanged(Element* from_element, |
| 851 Element* to_element, |
| 852 RequestType to_request_type) { |
| 853 DCHECK_NE(from_element, to_element); |
| 854 |
| 855 if (!GetDocument()) |
| 856 return; |
| 857 |
| 858 GetDocument()->GetStyleEngine().EnsureUAStyleForFullscreen(); |
| 859 |
| 860 if (full_screen_layout_object_) |
| 861 full_screen_layout_object_->UnwrapLayoutObject(); |
| 862 DCHECK(!full_screen_layout_object_); |
| 863 |
| 864 if (from_element) { |
| 865 DCHECK_NE(from_element, FullscreenElement()); |
| 866 |
| 867 from_element->PseudoStateChanged(CSSSelector::kPseudoFullScreen); |
| 868 |
| 869 from_element->SetContainsFullScreenElement(false); |
| 870 from_element |
| 871 ->SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); |
| 872 } |
| 873 |
| 874 if (to_element) { |
| 875 DCHECK_EQ(to_element, FullscreenElement()); |
| 876 |
| 877 to_element->PseudoStateChanged(CSSSelector::kPseudoFullScreen); |
| 878 |
| 879 // OOPIF: For RequestType::PrefixedForCrossProcessDescendant, |toElement| is |
| 880 // the iframe element for the out-of-process frame that contains the |
| 881 // fullscreen element. Hence, it must match :-webkit-full-screen-ancestor. |
| 882 if (to_request_type == RequestType::kPrefixedForCrossProcessDescendant) { |
| 883 DCHECK(isHTMLIFrameElement(to_element)); |
| 884 to_element->SetContainsFullScreenElement(true); |
| 885 } |
| 886 to_element->SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries( |
| 887 true); |
| 888 |
| 889 // Create a placeholder block for the fullscreen element, to keep the page |
| 890 // from reflowing when the element is removed from the normal flow. Only do |
| 891 // this for a LayoutBox, as only a box will have a frameRect. The |
| 892 // placeholder will be created in setFullScreenLayoutObject() during layout. |
| 893 LayoutObject* layout_object = to_element->GetLayoutObject(); |
| 894 bool should_create_placeholder = layout_object && layout_object->IsBox(); |
| 895 if (should_create_placeholder) { |
| 896 saved_placeholder_frame_rect_ = ToLayoutBox(layout_object)->FrameRect(); |
| 897 saved_placeholder_computed_style_ = |
| 898 ComputedStyle::Clone(layout_object->StyleRef()); |
| 899 } |
| 900 |
| 901 if (to_element != GetDocument()->documentElement()) { |
| 902 LayoutFullScreen::WrapLayoutObject( |
| 903 layout_object, layout_object ? layout_object->Parent() : 0, |
| 904 GetDocument()); |
| 905 } |
| 906 } |
| 907 |
| 908 if (LocalFrame* frame = GetDocument()->GetFrame()) { |
| 909 // TODO(foolip): Synchronize hover state changes with animation frames. |
| 910 // https://crbug.com/668758 |
| 911 frame->GetEventHandler().ScheduleHoverStateUpdate(); |
| 912 frame->GetChromeClient().FullscreenElementChanged(from_element, to_element); |
| 913 |
| 914 if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() && |
| 915 !RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { |
| 916 // Fullscreen status affects scroll paint properties through |
| 917 // LocalFrameView::UserInputScrollable(). |
| 918 if (LocalFrameView* frame_view = frame->View()) |
| 919 frame_view->SetNeedsPaintPropertyUpdate(); |
| 920 } |
| 921 } |
| 922 |
| 923 // TODO(foolip): This should not call updateStyleAndLayoutTree. |
| 924 GetDocument()->UpdateStyleAndLayoutTree(); |
| 871 } | 925 } |
| 872 | 926 |
| 873 DEFINE_TRACE(Fullscreen) { | 927 DEFINE_TRACE(Fullscreen) { |
| 874 visitor->Trace(pending_fullscreen_element_); | 928 visitor->Trace(pending_requests_); |
| 875 visitor->Trace(fullscreen_element_stack_); | 929 visitor->Trace(fullscreen_element_stack_); |
| 876 visitor->Trace(current_full_screen_element_); | |
| 877 visitor->Trace(event_queue_); | |
| 878 Supplement<Document>::Trace(visitor); | 930 Supplement<Document>::Trace(visitor); |
| 879 ContextLifecycleObserver::Trace(visitor); | 931 ContextLifecycleObserver::Trace(visitor); |
| 880 } | 932 } |
| 881 | 933 |
| 882 } // namespace blink | 934 } // namespace blink |
| OLD | NEW |