Support W3C Full Screen API proposal
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 16 Mar 2012 18:12:14 +0000 (18:12 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 16 Mar 2012 18:12:14 +0000 (18:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=80660

Reviewed by Alexey Proskuryakov.

Source/WebCore:

Tests: fullscreen/full-screen-element-stack.html
       fullscreen/full-screen-enabled.html
       fullscreen/full-screen-restrictions.html

The W3C proposal for taking arbitrary elements into full-screen mode is significantly
different than the Mozilla proposal. For example, the W3C has proposed a lower-case "s"
in "Fullscreen", which means the W3C and Mozilla "requestFullscreen" APIs differ only by
in that lower-case "s". Annoying as this is, it does allow us to retain the semantics for
the Mozilla case (har!).

A significant difficulty is obeying the new W3C spec rules is that we would like to apply the
fullscreen CSS rules while exiting fullscreen mode, though the W3C spec insists that the
webkitFullscreenElement returns the new value immediately.  As such, we retain the m_fullScreenElement
variable (distinct from the top of the m_fullScreenElements stack) which is controlled by the
webkit{Will,Did}{Enter,Exit}FullScreen functions.

New APIs for the W3C Fullscreen spec:
* dom/Document.h:
(WebCore::Document::webkitFullscreenElement):
(WebCore::Document::webkitFullscreenEnabled):
* dom/Document.idl:
* dom/Element.cpp:
(WebCore::Element::webkitRequestFullscreen):
* dom/Element.h:
* dom/Element.idl:

* dom/Document.cpp:
(WebCore::Document::removedLastRef): Clear m_fullScreenElementStack.
(WebCore::Document::requestFullScreenForElement): Implement the W3C requirements.
(WebCore::Document::webkitExitFullscreen): Ditto.
(WebCore::Document::webkitCancelFullScreen): Implement in terms of webkitCancelFullscreen.
(WebCore::Document::webkitDidEnterFullScreenForElement):
(WebCore::Document::webkitWillExitFullScreenForElement):
(WebCore::Document::webkitDidExitFullScreenForElement):
(WebCore::Document::fullScreenChangeDelayTimerFired): Protect against items being
    added to the event and error queue by swapping out empty queues before starting.
(WebCore::Document::clearFullscreenElementStack): Simple accessor.
(WebCore::Document::popFullscreenElementStack): Ditto.
(WebCore::Document::pushFullscreenElementStack): Ditto.
* dom/Element.cpp:
(WebCore::Element::webkitRequestFullScreen):

Add new RuntimeEnabledFeatures functions for the added Document and Element functions.
* bindings/generic/RuntimeEnabledFeatures.h:
(RuntimeEnabledFeatures):
(WebCore::RuntimeEnabledFeatures::webkitFullscreenEnabledEnabled):
(WebCore::RuntimeEnabledFeatures::webkitFullscreenElementEnabled):
(WebCore::RuntimeEnabledFeatures::webkitExitFullscreenEnabled):
(WebCore::RuntimeEnabledFeatures::webkitRequestFullscreenEnabled):

Source/WebKit/mac:

Allow full screen elements to access the keyboard.

* WebView/WebView.mm:
(-[WebView _supportsFullScreenForElement:WebCore::withKeyboard:]):

Source/WebKit2:

Allow full screen elements to access the keyboard.

* UIProcess/WebFullScreenManagerProxy.cpp:
(WebKit::WebFullScreenManagerProxy::supportsFullScreen):
* WebProcess/FullScreen/WebFullScreenManager.cpp:
(WebKit::WebFullScreenManager::exitFullScreenForElement):

LayoutTests:

* fullscreen/full-screen-element-stack-expected.txt: Added.
* fullscreen/full-screen-element-stack.html: Added.
* fullscreen/full-screen-enabled-expected.txt: Added.
* fullscreen/full-screen-enabled.html: Added.
* fullscreen/full-screen-request-rejected.html:
* fullscreen/full-screen-request-removed.html:
* fullscreen/full-screen-restrictions-expected.txt: Added.
* fullscreen/full-screen-restrictions.html: Added.
* fullscreen/full-screen-test.js:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@111028 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
LayoutTests/ChangeLog
LayoutTests/fullscreen/full-screen-element-stack-expected.txt [new file with mode: 0644]
LayoutTests/fullscreen/full-screen-element-stack.html [new file with mode: 0644]
LayoutTests/fullscreen/full-screen-enabled-expected.txt [new file with mode: 0644]
LayoutTests/fullscreen/full-screen-enabled.html [new file with mode: 0644]
LayoutTests/fullscreen/full-screen-request-rejected.html
LayoutTests/fullscreen/full-screen-request-removed.html
LayoutTests/fullscreen/full-screen-restrictions-expected.txt [new file with mode: 0644]
LayoutTests/fullscreen/full-screen-restrictions.html [new file with mode: 0644]
LayoutTests/fullscreen/full-screen-test.js
Source/WebCore/ChangeLog
Source/WebCore/bindings/generic/RuntimeEnabledFeatures.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/Document.idl
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Element.h
Source/WebCore/dom/Element.idl
Source/WebKit/mac/ChangeLog
Source/WebKit/mac/WebView/WebView.mm
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/WebFullScreenManagerProxy.cpp
Source/WebKit2/WebProcess/FullScreen/WebFullScreenManager.cpp
Tools/DumpRenderTree/mac/UIDelegate.mm

index 11e2290..ae8a434 100644 (file)
@@ -1,3 +1,20 @@
+2012-03-08  Jer Noble  <jer.noble@apple.com>
+
+        Support W3C Full Screen API proposal
+        https://bugs.webkit.org/show_bug.cgi?id=80660
+
+        Reviewed by Alexey Proskuryakov.
+
+        * fullscreen/full-screen-element-stack-expected.txt: Added.
+        * fullscreen/full-screen-element-stack.html: Added.
+        * fullscreen/full-screen-enabled-expected.txt: Added.
+        * fullscreen/full-screen-enabled.html: Added.
+        * fullscreen/full-screen-request-rejected.html:
+        * fullscreen/full-screen-request-removed.html:
+        * fullscreen/full-screen-restrictions-expected.txt: Added.
+        * fullscreen/full-screen-restrictions.html: Added.
+        * fullscreen/full-screen-test.js:
+
 2012-03-16  Xiaomei Ji  <xji@chromium.org>
 
         Skip visual word movement tests in gtk and qt after r110965 since isWordTextBreak is not implemented.
diff --git a/LayoutTests/fullscreen/full-screen-element-stack-expected.txt b/LayoutTests/fullscreen/full-screen-element-stack-expected.txt
new file mode 100644 (file)
index 0000000..5a06ce1
--- /dev/null
@@ -0,0 +1,15 @@
+Test for W3C Fullscreen element stack.
+
+To test manually, click the "Go full screen" button - the page should enter full screen mode.
+
+Go full screen (one)
+Go full screen (two)
+Exit full screen (two)
+EVENT(webkitfullscreenchange)
+EXPECTED (document.webkitFullscreenElement.id == 'one') OK
+EVENT(webkitfullscreenchange)
+EXPECTED (document.webkitFullscreenElement.id == 'two') OK
+EVENT(webkitfullscreenchange)
+EXPECTED (document.webkitFullscreenElement.id == 'one') OK
+END OF TEST
+
diff --git a/LayoutTests/fullscreen/full-screen-element-stack.html b/LayoutTests/fullscreen/full-screen-element-stack.html
new file mode 100644 (file)
index 0000000..78770ef
--- /dev/null
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="full-screen-test.js"></script>
+    <script>
+    var one;
+    var two;
+
+    function runTest() {
+        one = document.getElementById('one');
+        two = document.getElementById('two');
+
+        var callback;
+        var fullscreenChanged = function(event) {
+            if (callback)
+                callback(event)
+        };
+        waitForEvent(document, 'webkitfullscreenchange', fullscreenChanged);
+
+        var oneEnteredFullscreen = function() {
+            testExpected("document.webkitFullscreenElement.id", "one");
+            callback = twoEnteredFullscreen;
+            if (window.layoutTestController)
+                runWithKeyDown(function() { two.webkitRequestFullscreen(); });
+        };
+
+        var twoEnteredFullscreen = function() {
+            testExpected("document.webkitFullscreenElement.id", "two");
+            callback = twoExitedFullscreen;
+            if (window.layoutTestController)
+                document.webkitExitFullscreen();
+        };
+
+        var twoExitedFullscreen = function() { 
+            testExpected("document.webkitFullscreenElement.id", "one");
+            endTest();
+        };
+
+        callback = oneEnteredFullscreen;
+        if (window.layoutTestController)
+            runWithKeyDown(function() { one.webkitRequestFullscreen(); });
+    }
+    </script>
+</head>
+<body onload="runTest()">
+    <p>Test for <a href="http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#fullscreen-element-stack">W3C Fullscreen element stack</a>.</p>
+    <p>To test manually, click the "Go full screen" button - the page should enter full screen mode.</p>
+    <div>
+        <button onclick="one.webkitRequestFullscreen()">Go full screen (one)</button>
+    </div>
+    <div id=one>
+        <button onclick="two.webkitRequestFullscreen()">Go full screen (two)</button>
+        <div id=two>
+            <button onclick="document.webkitExitFullscreen()">Exit full screen (two)</button>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/LayoutTests/fullscreen/full-screen-enabled-expected.txt b/LayoutTests/fullscreen/full-screen-enabled-expected.txt
new file mode 100644 (file)
index 0000000..012c0d2
--- /dev/null
@@ -0,0 +1,5 @@
+This tests the fullscreenEnabled property laid out in section 4 of the W3C Full Screen API
+EXPECTED (iframe.contentDocument.webkitFullscreenEnabled == 'true') OK
+EXPECTED (iframe2.contentDocument.webkitFullscreenEnabled == 'false') OK
+END OF TEST
+
diff --git a/LayoutTests/fullscreen/full-screen-enabled.html b/LayoutTests/fullscreen/full-screen-enabled.html
new file mode 100644 (file)
index 0000000..6d10be5
--- /dev/null
@@ -0,0 +1,12 @@
+<body>
+<div>This tests the <code>fullscreenEnabled</code> property laid out in section 4 of the W3C 
+<a href="http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html">Full Screen API</a></div>
+<script src="full-screen-test.js"></script>
+<script>
+    var iframe = document.documentElement.appendChild(document.createElement('iframe'));
+    iframe.setAttribute('webkitallowfullscreen', 'true');
+    var iframe2 = document.documentElement.appendChild(document.createElement('iframe'));
+    testExpected('iframe.contentDocument.webkitFullscreenEnabled', true);
+    testExpected('iframe2.contentDocument.webkitFullscreenEnabled', false);
+    endTest();
+</script>
index 779832a..0e7819b 100644 (file)
@@ -9,7 +9,10 @@
     } else {
         waitForEvent(document, 'webkitfullscreenchange', function() {
            logResult("Entered full screen.", false); 
+           endTest();
         });
+        if (layoutTestController)
+            layoutTestController.setPopupBlockingEnabled(true);
         waitForEventAndEnd(document, 'webkitfullscreenerror');
         document.documentElement.webkitRequestFullScreen();
     }
index 9264219..2330ffb 100644 (file)
@@ -12,6 +12,9 @@ function runTest() {
         consoleWrite("SUCCEED - did not enter full screen!");
     });
 
