Tony Marston's Blog About software development, PHP and OOP

Back Button Blues

Posted on 16th February 2004 by Tony Marston
Introduction
What is the Back Button?
Browser "session" History
Browser Cache
Dynamic web pages
The GET and POST methods
Maintaining State between Requests
The advantage of keeping session data on the server
Why Dynamic pages and the Back Button do not mix
How to circumvent the conflict
1. Disable the browser's cache
2. Maintain a program stack
Conclusion
Comments

Introduction

When I began learning about web development, first with static and more recently with dynamic web pages, I have occasionally come across questions in newsgroups asking "How do I disable the browser's back button?" The short answer is that it cannot be done. It may be possible to create a browser window which does not show any buttons, but the processing for those buttons is still there through shortcut keys. Back button processing, for example, can be selected by using the backspace button in Internet Explorer.

What is the Back Button?

Why should web developers want to disable a feature that has always existed in web browsers? The answer lies in history. When the internet was first born and the first web browser was developed all the web pages were static in content. There was no way to interact with any page other than by selecting a hyperlink which could take the user to another page. A subsequent update to the browser would allow a hyperlink to take the user to another point within the current page.

Although it was possible to create an HTML document which contained hyperlinks which would take the user to other HTML documents, all this movement was in a forwards direction. It was not possible for any HTML document to contain a hyperlink to go back to the previous document as the identity of the previous document was simply not known. As it is possible to jump to a document from any other document within the internet the previous document could be any one of millions. It is the nature of static documents that their content is fixed, therefore the target in hyperlinks has to be hard-coded when the document is published. If the address of the previous document is not known it cannot be defined in any hyperlink, therefore it is not possible to have a hyperlink which takes the user back to the previous document.

Browser "session" History

Clearly the ability to browse forwards through new pages is very useful, but unless it is coupled with the ability to go backwards it is not very user friendly. If backward navigation through static pages is not possible with hyperlinks, then how is this ability given to the user? The answer is that the browser itself maintains a history of all requests which have been issued in the current session, and a BACK button is provided on the browser's toolbar which allows the user to navigate through the list of previous requests. When navigating backwards the browser will also enable a FORWARD button which will allow the user to step forwards without having to find and reselect the original hyperlink.

With static HTML documents each 'request' is equal to a different 'page'.

A browser "session" covers all those HTTP requests between starting a browser instance (window) and then terminating it. You can have several browser instances open at the same time, and each one will maintain its own back/forward history. Once you close a browser instance its back/forward history is lost.

With static documents all backwards navigation is provided by a button in the browser's toolbar rather than a hyperlink within the document itself. The browser button works by stepping through the history of HTTP requests where this history is maintained by the browser itself. This history exists only in the memory of the device on which the browser is running and is therefore totally invisible to any web servers from which documents may be requested.

Browser Cache

In the original implementation this browser history consisted of nothing but page addresses (URLs) which caused the entire page to be rebuilt from scratch on each visit. As we are still talking about the 'good old days' before high-speed internet connections it could take quite some time for the contents of each page to be transmitted from the web server to the web browser. To avoid this delay the browser software was modified to include a 'cache' so that the history consisted of the entire page contents, including images, instead of just its address. In this way it became possible to navigate backwards and forwards through the browser history and have each page displayed instantly from cache without the delay of having it retransmitted over the internet from the server. To deal with those cases where the copy of a web page in a browser's cache may be out of step with the current page contents, browsers also include a REFRESH button which, when pressed, retransmits the request to the web server, thus forcing the entire page to be rebuilt on demand. This also replaces the copy of the page in the browser's cache.

Dynamic web pages

It was not too long before users of the web began to demand features over and above that which could be supplied with static pages, so the concept of dynamic pages was born. The introduction of the FORM tag and associated elements allowed the construction of pages with which the user could interact with the web server. A web page could now be constructed with various controls into which the user could enter data, and by pressing a SUBMIT button have that data transmitted back to the web sever. Upon receipt of this data the web server could process it and take appropriate action, such as updating a database, selecting different records from a database or generating a new output page. Dynamic forms now give us the ability to browse through the products contained in an online shop, add them to an electronic shopping basket and then pay for them at an electronic checkout.

With dynamic HTML documents each 'request' may perform some processing on the current 'page' instead of switching to a different 'page'.

The GET and POST methods

With the introduction of dynamic forms came a different method of sending data from the web client (browser) to the web server. The original GET method includes the document address and any arguments within the URL which is displayed in the browser's address line. This means that all components of that request are clearly visible. The POST method, which was introduced later, sends any arguments in the body of the HTTP request, not the URL, so the separate arguments are not displayed in the browser's address line.

The GET method is best used for retrieving a web page as it can be bookmarked, but it is not advisable to use it for database updates. Apart from the fact that there should be no need to bookmark database updates, the problem with having the entire request displayed in the browser's address bar is that it is too easy to amend argument values and send data which the server-side script might not expect and which may therefore have unexpected consequences. Sending data to the server, such as from screens which update the database, should therefore be handled by the POST method.

When you consider how a dynamic web page actually works there are two distinct phases:

