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 11 matching lines...) Expand all Loading... |
22 * | 22 * |
23 * You should have received a copy of the GNU Library General Public License | 23 * You should have received a copy of the GNU Library General Public License |
24 * along with this library; see the file COPYING.LIB. If not, write to | 24 * along with this library; see the file COPYING.LIB. If not, write to |
25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
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/ExceptionMessages.h" |
32 #include "core/dom/Document.h" | 33 #include "core/dom/Document.h" |
33 #include "core/dom/ElementTraversal.h" | 34 #include "core/dom/StyleChangeReason.h" |
34 #include "core/dom/StyleEngine.h" | 35 #include "core/dom/StyleEngine.h" |
35 #include "core/events/Event.h" | 36 #include "core/events/Event.h" |
36 #include "core/frame/FrameHost.h" | 37 #include "core/frame/FrameHost.h" |
37 #include "core/frame/HostsUsingFeatures.h" | 38 #include "core/frame/HostsUsingFeatures.h" |
38 #include "core/frame/LocalFrame.h" | 39 #include "core/frame/LocalFrame.h" |
39 #include "core/frame/Settings.h" | 40 #include "core/frame/Settings.h" |
40 #include "core/frame/UseCounter.h" | 41 #include "core/frame/UseCounter.h" |
41 #include "core/html/HTMLIFrameElement.h" | 42 #include "core/html/HTMLFrameOwnerElement.h" |
42 #include "core/input/EventHandler.h" | 43 #include "core/input/EventHandler.h" |
43 #include "core/inspector/ConsoleMessage.h" | 44 #include "core/inspector/ConsoleMessage.h" |
44 #include "core/layout/LayoutBlockFlow.h" | |
45 #include "core/layout/LayoutFullScreen.h" | |
46 #include "core/layout/api/LayoutFullScreenItem.h" | |
47 #include "core/page/ChromeClient.h" | 45 #include "core/page/ChromeClient.h" |
48 #include "core/svg/SVGSVGElement.h" | 46 #include "core/svg/SVGSVGElement.h" |
49 #include "platform/ScopedOrientationChangeIndicator.h" | 47 #include "platform/ScopedOrientationChangeIndicator.h" |
50 #include "platform/UserGestureIndicator.h" | 48 #include "platform/UserGestureIndicator.h" |
51 | 49 |
52 namespace blink { | 50 namespace blink { |
53 | 51 |
54 namespace { | 52 namespace { |
55 | 53 |
| 54 // https://fullscreen.spec.whatwg.org/#fullscreen-an-element |
| 55 void fullscreen(Element& element, Fullscreen::RequestType requestType) { |
| 56 // To fullscreen an |element| within a |document|, set the |element|'s |
| 57 // fullscreen flag and add it to |document|'s top layer. |
| 58 DCHECK(!element.isFullscreen()); |
| 59 // DCHECK(!element.isIFrameFullscreen()); |
| 60 DCHECK(!element.isInTopLayer()); |
| 61 element.setIsFullscreen(true); |
| 62 element.document().addToTopLayer(&element); |
| 63 } |
| 64 |
| 65 // https://fullscreen.spec.whatwg.org/#unfullscreen-an-element |
| 66 void unfullscreen(Element& element) { |
| 67 // To unfullscreen an |element| within a |document|, unset the element's |
| 68 // fullscreen flag and iframe fullscreen flag (if any), and remove it from |
| 69 // |document|'s top layer. |
| 70 DCHECK(element.isFullscreen()); |
| 71 DCHECK(element.isInTopLayer()); |
| 72 element.setIsFullscreen(true); |
| 73 // element.setIsIFrameFullscreen(false); |
| 74 element.document().removeFromTopLayer(&element); |
| 75 } |
| 76 |
| 77 // https://fullscreen.spec.whatwg.org/#unfullscreen-a-document |
| 78 void unfullscreen(Document& document) { |
| 79 // To unfullscreen a |document|, unfullscreen all elements, within |
| 80 // |document|'s top layer, whose fullscreen flag is set. |
| 81 for (Element* element : document.topLayerElements()) { |
| 82 if (element->isFullscreen()) |
| 83 unfullscreen(*element); |
| 84 } |
| 85 } |
| 86 |
56 // https://html.spec.whatwg.org/multipage/embedded-content.html#allowed-to-use | 87 // https://html.spec.whatwg.org/multipage/embedded-content.html#allowed-to-use |
57 bool allowedToUseFullscreen(const Frame* frame) { | 88 bool allowedToUseFullscreen(const Frame* frame) { |
58 // To determine whether a Document object |document| is allowed to use the | 89 // To determine whether a Document object |document| is allowed to use the |
59 // feature indicated by attribute name |allowattribute|, run these steps: | 90 // feature indicated by attribute name |allowattribute|, run these steps: |
60 | 91 |
61 // 1. If |document| has no browsing context, then return false. | 92 // 1. If |document| has no browsing context, then return false. |
62 if (!frame) | 93 if (!frame) |
63 return false; | 94 return false; |
64 | 95 |
65 // 2. If |document|'s browsing context is a top-level browsing context, then | 96 // 2. If |document|'s browsing context is a top-level browsing context, then |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 | 144 |
114 // |element| is in a document. | 145 // |element| is in a document. |
115 if (!element.isConnected()) | 146 if (!element.isConnected()) |
116 return false; | 147 return false; |
117 | 148 |
118 // |element|'s node document is allowed to use the feature indicated by | 149 // |element|'s node document is allowed to use the feature indicated by |
119 // attribute name allowfullscreen. | 150 // attribute name allowfullscreen. |
120 if (!allowedToUseFullscreen(element.document().frame())) | 151 if (!allowedToUseFullscreen(element.document().frame())) |
121 return false; | 152 return false; |
122 | 153 |
123 // |element|'s node document's fullscreen element stack is either empty or its | |
124 // top element is an inclusive ancestor of |element|. | |
125 if (const Element* topElement = | |
126 Fullscreen::fullscreenElementFrom(element.document())) { | |
127 if (!topElement->contains(&element)) | |
128 return false; | |
129 } | |
130 | |
131 // |element| has no ancestor element whose local name is iframe and namespace | |
132 // is the HTML namespace. | |
133 if (Traversal<HTMLIFrameElement>::firstAncestor(element)) | |
134 return false; | |
135 | |
136 // |element|'s node document's browsing context either has a browsing context | 154 // |element|'s node document's browsing context either has a browsing context |
137 // container and the fullscreen element ready check returns true for | 155 // container and the fullscreen element ready check returns true for |
138 // |element|'s node document's browsing context's browsing context container, | 156 // |element|'s node document's browsing context's browsing context container, |
139 // or it has no browsing context container. | 157 // or it has no browsing context container. |
140 if (const Element* owner = element.document().localOwner()) { | 158 if (const Element* owner = element.document().localOwner()) { |
141 if (!fullscreenElementReady(*owner)) | 159 if (!fullscreenElementReady(*owner)) |
142 return false; | 160 return false; |
143 } | 161 } |
144 | 162 |
145 return true; | 163 return true; |
146 } | 164 } |
147 | 165 |
148 bool isPrefixed(const AtomicString& type) { | 166 size_t countFullscreenInTopLayer(const Document& document) { |
149 return type == EventTypeNames::webkitfullscreenchange || | 167 size_t count = 0; |
150 type == EventTypeNames::webkitfullscreenerror; | 168 for (Element* element : document.topLayerElements()) { |
| 169 if (element->isFullscreen()) |
| 170 ++count; |
| 171 } |
| 172 return count; |
151 } | 173 } |
152 | 174 |
153 Event* createEvent(const AtomicString& type, EventTarget& target) { | 175 // https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen |
154 EventInit initializer; | 176 HeapVector<Member<Document>> collectDocumentsToUnfullscreen(Document& doc) { |
155 initializer.setBubbles(isPrefixed(type)); | 177 // 1. If |doc|'s top layer consists of more than a single element that has |
156 Event* event = Event::create(type, initializer); | 178 // its fullscreen flag set, return the empty set. |
157 event->setTarget(&target); | 179 |
158 return event; | 180 // 2. Let |docs| be an ordered set consisting of |doc|. |
| 181 |
| 182 // 3. While |docs|'s last document has a browsing context container whose |
| 183 // node document's top layer consists of a single element that has its |
| 184 // fullscreen flag set and does not have its iframe fullscreen flag set (if |
| 185 // any), append that node document to |docs|. |
| 186 // TODO(foolip): Support the iframe fullscreen flag. |
| 187 // https://crbug.com/644695 |
| 188 |
| 189 // OOPIF: Skip over remote frames, assuming that they have exactly one |
| 190 // element in their fullscreen element stacks, thereby erring on the side of |
| 191 // exiting fullscreen. |
| 192 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 193 // https://crbug.com/617369. |
| 194 |
| 195 HeapVector<Member<Document>> docs; |
| 196 for (Frame* frame = doc.frame(); frame; frame = frame->tree().parent()) { |
| 197 if (frame->isRemoteFrame()) |
| 198 continue; |
| 199 if (Document* document = toLocalFrame(frame)->document()) { |
| 200 // TODO(foolip): Fix the spec to count number of "fullscreen flag" |
| 201 // elements in top layer, to avoid dialogs messing up the logic. |
| 202 if (countFullscreenInTopLayer(*document) == 1) |
| 203 docs.append(document); |
| 204 else |
| 205 break; |
| 206 } |
| 207 } |
| 208 |
| 209 // 4. Return |docs|. |
| 210 return docs; |
159 } | 211 } |
160 | 212 |
161 // Helper to walk the ancestor chain and return the Document of the topmost | 213 // Helper to walk the ancestor chain and return the Document of the topmost |
162 // local ancestor frame. Note that this is not the same as the topmost frame's | 214 // local ancestor frame. Note that this is not the same as the topmost frame's |
163 // Document, which might be unavailable in OOPIF scenarios. For example, with | 215 // Document, which might be unavailable in OOPIF scenarios. For example, with |
164 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in | 216 // OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in |
165 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. | 217 // process B, this will skip remote frame C and return this frame: A-[B]-C-B. |
166 Document& topmostLocalAncestor(Document& document) { | 218 Document& topmostLocalAncestor(Document& document) { |
167 Document* topmost = &document; | 219 Document* topmost = &document; |
168 Frame* frame = document.frame(); | 220 Frame* frame = document.frame(); |
169 while (frame) { | 221 while (frame) { |
170 frame = frame->tree().parent(); | 222 frame = frame->tree().parent(); |
171 if (frame && frame->isLocalFrame()) | 223 if (frame && frame->isLocalFrame()) |
172 topmost = toLocalFrame(frame)->document(); | 224 topmost = toLocalFrame(frame)->document(); |
173 } | 225 } |
174 return *topmost; | 226 return *topmost; |
175 } | 227 } |
176 | 228 |
177 // Helper to find the browsing context container in |doc| that embeds the | 229 void fireChangeEvent(Element& element, Fullscreen::RequestType requestType) { |
178 // |descendant| Document, possibly through multiple levels of nesting. This | 230 if (requestType == Fullscreen::UnprefixedRequest) { |
179 // works even in OOPIF scenarios like A-B-A, where there may be remote frames | 231 Event* event = Event::create(EventTypeNames::fullscreenchange); |
180 // in between |doc| and |descendant|. | 232 element.document().dispatchEvent(event); |
181 HTMLFrameOwnerElement* findContainerForDescendant(const Document& doc, | 233 } else { |
182 const Document& descendant) { | 234 bool wasConnected = element.isConnected(); |
183 Frame* frame = descendant.frame(); | 235 |
184 while (frame->tree().parent() != doc.frame()) | 236 Event* event = Event::createBubble(EventTypeNames::webkitfullscreenchange); |
185 frame = frame->tree().parent(); | 237 element.dispatchEvent(event); |
186 return toHTMLFrameOwnerElement(frame->owner()); | 238 |
| 239 // TODO(foolip): Talk to Edge and WebKit teams about what to do if the |
| 240 // element isn't connected. |
| 241 if (!wasConnected) { |
| 242 if (Element* documentElement = element.document().documentElement()) { |
| 243 Event* documentEvent = |
| 244 Event::createBubble(EventTypeNames::webkitfullscreenchange); |
| 245 documentElement->dispatchEvent(documentEvent); |
| 246 } |
| 247 } |
| 248 } |
| 249 } |
| 250 |
| 251 void fireErrorEvent(Element& element, Fullscreen::RequestType requestType) { |
| 252 if (requestType == Fullscreen::UnprefixedRequest) { |
| 253 Event* event = Event::create(EventTypeNames::fullscreenerror); |
| 254 element.document().dispatchEvent(event); |
| 255 } else { |
| 256 Event* event = Event::createBubble(EventTypeNames::webkitfullscreenerror); |
| 257 element.dispatchEvent(event); |
| 258 } |
187 } | 259 } |
188 | 260 |
189 } // anonymous namespace | 261 } // anonymous namespace |
190 | 262 |
191 const char* Fullscreen::supplementName() { | 263 Element* Fullscreen::fullscreenElement(Document& document) { |
192 return "Fullscreen"; | 264 // The fullscreen element is the topmost element in the document's top layer |
193 } | 265 // whose fullscreen flag is set, if any, and null otherwise. |
194 | 266 |
195 Fullscreen& Fullscreen::from(Document& document) { | 267 const auto& elements = document.topLayerElements(); |
196 Fullscreen* fullscreen = fromIfExists(document); | 268 for (auto it = elements.rbegin(); it != elements.rend(); ++it) { |
197 if (!fullscreen) { | 269 Element* element = (*it).get(); |
198 fullscreen = new Fullscreen(document); | 270 if (element->isFullscreen()) |
199 Supplement<Document>::provideTo(document, supplementName(), fullscreen); | 271 return element; |
200 } | 272 } |
201 | 273 |
202 return *fullscreen; | |
203 } | |
204 | |
205 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) { | |
206 return static_cast<Fullscreen*>( | |
207 Supplement<Document>::from(document, supplementName())); | |
208 } | |
209 | |
210 Element* Fullscreen::fullscreenElementFrom(Document& document) { | |
211 if (Fullscreen* found = fromIfExists(document)) | |
212 return found->fullscreenElement(); | |
213 return nullptr; | 274 return nullptr; |
214 } | 275 } |
215 | 276 |
216 Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) { | 277 // https://fullscreen.spec.whatwg.org/#fullscreen-element |
217 Element* element = fullscreenElementFrom(scope.document()); | 278 Element* Fullscreen::fullscreenElementForBinding(TreeScope& scope) { |
| 279 Element* element = fullscreenElement(scope.document()); |
218 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | 280 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) |
219 return element; | 281 return element; |
220 | 282 |
221 // TODO(kochi): Once V0 code is removed, we can use the same logic for | 283 // TODO(kochi): Once V0 code is removed, we can use the same logic for |
222 // Document and ShadowRoot. | 284 // Document and ShadowRoot. |
223 if (!scope.rootNode().isShadowRoot()) { | 285 if (!scope.rootNode().isShadowRoot()) { |
224 // For Shadow DOM V0 compatibility: We allow returning an element in V0 | 286 // For Shadow DOM V0 compatibility: We allow returning an element in V0 |
225 // shadow tree, even though it leaks the Shadow DOM. | 287 // shadow tree, even though it leaks the Shadow DOM. |
226 if (element->isInV0ShadowTree()) { | 288 if (element->isInV0ShadowTree()) { |
227 UseCounter::count(scope.document(), | 289 UseCounter::count(scope.document(), |
228 UseCounter::DocumentFullscreenElementInV0Shadow); | 290 UseCounter::DocumentFullscreenElementInV0Shadow); |
229 return element; | 291 return element; |
230 } | 292 } |
231 } else if (!toShadowRoot(scope.rootNode()).isV1()) { | 293 } else if (!toShadowRoot(scope.rootNode()).isV1()) { |
232 return nullptr; | 294 return nullptr; |
233 } | 295 } |
234 return scope.adjustedElement(*element); | 296 return scope.adjustedElement(*element); |
235 } | 297 } |
236 | 298 |
237 Element* Fullscreen::currentFullScreenElementFrom(Document& document) { | |
238 if (Fullscreen* found = fromIfExists(document)) | |
239 return found->currentFullScreenElement(); | |
240 return nullptr; | |
241 } | |
242 | |
243 Element* Fullscreen::currentFullScreenElementForBindingFrom( | |
244 Document& document) { | |
245 Element* element = currentFullScreenElementFrom(document); | |
246 if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled()) | |
247 return element; | |
248 | |
249 // For Shadow DOM V0 compatibility: We allow returning an element in V0 shadow | |
250 // tree, even though it leaks the Shadow DOM. | |
251 if (element->isInV0ShadowTree()) { | |
252 UseCounter::count(document, | |
253 UseCounter::DocumentFullscreenElementInV0Shadow); | |
254 return element; | |
255 } | |
256 return document.adjustedElement(*element); | |
257 } | |
258 | |
259 Fullscreen::Fullscreen(Document& document) | |
260 : ContextLifecycleObserver(&document), | |
261 m_fullScreenLayoutObject(nullptr), | |
262 m_eventQueueTimer(this, &Fullscreen::eventQueueTimerFired), | |
263 m_forCrossProcessDescendant(false) { | |
264 document.setHasFullscreenSupplement(); | |
265 } | |
266 | |
267 Fullscreen::~Fullscreen() {} | |
268 | |
269 inline Document* Fullscreen::document() { | |
270 return toDocument(lifecycleContext()); | |
271 } | |
272 | |
273 void Fullscreen::contextDestroyed() { | |
274 m_eventQueue.clear(); | |
275 | |
276 if (m_fullScreenLayoutObject) | |
277 m_fullScreenLayoutObject->destroy(); | |
278 | |
279 m_currentFullScreenElement = nullptr; | |
280 m_fullscreenElementStack.clear(); | |
281 } | |
282 | |
283 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen | 299 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen |
284 void Fullscreen::requestFullscreen(Element& element, | 300 void Fullscreen::requestFullscreen(Element& pending, RequestType requestType) { |
285 RequestType requestType, | 301 Document& document = pending.document(); |
286 bool forCrossProcessDescendant) { | |
287 Document& document = element.document(); | |
288 | |
289 // Use counters only need to be incremented in the process of the actual | |
290 // fullscreen element. | |
291 if (!forCrossProcessDescendant) { | |
292 if (document.isSecureContext()) { | |
293 UseCounter::count(document, UseCounter::FullscreenSecureOrigin); | |
294 } else { | |
295 UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); | |
296 HostsUsingFeatures::countAnyWorld( | |
297 document, HostsUsingFeatures::Feature::FullscreenInsecureHost); | |
298 } | |
299 } | |
300 | 302 |
301 // Ignore this request if the document is not in a live frame. | 303 // Ignore this request if the document is not in a live frame. |
| 304 // TODO(foolip): This would make sense as part of fullscreenIsSupported. |
302 if (!document.isActive()) | 305 if (!document.isActive()) |
303 return; | 306 return; |
304 | 307 |
305 // If |element| is on top of |doc|'s fullscreen element stack, terminate these | 308 if (document.isSecureContext()) { |
306 // substeps. | 309 UseCounter::count(document, UseCounter::FullscreenSecureOrigin); |
307 if (&element == fullscreenElementFrom(document)) | 310 } else { |
308 return; | 311 UseCounter::count(document, UseCounter::FullscreenInsecureOrigin); |
| 312 HostsUsingFeatures::countAnyWorld( |
| 313 document, HostsUsingFeatures::Feature::FullscreenInsecureHost); |
| 314 } |
309 | 315 |
| 316 // 1. Let |pending| be the context object. |
| 317 |
| 318 // 2. Let |error| be false. |
| 319 |
| 320 // 3. Let |promise| be a new promise. |
| 321 // TODO(foolip): Promises. https://crbug.com/644637 |
| 322 |
| 323 // 4. If any of the following conditions are false, then set |error| to |
| 324 // true: |
| 325 bool error = true; |
310 do { | 326 do { |
311 // 1. If any of the following conditions are false, then terminate these | 327 // |pending|'s namespace is the HTML namespace or |pending| is an SVG |
312 // steps and queue a task to fire an event named fullscreenerror with its | |
313 // bubbles attribute set to true on the context object's node document: | |
314 | |
315 // |element|'s namespace is the HTML namespace or |element| is an SVG | |
316 // svg or MathML math element. | 328 // svg or MathML math element. |
317 // Note: MathML is not supported. | 329 // Note: MathML is not supported. |
318 if (!element.isHTMLElement() && !isSVGSVGElement(element)) | 330 if (!pending.isHTMLElement() && !isSVGSVGElement(pending)) |
319 break; | 331 break; |
320 | 332 |
321 // The fullscreen element ready check for |element| returns true. | 333 // The fullscreen element ready check for |pending| returns false. |
322 if (!fullscreenElementReady(element)) | 334 if (!fullscreenElementReady(pending)) |
323 break; | 335 break; |
324 | 336 |
325 // Fullscreen is supported. | 337 // Fullscreen is supported. |
326 if (!fullscreenIsSupported(document)) | 338 if (!fullscreenIsSupported(document)) |
327 break; | 339 break; |
328 | 340 |
329 // This algorithm is allowed to request fullscreen. | 341 // This algorithm is allowed to request fullscreen. |
330 // OOPIF: If |forCrossProcessDescendant| is true, requestFullscreen was | 342 if (!allowedToRequestFullscreen(document)) |
331 // already called on a descendant element in another process, and | |
332 // getting here means that it was already allowed to request fullscreen. | |
333 if (!forCrossProcessDescendant && !allowedToRequestFullscreen(document)) | |
334 break; | 343 break; |
335 | 344 |
336 // 2. Let doc be element's node document. (i.e. "this") | 345 error = false; |
337 | 346 } while (false); |
338 // 3. Let docs be all doc's ancestor browsing context's documents (if any) | 347 |
339 // and doc. | 348 // 5. Return |promise|, and run the remaining steps in parallel. |
340 // | 349 // TODO(foolip): Promises. https://crbug.com/644637 |
341 // For OOPIF scenarios, |docs| will only contain documents for local | 350 |
342 // ancestors, and remote ancestors will be processed in their | 351 // 6. If |error| is false: Resize pending's top-level browsing context's |
343 // respective processes. This preserves the spec's event firing order | 352 // document's viewport's dimensions to match the dimensions of the screen of |
344 // for local ancestors, but not for remote ancestors. However, that | 353 // the output device. Optionally display a message how the end user can |
345 // difference shouldn't be observable in practice: a fullscreenchange | 354 // revert this. |
346 // event handler would need to postMessage a frame in another renderer | 355 if (!error) { |
347 // process, where the message should be queued up and processed after | 356 document.frameHost()->chromeClient().enterFullscreenForElement(&pending); |
348 // the IPC that dispatches fullscreenchange. | 357 } else { |
349 HeapDeque<Member<Document>> docs; | 358 document.enqueueAnimationFrameTask(WTF::bind(&animationFrameTaskAfterEnter, |
350 | 359 wrapPersistent(&pending), |
351 docs.prepend(&document); | 360 requestType, true)); |
352 for (Frame* frame = document.frame()->tree().parent(); frame; | 361 } |
353 frame = frame->tree().parent()) { | 362 } |
354 if (frame->isLocalFrame()) | 363 |
355 docs.prepend(toLocalFrame(frame)->document()); | 364 void Fullscreen::didEnterFullscreenForElement(Element& pending, |
| 365 RequestType requestType) { |
| 366 // 7. As part of the next animation frame task, run these substeps: |
| 367 pending.document().enqueueAnimationFrameTask( |
| 368 WTF::bind(&animationFrameTaskAfterEnter, wrapPersistent(&pending), |
| 369 requestType, false)); |
| 370 } |
| 371 |
| 372 void Fullscreen::animationFrameTaskAfterEnter(Element* element, |
| 373 RequestType requestType, |
| 374 bool error) { |
| 375 DCHECK(element); |
| 376 Element& pending = *element; |
| 377 |
| 378 // 7.1. If either |error| is true or the fullscreen element ready check for |
| 379 // |pending| returns false, fire an event named fullscreenerror on |
| 380 // |pending|'s node document, reject |promise| with a TypeError exception, |
| 381 // and terminate these steps. |
| 382 // TODO(foolip): Promises. https://crbug.com/644637 |
| 383 if (error || !fullscreenElementReady(pending)) { |
| 384 // TODO(foolip): What if we just entered fullscreen but the ready check |
| 385 // no longer passes, e.g. if the allowfullscreen attribute was just |
| 386 // removed? We need to exit fullscreen again, or at least maybe. |
| 387 fireErrorEvent(pending, requestType); |
| 388 return; |
| 389 } |
| 390 |
| 391 // 7.2. Let |fullscreenElements| be an ordered set initially consisting of |
| 392 // |pending|. |
| 393 HeapDeque<Member<Element>> fullscreenElements; |
| 394 fullscreenElements.append(pending); |
| 395 |
| 396 // 7.3. While the first element in |fullscreenElements| is in a nested |
| 397 // browsing context, prepend its browsing context container to |
| 398 // |fullscreenElements|. |
| 399 // |
| 400 // OOPIF: |fullscreenElements| will only contain elements for local |
| 401 // ancestors, and remote ancestors will be processed in their respective |
| 402 // processes. This preserves the spec's event firing order for local |
| 403 // ancestors, but not for remote ancestors. However, that difference |
| 404 // shouldn't be observable in practice: a fullscreenchange event handler |
| 405 // would need to postMessage a frame in another renderer process, where the |
| 406 // message should be queued up and processed after the IPC that dispatches |
| 407 // fullscreenchange. |
| 408 for (Frame* frame = pending.document().frame(); frame; |
| 409 frame = frame->tree().parent()) { |
| 410 if (frame->owner() && frame->owner()->isLocal()) { |
| 411 Element* element = toHTMLFrameOwnerElement(frame->owner()); |
| 412 if (!isFullscreenElement(*element)) |
| 413 fullscreenElements.prepend(element); |
356 } | 414 } |
357 | 415 } |
358 // 4. For each document in docs, run these substeps: | 416 |
359 HeapDeque<Member<Document>>::iterator current = docs.begin(), | 417 // 7.4. Let |eventDocs| be an empty list. |
360 following = docs.begin(); | 418 // Note: Rather than creating a separate |eventDocs| list with a subset of |
361 | 419 // the |fullscreenElements|' elements' documents, those elements are not |
362 do { | 420 // added to |fullscreenElements| at all, which simplifies the following. |
363 ++following; | 421 |
364 | 422 // 7.5. For each |element| in |fullscreenElements|, in order, run these |
365 // 1. Let following document be the document after document in docs, or | 423 // subsubsteps: |
366 // null if there is no such document. | 424 for (Element* element : fullscreenElements) { |
367 Document* currentDoc = *current; | 425 // 7.5.1. Let |doc| be |element|'s node document. |
368 Document* followingDoc = following != docs.end() ? *following : nullptr; | 426 |
369 | 427 // 7.5.2. If |element| is |doc|'s fullscreen element, terminate these |
370 // 2. If following document is null, push context object on document's | 428 // subsubsteps. |
371 // fullscreen element stack, and queue a task to fire an event named | 429 |
372 // fullscreenchange with its bubbles attribute set to true on the | 430 // 7.5.3. Otherwise, append |doc| to |eventDocs|. |
373 // document. | 431 |
374 if (!followingDoc) { | 432 // 7.5.4. If |element| is |pending| and |pending| is an iframe element, |
375 from(*currentDoc).pushFullscreenElementStack(element, requestType); | 433 // set |element|'s iframe fullscreen flag. |
376 from(document).enqueueChangeEvent(*currentDoc, requestType); | 434 // TODO(foolip): Support the iframe fullscreen flag. |
377 continue; | 435 // https://crbug.com/644695 |
378 } | 436 |
379 | 437 // 7.5.5. Fullscreen |element| within |doc|. |
380 // 3. Otherwise, if document's fullscreen element stack is either empty or | 438 fullscreen(*element, requestType); |
381 // its top element is not following document's browsing context container, | 439 } |
382 Element* topElement = fullscreenElementFrom(*currentDoc); | 440 |
383 HTMLFrameOwnerElement* followingOwner = | 441 // 7.6. For each |doc| in |eventDocs|, in order, fire an event named |
384 findContainerForDescendant(*currentDoc, *followingDoc); | 442 // fullscreenchange on |doc|. |
385 if (!topElement || topElement != followingOwner) { | 443 for (Element* element : fullscreenElements) |
386 // ...push following document's browsing context container on document's | 444 fireChangeEvent(*element, requestType); |
387 // fullscreen element stack, and queue a task to fire an event named | 445 |
388 // fullscreenchange with its bubbles attribute set to true on document. | 446 // 7.7. Fulfill |promise| with undefined. |
389 from(*currentDoc) | 447 // TODO(foolip): Promises. https://crbug.com/644637 |
390 .pushFullscreenElementStack(*followingOwner, requestType); | |
391 from(document).enqueueChangeEvent(*currentDoc, requestType); | |
392 continue; | |
393 } | |
394 | |
395 // 4. Otherwise, do nothing for this document. It stays the same. | |
396 } while (++current != docs.end()); | |
397 | |
398 from(document).m_forCrossProcessDescendant = forCrossProcessDescendant; | |
399 | |
400 // 5. Return, and run the remaining steps asynchronously. | |
401 // 6. Optionally, perform some animation. | |
402 document.frameHost()->chromeClient().enterFullscreenForElement(&element); | |
403 | |
404 // 7. Optionally, display a message indicating how the user can exit | |
405 // displaying the context object fullscreen. | |
406 return; | |
407 } while (false); | |
408 | |
409 from(document).enqueueErrorEvent(element, requestType); | |
410 } | 448 } |
411 | 449 |
412 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen | 450 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen |
413 void Fullscreen::fullyExitFullscreen(Document& document) { | 451 void Fullscreen::fullyExitFullscreen(Document& document) { |
414 // To fully exit fullscreen, run these steps: | 452 // 1. If |document|'s fullscreen element is null, terminate these steps. |
415 | 453 Element* fullscreenElement = Fullscreen::fullscreenElement(document); |
416 // 1. Let |doc| be the top-level browsing context's document. | 454 if (!fullscreenElement) |
417 // | 455 return; |
418 // Since the top-level browsing context's document might be unavailable in | 456 |
419 // OOPIF scenarios (i.e., when the top frame is remote), this actually uses | 457 // 2. Unfullscreen elements whose fullscreen flag is set, within |
420 // the Document of the topmost local ancestor frame. Without OOPIF, this | 458 // |document|'s top layer, except for |document|'s fullscreen element. |
421 // will be the top frame's document. With OOPIF, each renderer process for | 459 for (Element* element : document.topLayerElements()) { |
422 // the current page will separately call fullyExitFullscreen to cover all | 460 if (element->isFullscreen() && element != fullscreenElement) |
423 // local frames in each process. | 461 unfullscreen(*element); |
424 Document& doc = topmostLocalAncestor(document); | 462 } |
425 | 463 |
426 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps. | 464 // 3. Exit fullscreen |document|. |
427 if (!fullscreenElementFrom(doc)) | 465 exitFullscreen(document); |
428 return; | |
429 | |
430 // 3. Remove elements from |doc|'s fullscreen element stack until only the top | |
431 // element is left. | |
432 size_t stackSize = from(doc).m_fullscreenElementStack.size(); | |
433 from(doc).m_fullscreenElementStack.remove(0, stackSize - 1); | |
434 DCHECK_EQ(from(doc).m_fullscreenElementStack.size(), 1u); | |
435 | |
436 // 4. Act as if the exitFullscreen() method was invoked on |doc|. | |
437 exitFullscreen(doc); | |
438 } | 466 } |
439 | 467 |
440 // https://fullscreen.spec.whatwg.org/#exit-fullscreen | 468 // https://fullscreen.spec.whatwg.org/#exit-fullscreen |
441 void Fullscreen::exitFullscreen(Document& document) { | 469 void Fullscreen::exitFullscreen(Document& doc) { |
442 // The exitFullscreen() method must run these steps: | 470 if (!doc.isActive()) |
443 | 471 return; |
444 // 1. Let doc be the context object. (i.e. "this") | 472 |
445 if (!document.isActive()) | 473 // 1. Let |promise| be a new promise. |
446 return; | 474 // 2. If |doc|'s fullscreen element is null, reject |promise| with a |
447 | 475 // TypeError exception, and return |promise|. |
448 // 2. If doc's fullscreen element stack is empty, terminate these steps. | 476 // TODO(foolip): Promises. https://crbug.com/644637 |
449 if (!fullscreenElementFrom(document)) | 477 if (!fullscreenElement(doc)) |
450 return; | 478 return; |
451 | 479 |
452 // 3. Let descendants be all the doc's descendant browsing context's documents | 480 // 3. Let |resize| be false. |
453 // with a non-empty fullscreen element stack (if any), ordered so that the | 481 bool resize = false; |
454 // child of the doc is last and the document furthest away from the doc is | 482 |
455 // first. | 483 // 4. Let |docs| be the result of collecting documents to unfullscreen given |
456 HeapDeque<Member<Document>> descendants; | 484 // |doc|. |
457 for (Frame* descendant = | 485 HeapVector<Member<Document>> docs = collectDocumentsToUnfullscreen(doc); |
458 document.frame() ? document.frame()->tree().traverseNext() : nullptr; | 486 |
459 descendant; descendant = descendant->tree().traverseNext()) { | 487 // 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document. |
| 488 Document& topLevelDoc = topmostLocalAncestor(doc); |
| 489 |
| 490 // 6. If |topLevelDoc| is in |docs|, set |resize| to true. |
| 491 // OOPIF: Use |isLocalRoot| as oppposed to |isMainFrame|, so that if the |
| 492 // main frame is in another process, we will still fully exit fullscreen |
| 493 // even though that's wrong if the main frame was in nested fullscreen. |
| 494 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 495 // https://crbug.com/617369. |
| 496 if (!docs.isEmpty() && docs.last()->frame()->isLocalRoot()) |
| 497 resize = true; |
| 498 |
| 499 // 7. Return |promise|, and run the remaining steps in parallel. |
| 500 // TODO(foolip): Promises. https://crbug.com/644637 |
| 501 |
| 502 // 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal" |
| 503 // dimensions. |
| 504 if (resize) { |
| 505 topLevelDoc.frameHost()->chromeClient().exitFullscreenForElement( |
| 506 fullscreenElement(topLevelDoc)); |
| 507 } else { |
| 508 topLevelDoc.enqueueAnimationFrameTask( |
| 509 WTF::bind(&animationFrameTaskAfterExit, wrapPersistent(&topLevelDoc))); |
| 510 } |
| 511 } |
| 512 |
| 513 void Fullscreen::didExitFullscreen(Document& doc) { |
| 514 // 9. As part of the next animation frame task, run these substeps: |
| 515 doc.enqueueAnimationFrameTask( |
| 516 WTF::bind(&animationFrameTaskAfterExit, wrapPersistent(&doc))); |
| 517 } |
| 518 |
| 519 void Fullscreen::animationFrameTaskAfterExit(Document* document) { |
| 520 DCHECK(document); |
| 521 Document& doc = *document; |
| 522 |
| 523 // 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen |
| 524 // given |doc|. |
| 525 HeapVector<Member<Document>> exitDocs = collectDocumentsToUnfullscreen(doc); |
| 526 |
| 527 // 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully |
| 528 // exit fullscreen |topLevelDoc|, reject promise with a TypeError exception, |
| 529 // and terminate these steps. |
| 530 // TODO(foolip): Promises. https://crbug.com/644637 |
| 531 if (exitDocs.isEmpty() || !exitDocs.last()->frame()->isLocalRoot()) { |
| 532 return; |
| 533 } |
| 534 |
| 535 // 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|. |
| 536 if (exitDocs.isEmpty()) |
| 537 exitDocs.append(&doc); |
| 538 |
| 539 // 9.4. If |exitDocs|'s last document has a browsing context container, |
| 540 // append that browsing context container's node document to |exitDocs|. |
| 541 // OOPIF: Skip over remote frames, assuming that they have exactly one |
| 542 // element in their fullscreen element stacks, thereby erring on the side of |
| 543 // exiting fullscreen. |
| 544 // TODO(alexmos): Deal with nested fullscreen cases, see |
| 545 // https://crbug.com/617369. |
| 546 for (Frame* frame = exitDocs.last()->frame()->tree().parent(); frame; |
| 547 frame = frame->tree().parent()) { |
| 548 if (frame->isLocalFrame()) { |
| 549 if (Document* document = toLocalFrame(frame)->document()) { |
| 550 exitDocs.append(document); |
| 551 break; |
| 552 } |
| 553 } |
| 554 } |
| 555 |
| 556 // 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s |
| 557 // descendant browsing contexts' documents whose fullscreen element is |
| 558 // non-null, if any, in *reverse* tree order. |
| 559 HeapDeque<Member<Document>> descendantDocs; |
| 560 for (Frame* descendant = doc.frame()->tree().firstChild(); descendant; |
| 561 descendant = descendant->tree().traverseNext(doc.frame())) { |
460 if (!descendant->isLocalFrame()) | 562 if (!descendant->isLocalFrame()) |
461 continue; | 563 continue; |
462 DCHECK(toLocalFrame(descendant)->document()); | 564 DCHECK(toLocalFrame(descendant)->document()); |
463 if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) | 565 if (fullscreenElement(*toLocalFrame(descendant)->document())) |
464 descendants.prepend(toLocalFrame(descendant)->document()); | 566 descendantDocs.prepend(toLocalFrame(descendant)->document()); |
465 } | 567 } |
466 | 568 |
467 // 4. For each descendant in descendants, empty descendant's fullscreen | 569 HeapVector<Member<Element>> eventElements; |
468 // element stack, and queue a task to fire an event named fullscreenchange | 570 |
469 // with its bubbles attribute set to true on descendant. | 571 // 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen |
470 for (auto& descendant : descendants) { | 572 // |descendantDoc|. |
471 DCHECK(descendant); | 573 for (Document* descendantDoc : descendantDocs) { |
472 RequestType requestType = | 574 eventElements.append(fullscreenElement(*descendantDoc)); |
473 from(*descendant).m_fullscreenElementStack.last().second; | 575 unfullscreen(*descendantDoc); |
474 from(*descendant).clearFullscreenElementStack(); | 576 } |
475 from(document).enqueueChangeEvent(*descendant, requestType); | 577 |
476 } | 578 // 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s |
477 | 579 // fullscreen element. |
478 // 5. While doc is not null, run these substeps: | 580 for (Document* exitDoc : exitDocs) { |
479 Element* newTop = nullptr; | 581 eventElements.append(fullscreenElement(*exitDoc)); |
480 for (Document* currentDoc = &document; currentDoc;) { | 582 unfullscreen(*fullscreenElement(*exitDoc)); |
481 RequestType requestType = | 583 } |
482 from(*currentDoc).m_fullscreenElementStack.last().second; | 584 |
483 | 585 // 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an |
484 // 1. Pop the top element of doc's fullscreen element stack. | 586 // event named fullscreenchange on |descendantDoc|. |
485 from(*currentDoc).popFullscreenElementStack(); | 587 // 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named |
486 | 588 // fullscreenchange on |exitDoc|. |
487 // If doc's fullscreen element stack is non-empty and the element now at | 589 // TODO(foolip): Fire the unprefixed event. |
488 // the top is either not in a document or its node document is not doc, | 590 for (Element* element : eventElements) |
489 // repeat this substep. | 591 fireChangeEvent(*element, PrefixedRequest); |
490 newTop = fullscreenElementFrom(*currentDoc); | 592 |
491 if (newTop && (!newTop->isConnected() || newTop->document() != currentDoc)) | 593 // 9.10. Fulfill |promise| with undefined. |
492 continue; | 594 // TODO(foolip): Promises. https://crbug.com/644637 |
493 | |
494 // 2. Queue a task to fire an event named fullscreenchange with its bubbles | |
495 // attribute set to true on doc. | |
496 from(document).enqueueChangeEvent(*currentDoc, requestType); | |
497 | |
498 // 3. If doc's fullscreen element stack is empty and doc's browsing context | |
499 // has a browsing context container, set doc to that browsing context | |
500 // container's node document. | |
501 // | |
502 // OOPIF: If browsing context container's document is in another | |
503 // process, keep moving up the ancestor chain and looking for a | |
504 // browsing context container with a local document. | |
505 // TODO(alexmos): Deal with nested fullscreen cases, see | |
506 // https://crbug.com/617369. | |
507 if (!newTop) { | |
508 Frame* frame = currentDoc->frame()->tree().parent(); | |
509 while (frame && frame->isRemoteFrame()) | |
510 frame = frame->tree().parent(); | |
511 if (frame) { | |
512 currentDoc = toLocalFrame(frame)->document(); | |
513 continue; | |
514 } | |
515 } | |
516 | |
517 // 4. Otherwise, set doc to null. | |
518 currentDoc = nullptr; | |
519 } | |
520 | |
521 // 6. Return, and run the remaining steps asynchronously. | |
522 // 7. Optionally, perform some animation. | |
523 | |
524 FrameHost* host = document.frameHost(); | |
525 | |
526 // Speculative fix for engaget.com/videos per crbug.com/336239. | |
527 // FIXME: This check is wrong. We DCHECK(document->isActive()) above | |
528 // so this should be redundant and should be removed! | |
529 if (!host) | |
530 return; | |
531 | |
532 // Only exit out of full screen window mode if there are no remaining elements | |
533 // in the full screen stack. | |
534 if (!newTop) { | |
535 // FIXME: if the frame exiting fullscreen is not the frame that entered | |
536 // fullscreen (but a parent frame for example), | |
537 // m_currentFullScreenElement might be null. We want to pass an element | |
538 // that is part of the document so we will pass the documentElement in | |
539 // that case. This should be fix by exiting fullscreen for a frame | |
540 // instead of an element, see https://crbug.com/441259 | |
541 Element* currentFullScreenElement = currentFullScreenElementFrom(document); | |
542 host->chromeClient().exitFullscreenForElement( | |
543 currentFullScreenElement ? currentFullScreenElement | |
544 : document.documentElement()); | |
545 return; | |
546 } | |
547 | |
548 // Otherwise, notify the chrome of the new full screen element. | |
549 host->chromeClient().enterFullscreenForElement(newTop); | |
550 } | 595 } |
551 | 596 |
552 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled | 597 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled |
553 bool Fullscreen::fullscreenEnabled(Document& document) { | 598 bool Fullscreen::fullscreenEnabled(Document& document) { |
554 // The fullscreenEnabled attribute's getter must return true if the context | 599 // The fullscreenEnabled attribute's getter must return true if the context |
555 // object is allowed to use the feature indicated by attribute name | 600 // object is allowed to use the feature indicated by attribute name |
556 // allowfullscreen and fullscreen is supported, and false otherwise. | 601 // allowfullscreen and fullscreen is supported, and false otherwise. |
557 return allowedToUseFullscreen(document.frame()) && | 602 return allowedToUseFullscreen(document.frame()) && |
558 fullscreenIsSupported(document); | 603 fullscreenIsSupported(document); |
559 } | 604 } |
560 | 605 |
561 void Fullscreen::didEnterFullscreenForElement(Element* element) { | 606 void Fullscreen::didUpdateSize(Element& element) { |
562 DCHECK(element); | 607 // StyleAdjuster will set the size so we need to do a style recalc. |
563 if (!document()->isActive()) | 608 // Normally changing size means layout so just doing a style recalc is a |
564 return; | 609 // bit surprising. |
565 | 610 element.setNeedsStyleRecalc( |
566 if (m_fullScreenLayoutObject) | 611 LocalStyleChange, |
567 m_fullScreenLayoutObject->unwrapLayoutObject(); | 612 StyleChangeReasonForTracing::create(StyleChangeReason::Fullscreen)); |
568 | |
569 m_currentFullScreenElement = element; | |
570 | |
571 // Create a placeholder block for a the full-screen element, to keep the page | |
572 // from reflowing when the element is removed from the normal flow. Only do | |
573 // this for a LayoutBox, as only a box will have a frameRect. The placeholder | |
574 // will be created in setFullScreenLayoutObject() during layout. | |
575 LayoutObject* layoutObject = m_currentFullScreenElement->layoutObject(); | |
576 bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox(); | |
577 if (shouldCreatePlaceholder) { | |
578 m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect(); | |
579 m_savedPlaceholderComputedStyle = | |
580 ComputedStyle::clone(layoutObject->styleRef()); | |
581 } | |
582 | |
583 // TODO(alexmos): When |m_forCrossProcessDescendant| is true, some of | |
584 // this layout work has already been done in another process, so it should | |
585 // not be necessary to repeat it here. | |
586 if (m_currentFullScreenElement != document()->documentElement()) | |
587 LayoutFullScreen::wrapLayoutObject( | |
588 layoutObject, layoutObject ? layoutObject->parent() : 0, document()); | |
589 | |
590 // When |m_forCrossProcessDescendant| is true, m_currentFullScreenElement | |
591 // corresponds to the HTMLFrameOwnerElement for the out-of-process iframe | |
592 // that contains the actual fullscreen element. Hence, it must also set | |
593 // the ContainsFullScreenElement flag (so that it gains the | |
594 // -webkit-full-screen-ancestor style). | |
595 if (m_forCrossProcessDescendant) { | |
596 DCHECK(m_currentFullScreenElement->isFrameOwnerElement()); | |
597 DCHECK(toHTMLFrameOwnerElement(m_currentFullScreenElement) | |
598 ->contentFrame() | |
599 ->isRemoteFrame()); | |
600 m_currentFullScreenElement->setContainsFullScreenElement(true); | |
601 } | |
602 | |
603 m_currentFullScreenElement | |
604 ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); | |
605 | |
606 document()->styleEngine().ensureUAStyleForFullscreen(); | |
607 m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); | |
608 | |
609 // FIXME: This should not call updateStyleAndLayoutTree. | |
610 document()->updateStyleAndLayoutTree(); | |
611 | |
612 m_currentFullScreenElement->didBecomeFullscreenElement(); | |
613 | |
614 if (document()->frame()) | |
615 document()->frame()->eventHandler().scheduleHoverStateUpdate(); | |
616 | |
617 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); | |
618 } | 613 } |
619 | 614 |
620 void Fullscreen::didExitFullscreen() { | 615 void Fullscreen::elementRemoved(Element& node) { |
621 if (!m_currentFullScreenElement) | 616 // Whenever the removing steps run with an oldNode, run these steps: |
622 return; | |
623 | 617 |
624 if (!document()->isActive()) | 618 // 1. Let |nodes| be |oldNode|'s inclusive descendants that have their |
625 return; | 619 // fullscreen flag set, in tree order. |
626 | 620 |
627 m_currentFullScreenElement->willStopBeingFullscreenElement(); | 621 // 2. For each |node| in |nodes|, run these substeps: |
628 | 622 |
629 if (m_forCrossProcessDescendant) | 623 // Note: The iteration of descendants is done in |
630 m_currentFullScreenElement->setContainsFullScreenElement(false); | 624 // ContainerNode::notifyNodeRemoved, and Element::removedFrom calls |
| 625 // Fullscreen::elementRemoved only for fullscreen elements. |
| 626 DCHECK(node.isInTopLayer()); |
| 627 DCHECK(node.isFullscreen()); |
631 | 628 |
632 m_currentFullScreenElement | 629 // 2.1. If |node| is its node document's fullscreen element, exit fullscreen |
633 ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); | 630 // that document. |
634 | 631 if (&node == fullscreenElement(node.document())) { |
635 if (m_fullScreenLayoutObject) | 632 exitFullscreen(node.document()); |
636 LayoutFullScreenItem(m_fullScreenLayoutObject).unwrapLayoutObject(); | |
637 | |
638 document()->styleEngine().ensureUAStyleForFullscreen(); | |
639 m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen); | |
640 m_currentFullScreenElement = nullptr; | |
641 | |
642 if (document()->frame()) | |
643 document()->frame()->eventHandler().scheduleHoverStateUpdate(); | |
644 | |
645 // When fullyExitFullscreen is called, we call exitFullscreen on the | |
646 // topDocument(). That means that the events will be queued there. So if we | |
647 // have no events here, start the timer on the exiting document. | |
648 Document* exitingDocument = document(); | |
649 if (m_eventQueue.isEmpty()) | |
650 exitingDocument = &topmostLocalAncestor(*document()); | |
651 DCHECK(exitingDocument); | |
652 from(*exitingDocument).m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); | |
653 | |
654 m_forCrossProcessDescendant = false; | |
655 } | |
656 | |
657 void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) { | |
658 if (layoutObject == m_fullScreenLayoutObject) | |
659 return; | |
660 | |
661 if (layoutObject && m_savedPlaceholderComputedStyle) { | |
662 layoutObject->createPlaceholder(m_savedPlaceholderComputedStyle.release(), | |
663 m_savedPlaceholderFrameRect); | |
664 } else if (layoutObject && m_fullScreenLayoutObject && | |
665 m_fullScreenLayoutObject->placeholder()) { | |
666 LayoutBlockFlow* placeholder = m_fullScreenLayoutObject->placeholder(); | |
667 layoutObject->createPlaceholder( | |
668 ComputedStyle::clone(placeholder->styleRef()), | |
669 placeholder->frameRect()); | |
670 } | |
671 | |
672 if (m_fullScreenLayoutObject) | |
673 m_fullScreenLayoutObject->unwrapLayoutObject(); | |
674 DCHECK(!m_fullScreenLayoutObject); | |
675 | |
676 m_fullScreenLayoutObject = layoutObject; | |
677 } | |
678 | |
679 void Fullscreen::fullScreenLayoutObjectDestroyed() { | |
680 m_fullScreenLayoutObject = nullptr; | |
681 } | |
682 | |
683 void Fullscreen::enqueueChangeEvent(Document& document, | |
684 RequestType requestType) { | |
685 Event* event; | |
686 if (requestType == UnprefixedRequest) { | |
687 event = createEvent(EventTypeNames::fullscreenchange, document); | |
688 } else { | |
689 DCHECK(document.hasFullscreenSupplement()); | |
690 Fullscreen& fullscreen = from(document); | |
691 EventTarget* target = fullscreen.fullscreenElement(); | |
692 if (!target) | |
693 target = fullscreen.currentFullScreenElement(); | |
694 if (!target) | |
695 target = &document; | |
696 event = createEvent(EventTypeNames::webkitfullscreenchange, *target); | |
697 } | |
698 m_eventQueue.append(event); | |
699 // NOTE: The timer is started in | |
700 // didEnterFullscreenForElement/didExitFullscreen. | |
701 } | |
702 | |
703 void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType) { | |
704 Event* event; | |
705 if (requestType == UnprefixedRequest) | |
706 event = createEvent(EventTypeNames::fullscreenerror, element.document()); | |
707 else | |
708 event = createEvent(EventTypeNames::webkitfullscreenerror, element); | |
709 m_eventQueue.append(event); | |
710 m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE); | |
711 } | |
712 | |
713 void Fullscreen::eventQueueTimerFired(TimerBase*) { | |
714 HeapDeque<Member<Event>> eventQueue; | |
715 m_eventQueue.swap(eventQueue); | |
716 | |
717 while (!eventQueue.isEmpty()) { | |
718 Event* event = eventQueue.takeFirst(); | |
719 Node* target = event->target()->toNode(); | |
720 | |
721 // If the element was removed from our tree, also message the | |
722 // documentElement. | |
723 if (!target->isConnected() && document()->documentElement()) { | |
724 DCHECK(isPrefixed(event->type())); | |
725 eventQueue.append( | |
726 createEvent(event->type(), *document()->documentElement())); | |
727 } | |
728 | |
729 target->dispatchEvent(event); | |
730 } | |
731 } | |
732 | |
733 void Fullscreen::elementRemoved(Element& oldNode) { | |
734 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its | |
735 // node document's fullscreen element stack, run these steps: | |
736 | |
737 // 1. If |oldNode| is at the top of its node document's fullscreen element | |
738 // stack, act as if the exitFullscreen() method was invoked on that document. | |
739 if (fullscreenElement() == &oldNode) { | |
740 exitFullscreen(oldNode.document()); | |
741 return; | 633 return; |
742 } | 634 } |
743 | 635 |
744 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element | 636 // 2.2. Otherwise, unfullscreen |node| within its node document. |
745 // stack. | 637 unfullscreen(node); |
746 for (size_t i = 0; i < m_fullscreenElementStack.size(); ++i) { | |
747 if (m_fullscreenElementStack[i].first.get() == &oldNode) { | |
748 m_fullscreenElementStack.remove(i); | |
749 return; | |
750 } | |
751 } | |
752 | |
753 // NOTE: |oldNode| was not in the fullscreen element stack. | |
754 } | |
755 | |
756 void Fullscreen::clearFullscreenElementStack() { | |
757 m_fullscreenElementStack.clear(); | |
758 } | |
759 | |
760 void Fullscreen::popFullscreenElementStack() { | |
761 if (m_fullscreenElementStack.isEmpty()) | |
762 return; | |
763 | |
764 m_fullscreenElementStack.removeLast(); | |
765 } | |
766 | |
767 void Fullscreen::pushFullscreenElementStack(Element& element, | |
768 RequestType requestType) { | |
769 m_fullscreenElementStack.append(std::make_pair(&element, requestType)); | |
770 } | |
771 | |
772 DEFINE_TRACE(Fullscreen) { | |
773 visitor->trace(m_currentFullScreenElement); | |
774 visitor->trace(m_fullscreenElementStack); | |
775 visitor->trace(m_eventQueue); | |
776 Supplement<Document>::trace(visitor); | |
777 ContextLifecycleObserver::trace(visitor); | |
778 } | 638 } |
779 | 639 |
780 } // namespace blink | 640 } // namespace blink |
OLD | NEW |