+    if (layoutTestController)
+        layoutTestController.setPopupBlockingEnabled(true);
+
     var div = document.createElement('div');
     document.documentElement.appendChild(div);
     consoleWrite("Added child element.")
diff --git a/LayoutTests/fullscreen/full-screen-restrictions-expected.txt b/LayoutTests/fullscreen/full-screen-restrictions-expected.txt
new file mode 100644 (file)
index 0000000..84030de
--- /dev/null
@@ -0,0 +1,19 @@
+This tests the restrictions to entering full screen mode laid out in section 4.1 of the W3C Full Screen API
+"The context object is not in a document."
+EVENT(webkitfullscreenerror)
+"The context object's node document, or an ancestor browsing context's document does not have the fullscreen enabled flag set."
+EVENT(webkitfullscreenerror)
+"The context object's node document fullscreen element stack is not empty and its top element is not an ancestor of the context object."
+EVENT(webkitfullscreenchange)
+EVENT(webkitfullscreenerror)
+EVENT(webkitfullscreenchange)
+"A descendant browsing context's document has a non-empty fullscreen element stack."
+EVENT(webkitfullscreenchange)
+EVENT(webkitfullscreenerror)
+EVENT(webkitfullscreenchange)
+"This algorithm is not allowed to show a pop-up."
+EVENT(webkitfullscreenerror)
+END OF TEST
+
+
+
diff --git a/LayoutTests/fullscreen/full-screen-restrictions.html b/LayoutTests/fullscreen/full-screen-restrictions.html
new file mode 100644 (file)
index 0000000..e924b03
--- /dev/null
@@ -0,0 +1,67 @@
+<body onload="runTest();">
+<div>This tests the restrictions to entering full screen mode laid out in section 4.1 of the W3C 
+<a href="http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html">Full Screen API</a></div>
+<script src="full-screen-test.js"></script>
+<script>
+    // Bail out early if the full screen API is not enabled or is missing:
+    if (Element.prototype.webkitRequestFullScreen == undefined) {
+        logResult(false, "Element.prototype.webkitRequestFullScreen == undefined");
+        endTest();
+    } else {
+        var runTest = function() {
+            consoleWrite('"The context object is not in a document."');
+            var div = document.createElement('div');
+            waitForEventOnce(document, 'webkitfullscreenerror', step2);
+            runWithKeyDown(function(){div.webkitRequestFullscreen()});
+        };
+
+        var step2 = function() {
+            consoleWrite('"The context object\'s node document, or an ancestor browsing context\'s document does not have the fullscreen enabled flag set."')
+            var iframe = document.documentElement.appendChild(document.createElement('iframe'));
+            var div = iframe.contentDocument.documentElement.appendChild(iframe.contentDocument.createElement('div'));
+
+            waitForEventOnce(iframe.contentDocument, 'webkitfullscreenerror', step3);
+            runWithKeyDown(function(){div.webkitRequestFullscreen()});
+        };
+
+        var step3 = function() {
+            consoleWrite('"The context object\'s node document fullscreen element stack is not empty and its top element is not an ancestor of the context object."');
+            var div = document.documentElement.appendChild(document.createElement('div'));
+            var div2 = document.documentElement.appendChild(document.createElement('div'));
+            waitForEventOnce(document, 'webkitfullscreenchange', function() {
+                waitForEventOnce(document, 'webkitfullscreenerror', function() {
+                   waitForEventOnce(document, 'webkitfullscreenchange', step4);
+                   document.webkitExitFullscreen(); 
+                });
+                runWithKeyDown(function(){div2.webkitRequestFullscreen()});
+            });
+            runWithKeyDown(function(){div.webkitRequestFullscreen()});
+        };
+
+        var step4 = function() {
+            consoleWrite('"A descendant browsing context\'s document has a non-empty fullscreen element stack."');
+            var iframe = document.documentElement.appendChild(document.createElement('iframe'));
+            iframe.setAttribute('webkitallowfullscreen', 'true');
+            var div = iframe.contentDocument.documentElement.appendChild(iframe.contentDocument.createElement('div'));
+            var div2 = document.documentElement.appendChild(document.createElement('div'));
+            waitForEventOnce(iframe.contentDocument, 'webkitfullscreenchange', function() {
+                waitForEventOnce(document, 'webkitfullscreenerror', function(){
+                    waitForEventOnce(iframe.contentDocument, 'webkitfullscreenchange', step5);
+                    iframe.contentDocument.webkitExitFullscreen(); 
+                });
+                runWithKeyDown(function(){div2.webkitRequestFullscreen()});
+            });
+            runWithKeyDown(function(){div.webkitRequestFullscreen()});
+        };
+
+        var step5 = function() {
+            consoleWrite('"This algorithm is not allowed to show a pop-up."');
+            var div = document.documentElement.appendChild(document.createElement('div'));
+            waitForEventOnce(document, 'webkitfullscreenerror', endTest);
+            div.webkitRequestFullscreen();
+        };
+
+        if (typeof(layoutTestController) != 'undefined')
+            layoutTestController.setPopupBlockingEnabled(true);
+    }
+</script>
index 5e5bb1a..801b5ba 100644 (file)
@@ -98,10 +98,18 @@ function waitForEventAndEnd(element, eventName, funcString)
     waitForEvent(element, eventName, funcString, true)
 }
 