It therefore seems logical to me that the GET method be used for the first phase while the POST method be used for the second. It is also possible for the GET an POST methods to be handled in separate scripts, but I generally put them together in a single script. This constitutes what is sometimes referred to as a "self-executing" script.

Maintaining State between Requests

The introduction of dynamic forms was complicated by the fact that the HTTP protocol is essentially "stateless". This means that each request/response cycle (request from the client followed by a response from the server) is treated as a totally separate unit which has no memory of any previous requests from the same client. Various solutions to this problem have materialised over the years:

  1. Hidden fields - Within an HTML document it is possible to designate individual fields as hidden, which means that they will not be displayed by the browser but they will be sent back to the server, along with any visible fields, when a SUBMIT button is pressed. In this way any data which is needed to maintain state is carried around within each document.
  2. Cookies - Instead of writing data as hidden fields within the HTML document an alternative appeared with the introduction of cookies. A cookie is a separate file, which can be either temporary or permanent, which is maintained by the web browser on the client device at the request of a site's web server. A web site may write to one or more cookies, and the content of these cookies is sent back to the server with each client request to that server. The cookies maintained by one site are not sent to any other site that the client may visit.
  3. Server-Side Sessions - In this method any data which is required to maintain state is not sent back and forth between the client and the server in the form of hidden fields or cookies, it is held only on the server. For this to work the essential data is held on a disk file or a database table, but to keep one person's data separate from another's each set of data requires a unique identity, known as a session id. This session id must be included in every HTTP request, either as a hidden field or a cookie, so that the web server can connect the request with the session data from previous requests with the same session id. Session management within PHP is very easy - the session_start() function will either create a new session or open an existing one, after which the reading and writing of session data is as easy as reading from and writing to the $_SESSION array.

The advantage of keeping session data on the server

Of the three methods of maintaining state for dynamic web applications the latter, which keeps all session data on the server rather than transmitting it to the client, is preferable for the following reasons:

Why Dynamic pages and the Back Button do not mix

Having stated that the BACK button was specifically designed to cater for static pages, how does this cause a problem with dynamic pages? There must be a problem, otherwise there would not be so many developers asking for the means to disable it. The problem does not become obvious until you convert a traditional desktop application to the web. There are major differences between a desktop application and a web application:

As has been stated earlier in The GET and POST methods the processing of a dynamic web page requires two distinct phases - retrieving a form to be filled in (via the GET method) and sending the completed form back to the server for processing (usually via the POST method). It is likely that certain values, such as primary keys or other non-displayed data, may need to be stored in the session array by the server when it processes the GET method so that those values are available when processing the subsequent POST method for the same self-executing script. This then introduces the concept of 'current script'. This means that after processing the GET request for a particular script the server expects the next request to be the POST method for the exact same script. By stepping back through the browser's history it is possible for the user to bring up a totally different page and then POST that one back to the server. As this will be for a previous script which has already been processed all memory of that script would have been dropped from the session array, therefore the POST method will be unable to attach itself to any data stored by the GET method and it will be unable to function correctly. This happens because the identity of the 'current script' as known to the application running on the server has become completely detached from the 'current script' as known to the client browser.

The concept of 'previous page' or 'previous screen' therefore has a totally different meaning between a series of static web pages and a dynamic web application as the former relies on the browser history held on the client while the latter relies on session history held on the server. The differences can be demonstrated in the following diagram:

Figure 1 - A sequence of forms in a dynamic web application

backbuttonblues-01 (4K)

This sequence can be described as follows:

  1. The user starts the session with the LOGIN screen.
  2. After processing the LOGIN screen the user is taken to a MENU screen which contains a series of different options.
  3. From the MENU screen the user selects a LIST screen.
  4. The user chooses an option the LIST the current contents of a database table. As there are more records than can comfortably be displayed at the same time only the first 10 are shown, with pagination options to show the other records.
  5. The user cannot see what he is looking for on that page of details, so he selects a second page.
  6. He cannot see what he is looking for on that page, so he selects a third page.
  7. The user decides to provide a filter for the LIST screen by going into a SEARCH screen. This is done by pressing a button in the navigation bar of the LIST screen.
  8. By pressing the SUBMIT button in the SEARCH screen all values entered are passed back to the LIST screen which uses them as selection criteria in the WHERE clause of the SELECT statement.
  9. The user decides that the record he is looking for does not exist so he activates the ADD screen using a navigation button. He enters the details for the new record and presses the SUBMIT button.
  10. This takes the user back to the LIST screen which refreshes its display which now incorporates the record which he has just added.
  11. The user realises that he has made a mistake with the data he has just added, so he activates the UPDATE screen from a navigation button. The user changes the data and presses the SUBMIT button.
  12. This returns to the LIST screen.
  13. The user then presses the FINISH button on the LIST screen which takes him back to the MENU screen.

At the end of this sequence the browser history will contain all 13 steps. In a traditional desktop application there is a separate mechanism called a program stack which will contain just two items - LOGIN and MENU. This program stack is maintained in the following ways:

A vertical arrow signifies an action performed within the current script, therefore there is no change in the stack. In the above example you will see that there are never more than 4 entries in the stack.

Just suppose the user is positioned as the LIST screen at step 12. There are two ways of moving backwards but each will yield a different result:

Because the UPDATE screen has already been processed and terminated all reference to it has been removed from the session array, therefore there is no data to link up to should the user decide to press a SUBMIT button while in this screen. The application still thinks that the LIST screen is the current screen and expects the next request to come from that screen - anything else will throw it into confusion.

How to circumvent the conflict

As I said at the start of this paper there is no way to permanently remove or disable the browser's BACK button, but there is a way in which any nasty side-effects caused by its use can be trapped and circumvented.

1. Disable the browser's cache

It is possible to turn off the browser cache for selected pages by using code similar to the following:

// disable any caching by the browser
header('Expires: Mon, 14 Oct 2002 05:00:00 GMT');              // Date in the past
header('Last-Modified: ' . gmdate("D, d M Y H:i:s") . ' GMT'); // always modified
header('Cache-Control: no-store, no-cache, must-revalidate');  // HTTP 1.1
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');                                    // HTTP 1.0

Note that this code must be processed BEFORE anything is output to the browser.

2. Maintain a program stack

I have created separate functions which deal with forwards movement which adds to the stack, backwards movement which removes from the stack, and script initialisation which verifies that the current script is contained in the stack.

All forwards movement is performed by using my scriptNext function. This is called within the current script in order to activate the new script.

function scriptNext($script_id)
// proceed forwards to a new script
{
   if (empty($script_id)) {
      trigger_error("script id is not defined", E_USER_ERROR);
   } // if
   
   // get list of screens used in this session
   $page_stack = $_SESSION['page_stack'];
   if (in_array($script_id, $page_stack)) {
      // remove this item and any following items from the stack array
      do {
         $last = array_pop($page_stack);
      } while ($last != $script_id);
   } // if
   
   // add next script to end of array and update session data
   $page_stack[] = $script_id;
   $_SESSION['page_stack'] = $page_stack;
   
   // now pass control to the designated script
   $location = 'http://' .$_SERVER['HTTP_HOST'] .$script_id;
   header('Location: ' .$location); 
   exit;
	
} // scriptNext

When any script has finished its processing it terminates by calling my scriptPrevious function. This will drop the current script from the end of the stack array and reactivate the previous script in the array.

function scriptPrevious()
// go back to the previous script (as defined in PAGE_STACK)
{
   // get id of current script
   $script_id = $_SERVER['PHP_SELF'];
   
   // get list of screens used in this session
   $page_stack = $_SESSION['page_stack'];
   if (in_array($script_id, $page_stack)) {
      // remove this item and any following items from the stack array
      do {
         $last = array_pop($page_stack);
      } while ($last != $script_id);
      // update session data
      $_SESSION['page_stack'] = $page_stack;
   } // if
	 
   if (count($page_stack) > 0) {
      $previous = array_pop($page_stack);
      // reactivate previous script
      $location = 'http://' .$_SERVER['HTTP_HOST'] .$previous;
   } else {
      // no previous scripts, so terminate session
      session_unset();
      session_destroy();
      // revert to default start page
      $location = 'http://' .$_SERVER['HTTP_HOST'] .'/index.php';
   } // if
	
   header('Location: ' .$location); 
   exit;

} // scriptPrevious

Whenever a script is activated, which can be either through the scriptNext or scriptPrevious functions, or because of the BACK button in the browser, it will call the following function to verify that it is the current script according to the contents of the program stack and take appropriate action if it is not.

function initSession()
// initialise session data
{
   // get program stack
   if (isset($_SESSION['page_stack'])) {
      // use existing stack
      $page_stack = $_SESSION['page_stack'];
   } else {
      // create new stack which starts with current script
      $page_stack[] = $_SERVER['PHP_SELF'];
      $_SESSION['page_stack'] = $page_stack;
   } // if
   
   // check that this script is at the end of the current stack
   $actual = $_SERVER['PHP_SELF'];
   $expected = $page_stack[count($page_stack)-1];
   if ($expected != $actual) {
      if (in_array($actual, $page_stack)) {
         // script is within current stack, so remove anything which follows
         while ($page_stack[count($page_stack)-1] != $actual ) {
            $null = array_pop($page_stack);
         } // while
         $_SESSION['page_stack'] = $page_stack;
      } // if
      // set script id to last entry in program stack
      $actual = $page_stack[count($page_stack)-1];
      $location = 'http://' .$_SERVER['HTTP_HOST'] .$actual;
      header('Location: ' .$location);
      exit;
   } // if
   
   ... // continue processing
   
} // initSession

The action taken depends on whether the current script exists within the program stack or not. There are three possibilities:

Conclusion

Although it is not possible to disable the browser's BACK button I hope I have demonstrated that with dynamic web pages there is sometimes a need to prevent program flow via the BACK button from interfering with the program flow expected by the application. By using a simple mechanism encountered during my past experience with desktop systems I am now able to prevent any usage of the BACK button from causing any damage.

Don't applaud, just throw money.


counter