diff --git README.txt README.txt
index 6dff71e..49f9366 100644
--- README.txt
+++ README.txt
@@ -42,10 +42,10 @@ To install:
   themes used for Facebook Connect, iframe Canvas Pages, and Social
   Plugins (i.e. like buttons).  Without this attribute, IE will fail.
 
-- To support canvas pages, url rewriting and other settings must be
-  initialized before modules are loaded, so you must add this code to
-  your settings.php.  This is easily done by adding these two lines to
-  the end of sites/default/settings.php (or
+- To support canvas pages and/or page tabs, url rewriting and other
+  settings must be initialized before modules are loaded, so you must
+  add this code to your settings.php.  This is done by adding these
+  two lines to the end of sites/default/settings.php (or
   sites/YOUR_DOMAIN/settings.php).
 
   include "sites/all/modules/fb/fb_url_rewrite.inc";
diff --git contrib/fb_example.module contrib/fb_example.module
index 5deece1..57c4e9e 100644
--- contrib/fb_example.module
+++ contrib/fb_example.module
@@ -365,9 +365,14 @@ function fb_example_fb_user($op, $data, &$return) {
  */
 function fb_example_preprocess_page(&$variables) {
   if (module_exists('fb_canvas') && fb_is_canvas()) {
+    include_once drupal_get_path('module', 'fb') . '/fb.process.inc';
+
     // Process links in these regions.
     foreach (array('content', 'header', 'footer', 'left', 'preface_first', 'preface_middle', 'preface_last') as $region) {
-      $variables[$region] = fb_canvas_process($variables[$region], array('add_target' => TRUE, 'absolute_links' => TRUE));
+      $variables[$region] = fb_process($variables[$region], array(
+                                         'add_target' => '_top',
+                                         'absolute_links' => TRUE,
+                                       ));
     }
   }
 }
diff --git fb.module fb.module
index 3f6ac77..e3cfd5f 100644
--- fb.module
+++ fb.module
@@ -541,11 +541,6 @@ function fb_fql_query($fb, $query, $params = array()) {
 function fb_footer($is_front) {
   global $_fb, $_fb_app;
 
-  // No javascript on strict FBML pages.
-  if (fb_is_tab()) {
-    return;
-  }
-
   // This element recommended by facebook. http://developers.facebook.com/docs/reference/javascript/
   $output = "<div id=\"fb-root\"></div>\n";
 
@@ -601,11 +596,15 @@ function fb_is_canvas() {
  */
 function fb_is_tab() {
   global $_fb;
-  if (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE) {
+
+  if (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PAGE_TAB) {
+    return TRUE;
+  }
+  elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE) { // deprecated FBML tab
     return TRUE;
   }
   elseif (isset($_REQUEST['fb_sig_in_profile_tab']) &&
-          $_REQUEST['fb_sig_in_profile_tab']) {
+          $_REQUEST['fb_sig_in_profile_tab']) { // deprecated ancient history
     // Old way, no migrations enabled.
     return TRUE;
   }
@@ -716,6 +715,10 @@ function fb_require_authorization($fb = NULL, $destination = NULL) {
   if (!$fb)
     $fb = $GLOBALS['_fb'];
 
+  if (!$fb) {
+    throw new Exception(t('Failed to authorize facebook application.  Could not determine application id.'));
+  }
+
   $fbu = fb_facebook_user($fb);
   if (!$fbu) {
     $client_id = $fb->getAppId();
diff --git fb.process.inc fb.process.inc
new file mode 100644
index 0000000..077ee57
--- /dev/null
+++ fb.process.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Helper functions for processing links.
+ *
+ * Both canvas pages and tab pages need to alter links, changing both URLs and
+ * attributes such as target.  This cannot always be done via Drupal's url
+ * rewriting.
+ */
+
+
+/**
+ * This function uses regular expressions to convert links on canvas pages
+ * to URLs that begin http://apps.facebook.com/...
+ *
+ * This is a relatively expensive operation.  In the past, this had to be run
+ * on all FBML canvas pages.  Now with iframe canvas pages, this is not
+ * strictly needed, but remains useful.  If processed, links on the page will
+ * change the parent frame (the one with the URL shown in the browser).  If
+ * not processed, the links will change only the iframe.
+ *
+ *
+ * @param $output is the page (or iframe block) about to be returned.
+ *
+ * @param $options - 'add_target' will cause target=_top to be added
+ * when producing an iframe. 'absolute_links' will change hrefs with absolute
+ * URLs to refer to canvas pages.
+ *
+ */
+function fb_process($output, $options = array()) {
+  global $base_url;
+  global $_fb, $_fb_app;
+
+  // Default to canvas links
+  if (!isset($options['to_canvas'])) {
+    $options['to_canvas'] = $_fb_app->canvas;
+  }
+
+  $patterns = array();
+  $replacements = array();
+  $base_path = base_path();
+
+  if ($_fb) {
+    if (function_exists('fb_url_outbound_alter')) {
+      $base_before_rewrite = '';
+      $rewrite_options = array(FB_SETTINGS_CB_SESSION => FALSE, FB_SETTINGS_CB_TYPE => FALSE);
+      $base = $base_path . fb_url_outbound_alter($base_before_rewrite, $rewrite_options, '');  // short URL with rewrite applied.
+    }
+    else {
+      // If no url_alter, use normal base_path.
+      $base = $base_path;
+    }
+
+    if (isset($options['to_canvas']) && ($canvas = $options['to_canvas'])) {
+      // Make relative links point to canvas pages.
+      $patterns[] = "|<a([^>]*)href=\"{$base}|";
+      $replacements[] = "<a $1 href=\"http://apps.facebook.com/{$canvas}/";
+    }
+    else {
+      // Make relative links point to website.
+      $site_base = url('<front>', array(
+                         'absolute' => TRUE,
+                         'fb_url_alter' => FALSE,
+                       ));
+      $patterns[] = "|<a([^>]*)href=\"{$base}|";
+      $replacements[] = "<a $1 href=\"{$site_base}";
+    }
+
+    // Add target=_top so that entire pages do not appear within an iframe.
+    // TODO: make these pattern replacements more sophisticated, detect whether target is already set.
+    if (isset($options['add_target']) && ($target = $options['add_target'])) {
+      // Add target=_top to all links
+      $patterns[] = "|<a ([^>]*)href=\"|";
+      $replacements[] = "<a $1 target=\"{$target}\" href=\"";
+      // Do not change local forms, but do change external ones
+      $patterns[] = "|<form([^>]*)action=\"([^:\"]*):|";
+      $replacements[] = "<form target=\"{$target}\" $1 action=\"$2:";
+    }
+    elseif (!isset($options['add_target'])) {
+      // Add target=_top to only external links
+      $patterns[] = "|<a([^>]*)href=\"([^:\"]*):|";
+      $replacements[] = "<a target=\"_top\" $1 href=\"$2:";
+      $patterns[] = "|<form([^>]*)action=\"([^:\"]*):|";
+      $replacements[] = "<form target=\"_top\" $1 action=\"$2:";
+    }
+
+    if (isset($options['absolute_links']) && $options['absolute_links']) {
+      // Make absolute links point to canvas pages.
+      $absolute_base = url('<front>', array('absolute' => TRUE));
+      $patterns[] = "|<a([^>]*)href=\"{$absolute_base}|";
+      $replacements[] = "<a $1 href=\"http://apps.facebook.com/{$_fb_app->canvas}/";
+    }
+  }
+  if (count($patterns)) {
+    $count = 0;
+    $return = preg_replace($patterns, $replacements, $output, -1, $count);
+    //print ("fb_process replaced $count.\n\n"); // debug
+    return $return;
+  }
+  else
+    return $output;
+}
+
diff --git fb_canvas.admin.inc fb_canvas.admin.inc
index 6e92aae..18ecdc5 100644
--- fb_canvas.admin.inc
+++ fb_canvas.admin.inc
@@ -16,7 +16,7 @@ function fb_canvas_fb_admin($op, $data, &$return) {
   if ($op == FB_ADMIN_OP_SET_PROPERTIES) {
     // Compute properties which we can set automatically.
     if (function_exists('fb_url_inbound_alter')) {
-      $callback_url = url('', array('absolute' => TRUE)) . FB_SETTINGS_CB . '/' . $fb_app->apikey . '/';
+      $callback_url = url('', array('absolute' => TRUE)) . FB_SETTINGS_CB . '/' . $fb_app->id . '/';
     }
     else {
       // Paving the way to make URL alters optional.
@@ -40,17 +40,18 @@ function fb_canvas_admin_settings() {
 
   $form['process_settings'] = array(
     '#type' => 'fieldset',
-    '#title' => t('URL processing'),
-    '#description' => t('This option alters links, so that instead of referring directly to this server, they point to <em>apps.facebook.com/APP/...</em>.  While this impedes the performance of your server, it is recommended unless you are quite sure your theme and/or code has been specially written to handle this some other way.'),
+    '#title' => t('Canvas page URL processing'),
+    '#description' => t('This option alters links, so that instead of changing the iframe\'s URL, they change the top frame, to something starting <em>apps.facebook.com/APP/...</em>  This adds some overhead to each canvas page served. Still, most sites will want this enabled.'),
   );
   $form['process_settings'][FB_CANVAS_VAR_PROCESS_IFRAME] = array(
     '#type' => 'checkbox',
     '#title' => t('Enable on iframe canvas pages.'),
     '#default_value' => variable_get(FB_CANVAS_VAR_PROCESS_IFRAME, TRUE),
+    '#description' => t('If unchecked, settings below have no effect.'),
   );
   $form['process_settings'][FB_CANVAS_VAR_PROCESS_ABSOLUTE] = array(
     '#type' => 'checkbox',
-    '#title' => t('Replace absolute hrefs with canvas page URLs.  (Not just relative links.)'),
+    '#title' => t('Replace absolute hrefs, not just relative, with canvas page URLs.'),
     '#default_value' => variable_get(FB_CANVAS_VAR_PROCESS_ABSOLUTE, TRUE),
   );
   return system_settings_form($form);
diff --git fb_canvas.module fb_canvas.module
index 19dc710..163305c 100644
--- fb_canvas.module
+++ fb_canvas.module
@@ -53,9 +53,14 @@ function fb_canvas_fb($op, $data, &$return) {
     if (function_exists('fb_settings')) {
       if ((fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_CANVAS)) {
         // fb_settings.inc has determined this is a canvas page.
-        if ($app_key = fb_settings(FB_SETTINGS_CB)) {
+        if ($id = fb_settings(FB_SETTINGS_CB)) {
           // Using fb_url_rewrite.
-          $fb_app = fb_get_app(array('apikey' => $app_key));
+          $fb_app = fb_get_app(array('id' => $id));
+
+          if (!$fb_app) {
+            // DEPRECATED.  For backward compatibility, accept apikey in FB_SETTINGS_CB
+            $fb_app = fb_get_app(array('apikey' => $app_key));
+          }
           if (!$fb_app) {
             // DEPRECATED.  For backward compatibility, accept label in FB_SETTINGS_CB
             $fb_app = fb_get_app(array('label' => $app_key));
@@ -163,10 +168,12 @@ function fb_canvas_fb($op, $data, &$return) {
       $output = ob_get_contents();
       ob_end_clean();
       if (fb_canvas_is_iframe()) {
-        $output = fb_canvas_process($output, array(
-                                      'add_target' => TRUE,
-                                      'absolute_links' => variable_get(FB_CANVAS_VAR_PROCESS_ABSOLUTE, TRUE),
-                                    ));
+        include_once(drupal_get_path('module', 'fb') . '/fb.process.inc');
+        $output = fb_process($output, array(
+                               'add_target' => '_top',
+                               'absolute_links' => variable_get(FB_CANVAS_VAR_PROCESS_ABSOLUTE, TRUE),
+                               'to_canvas' => $fb_app->canvas,
+                             ));
       }
     }
 
@@ -356,86 +363,3 @@ function fb_canvas_url_outbound_alter(&$path, &$options, $original_path) {
   }
 }
 
-
-/**
- * This function uses regular expressions to convert links on canvas pages
- * to URLs that begin http://apps.facebook.com/...
- *
- * Call this method from themes when producing iframe canvas
- * pages, or from a page_preprocess hook.
- *
- * This is a relatively expensive operation.  In the past, this had to be run
- * on all FBML canvas pages.  Now with iframe canvas pages, this is not
- * strictly needed, but remains useful.  If processed, links on the page will
- * change the parent frame (the one with the URL shown in the browser).  If
- * not processed, the links will change only the iframe.
- *
- * In Drupal 7.x, there may be a way to alter URLs before they are
- * rendered.  That could provide a more efficient solution.  Until
- * then we are stuck with this.
- *
- * @param $output is the page (or iframe block) about to be returned.
- *
- * @param $options - 'add_target' will cause target=_top to be added
- * when producing an iframe. 'absolute_links' will change hrefs with absolute
- * URLs to refer to canvas pages.
- *
- */
-function fb_canvas_process($output, $options = array()) {
-  global $base_url;
-  global $_fb, $_fb_app;
-
-  $patterns = array();
-  $replacements = array();
-  $base_path = base_path();
-
-  if ($_fb) {
-    if (function_exists('fb_url_outbound_alter')) {
-      $base_before_rewrite = '';
-      $rewrite_options = array(FB_SETTINGS_CB_SESSION => FALSE, FB_SETTINGS_CB_TYPE => FALSE);
-      $base = $base_path . fb_url_outbound_alter($base_before_rewrite, $rewrite_options, '');  // short URL with rewrite applied.
-    }
-    else {
-      // If no url_alter, use normal base_path.
-      $base = $base_path;
-    }
-
-    // Make relative links point to canvas pages.
-    $patterns[] = "|<a([^>]*)href=\"{$base}|";
-    $replacements[] = "<a $1 href=\"http://apps.facebook.com/{$_fb_app->canvas}/";
-
-    // Add target=_top so that entire pages do not appear within an iframe.
-    // TODO: make these pattern replacements more sophisticated, detect whether target is already set.
-    if (isset($options['add_target']) && $options['add_target']) {
-      // Add target=_top to all links
-      $patterns[] = "|<a ([^>]*)href=\"|";
-      $replacements[] = "<a $1 target=\"_top\" href=\"";
-      // Do not change local forms, but do change external ones
-      $patterns[] = "|<form([^>]*)action=\"([^:\"]*):|";
-      $replacements[] = "<form target=\"_top\" $1 action=\"$2:";
-    }
-    elseif (!isset($options['add_target'])) {
-      // Add target=_top to only external links
-      $patterns[] = "|<a([^>]*)href=\"([^:\"]*):|";
-      $replacements[] = "<a target=\"_top\" $1 href=\"$2:";
-      $patterns[] = "|<form([^>]*)action=\"([^:\"]*):|";
-      $replacements[] = "<form target=\"_top\" $1 action=\"$2:";
-    }
-
-    if (isset($options['absolute_links']) && $options['absolute_links']) {
-      // Make absolute links point to canvas pages.
-      $absolute_base = url('<front>', array('absolute' => TRUE));
-      $patterns[] = "|<a([^>]*)href=\"{$absolute_base}|";
-      $replacements[] = "<a $1 href=\"http://apps.facebook.com/{$_fb_app->canvas}/";
-    }
-  }
-  if (count($patterns)) {
-    $count = 0;
-    $return = preg_replace($patterns, $replacements, $output, -1, $count);
-    //print ("fb_canvas_process replaced $count.\n\n"); // debug
-    return $return;
-  }
-  else
-    return $output;
-}
-
diff --git fb_devel.module fb_devel.module
index f4d84e2..e044bb9 100644
--- fb_devel.module
+++ fb_devel.module
@@ -98,6 +98,22 @@ function fb_devel_init() {
       watchdog('fb_devel', $message, $args, WATCHDOG_ERROR);
     }
   }
+
+  // Old url rewrite sanity check.
+  if ($id = fb_settings(FB_SETTINGS_CB)) {
+    if ($GLOBALS['_fb_app']->apikey == $id) {
+      $message = t('Facebook callback URLs have changed.  They now include the app\'s ID, instead of APIKEY.  Your application %label has not been updated.  Either <a target=_top href=!sync_url>sync properties</a> or manually change callbacks on remote settings.', array(
+                     '%label' => $GLOBALS['_fb_app']->title,
+                     '!sync_url' => url(FB_PATH_ADMIN_APPS . '/' . $GLOBALS['_fb_app']->label . '/fb/set_props', array(
+                                          'fb_url_alter' => FALSE,
+                                        )),
+                   ));
+      if (user_access('access administration pages')) {
+        drupal_set_message(t($message, $args), 'warning');
+      }
+      watchdog('fb_devel', $message, $args, WATCHDOG_WARNING);
+    }
+  }
 }
 
 /**
@@ -122,13 +138,13 @@ function fb_devel_fb($op, $data, &$return) {
     }
 
     // This value comes from url rewriting.  Recommended on canvas pages.
-    $apikey = fb_settings(FB_SETTINGS_CB);
+    $id = fb_settings(FB_SETTINGS_CB);
 
     // Sanity check.
-    if ($apikey && $apikey != $fb_app->apikey) {
+    if ($id && $id != $fb_app->id) {
       $message = t('fb_app id (%fb_app_id) does not equal fb settings id (%fb_settings_id).  This is usually caused by the wrong callback url on your <a href="!url">facebook application settings form</a>.',
-                   array('%fb_app_id' => $fb_app->apikey,
-                         '%fb_settings_id' => $apikey,
+                   array('%fb_app_id' => $fb_app->id,
+                         '%fb_settings_id' => $id,
                          '!url' => "http://www.facebook.com/developers/apps.php",
                    ));
       drupal_set_message($message, 'warning');
@@ -150,11 +166,11 @@ function fb_devel_fb($op, $data, &$return) {
     }
 
     // Catch badly formed links ASAP.
-    if ($apikey && !fb_is_canvas() && !fb_is_tab()) {
+    if ($id && !fb_is_canvas() && !fb_is_tab()) {
       // Skip check on callbacks from facebook.
       if ((arg(0) != 'fb_app') || (arg(1) != 'event')) {
         $message = t('URL starts with %prefix.  This should never happen on connect pages.  Did the previous page have a badly formed link?', array(
-                     '%prefix' => FB_SETTINGS_CB . '/' . $apikey,
+                       '%prefix' => FB_SETTINGS_CB . '/' . $id,
                    ));
         drupal_set_message($message, 'error');
         $errors++;
@@ -500,7 +516,12 @@ function fb_devel_info() {
       $info['Page Status'] = t('Connected via Facebook Connect');
     }
     elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE) {
-      $info['Page Status'] = t('Serving profile tab');
+      $info['Page Status'] = t('Serving deprecated FBML profile tab');
+    }
+    elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PAGE_TAB) {
+      $info['Page Status'] = t('Iframe tab on page %page', array(
+                                 '%page' => fb_settings(FB_SETTINGS_PAGE_ID),
+                               ));
     }
     elseif (!fb_settings(FB_SETTINGS_TYPE)) {
       $info['Page Status'] = t('Facebook SDK initialized. User is not logged into facebook or has not authorized application.');
@@ -702,7 +723,7 @@ function fb_devel_cron() {
 
 function fb_devel_fb_tab($op, $data, &$return) {
   // debug.
-  if (fb_verbose() == 'extreme') {
+  if (fb_verbose() === 'extreme') {
     $return['fb_devel'] = array(
       '#type' => 'markup',
       '#value' => __FUNCTION__ . ':' . print_r($data, 1),
@@ -710,7 +731,7 @@ function fb_devel_fb_tab($op, $data, &$return) {
       '#suffix' => '</pre>',
       '#weight' => 99,
     );
-    if ($data['profile_id']) {
+    if ($data['profile_id'] && isset($data['fb'])) {
       $return['fb_devel']['profile_id'] = array(
         '#type' => 'markup',
         '#value' => __FUNCTION__ . ':' . print_r($data['fb']->api($data['profile_id']), 1),
diff --git fb_settings.inc fb_settings.inc
index 3990b7b..07657d6 100644
--- fb_settings.inc
+++ fb_settings.inc
@@ -15,24 +15,25 @@
 // Each of these are things we can learn and store in fb_settings().
 // The CB (callback) values are learned via URL rewriting.
 // Include fb_url_rewrite.inc in your settings.php to enable this.
-define('FB_SETTINGS_CB', 'fb_cb');
-define('FB_SETTINGS_CB_TYPE', 'fb_cb_type');
-define('FB_SETTINGS_CB_SESSION', 'fb_sess');
+define('FB_SETTINGS_CB', 'fb_cb'); // The app id.
+define('FB_SETTINGS_CB_PAGE', 'fb_page'); // Page id, for tabs.
+define('FB_SETTINGS_CB_TYPE', 'fb_cb_type'); // For iframes within FBML canvas pages, now DEPRECATED.
+define('FB_SETTINGS_CB_SESSION', 'fb_sess'); // For embedding session id within URL.
 
 // Things we can learn from the facebook session.
 define('FB_SETTINGS_APIKEY', 'apikey');
 define('FB_SETTINGS_ID', 'app_id');
+define('FB_SETTINGS_PAGE_ID', 'page_id');
 define('FB_SETTINGS_FBU', 'fbu');
 define('FB_SETTINGS_TOKEN', 'token');
-define('FB_SETTINGS_PROFILE_ID', 'profile_id');
 define('FB_SETTINGS_TYPE', 'type'); // page type not same as cb type
 define('FB_SETTINGS_COOKIE_DOMAIN', 'cookie_domain');
 
 // Possible values for page type.
 define('FB_SETTINGS_TYPE_CANVAS', 'canvas');
 define('FB_SETTINGS_TYPE_CONNECT', 'connect');
-define('FB_SETTINGS_TYPE_PROFILE', 'profile');
-
+define('FB_SETTINGS_TYPE_PROFILE', 'profile'); // deprecated, FBML tab
+define('FB_SETTINGS_TYPE_PAGE_TAB', 'page_tab'); // iframe tab
 
 /**
  * Helper function to remember values as we learn them.
@@ -85,9 +86,19 @@ function _fb_settings_parse_signed_request($signed_request) {
  * http://developers.facebook.com/docs/authentication/canvas
  */
 function _fb_settings_honor_signed_request($sr) {
+  if (isset($sr['page'])) {
+    // Iframe page tab.
+    fb_settings(FB_SETTINGS_CB_PAGE, $sr['page']['id']);
+    fb_settings(FB_SETTINGS_PAGE_ID, $sr['page']['id']);
+    fb_settings(FB_SETTINGS_TYPE, FB_SETTINGS_TYPE_PAGE_TAB);
+    if (isset($sr['user_id'])) {
+      fb_settings(FB_SETTINGS_FBU, $sr['user_id']);
+    }
+  }
   if (isset($sr['profile_id'])) {
-    // Only on tabs.
-    fb_settings(FB_SETTINGS_PROFILE_ID, $sr['profile_id']);
+    // Only on old FBML tabs.  Deprecated now that iframe tabs are preferred.
+    fb_settings(FB_SETTINGS_CB_PAGE, $sr['profile_id']);
+    fb_settings(FB_SETTINGS_CB_PAGE, $sr['profile_id']);
     fb_settings(FB_SETTINGS_TYPE, FB_SETTINGS_TYPE_PROFILE);
     if ($sr['user_id'] != $sr['profile_id']) {
       fb_settings(FB_SETTINGS_FBU, $sr['user_id']);
@@ -144,7 +155,7 @@ function fb_settings_get_facebook_cookie($app_id, $application_secret = NULL) {
 /**
  * By changing the $cookie_domain, we force drupal to use a different session
  * when a user is logged into a facebook application.  We base the
- * $cookie_domain on the apikey of the application, if we can learn it.
+ * $cookie_domain on the id of the application, if we can learn it.
  *
  * Facebook provides a number of "migrations" and historically has offered
  * different data to applications.  So the code below tries a variety of ways
@@ -156,7 +167,15 @@ if (function_exists('_fb_settings_parse') &&
   // Learned id from url rewrite.
   // Either canvas page or profile tab.
   fb_settings(FB_SETTINGS_ID, $id);
-  fb_settings(FB_SETTINGS_TYPE, FB_SETTINGS_TYPE_CANVAS); // @TODO detect iframe tab
+
+  if ($page_id = _fb_settings_parse(FB_SETTINGS_CB_PAGE)) {
+    fb_settings(FB_SETTINGS_TYPE, FB_SETTINGS_TYPE_PAGE_TAB);
+    fb_settings(FB_SETTINGS_PAGE_ID, $page_id);
+  }
+  else {
+    fb_settings(FB_SETTINGS_TYPE, FB_SETTINGS_TYPE_CANVAS);
+  }
+
   if (isset($_REQUEST['signed_request']) &&
       ($sr = _fb_settings_parse_signed_request($_REQUEST['signed_request']))) {
     // Prefer signed request data to cookie data.
diff --git fb_tab.admin.inc fb_tab.admin.inc
index 3f0c66c..d75adce 100644
--- fb_tab.admin.inc
+++ fb_tab.admin.inc
@@ -25,6 +25,42 @@ function fb_tab_fb_admin($op, $data, &$return) {
   }
 }
 
+
+/**
+ * Form builder; Configure settings for this site.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function fb_tab_admin_settings() {
+
+  $form['process_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Tab URL processing'),
+    '#description' => t('This option alters links, so that instead of changing the iframe\'s URL, they change the top frame.  This adds some overhead to each tab served. Still, most sites will want this enabled.'),
+  );
+  $form['process_settings'][FB_TAB_VAR_PROCESS_IFRAME] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable processing on iframe tabs.'),
+    '#default_value' => variable_get(FB_TAB_VAR_PROCESS_IFRAME, TRUE),
+    '#description' => t('Uncheck if you want the user to stay on the tab, instead of linking to canvas page or website. <br/>If unchecked, settings below have no effect.'),
+  );
+  $form['process_settings'][FB_TAB_VAR_PROCESS_TO_CANVAS] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Links send user to canvas pages.'),
+    '#default_value' => variable_get(FB_TAB_VAR_PROCESS_TO_CANVAS, TRUE),
+    '#description' => t('If unchecked, links send user to normal website.'),
+  );
+  $form['process_settings'][FB_TAB_VAR_PROCESS_ABSOLUTE] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Replace absolute hrefs, not just relative links.'),
+    '#default_value' => variable_get(FB_TAB_VAR_PROCESS_ABSOLUTE, TRUE),
+  );
+
+  return system_settings_form($form);
+}
+
+
 /**
  * See fb_tab_form_alter.
  */
diff --git fb_tab.info fb_tab.info
index d055ea5..d80a6bb 100644
--- fb_tab.info
+++ fb_tab.info
@@ -3,5 +3,4 @@ description = (fb_tab.module) Enable <a href=http://developers.facebook.com/docs
 package = Drupal for Facebook
 dependencies[] = fb
 dependencies[] = fb_app
-dependencies[] = fb_canvas
 core = 6.x
diff --git fb_tab.js fb_tab.js
new file mode 100644
index 0000000..d11e8f0
--- /dev/null
+++ fb_tab.js
@@ -0,0 +1,26 @@
+/**
+ * @file
+ *
+ * Javascript specific to iframe tabs.
+ */
+
+/**
+ * Enable canvas page specific javascript on this page.
+ */
+Drupal.behaviors.fb_tab = function(context) {
+  // Resize if body class includes fb_canvas-resizable.
+  $('body.fb_tab-resizable:not(.fb_tab-processed)').each(function () {
+    $(this).addClass('fb_tab-processed');
+    jQuery(document).bind('fb_init', FB_Tab.setAutoResize);
+  });
+};
+
+FB_Tab = function(){};
+
+/**
+ * Called after Facebook javascript has initialized.  Global FB will be set.
+ */
+FB_Tab.setAutoResize = function() {
+  // Auto resize hopefully works same on tab iframes as it does on canvas pages.
+  FB.Canvas.setAutoResize(true, 100); // time in ms, default 100.
+};
diff --git fb_tab.module fb_tab.module
index f46fd24..b5d879b 100644
--- fb_tab.module
+++ fb_tab.module
@@ -19,7 +19,10 @@ define('FB_TAB_PATH_VIEW', 'fb_tab/view');
 define('FB_TAB_PATH_FORM', 'fb_tab/config');
 define('FB_TAB_PATH_FORM_ARGS', 2);
 
-define('FB_TAB_VAR_PROCESS', 'fb_tab_process_page');
+define('FB_TAB_VAR_PROCESS_FBML', 'fb_tab_process_fbml');
+define('FB_TAB_VAR_PROCESS_IFRAME', 'fb_tab_process_iframe');
+define('FB_TAB_VAR_PROCESS_ABSOLUTE', 'fb_tab_process_absolute_links');
+define('FB_TAB_VAR_PROCESS_TO_CANVAS', 'fb_tab_process_to_canvas');
 
 function fb_tab_menu() {
   $items = array();
@@ -44,6 +47,17 @@ function fb_tab_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  // Admin pages
+  $items[FB_PATH_ADMIN .'/fb_tab'] = array(
+    'title' => 'Page Tabs',
+    'description' => 'Configure Tabs',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('fb_tab_admin_settings'),
+    'access arguments' => array(FB_PERM_ADMINISTER),
+    'file' => 'fb_tab.admin.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+
   return $items;
 }
 
@@ -63,11 +77,43 @@ function fb_tab_fb($op, $data, &$return) {
     if (fb_is_fb_admin_page()) {
       require drupal_get_path('module', 'fb_tab') . '/fb_tab.admin.inc';
     }
+
+    if (fb_is_tab()) {
+      // Include our javascript.
+      drupal_add_js(array(
+                      'fb_tab' => array(
+                        'fbu' => fb_facebook_user(),
+                        'uid' => $GLOBALS['user']->uid,
+                        'canvas' => $fb_app->canvas,
+                      ),
+                    ), 'setting');
+      drupal_add_js(drupal_get_path('module', 'fb_tab') . '/fb_tab.js');
+    }
   }
-  elseif ($op == FB_OP_CURRENT_APP) {
-    if (fb_is_tab() && isset($_REQUEST['fb_sig_api_key'])) {
+  elseif ($op == FB_OP_CURRENT_APP && fb_is_tab()) {
+    if ($id = fb_settings(FB_SETTINGS_CB)) {
+      // Using fb_url_rewrite.
+      $fb_app = fb_get_app(array('id' => $id));
+
+      if (!$fb_app) {
+        // DEPRECATED.  For backward compatibility, accept apikey in FB_SETTINGS_CB
+        $fb_app = fb_get_app(array('apikey' => $id));
+      }
+
+    }
+    elseif ($id = fb_settings(FB_SETTINGS_ID)) {
+      // New SDK includes ID when session is present.
+      $fb_app = fb_get_app(array('id' => $id));
+    }
+
+    // Old, deprecated, FBML tabs.
+    elseif (isset($_REQUEST['fb_sig_api_key'])) {
       $return = fb_get_app(array('apikey' => $_REQUEST['fb_sig_api_key']));
     }
+
+    if ($fb_app) {
+      $return = $fb_app;
+    }
   }
   elseif ($op == FB_OP_INITIALIZE) {
     if (fb_is_tab()) {
@@ -75,7 +121,19 @@ function fb_tab_fb($op, $data, &$return) {
       if (!isset($GLOBALS['custom_theme'])) {
         $GLOBALS['custom_theme'] = $config['custom_theme'];
       }
-      $use_ob = variable_get(FB_TAB_VAR_PROCESS, TRUE);
+      if (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PAGE_TAB &&
+          variable_get(FB_TAB_VAR_PROCESS_IFRAME, TRUE)) {
+        // Process iframe
+        $use_ob = TRUE;
+      }
+      elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE &&
+          variable_get(FB_TAB_VAR_PROCESS_FBML, TRUE)) {
+        // Process FBML
+        $use_ob = TRUE;
+      }
+      else {
+        $use_ob = FALSE;
+      }
 
       // Hack to init the theme before _drupal_maintenance_theme initializes the wrong one.
       if (variable_get('site_offline', FALSE)) {
@@ -88,19 +146,37 @@ function fb_tab_fb($op, $data, &$return) {
         $GLOBALS['fb_tab_post_process'] = TRUE;
       }
 
-
     }
   }
   elseif ($op == FB_OP_EXIT && fb_is_tab()) {
     if (isset($GLOBALS['fb_tab_post_process']) && $GLOBALS['fb_tab_post_process']) {
       $output = ob_get_contents();
       ob_end_clean();
-      //print '<pre>' . htmlentities($output) . '</pre>'; flush();
-      $output = fb_canvas_process($output, array(
-                  'add_target' => FALSE,
-                  'absolute_links' => TRUE,
-                ));
+      include_once(drupal_get_path('module', 'fb') . '/fb.process.inc');
 
+      if (variable_get(FB_TAB_VAR_PROCESS_TO_CANVAS, TRUE)) {
+        $to_canvas = $fb_app->canvas;
+      }
+      else {
+        $to_canvas = FALSE;
+      }
+
+      if (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PAGE_TAB) {
+        // Process iframe
+        $output = fb_process($output, array(
+                               'add_target' => '_top',
+                               'absolute_links' => TRUE,
+                               'to_canvas' => $to_canvas,
+                             ));
+      }
+      elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_PROFILE) {
+        // Process FBML (deprecated)
+        $output = fb_process($output, array(
+                               'add_target' => FALSE,
+                               'absolute_links' => TRUE,
+                               'to_canvas' => $to_canvas,
+                             ));
+      }
       if (isset($output)) {
         print($output);
       }
@@ -184,7 +260,7 @@ function fb_tab_view() {
   $fb_app = $GLOBALS['_fb_app'];
   $fb = $GLOBALS['_fb'];
 
-  $profile_id = fb_settings(FB_SETTINGS_PROFILE_ID);
+  $profile_id = fb_settings(FB_SETTINGS_CB_PAGE);
 
   $config = fb_tab_fetch_config($fb_app, $profile_id);
 
@@ -197,9 +273,6 @@ function fb_tab_view() {
 
   $content = fb_invoke(FB_TAB_OP_VIEW, $data, array(), FB_TAB_HOOK);
 
-  //print('<pre>' . print_r($data, 1) . '</pre>'); flush();
-  //print(print_r($content, 1)); flush();
-
   return drupal_render($content);
 }
 
@@ -219,9 +292,9 @@ function fb_tab_config_form($form_state, $profile_id) {
     'config' => isset($config['data']) ? $config['data'] : NULL,
   );
 
-  //$fbu = fb_facebook_user($fb);
-  $fbu = fb_require_authorization($fb);
   try {
+    //$fbu = fb_facebook_user($fb);
+    $fbu = fb_require_authorization($fb);
     $page_info = $fb->api($profile_id, array(
                             'access_token' => fb_get_token($fb),
                           ));
@@ -373,7 +446,7 @@ function fb_tab_pages() {
  */
 function fb_tab_config_url($profile_id = NULL, $options = array()) {
   if (!$profile_id) {
-    $profile_id = fb_settings(FB_SETTINGS_PROFILE_ID);
+    $profile_id = fb_settings(FB_SETTINGS_CB_PAGE);
   }
   return url(FB_TAB_PATH_FORM . '/' . $profile_id, $options);
 }
diff --git fb_url_rewrite.inc fb_url_rewrite.inc
index b81b31f..842e550 100644
--- fb_url_rewrite.inc
+++ fb_url_rewrite.inc
@@ -34,6 +34,7 @@ function _fb_settings_url_rewrite_prefixes() {
   if (!isset($prefixes)) {
     $prefixes = array(
       FB_SETTINGS_CB,
+      FB_SETTINGS_CB_PAGE,
       FB_SETTINGS_CB_TYPE,
       FB_SETTINGS_CB_SESSION,
     );