-function waitForEvent(element, eventName, func, endit)
+function waitForEventOnce(element, eventName, func, endit)
+{
+    waitForEvent(element, eventName, func, endit, true)
+}
+
+function waitForEvent(element, eventName, func, endit, once)
 {
     function _eventCallback(event)
     {
+        if (once)
+            element.removeEventListener(eventName, _eventCallback);
+
         consoleWrite("EVENT(" + eventName + ")");
 
         if (func)
index 863860b..82c933a 100644 (file)
@@ -1,3 +1,61 @@
+2012-03-08  Jer Noble  <jer.noble@apple.com>
+
+        Support W3C Full Screen API proposal
+        https://bugs.webkit.org/show_bug.cgi?id=80660
+
+        Reviewed by Alexey Proskuryakov.
+
+        Tests: fullscreen/full-screen-element-stack.html
+               fullscreen/full-screen-enabled.html
+               fullscreen/full-screen-restrictions.html
+
+        The W3C proposal for taking arbitrary elements into full-screen mode is significantly
+        different than the Mozilla proposal. For example, the W3C has proposed a lower-case "s"
+        in "Fullscreen", which means the W3C and Mozilla "requestFullscreen" APIs differ only by
+        in that lower-case "s". Annoying as this is, it does allow us to retain the semantics for
+        the Mozilla case (har!).
+
+        A significant difficulty is obeying the new W3C spec rules is that we would like to apply the
+        fullscreen CSS rules while exiting fullscreen mode, though the W3C spec insists that the
+        webkitFullscreenElement returns the new value immediately.  As such, we retain the m_fullScreenElement
+        variable (distinct from the top of the m_fullScreenElements stack) which is controlled by the
+        webkit{Will,Did}{Enter,Exit}FullScreen functions.
+
+        New APIs for the W3C Fullscreen spec:
+        * dom/Document.h:
+        (WebCore::Document::webkitFullscreenElement):
+        (WebCore::Document::webkitFullscreenEnabled):
+        * dom/Document.idl:
+        * dom/Element.cpp:
+        (WebCore::Element::webkitRequestFullscreen):
+        * dom/Element.h:
+        * dom/Element.idl:
+
+        * dom/Document.cpp:
+        (WebCore::Document::removedLastRef): Clear m_fullScreenElementStack.
+        (WebCore::Document::requestFullScreenForElement): Implement the W3C requirements.
+        (WebCore::Document::webkitExitFullscreen): Ditto.
+        (WebCore::Document::webkitCancelFullScreen): Implement in terms of webkitCancelFullscreen.
+        (WebCore::Document::webkitDidEnterFullScreenForElement): 
+        (WebCore::Document::webkitWillExitFullScreenForElement):
+        (WebCore::Document::webkitDidExitFullScreenForElement):
+        (WebCore::Document::fullScreenChangeDelayTimerFired): Protect against items being
+            added to the event and error queue by swapping out empty queues before starting.
+        (WebCore::Document::clearFullscreenElementStack): Simple accessor.
+        (WebCore::Document::popFullscreenElementStack): Ditto.
+        (WebCore::Document::pushFullscreenElementStack): Ditto.
+        * dom/Element.cpp:
+        (WebCore::Element::webkitRequestFullScreen):
+
+        Add new RuntimeEnabledFeatures functions for the added Document and Element functions.
+        * bindings/generic/RuntimeEnabledFeatures.h:
+        (RuntimeEnabledFeatures):
+        (WebCore::RuntimeEnabledFeatures::webkitFullscreenEnabledEnabled):
+        (WebCore::RuntimeEnabledFeatures::webkitFullscreenElementEnabled):
+        (WebCore::RuntimeEnabledFeatures::webkitExitFullscreenEnabled):
+        (WebCore::RuntimeEnabledFeatures::webkitRequestFullscreenEnabled):
+
+
 2012-03-16  Adam Klein  <adamk@chromium.org>
 
         Make HTMLInputElement::isRadioButton non-virtual and remove unused HTMLFormControlElement::isRadioButton method
index d0cce5f..dd58089 100644 (file)
@@ -71,6 +71,7 @@ public:
     static bool webkitIDBTransactionEnabled() { return isIndexedDBEnabled; }
 
 #if ENABLE(FULLSCREEN_API)
+    // Mozilla version
     static bool webkitFullScreenAPIEnabled() { return isFullScreenAPIEnabled; }
     static void setWebkitFullScreenAPIEnabled(bool isEnabled) { isFullScreenAPIEnabled = isEnabled; }
     static bool webkitRequestFullScreenEnabled() { return isFullScreenAPIEnabled; }
@@ -78,6 +79,12 @@ public:
     static bool webkitFullScreenKeyboardInputAllowedEnabled() { return isFullScreenAPIEnabled; }
     static bool webkitCurrentFullScreenElementEnabled() { return isFullScreenAPIEnabled; }
     static bool webkitCancelFullScreenEnabled() { return isFullScreenAPIEnabled; }
+
+    // W3C version
+    static bool webkitFullscreenEnabledEnabled() { return isFullScreenAPIEnabled; }
+    static bool webkitFullscreenElementEnabled() { return isFullScreenAPIEnabled; }
+    static bool webkitExitFullscreenEnabled() { return isFullScreenAPIEnabled; }
+    static bool webkitRequestFullscreenEnabled() { return isFullScreenAPIEnabled; }
 #endif
 
 #if ENABLE(POINTER_LOCK)
index 190c980..3c8d6cf 100644 (file)
@@ -607,6 +607,7 @@ void Document::removedLastRef()
         m_documentElement = 0;
 #if ENABLE(FULLSCREEN_API)
         m_fullScreenElement = 0;
+        m_fullScreenElementStack.clear();
 #endif
 
         // removeAllChildren() doesn't always unregister IDs,
@@ -5056,43 +5057,231 @@ bool Document::fullScreenIsAllowedForElement(Element* element) const
 
 void Document::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType)
 {
-    do {
-        if (!page() || !page()->settings()->fullScreenEnabled())
-            break;
+    // The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements
+    // for full screen mode, and do not have the concept of a full screen element stack.
+    bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST);
 
+    do {
         if (!element)
             element = documentElement();
-        
+        // 1. If any of the following conditions are true, terminate these steps and queue a task to fire
+        // an event named fullscreenerror with its bubbles attribute set to true on the context object's 
+        // node document:
+
+        // The context object is not in a document.
+        if (!element->inDocument())
+            break;
+
+        // The context object's node document, or an ancestor browsing context's document does not have
+        // the fullscreen enabled flag set.
         if (checkType == EnforceIFrameAllowFulScreenRequirement && !fullScreenIsAllowedForElement(element))
             break;
-        
-        if (!ScriptController::processingUserGesture())
+
+        // The context object's node document fullscreen element stack is not empty and its top element
+        // is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was
+        // made via the legacy Mozilla-style API.)
+        if (!m_fullScreenElementStack.isEmpty() && !m_fullScreenElementStack.first()->contains(element) && !inLegacyMozillaMode)
+            break;
+
+        // A descendant browsing context's document has a non-empty fullscreen element stack.
+        bool descendentHasNonEmptyStack = false;
+        for (Frame* descendant = frame() ? frame()->tree()->traverseNext() : 0; descendant; descendant = descendant->tree()->traverseNext()) {
+            if (descendant->document()->webkitFullscreenElement()) {
+                descendentHasNonEmptyStack = true;
+                break;
+            }
+        }
+        if (descendentHasNonEmptyStack && !inLegacyMozillaMode)
+            break;
+
+        // This algorithm is not allowed to show a pop-up.
+        if (!domWindow()->allowPopUp())
+            break;
+
+        // There is a previously-established user preference, security risk, or platform limitation.
+        if (!page() || !page()->settings()->fullScreenEnabled())
             break;
         
         if (!page()->chrome()->client()->supportsFullScreenForElement(element, flags & Element::ALLOW_KEYBOARD_INPUT))
             break;
-        
+
+        // 2. Let doc be element's node document. (i.e. "this")
+        Document* currentDoc = this;
+
+        // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
+        Deque<Document*> docs;
+
+        do {
+            docs.prepend(currentDoc);
+            currentDoc = currentDoc->ownerElement() ? currentDoc->ownerElement()->document() : 0;
+        } while (currentDoc);
+
+        // 4. For each document in docs, run these substeps:
+        Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
+
+        do {
+            ++following;
+
+            // 1. Let following document be the document after document in docs, or null if there is no
+            // such document.
+            Document* currentDoc = *current;
+            Document* followingDoc = following != docs.end() ? *following : 0;
+
+            // 2. If following document is null, push context object on document's fullscreen element
+            // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
+            // set to true on the document.
+            if (!followingDoc) {
+                currentDoc->pushFullscreenElementStack(element);
+                addDocumentToFullScreenChangeEventQueue(currentDoc);
+                continue;
+            }
+
+            // 3. Otherwise, if document's fullscreen element stack is either empty or its top element
+            // is not following document's browsing context container,
+            Element* topElement = currentDoc->webkitFullscreenElement();
+            if (!topElement || topElement != followingDoc->ownerElement()) {
+                // ...push following document's browsing context container on document's fullscreen element
+                // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
+                // set to true on document.
+                currentDoc->pushFullscreenElementStack(followingDoc->ownerElement());
+                addDocumentToFullScreenChangeEventQueue(currentDoc);
+                continue;
+            }
+
+            // 4. Otherwise, do nothing for this document. It stays the same.
+        } while (++current != docs.end());
+
+        // 5. Return, and run the remaining steps asynchronously.
+        // 6. Optionally, perform some animation.
         m_areKeysEnabledInFullScreen = flags & Element::ALLOW_KEYBOARD_INPUT;
         page()->chrome()->client()->enterFullScreenForElement(element);
+
+        // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
         return;
     } while (0);
-    
+
     m_fullScreenErrorEventTargetQueue.append(element ? element : documentElement());
     m_fullScreenChangeDelayTimer.startOneShot(0);
 }
 
 void Document::webkitCancelFullScreen()
 {
-    if (!page() || !m_fullScreenElement)
+    // The Mozilla "cancelFullScreen()" API behaves like the W3C "fully exit fullscreen" behavior, which
+    // is defined as: 
+    // "To fully exit fullscreen act as if the exitFullscreen() method was invoked on the top-level browsing
+    // context's document and subsequently empty that document's fullscreen element stack."
+    if (!topDocument()->webkitFullscreenElement())
+        return;
+
+    // To achieve that aim, remove all the elements from the top document's stack except for the first before
+    // calling webkitExitFullscreen():
+    Deque<RefPtr<Element> > replacementFullscreenElementStack;
+    replacementFullscreenElementStack.prepend(topDocument()->webkitFullscreenElement());
+    topDocument()->m_fullScreenElementStack.swap(replacementFullscreenElementStack);
+
+    topDocument()->webkitExitFullscreen();
+}
+
+void Document::webkitExitFullscreen()
+{
+    // The exitFullscreen() method must run these steps:
+    
+    // 1. Let doc be the context object. (i.e. "this")
+    Document* currentDoc = this;
+
+    // 2. If doc's fullscreen element stack is empty, terminate these steps.
+    if (m_fullScreenElementStack.isEmpty())
         return;
     
-    page()->chrome()->client()->exitFullScreenForElement(m_fullScreenElement.get());
+    // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen
+    // element stack (if any), ordered so that the child of the doc is last and the document furthest
+    // away from the doc is first.
+    Deque<RefPtr<Document> > descendants;
+    for (Frame* descendant = frame() ? frame()->tree()->traverseNext() : 0; descendant; descendant = descendant->tree()->traverseNext()) {
+        if (descendant->document()->webkitFullscreenElement())
+            descendants.prepend(descendant->document());
+    }
+        
+    // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a
+    // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant.
+    for (Deque<RefPtr<Document> >::iterator i = descendants.begin(); i != descendants.end(); ++i) {
+        (*i)->clearFullscreenElementStack();
+        addDocumentToFullScreenChangeEventQueue(i->get());
+    }
+
+    // 5. While doc is not null, run these substeps:
+    Element* newTop = 0;
+    while (currentDoc) {
+        // 1. Pop the top element of doc's fullscreen element stack.
+        currentDoc->popFullscreenElementStack();
+
+        //    If doc's fullscreen element stack is non-empty and the element now at the top is either
+        //    not in a document or its node document is not doc, repeat this substep.
+        newTop = currentDoc->webkitFullscreenElement();
+        if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc))
+            continue;
+
+        // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
+        // on doc.
+        Node* target = currentDoc->m_fullScreenElement.get();
+        if (!target)
+            target = currentDoc;
+        addDocumentToFullScreenChangeEventQueue(currentDoc);
+
+        // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context
+        // container, set doc to that browsing context container's node document.
+        if (!newTop && currentDoc->ownerElement())
+            currentDoc = currentDoc->ownerElement()->document();
+
+        // 4. Otherwise, set doc to null.
+        currentDoc = 0;
+    }
+
+    // 6. Return, and run the remaining steps asynchronously.
+    // 7. Optionally, perform some animation.
+
+    // Only exit out of full screen window mode if there are no remaining elements in the 
+    // full screen stack.
+    if (!newTop) {
+        page()->chrome()->client()->exitFullScreenForElement(m_fullScreenElement.get());
+        return;
+    }
+
+    // Otherwise, notify the chrome of the new full screen element.
+    page()->chrome()->client()->enterFullScreenForElement(newTop);      
+}
+
+bool Document::webkitFullscreenEnabled() const
+{
+    // 4. The fullscreenEnabled attribute must return true if the context object and all ancestor
+    // browsing context's documents have their fullscreen enabled flag set, or false otherwise.
+
+    // Top-level browsing contexts are implied to have their allowFullScreen attribute set.
+    HTMLFrameOwnerElement* owner = ownerElement();
+    if (!owner)
+        return true;
+
+    do {
+        if (!owner->isFrameElementBase())
+            continue;
+
+        if (!static_cast<HTMLFrameElementBase*>(owner)->allowFullScreen())
+            return false;
+    } while ((owner = owner->document()->ownerElement()));
+
+    return true;        
 }
 
 void Document::webkitWillEnterFullScreenForElement(Element* element)
 {
     ASSERT(element);
-    ASSERT(page() && page()->settings()->fullScreenEnabled());
+
+    // Protect against being called after the document has been removed from the page.
+    if (!page())
+        return;
+
+    ASSERT(page()->settings()->fullScreenEnabled());
 
     if (m_fullScreenRenderer)
         m_fullScreenRenderer->unwrapRenderer();
@@ -5120,14 +5309,19 @@ void Document::webkitWillEnterFullScreenForElement(Element* element)
     
 void Document::webkitDidEnterFullScreenForElement(Element*)
 {
+    if (!m_fullScreenElement)
+        return;
+    
     m_fullScreenElement->didBecomeFullscreenElement();
 
-    m_fullScreenChangeEventTargetQueue.append(m_fullScreenElement);
     m_fullScreenChangeDelayTimer.startOneShot(0);
 }
 
 void Document::webkitWillExitFullScreenForElement(Element*)
 {
+    if (!m_fullScreenElement)
+        return;
+
     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
     
     m_fullScreenElement->willStopBeingFullscreenElement();
@@ -5140,7 +5334,7 @@ void Document::webkitDidExitFullScreenForElement(Element*)
     if (m_fullScreenRenderer)
         m_fullScreenRenderer->unwrapRenderer();
 
-    m_fullScreenChangeEventTargetQueue.append(m_fullScreenElement.release());
+    m_fullScreenElement = 0;
     scheduleForcedStyleRecalc();
     
     m_fullScreenChangeDelayTimer.startOneShot(0);
@@ -5206,28 +5400,34 @@ void Document::setFullScreenRendererBackgroundColor(Color backgroundColor)
     
 void Document::fullScreenChangeDelayTimerFired(Timer<Document>*)
 {
-    while (!m_fullScreenChangeEventTargetQueue.isEmpty()) {
-        RefPtr<Element> element = m_fullScreenChangeEventTargetQueue.takeFirst();
-        if (!element)
-            element = documentElement();
+    Deque<RefPtr<Node> > changeQueue;
+    m_fullScreenChangeEventTargetQueue.swap(changeQueue);
+
+    while (!changeQueue.isEmpty()) {
+        RefPtr<Node> node = changeQueue.takeFirst();
+        if (!node)
+            node = documentElement();
 
         // If the element was removed from our tree, also message the documentElement.
-        if (!contains(element.get()))
-            m_fullScreenChangeEventTargetQueue.append(documentElement());
+        if (!contains(node.get()))
+            changeQueue.append(documentElement());
         
-        element->dispatchEvent(Event::create(eventNames().webkitfullscreenchangeEvent, true, false));
+        node->dispatchEvent(Event::create(eventNames().webkitfullscreenchangeEvent, true, false));
     }
 
-    while (!m_fullScreenErrorEventTargetQueue.isEmpty()) {
-        RefPtr<Element> element = m_fullScreenErrorEventTargetQueue.takeFirst();
-        if (!element)
-            element = documentElement();
+    Deque<RefPtr<Node> > errorQueue;
+    m_fullScreenErrorEventTargetQueue.swap(errorQueue);
+    
+    while (!errorQueue.isEmpty()) {
+        RefPtr<Node> node = errorQueue.takeFirst();
+        if (!node)
+            node = documentElement();
         
-        // If the element was removed from our tree, also message the documentElement.
-        if (!contains(element.get()))
-            m_fullScreenErrorEventTargetQueue.append(documentElement());
+        // If the node was removed from our tree, also message the documentElement.
+        if (!contains(node.get()))
+            errorQueue.append(documentElement());
         
-        element->dispatchEvent(Event::create(eventNames().webkitfullscreenerrorEvent, true, false));
+        node->dispatchEvent(Event::create(eventNames().webkitfullscreenerrorEvent, true, false));
     }
 }
 
@@ -5268,6 +5468,35 @@ void Document::setAnimatingFullScreen(bool flag)
         scheduleForcedStyleRecalc();
     }
 }
+
+void Document::clearFullscreenElementStack()
+{
+    m_fullScreenElementStack.clear();
+}
+
+void Document::popFullscreenElementStack()
+{
+    if (m_fullScreenElementStack.isEmpty())
+        return;
+
+    m_fullScreenElementStack.removeFirst();
+}
+
+void Document::pushFullscreenElementStack(Element* element)
+{
+    m_fullScreenElementStack.prepend(element);
+}
+
+void Document::addDocumentToFullScreenChangeEventQueue(Document* doc)
+{
+    ASSERT(doc);
+    Node* target = doc->webkitFullscreenElement();
+    if (!target)
+        target = doc->webkitCurrentFullScreenElement();
+    if (!target)
+        target = doc;
+    m_fullScreenChangeEventTargetQueue.append(target);
+}
 #endif
 
 void Document::decrementLoadEventDelayCount()
index 4aac16f..f79d45c 100644 (file)
@@ -1079,6 +1079,11 @@ public:
     void removeFullScreenElementOfSubtree(Node*, bool amongChildrenOnly = false);
     bool isAnimatingFullScreen() const;
     void setAnimatingFullScreen(bool);
+
+    // W3C API
+    bool webkitFullscreenEnabled() const;
+    Element* webkitFullscreenElement() const { return !m_fullScreenElementStack.isEmpty() ? m_fullScreenElementStack.first().get() : 0; }
+    void webkitExitFullscreen();
 #endif
 
     // Used to allow element that loads data without going through a FrameLoader to delay the 'load' event.
@@ -1189,6 +1194,13 @@ private:
 
     HTMLCollection* cachedCollection(CollectionType);
 
+#if ENABLE(FULLSCREEN_API)
+    void clearFullscreenElementStack();
+    void popFullscreenElementStack();
+    void pushFullscreenElementStack(Element*);
+    void addDocumentToFullScreenChangeEventQueue(Document*);
+#endif
+
     int m_guardRefCount;
 
     OwnPtr<CSSStyleSelector> m_styleSelector;
@@ -1421,10 +1433,11 @@ private:
 #if ENABLE(FULLSCREEN_API)
     bool m_areKeysEnabledInFullScreen;
     RefPtr<Element> m_fullScreenElement;
+    Deque<RefPtr<Element> > m_fullScreenElementStack;
     RenderFullScreen* m_fullScreenRenderer;
     Timer<Document> m_fullScreenChangeDelayTimer;
-    Deque<RefPtr<Element> > m_fullScreenChangeEventTargetQueue;
-    Deque<RefPtr<Element> > m_fullScreenErrorEventTargetQueue;
+    Deque<RefPtr<Node> > m_fullScreenChangeEventTargetQueue;
+    Deque<RefPtr<Node> > m_fullScreenErrorEventTargetQueue;
     bool m_isAnimatingFullScreen;
     LayoutRect m_savedPlaceholderFrameRect;
     RefPtr<RenderStyle> m_savedPlaceholderRenderStyle;
index b3e0aa3..4e4b7e3 100644 (file)
@@ -241,10 +241,16 @@ module core {
             raises(DOMException);
 
 #if defined(ENABLE_FULLSCREEN_API) && ENABLE_FULLSCREEN_API
+        // Mozilla version
         readonly attribute [V8EnabledAtRuntime] boolean webkitIsFullScreen;
         readonly attribute [V8EnabledAtRuntime] boolean webkitFullScreenKeyboardInputAllowed;
         readonly attribute [V8EnabledAtRuntime] Element webkitCurrentFullScreenElement;
         [V8EnabledAtRuntime] void webkitCancelFullScreen();
+
+        // W3C version
+        readonly attribute [V8EnabledAtRuntime] boolean webkitFullscreenEnabled;
+        readonly attribute [V8EnabledAtRuntime] Element webkitFullscreenElement;
+        [V8EnabledAtRuntime] void webkitExitFullscreen();
 #endif
 
         WebKitNamedFlow webkitGetFlowByName(in DOMString name);
index 2ba7ebf..2df6d4e 100644 (file)
@@ -1858,9 +1858,14 @@ bool Element::childShouldCreateRenderer(const NodeRenderingContext& childContext
 #endif
     
 #if ENABLE(FULLSCREEN_API)
+void Element::webkitRequestFullscreen()
+{
+    document()->requestFullScreenForElement(this, ALLOW_KEYBOARD_INPUT, Document::EnforceIFrameAllowFulScreenRequirement);
+}
+
 void Element::webkitRequestFullScreen(unsigned short flags)
 {
-    document()->requestFullScreenForElement(this, flags, Document::EnforceIFrameAllowFulScreenRequirement);
+    document()->requestFullScreenForElement(this, (flags | LEGACY_MOZILLA_REQUEST), Document::EnforceIFrameAllowFulScreenRequirement);
 }
 
 bool Element::containsFullScreenElement() const
index 4e563ff..def48e9 100644 (file)
@@ -377,13 +377,17 @@ public:
     
 #if ENABLE(FULLSCREEN_API)
     enum {
-        ALLOW_KEYBOARD_INPUT = 1
+        ALLOW_KEYBOARD_INPUT = 1 << 0,
+        LEGACY_MOZILLA_REQUEST = 1 << 1,
     };
     
     void webkitRequestFullScreen(unsigned short flags);
     virtual bool containsFullScreenElement() const;
     virtual void setContainsFullScreenElement(bool);
     virtual void setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(bool);
+
+    // W3C API
+    void webkitRequestFullscreen();
 #endif
 
     virtual bool isSpellCheckingEnabled() const;
index 8725bcb..955bd47 100644 (file)
@@ -129,8 +129,12 @@ module core {
 #endif
 
 #if defined(ENABLE_FULLSCREEN_API) && ENABLE_FULLSCREEN_API
+        // Mozilla version
         const unsigned short ALLOW_KEYBOARD_INPUT = 1;
         [V8EnabledAtRuntime] void webkitRequestFullScreen(in [Optional=DefaultIsUndefined] unsigned short flags);
+
+        // W3C version
+        [V8EnabledAtRuntime] void webkitRequestFullscreen();
 #endif
 
         // CSS Regions API
index 6ae662e..675c292 100644 (file)
@@ -1,3 +1,15 @@
+2012-03-08  Jer Noble  <jer.noble@apple.com>
+
+        Support W3C Full Screen API proposal
+        https://bugs.webkit.org/show_bug.cgi?id=80660
+
+        Reviewed by Alexey Proskuryakov.
+
+        Allow full screen elements to access the keyboard.
+
+        * WebView/WebView.mm:
+        (-[WebView _supportsFullScreenForElement:WebCore::withKeyboard:]):
+
 2012-03-07  Jon Lee  <jonlee@apple.com>
 
         Move NotificationContents into Notification
index 88a11dc..9281148 100644 (file)
@@ -6284,14 +6284,9 @@ bool LayerFlushController::flushLayers()
 #if ENABLE(FULLSCREEN_API)
 - (BOOL)_supportsFullScreenForElement:(const WebCore::Element*)element withKeyboard:(BOOL)withKeyboard
 {
-    if (withKeyboard)
-        return NO;
-
     if (![[WebPreferences standardPreferences] fullScreenEnabled])
         return NO;
 
-    // FIXME: If the element is in an IFrame, we should ensure it has 
-    // an AllowsFullScreen=YES attribute before allowing fullscreen access.
     return YES;
 }
 
index 0476f83..502c8a6 100644 (file)
@@ -1,3 +1,17 @@
+2012-03-08  Jer Noble  <jer.noble@apple.com>
+
+        Support W3C Full Screen API proposal
+        https://bugs.webkit.org/show_bug.cgi?id=80660
+
+        Reviewed by Alexey Proskuryakov.
+
+        Allow full screen elements to access the keyboard.
+
+        * UIProcess/WebFullScreenManagerProxy.cpp:
+        (WebKit::WebFullScreenManagerProxy::supportsFullScreen):
+        * WebProcess/FullScreen/WebFullScreenManager.cpp:
+        (WebKit::WebFullScreenManager::exitFullScreenForElement):
+
 2012-03-16  Andras Becsi  <andras.becsi@nokia.com>
 
         [Qt][WK2] Fix bounce-back behaviour for panning
index 17c070b..21fe9cd 100644 (file)
@@ -91,10 +91,7 @@ void WebFullScreenManagerProxy::setAnimatingFullScreen(bool animating)
 
 void WebFullScreenManagerProxy::supportsFullScreen(bool withKeyboard, bool& supports)
 {
-    if (withKeyboard)
-        supports = false;
-    else
-        supports = true;
+    supports = true;
 }
 
 } // namespace WebKit
index 30fd4b3..bbe64e8 100644 (file)
@@ -101,8 +101,6 @@ void WebFullScreenManager::enterFullScreenForElement(WebCore::Element* element)
 
 void WebFullScreenManager::exitFullScreenForElement(WebCore::Element* element)
 {
-    ASSERT(element);
-    ASSERT(m_element == element);
     m_page->injectedBundleFullScreenClient().exitFullScreenForElement(m_page.get(), element);
 }
 
index 87eece4..e855791 100644 (file)
@@ -252,18 +252,28 @@ DumpRenderTreeDraggingInfo *draggingInfo = nil;
     return YES;
 }
 
-- (void)webView:(WebView *)webView enterFullScreenForElement:(DOMElement*)element listener:(NSObject<WebKitFullScreenListener>*)listener
+- (void)enterFullScreenWithListener:(NSObject<WebKitFullScreenListener>*)listener
 {
     [listener webkitWillEnterFullScreen];
     [listener webkitDidEnterFullScreen];
 }
 
-- (void)webView:(WebView *)webView exitFullScreenForElement:(DOMElement*)element listener:(NSObject<WebKitFullScreenListener>*)listener
+- (void)webView:(WebView *)webView enterFullScreenForElement:(DOMElement*)element listener:(NSObject<WebKitFullScreenListener>*)listener
+{
+    [self performSelector:@selector(enterFullScreenWithListener:) withObject:listener afterDelay:0];
+}
+
+- (void)exitFullScreenWithListener:(NSObject<WebKitFullScreenListener>*)listener
 {
     [listener webkitWillExitFullScreen];
     [listener webkitDidExitFullScreen];
 }
 
+- (void)webView:(WebView *)webView exitFullScreenForElement:(DOMElement*)element listener:(NSObject<WebKitFullScreenListener>*)listener
+{
+    [self performSelector:@selector(exitFullScreenWithListener:) withObject:listener afterDelay:0];
+}
+
 - (BOOL)webView:(WebView *)webView didPressMissingPluginButton:(DOMElement *)element
 {
     printf("MISSING PLUGIN BUTTON PRESSED\n");