1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/fullscreen/fullscreen_controller_state_test.h"
6
7#include <memory.h>
8
9#include <iomanip>
10#include <iostream>
11
12#include "chrome/browser/fullscreen.h"
13#include "chrome/browser/ui/browser.h"
14#include "chrome/browser/ui/browser_window.h"
15#include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
16#include "chrome/browser/ui/fullscreen/fullscreen_controller_test.h"
17#include "chrome/browser/ui/tabs/tab_strip_model.h"
18#include "content/public/browser/web_contents.h"
19#include "content/public/common/url_constants.h"
20#include "testing/gtest/include/gtest/gtest.h"
21
22#if defined(OS_MACOSX)
23#include "base/mac/mac_util.h"
24#endif
25
26namespace {
27
28bool SupportsMacSystemFullscreen() {
29#if defined(OS_MACOSX)
30  return chrome::mac::SupportsSystemFullscreen();
31#else
32  return false;
33#endif
34}
35
36}  // namespace
37
38FullscreenControllerStateTest::FullscreenControllerStateTest()
39    : state_(STATE_NORMAL),
40      last_notification_received_state_(STATE_NORMAL) {
41  // Human specified state machine data.
42  // For each state, for each event, define the resulting state.
43  State transition_table_data[][NUM_EVENTS] = {
44    { // STATE_NORMAL:
45      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event TOGGLE_FULLSCREEN
46      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event TOGGLE_FULLSCREEN_CHROME
47      STATE_TO_TAB_FULLSCREEN,                // Event TAB_FULLSCREEN_TRUE
48      STATE_NORMAL,                           // Event TAB_FULLSCREEN_FALSE
49      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
50      STATE_NORMAL,                           // Event METRO_SNAP_FALSE
51      STATE_NORMAL,                           // Event BUBBLE_EXIT_LINK
52      STATE_NORMAL,                           // Event BUBBLE_ALLOW
53      STATE_NORMAL,                           // Event BUBBLE_DENY
54      STATE_NORMAL,                           // Event WINDOW_CHANGE
55    },
56    { // STATE_BROWSER_FULLSCREEN_NO_CHROME:
57      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN
58      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event TOGGLE_FULLSCREEN_CHROME
59      STATE_TAB_BROWSER_FULLSCREEN,           // Event TAB_FULLSCREEN_TRUE
60      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event TAB_FULLSCREEN_FALSE
61      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
62      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event METRO_SNAP_FALSE
63      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
64      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event BUBBLE_ALLOW
65      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event BUBBLE_DENY
66      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event WINDOW_CHANGE
67    },
68    { // STATE_BROWSER_FULLSCREEN_WITH_CHROME:
69      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event TOGGLE_FULLSCREEN
70      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN_CHROME
71      STATE_TAB_BROWSER_FULLSCREEN_CHROME,    // Event TAB_FULLSCREEN_TRUE
72      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event TAB_FULLSCREEN_FALSE
73      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event METRO_SNAP_TRUE
74      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event METRO_SNAP_FALSE
75      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
76      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event BUBBLE_ALLOW
77      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event BUBBLE_DENY
78      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event WINDOW_CHANGE
79    },
80    { // STATE_METRO_SNAP:
81      STATE_METRO_SNAP,                       // Event TOGGLE_FULLSCREEN
82      STATE_METRO_SNAP,                       // Event TOGGLE_FULLSCREEN_CHROME
83      STATE_METRO_SNAP,                       // Event TAB_FULLSCREEN_TRUE
84      STATE_METRO_SNAP,                       // Event TAB_FULLSCREEN_FALSE
85      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
86      STATE_NORMAL,                           // Event METRO_SNAP_FALSE
87      STATE_METRO_SNAP,                       // Event BUBBLE_EXIT_LINK
88      STATE_METRO_SNAP,                       // Event BUBBLE_ALLOW
89      STATE_METRO_SNAP,                       // Event BUBBLE_DENY
90      STATE_METRO_SNAP,                       // Event WINDOW_CHANGE
91    },
92    { // STATE_TAB_FULLSCREEN:
93      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN
94      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN_CHROME
95      STATE_TAB_FULLSCREEN,                   // Event TAB_FULLSCREEN_TRUE
96      STATE_TO_NORMAL,                        // Event TAB_FULLSCREEN_FALSE
97      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
98      STATE_TAB_FULLSCREEN,                   // Event METRO_SNAP_FALSE
99      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
100      STATE_TAB_FULLSCREEN,                   // Event BUBBLE_ALLOW
101      STATE_TO_NORMAL,                        // Event BUBBLE_DENY
102      STATE_TAB_FULLSCREEN,                   // Event WINDOW_CHANGE
103    },
104    { // STATE_TAB_BROWSER_FULLSCREEN:
105      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN
106      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN_CHROME
107      STATE_TAB_BROWSER_FULLSCREEN,           // Event TAB_FULLSCREEN_TRUE
108      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event TAB_FULLSCREEN_FALSE
109      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
110      STATE_TAB_BROWSER_FULLSCREEN,           // Event METRO_SNAP_FALSE
111      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event BUBBLE_EXIT_LINK
112      STATE_TAB_BROWSER_FULLSCREEN,           // Event BUBBLE_ALLOW
113      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event BUBBLE_DENY
114      STATE_TAB_BROWSER_FULLSCREEN,           // Event WINDOW_CHANGE
115    },
116    { // STATE_TAB_BROWSER_FULLSCREEN_CHROME:
117      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN
118      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN_CHROME
119      STATE_TAB_BROWSER_FULLSCREEN_CHROME,    // Event TAB_FULLSCREEN_TRUE
120      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event TAB_FULLSCREEN_FALSE
121      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
122      STATE_TAB_BROWSER_FULLSCREEN_CHROME,    // Event METRO_SNAP_FALSE
123      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event BUBBLE_EXIT_LINK
124      STATE_TAB_BROWSER_FULLSCREEN_CHROME,    // Event BUBBLE_ALLOW
125      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event BUBBLE_DENY
126      STATE_TAB_BROWSER_FULLSCREEN_CHROME,    // Event WINDOW_CHANGE
127    },
128    { // STATE_TO_NORMAL:
129      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN
130      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event TOGGLE_FULLSCREEN_CHROME
131      // TODO(scheib) Should be a route back to TAB. http://crbug.com/154196
132      STATE_TO_NORMAL,                        // Event TAB_FULLSCREEN_TRUE
133      STATE_TO_NORMAL,                        // Event TAB_FULLSCREEN_FALSE
134      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
135      STATE_TO_NORMAL,                        // Event METRO_SNAP_FALSE
136      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
137      STATE_TO_NORMAL,                        // Event BUBBLE_ALLOW
138      STATE_TO_NORMAL,                        // Event BUBBLE_DENY
139      STATE_NORMAL,                           // Event WINDOW_CHANGE
140    },
141    { // STATE_TO_BROWSER_FULLSCREEN_NO_CHROME:
142      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event TOGGLE_FULLSCREEN
143      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event TOGGLE_FULLSCREEN_CHROME
144      // TODO(scheib) Should be a route to TAB_BROWSER http://crbug.com/154196
145      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event TAB_FULLSCREEN_TRUE
146      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event TAB_FULLSCREEN_FALSE
147      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
148      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event METRO_SNAP_FALSE
149#if defined(OS_MACOSX)
150      // Mac window reports fullscreen immediately and an exit triggers exit.
151      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
152#else
153      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event BUBBLE_EXIT_LINK
154#endif
155      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event BUBBLE_ALLOW
156      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event BUBBLE_DENY
157      STATE_BROWSER_FULLSCREEN_NO_CHROME,     // Event WINDOW_CHANGE
158    },
159    { // STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME:
160      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event TOGGLE_FULLSCREEN
161      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN_CHROME
162      // TODO(scheib) Should be a route to TAB_BROWSER http://crbug.com/154196
163      STATE_TAB_BROWSER_FULLSCREEN,           // Event TAB_FULLSCREEN_TRUE
164      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event TAB_FULLSCREEN_FALSE
165      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event METRO_SNAP_TRUE
166      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event METRO_SNAP_FALSE
167      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
168      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event BUBBLE_ALLOW
169      STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME,// Event BUBBLE_DENY
170      STATE_BROWSER_FULLSCREEN_WITH_CHROME,   // Event WINDOW_CHANGE
171    },
172    { // STATE_TO_TAB_FULLSCREEN:
173      // TODO(scheib) Should be a route to TAB_BROWSER http://crbug.com/154196
174      STATE_TO_TAB_FULLSCREEN,                // Event TOGGLE_FULLSCREEN
175      STATE_TO_NORMAL,                        // Event TOGGLE_FULLSCREEN_CHROME
176      STATE_TO_TAB_FULLSCREEN,                // Event TAB_FULLSCREEN_TRUE
177#if defined(OS_MACOSX)
178      // Mac runs as expected due to a forced NotifyTabOfExitIfNecessary();
179      STATE_TO_NORMAL,                        // Event TAB_FULLSCREEN_FALSE
180#else
181      // TODO(scheib) Should be a route back to NORMAL. http://crbug.com/154196
182      STATE_TO_BROWSER_FULLSCREEN_NO_CHROME,  // Event TAB_FULLSCREEN_FALSE
183#endif
184      STATE_METRO_SNAP,                       // Event METRO_SNAP_TRUE
185      STATE_TO_TAB_FULLSCREEN,                // Event METRO_SNAP_FALSE
186#if defined(OS_MACOSX)
187      // Mac window reports fullscreen immediately and an exit triggers exit.
188      STATE_TO_NORMAL,                        // Event BUBBLE_EXIT_LINK
189#else
190      STATE_TO_TAB_FULLSCREEN,                // Event BUBBLE_EXIT_LINK
191#endif
192      STATE_TO_TAB_FULLSCREEN,                // Event BUBBLE_ALLOW
193#if defined(OS_MACOSX)
194      // Mac window reports fullscreen immediately and an exit triggers exit.
195      STATE_TO_NORMAL,                        // Event BUBBLE_DENY
196#else
197      STATE_TO_TAB_FULLSCREEN,                // Event BUBBLE_DENY
198#endif
199      STATE_TAB_FULLSCREEN,                   // Event WINDOW_CHANGE
200    },
201  };
202  COMPILE_ASSERT(sizeof(transition_table_data) == sizeof(transition_table_),
203                 transition_table_incorrect_size);
204  memcpy(transition_table_, transition_table_data,
205         sizeof(transition_table_data));
206
207  // Verify that transition_table_ has been completely defined.
208  for (int source = 0; source < NUM_STATES; ++source) {
209    for (int event = 0; event < NUM_EVENTS; ++event) {
210      EXPECT_NE(transition_table_[source][event], STATE_INVALID);
211      EXPECT_GE(transition_table_[source][event], 0);
212      EXPECT_LT(transition_table_[source][event], NUM_STATES);
213    }
214  }
215
216  // Copy transition_table_ data into state_transitions_ table.
217  for (int source = 0; source < NUM_STATES; ++source) {
218    for (int event = 0; event < NUM_EVENTS; ++event) {
219      if (ShouldSkipStateAndEventPair(static_cast<State>(source),
220                                      static_cast<Event>(event)))
221        continue;
222      State destination = transition_table_[source][event];
223      state_transitions_[source][destination].event = static_cast<Event>(event);
224      state_transitions_[source][destination].state = destination;
225      state_transitions_[source][destination].distance = 1;
226    }
227  }
228}
229
230FullscreenControllerStateTest::~FullscreenControllerStateTest() {
231}
232
233// static
234const char* FullscreenControllerStateTest::GetStateString(State state) {
235  switch (state) {
236    ENUM_TO_STRING(STATE_NORMAL);
237    ENUM_TO_STRING(STATE_BROWSER_FULLSCREEN_NO_CHROME);
238    ENUM_TO_STRING(STATE_BROWSER_FULLSCREEN_WITH_CHROME);
239    ENUM_TO_STRING(STATE_METRO_SNAP);
240    ENUM_TO_STRING(STATE_TAB_FULLSCREEN);
241    ENUM_TO_STRING(STATE_TAB_BROWSER_FULLSCREEN);
242    ENUM_TO_STRING(STATE_TAB_BROWSER_FULLSCREEN_CHROME);
243    ENUM_TO_STRING(STATE_TO_NORMAL);
244    ENUM_TO_STRING(STATE_TO_BROWSER_FULLSCREEN_NO_CHROME);
245    ENUM_TO_STRING(STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME);
246    ENUM_TO_STRING(STATE_TO_TAB_FULLSCREEN);
247    ENUM_TO_STRING(STATE_INVALID);
248    default:
249      NOTREACHED() << "No string for state " << state;
250      return "State-Unknown";
251  }
252}
253
254// static
255const char* FullscreenControllerStateTest::GetEventString(Event event) {
256  switch (event) {
257    ENUM_TO_STRING(TOGGLE_FULLSCREEN);
258    ENUM_TO_STRING(TOGGLE_FULLSCREEN_CHROME);
259    ENUM_TO_STRING(TAB_FULLSCREEN_TRUE);
260    ENUM_TO_STRING(TAB_FULLSCREEN_FALSE);
261    ENUM_TO_STRING(METRO_SNAP_TRUE);
262    ENUM_TO_STRING(METRO_SNAP_FALSE);
263    ENUM_TO_STRING(BUBBLE_EXIT_LINK);
264    ENUM_TO_STRING(BUBBLE_ALLOW);
265    ENUM_TO_STRING(BUBBLE_DENY);
266    ENUM_TO_STRING(WINDOW_CHANGE);
267    ENUM_TO_STRING(EVENT_INVALID);
268    default:
269      NOTREACHED() << "No string for event " << event;
270      return "Event-Unknown";
271  }
272}
273
274// static
275bool FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant() {
276#if defined(TOOLKIT_VIEWS)
277  return true;
278#else
279  return false;
280#endif
281}
282
283// static
284bool FullscreenControllerStateTest::IsPersistentState(State state) {
285  switch (state) {
286    case STATE_NORMAL:
287    case STATE_BROWSER_FULLSCREEN_NO_CHROME:
288    case STATE_BROWSER_FULLSCREEN_WITH_CHROME:
289    case STATE_METRO_SNAP:
290    case STATE_TAB_FULLSCREEN:
291    case STATE_TAB_BROWSER_FULLSCREEN:
292    case STATE_TAB_BROWSER_FULLSCREEN_CHROME:
293      return true;
294
295    case STATE_TO_NORMAL:
296    case STATE_TO_BROWSER_FULLSCREEN_NO_CHROME:
297    case STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME:
298    case STATE_TO_TAB_FULLSCREEN:
299      return false;
300
301    default:
302      NOTREACHED();
303      return false;
304  }
305}
306
307void FullscreenControllerStateTest::TransitionToState(State final_state) {
308  int max_steps = NUM_STATES;
309  while (max_steps-- && TransitionAStepTowardState(final_state))
310    continue;
311  ASSERT_GE(max_steps, 0) << "TransitionToState was unable to achieve desired "
312      << "target state. TransitionAStepTowardState iterated too many times."
313      << GetAndClearDebugLog();
314  ASSERT_EQ(final_state, state_) << "TransitionToState was unable to achieve "
315      << "desired target state. TransitionAStepTowardState returned false."
316      << GetAndClearDebugLog();
317}
318
319bool FullscreenControllerStateTest::TransitionAStepTowardState(
320    State destination_state) {
321  State source_state = state_;
322  if (source_state == destination_state)
323    return false;
324
325  StateTransitionInfo next = NextTransitionInShortestPath(source_state,
326                                                          destination_state,
327                                                          NUM_STATES);
328  if (next.state == STATE_INVALID) {
329    NOTREACHED() << "TransitionAStepTowardState unable to transition. "
330        << "NextTransitionInShortestPath("
331        << GetStateString(source_state) << ", "
332        << GetStateString(destination_state) << ") returned STATE_INVALID."
333        << GetAndClearDebugLog();
334    return false;
335  }
336
337  return InvokeEvent(next.event);
338}
339
340const char* FullscreenControllerStateTest::GetWindowStateString() {
341  return NULL;
342}
343
344bool FullscreenControllerStateTest::InvokeEvent(Event event) {
345  if (!fullscreen_notification_observer_.get()) {
346    // Start observing NOTIFICATION_FULLSCREEN_CHANGED. Construct the
347    // notification observer here instead of in
348    // FullscreenControllerStateTest::FullscreenControllerStateTest() so that we
349    // listen to notifications on the proper thread.
350    fullscreen_notification_observer_.reset(
351        new FullscreenNotificationObserver());
352  }
353
354  State source_state = state_;
355  State next_state = transition_table_[source_state][event];
356
357  EXPECT_FALSE(ShouldSkipStateAndEventPair(source_state, event))
358      << GetAndClearDebugLog();
359
360  // When simulating reentrant window change calls, expect the next state
361  // automatically.
362  if (IsWindowFullscreenStateChangedReentrant())
363    next_state = transition_table_[next_state][WINDOW_CHANGE];
364
365  debugging_log_ << "  InvokeEvent(" << std::left
366      << std::setw(kMaxStateNameLength) << GetEventString(event)
367      << ") to "
368      << std::setw(kMaxStateNameLength) << GetStateString(next_state);
369
370  state_ = next_state;
371
372  switch (event) {
373    case TOGGLE_FULLSCREEN:
374      GetFullscreenController()->ToggleBrowserFullscreenMode();
375      break;
376
377    case TOGGLE_FULLSCREEN_CHROME:
378#if defined(OS_MACOSX)
379      if (chrome::mac::SupportsSystemFullscreen()) {
380        GetFullscreenController()->ToggleBrowserFullscreenWithChrome();
381        break;
382      }
383#endif
384      NOTREACHED() << GetAndClearDebugLog();
385      break;
386
387    case TAB_FULLSCREEN_TRUE:
388    case TAB_FULLSCREEN_FALSE: {
389      content::WebContents* const active_tab =
390          GetBrowser()->tab_strip_model()->GetActiveWebContents();
391      GetFullscreenController()->
392          ToggleFullscreenModeForTab(active_tab, event == TAB_FULLSCREEN_TRUE);
393      // Activating/Deactivating tab fullscreen on a captured tab should not
394      // evoke a state change in the browser window.
395      if (active_tab->GetCapturerCount() > 0)
396        state_ = source_state;
397      break;
398    }
399
400    case METRO_SNAP_TRUE:
401#if defined(OS_WIN)
402      GetFullscreenController()->SetMetroSnapMode(true);
403#else
404      NOTREACHED() << GetAndClearDebugLog();
405#endif
406      break;
407
408    case METRO_SNAP_FALSE:
409#if defined(OS_WIN)
410      GetFullscreenController()->SetMetroSnapMode(false);
411#else
412      NOTREACHED() << GetAndClearDebugLog();
413#endif
414      break;
415
416    case BUBBLE_EXIT_LINK:
417      GetFullscreenController()->ExitTabOrBrowserFullscreenToPreviousState();
418      break;
419
420    case BUBBLE_ALLOW:
421      GetFullscreenController()->OnAcceptFullscreenPermission();
422      break;
423
424    case BUBBLE_DENY:
425      GetFullscreenController()->OnDenyFullscreenPermission();
426      break;
427
428    case WINDOW_CHANGE:
429      ChangeWindowFullscreenState();
430      break;
431
432    default:
433      NOTREACHED() << "InvokeEvent needs a handler for event "
434          << GetEventString(event) << GetAndClearDebugLog();
435      return false;
436  }
437
438  if (GetWindowStateString())
439    debugging_log_ << " Window state now " << GetWindowStateString() << "\n";
440  else
441    debugging_log_ << "\n";
442
443  MaybeWaitForNotification();
444  VerifyWindowState();
445
446  return true;
447}
448
449void FullscreenControllerStateTest::VerifyWindowState() {
450  switch (state_) {
451    case STATE_NORMAL:
452      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
453                                    FULLSCREEN_WITHOUT_CHROME_FALSE,
454                                    FULLSCREEN_FOR_BROWSER_FALSE,
455                                    FULLSCREEN_FOR_TAB_FALSE,
456                                    IN_METRO_SNAP_FALSE);
457      break;
458    case STATE_BROWSER_FULLSCREEN_NO_CHROME:
459      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
460                                    FULLSCREEN_WITHOUT_CHROME_TRUE,
461                                    FULLSCREEN_FOR_BROWSER_TRUE,
462                                    FULLSCREEN_FOR_TAB_FALSE,
463                                    IN_METRO_SNAP_FALSE);
464      break;
465    case STATE_BROWSER_FULLSCREEN_WITH_CHROME:
466      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_TRUE,
467                                    FULLSCREEN_WITHOUT_CHROME_FALSE,
468                                    FULLSCREEN_FOR_BROWSER_TRUE,
469                                    FULLSCREEN_FOR_TAB_FALSE,
470                                    IN_METRO_SNAP_FALSE);
471      break;
472    case STATE_METRO_SNAP:
473      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_NO_EXPECTATION,
474                                    FULLSCREEN_WITHOUT_CHROME_NO_EXPECTATION,
475                                    FULLSCREEN_FOR_BROWSER_NO_EXPECTATION,
476                                    FULLSCREEN_FOR_TAB_NO_EXPECTATION,
477                                    IN_METRO_SNAP_TRUE);
478      break;
479    case STATE_TAB_FULLSCREEN:
480      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
481                                    FULLSCREEN_WITHOUT_CHROME_TRUE,
482                                    FULLSCREEN_FOR_BROWSER_FALSE,
483                                    FULLSCREEN_FOR_TAB_TRUE,
484                                    IN_METRO_SNAP_FALSE);
485      break;
486    case STATE_TAB_BROWSER_FULLSCREEN:
487      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
488                                    FULLSCREEN_WITHOUT_CHROME_TRUE,
489                                    FULLSCREEN_FOR_BROWSER_TRUE,
490                                    FULLSCREEN_FOR_TAB_TRUE,
491                                    IN_METRO_SNAP_FALSE);
492      break;
493    case STATE_TAB_BROWSER_FULLSCREEN_CHROME:
494      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
495                                    FULLSCREEN_WITHOUT_CHROME_TRUE,
496                                    FULLSCREEN_FOR_BROWSER_TRUE,
497                                    FULLSCREEN_FOR_TAB_TRUE,
498                                    IN_METRO_SNAP_FALSE);
499      break;
500    case STATE_TO_NORMAL:
501      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
502                                    FULLSCREEN_WITHOUT_CHROME_FALSE,
503                                    FULLSCREEN_FOR_BROWSER_NO_EXPECTATION,
504                                    FULLSCREEN_FOR_TAB_NO_EXPECTATION,
505                                    IN_METRO_SNAP_FALSE);
506      break;
507
508    case STATE_TO_BROWSER_FULLSCREEN_NO_CHROME:
509      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_FALSE,
510                                    FULLSCREEN_WITHOUT_CHROME_TRUE,
511#if defined(OS_MACOSX)
512                                    FULLSCREEN_FOR_BROWSER_TRUE,
513#else
514                                    FULLSCREEN_FOR_BROWSER_FALSE,
515#endif
516                                    FULLSCREEN_FOR_TAB_NO_EXPECTATION,
517                                    IN_METRO_SNAP_FALSE);
518      break;
519
520    case STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME:
521      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_TRUE,
522                                    FULLSCREEN_WITHOUT_CHROME_FALSE,
523#if defined(OS_MACOSX)
524                                    FULLSCREEN_FOR_BROWSER_TRUE,
525#else
526                                    FULLSCREEN_FOR_BROWSER_FALSE,
527#endif
528                                    FULLSCREEN_FOR_TAB_NO_EXPECTATION,
529                                    IN_METRO_SNAP_FALSE);
530      break;
531
532    case STATE_TO_TAB_FULLSCREEN:
533#if defined(OS_MACOSX)
534      // TODO(scheib) InPresentationMode returns false when invoking events:
535      // TAB_FULLSCREEN_TRUE, TOGGLE_FULLSCREEN. http://crbug.com/156645
536      // It may be that a new testing state TO_TAB_BROWSER_FULLSCREEN
537      // would help work around this http://crbug.com/154196
538      // Test with: STATE_TO_TAB_FULLSCREEN__TOGGLE_FULLSCREEN
539      //
540      // EXPECT_TRUE(GetBrowser()->window()->InPresentationMode())
541      //     << GetAndClearDebugLog();
542#endif
543      VerifyWindowStateExpectations(FULLSCREEN_WITH_CHROME_NO_EXPECTATION,
544                                    FULLSCREEN_WITHOUT_CHROME_NO_EXPECTATION,
545                                    FULLSCREEN_FOR_BROWSER_FALSE,
546                                    FULLSCREEN_FOR_TAB_TRUE,
547                                    IN_METRO_SNAP_FALSE);
548      break;
549
550    default:
551      NOTREACHED() << GetAndClearDebugLog();
552  }
553}
554
555void FullscreenControllerStateTest::MaybeWaitForNotification() {
556  // We should get a fullscreen notification each time we get to a new
557  // persistent state. If we don't get a notification, the test will
558  // fail by timing out.
559  if (state_ != last_notification_received_state_ &&
560      IsPersistentState(state_)) {
561    fullscreen_notification_observer_->Wait();
562    last_notification_received_state_ = state_;
563    fullscreen_notification_observer_.reset(
564        new FullscreenNotificationObserver());
565  }
566}
567
568void FullscreenControllerStateTest::TestTransitionsForEachState() {
569  for (int source_int = 0; source_int < NUM_STATES; ++source_int) {
570    for (int event1_int = 0; event1_int < NUM_EVENTS; ++event1_int) {
571      State state = static_cast<State>(source_int);
572      Event event1 = static_cast<Event>(event1_int);
573
574      // Early out if skipping all tests for this state, reduces log noise.
575      if (ShouldSkipTest(state, event1))
576        continue;
577
578      for (int event2_int = 0; event2_int < NUM_EVENTS; ++event2_int) {
579        for (int event3_int = 0; event3_int < NUM_EVENTS; ++event3_int) {
580          Event event2 = static_cast<Event>(event2_int);
581          Event event3 = static_cast<Event>(event3_int);
582
583          // Test each state and each event.
584          ASSERT_NO_FATAL_FAILURE(TestStateAndEvent(state, event1))
585              << GetAndClearDebugLog();
586
587          // Then, add an additional event to the sequence.
588          if (ShouldSkipStateAndEventPair(state_, event2))
589            continue;
590          ASSERT_TRUE(InvokeEvent(event2)) << GetAndClearDebugLog();
591
592          // Then, add an additional event to the sequence.
593          if (ShouldSkipStateAndEventPair(state_, event3))
594            continue;
595          ASSERT_TRUE(InvokeEvent(event3)) << GetAndClearDebugLog();
596        }
597      }
598    }
599  }
600}
601
602FullscreenControllerStateTest::StateTransitionInfo
603    FullscreenControllerStateTest::NextTransitionInShortestPath(
604    State source,
605    State destination,
606    int search_limit) {
607  if (search_limit <= 0)
608    return StateTransitionInfo();  // Return a default (invalid) state.
609
610  if (state_transitions_[source][destination].state == STATE_INVALID) {
611    // Don't know the next state yet, do a depth first search.
612    StateTransitionInfo result;
613
614    // Consider all states reachable via each event from the source state.
615    for (int event_int = 0; event_int < NUM_EVENTS; ++event_int) {
616      Event event = static_cast<Event>(event_int);
617      State next_state_candidate = transition_table_[source][event];
618
619      if (ShouldSkipStateAndEventPair(source, event))
620        continue;
621
622      // Recurse.
623      StateTransitionInfo candidate = NextTransitionInShortestPath(
624          next_state_candidate, destination, search_limit - 1);
625
626      if (candidate.distance + 1 < result.distance) {
627        result.event = event;
628        result.state = next_state_candidate;
629        result.distance = candidate.distance + 1;
630      }
631    }
632
633    // Cache result so that a search is not required next time.
634    state_transitions_[source][destination] = result;
635  }
636
637  return state_transitions_[source][destination];
638}
639
640std::string FullscreenControllerStateTest::GetAndClearDebugLog() {
641  debugging_log_ << "(End of Debugging Log)\n";
642  std::string output_log = "\nDebugging Log:\n" + debugging_log_.str();
643  debugging_log_.str(std::string());
644  return output_log;
645}
646
647bool FullscreenControllerStateTest::ShouldSkipStateAndEventPair(State state,
648                                                                Event event) {
649  // TODO(scheib) Toggling Tab fullscreen while pending Tab or
650  // Browser fullscreen is broken currently http://crbug.com/154196
651  if ((state == STATE_TO_BROWSER_FULLSCREEN_NO_CHROME ||
652       state == STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME ||
653       state == STATE_TO_TAB_FULLSCREEN) &&
654      (event == TAB_FULLSCREEN_TRUE || event == TAB_FULLSCREEN_FALSE))
655    return true;
656  if (state == STATE_TO_NORMAL && event == TAB_FULLSCREEN_TRUE)
657    return true;
658
659  // Skip metro snap state and events when not on windows.
660#if !defined(OS_WIN)
661  if (state == STATE_METRO_SNAP ||
662      event == METRO_SNAP_TRUE ||
663      event == METRO_SNAP_FALSE)
664    return true;
665#endif
666
667  // Skip Mac Lion Fullscreen state and events when not on OSX 10.7+.
668  if (!SupportsMacSystemFullscreen()) {
669    if (state == STATE_BROWSER_FULLSCREEN_WITH_CHROME ||
670        state == STATE_TAB_BROWSER_FULLSCREEN_CHROME ||
671        state == STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME ||
672        event == TOGGLE_FULLSCREEN_CHROME) {
673      return true;
674    }
675  }
676
677  return false;
678}
679
680bool FullscreenControllerStateTest::ShouldSkipTest(State state, Event event) {
681  // Quietly skip metro snap tests when not on windows.
682#if !defined(OS_WIN)
683  if (state == STATE_METRO_SNAP ||
684      event == METRO_SNAP_TRUE ||
685      event == METRO_SNAP_FALSE) {
686    debugging_log_ << "\nSkipping metro snap test on non-Windows.\n";
687    return true;
688  }
689#endif
690
691  // Quietly skip Mac Lion Fullscreen tests when not on OSX 10.7+.
692  if (!SupportsMacSystemFullscreen()) {
693    if (state == STATE_BROWSER_FULLSCREEN_WITH_CHROME ||
694        event == TOGGLE_FULLSCREEN_CHROME) {
695      debugging_log_ << "\nSkipping Lion Fullscreen test on non-OSX 10.7+.\n";
696      return true;
697    }
698  }
699
700  // When testing reentrancy there are states the fullscreen controller
701  // will be unable to remain in, as they will progress due to the
702  // reentrant window change call. Skip states that will be instantly
703  // exited by the reentrant call.
704  if (IsWindowFullscreenStateChangedReentrant() &&
705      (transition_table_[state][WINDOW_CHANGE] != state)) {
706    debugging_log_ << "\nSkipping reentrant test for transitory source state "
707        << GetStateString(state) << ".\n";
708    return true;
709  }
710
711  if (ShouldSkipStateAndEventPair(state, event)) {
712    debugging_log_ << "\nSkipping test due to ShouldSkipStateAndEventPair("
713        << GetStateString(state) << ", "
714        << GetEventString(event) << ").\n";
715    LOG(INFO) << "Skipping test due to ShouldSkipStateAndEventPair("
716        << GetStateString(state) << ", "
717        << GetEventString(event) << ").";
718    return true;
719  }
720
721  return false;
722}
723
724void FullscreenControllerStateTest::TestStateAndEvent(State state,
725                                                      Event event) {
726  if (ShouldSkipTest(state, event))
727    return;
728
729  debugging_log_ << "\nTest transition from state "
730      << GetStateString(state)
731      << (IsWindowFullscreenStateChangedReentrant() ?
732          " with reentrant calls.\n" : ".\n");
733
734  // Spaced out text to line up with columns printed in InvokeEvent().
735  debugging_log_ << "First,                                               from "
736      << GetStateString(state_) << "\n";
737  ASSERT_NO_FATAL_FAILURE(TransitionToState(state))
738      << GetAndClearDebugLog();
739
740  debugging_log_ << " Then,\n";
741  ASSERT_TRUE(InvokeEvent(event)) << GetAndClearDebugLog();
742}
743
744void FullscreenControllerStateTest::VerifyWindowStateExpectations(
745    FullscreenWithChromeExpectation fullscreen_with_chrome,
746    FullscreenWithoutChromeExpectation fullscreen_without_chrome,
747    FullscreenForBrowserExpectation fullscreen_for_browser,
748    FullscreenForTabExpectation fullscreen_for_tab,
749    InMetroSnapExpectation in_metro_snap) {
750#if defined(OS_MACOSX)
751  if (fullscreen_with_chrome != FULLSCREEN_WITH_CHROME_NO_EXPECTATION) {
752    EXPECT_EQ(GetBrowser()->window()->IsFullscreenWithChrome(),
753              !!fullscreen_with_chrome) << GetAndClearDebugLog();
754  }
755  if (fullscreen_without_chrome != FULLSCREEN_WITHOUT_CHROME_NO_EXPECTATION) {
756    EXPECT_EQ(GetBrowser()->window()->IsFullscreenWithoutChrome(),
757              !!fullscreen_without_chrome) << GetAndClearDebugLog();
758  }
759#endif
760  if (fullscreen_for_browser != FULLSCREEN_FOR_BROWSER_NO_EXPECTATION) {
761    EXPECT_EQ(GetFullscreenController()->IsFullscreenForBrowser(),
762              !!fullscreen_for_browser) << GetAndClearDebugLog();
763  }
764  if (fullscreen_for_tab != FULLSCREEN_FOR_TAB_NO_EXPECTATION) {
765    EXPECT_EQ(GetFullscreenController()->IsWindowFullscreenForTabOrPending(),
766              !!fullscreen_for_tab) << GetAndClearDebugLog();
767  }
768  if (in_metro_snap != IN_METRO_SNAP_NO_EXPECTATION) {
769    EXPECT_EQ(GetFullscreenController()->IsInMetroSnapMode(),
770              !!in_metro_snap) << GetAndClearDebugLog();
771  }
772}
773
774FullscreenController* FullscreenControllerStateTest::GetFullscreenController() {
775    return GetBrowser()->fullscreen_controller();
776}
777
778std::string FullscreenControllerStateTest::GetTransitionTableAsString() const {
779  std::ostringstream output;
780  output << "transition_table_[NUM_STATES = " << NUM_STATES
781      << "][NUM_EVENTS = " << NUM_EVENTS
782      << "] =\n";
783  for (int state_int = 0; state_int < NUM_STATES; ++state_int) {
784    State state = static_cast<State>(state_int);
785    output << "    { // " << GetStateString(state) << ":\n";
786    for (int event_int = 0; event_int < NUM_EVENTS; ++event_int) {
787      Event event = static_cast<Event>(event_int);
788      output << "      "
789          << std::left << std::setw(kMaxStateNameLength+1)
790          << std::string(GetStateString(transition_table_[state][event])) + ","
791          << "// Event "
792          << GetEventString(event) << "\n";
793    }
794    output << "    },\n";
795  }
796  output << "  };\n";
797  return output.str();
798}
799
800std::string FullscreenControllerStateTest::GetStateTransitionsAsString() const {
801  std::ostringstream output;
802  output << "state_transitions_[NUM_STATES = " << NUM_STATES
803      << "][NUM_STATES = " << NUM_STATES << "] =\n";
804  for (int state1_int = 0; state1_int < NUM_STATES; ++state1_int) {
805    State state1 = static_cast<State>(state1_int);
806    output << "{ // " << GetStateString(state1) << ":\n";
807    for (int state2_int = 0; state2_int < NUM_STATES; ++state2_int) {
808      State state2 = static_cast<State>(state2_int);
809      const StateTransitionInfo& info = state_transitions_[state1][state2];
810      output << "  { "
811        << std::left << std::setw(kMaxStateNameLength+1)
812        << std::string(GetEventString(info.event)) + ","
813        << std::left << std::setw(kMaxStateNameLength+1)
814        << std::string(GetStateString(info.state)) + ","
815        << std::right << std::setw(2)
816        << info.distance
817        << " }, // "
818        << GetStateString(state2) << "\n";
819    }
820    output << "},\n";
821  }
822  output << "};";
823  return output.str();
824}
825