Search code examples
firefoxfullscreenelm

Fullscreen API using ports in elm does not work in Firefox


I am using ports in elm to access the browser's fullscreen API. Everything works well in Chrome, but it does not work in Firefox. The error I get is: Request for full-screen was denied because Element.mozRequestFullScreen() was not called from inside a short running user-generated event handler. I think I understand the error message, however, in a way I would expect it to work because I do access the fullscreen API via a button click. There is just an elm port in between. Has anyone solved this problem?

This is my elm code:

port module Main exposing (..)

import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import Html.Attributes exposing (class)


main =
    Html.program { init = init, view = view, update = update, subscriptions = subscriptions }



-- Model


type alias Model =
    { fullscreen : Bool }


init : ( Model, Cmd Msg )
init =
    ( { fullscreen = False }
    , Cmd.none
    )



-- Ports


port activateFullscreen : String -> Cmd msg


port deactivateFullscreen : String -> Cmd msg


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none


type Msg
    = FullscreenMode Bool



-- Update


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FullscreenMode on ->
            let
                m =
                    { model | fullscreen = on }
            in
                if on then
                    ( m, activateFullscreen "" )
                else
                    ( m, deactivateFullscreen "" )



-- views


fullScreenButton : Model -> Html Msg
fullScreenButton model =
    case model.fullscreen of
        False ->
            button [ onClick (FullscreenMode True) ]
                [ text "fullscreen on" ]

        True ->
            button [ onClick (FullscreenMode False) ]
                [ text "fullscreen off" ]


view : Model -> Html Msg
view model =
    div [ class "app" ] [ fullScreenButton model ]

and my html code:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>Fullscreen Test</title>
  </head>
  <body>
    <div id="main"></div>
    <script src="main.js"></script>
    <script>
      (function () {
        window.onload = function () {
          var node = document.getElementById('main');
          var app = Elm.Main.embed(node);
          app.ports.activateFullscreen.subscribe( function () {
            var element = document.querySelector('.app');
            if (element.requestFullscreen) {
              element.requestFullscreen();
            } else if (element.webkitRequestFullscreen) {
              element.webkitRequestFullscreen();
            } else if (element.mozRequestFullScreen) {
              element.mozRequestFullScreen();
            } else if (element.msRequestFullscreen) {
              element.msRequestFullscreen();
            }
          });
          app.ports.deactivateFullscreen.subscribe( function () {
            if (document.exitFullscreen) {
              document.exitFullscreen();
            } else if (document.webkitExitFullscreen) {
              document.webkitExitFullscreen();
            } else if (document.mozCancelFullScreen) {
              document.mozCancelFullScreen();
            } else if (document.msExitFullscreen) {
              document.msExitFullscreen();
            }
          });
        };
      }());
    </script>
  </body>
</html>

Solution

  • as bdukes's comment above points out, there is a workaround to this issue. Instead of using ports for the FullScreen handling, we can use functions that we put on the global window object. The resulting elm code for the button would be something like this:

    button [ onClick (FullscreenMode True), attribute "onClick" "window.enterFullScreen()" ]
        [ text "fullscreen on" ]
    

    I would still keep elm's onClick there to update the internal state.

    In the JavaScript we can then define the enterFullScreen function and handle our logic there:

    window.enterFullScreen = function () {
        var element = document.querySelector('.app');
        if (element.requestFullscreen) {
          element.requestFullscreen();
        } else if (element.webkitRequestFullscreen) {
          element.webkitRequestFullscreen();
        } else if (element.mozRequestFullScreen) {
          element.mozRequestFullScreen();
        } else if (element.msRequestFullscreen) {
          element.msRequestFullscreen();
        }
    };