Implement an error console. The console is displayed when the user has enabled debug in the browser (been to about:debug) and there are errors on the page. It can be toggled on/off in debug mode in the settings menu.
diff --git a/res/layout/custom_screen.xml b/res/layout/custom_screen.xml
index 2c41fef..3ea8ec9 100644
--- a/res/layout/custom_screen.xml
+++ b/res/layout/custom_screen.xml
@@ -28,10 +28,16 @@
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
         />
+
+        <LinearLayout android:id="@+id/error_console"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+        />
+
         <FrameLayout android:id="@+id/main_content"
             android:layout_width="fill_parent"
             android:layout_height="fill_parent"
             android:foreground="?android:attr/windowContentOverlay"
         />
     </LinearLayout>
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/res/layout/error_console.xml b/res/layout/error_console.xml
new file mode 100644
index 0000000..0fffcde
--- /dev/null
+++ b/res/layout/error_console.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/error_console_view_group_id"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:background="#000000">
+
+    <TextView android:id="@+id/error_console_header_id"
+        android:text="@string/error_console_header_text_minimized"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="2dip"
+        android:paddingBottom="2dip"
+        android:paddingLeft="5dip"
+        style="?android:attr/listSeparatorTextViewStyle"
+        android:visibility="gone"
+    />
+
+    <view class="com.android.browser.ErrorConsoleView$ErrorConsoleListView"
+        android:id="@+id/error_console_list_id"
+        android:layout_width="fill_parent"
+        android:layout_height="200dip"
+        android:visibility="gone"
+        android:layout_weight="1"
+        android:cacheColorHint="#000000"
+    />
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/error_console_eval_view_group_id"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone">
+
+        <EditText android:id="@+id/error_console_eval_text_id"
+            android:hint="@string/error_console_eval_text_hint"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true"
+            android:inputType="text"
+            android:layout_width="0dip"
+            android:layout_gravity="left"
+            android:layout_weight="1.0"
+        />
+
+        <Button android:id="@+id/error_console_eval_button_id"
+            android:text="@string/error_console_eval_button_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b8fa9cf..0d1c229 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -447,6 +447,7 @@
         <item>1</item>
         <item>2</item>
     </string-array>
+    <string name="pref_development_error_console" translatable="false">Show JavaScript Console</string>
     <!-- Settings screen, setting option name -->
     <string name="pref_default_text_encoding">Text encoding</string>
     <!-- Options in the Default encoding dialog box -->
@@ -780,4 +781,9 @@
     <!-- Zoom-related strings --><skip />
     <!-- Caption for a button that is shown when the zoom widget is showing.  The button's action will switch to the zoom overview mode. -->
     <string name="zoom_overview_button_text">Overview</string>
+
+    <string name="error_console_header_text_minimized" translatable="false">Show JavaScript Console</string>
+    <string name="error_console_header_text_maximized" translatable="false">JavaScript Console</string>
+    <string name="error_console_eval_text_hint" translatable="false">Evaluate JavaScript</string>
+    <string name="error_console_eval_button_text" translatable="false">Evaluate</string>
 </resources>
diff --git a/res/xml/debug_preferences.xml b/res/xml/debug_preferences.xml
index 00982fc..c1ed1e6 100644
--- a/res/xml/debug_preferences.xml
+++ b/res/xml/debug_preferences.xml
@@ -19,6 +19,13 @@
     <PreferenceCategory
         android:title="@string/pref_development_title" 
         android:key="debug_menu" >
+
+        <!-- The javascript console is enabled by default when the user has
+             also enabled debug mode by navigating to about:debug. -->
+        <CheckBoxPreference
+            android:key="javascript_console"
+            android:defaultValue="true"
+            android:title="@string/pref_development_error_console" />
                         
         <CheckBoxPreference
             android:key="small_screen"
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 1bccd41..da950ed 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -689,6 +689,8 @@
             mTitleBar.setBrowserActivity(this);
             mContentView = (FrameLayout) browserFrameLayout.findViewById(
                     R.id.main_content);
+            mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
+                    R.id.error_console);
             mCustomViewContainer = (FrameLayout) browserFrameLayout
                     .findViewById(R.id.fullscreen_custom_content);
             frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
@@ -696,8 +698,15 @@
             mCustomViewContainer = new FrameLayout(this);
             mCustomViewContainer.setBackgroundColor(Color.BLACK);
             mContentView = new FrameLayout(this);
+
+            LinearLayout linearLayout = new LinearLayout(this);
+            linearLayout.setOrientation(LinearLayout.VERTICAL);
+            mErrorConsoleContainer = new LinearLayout(this);
+            linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
+                    ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
             frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
-            frameLayout.addView(mContentView, COVER_SCREEN_PARAMS);
+            frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
         }
 
         // Create the tab control and our initial tab
@@ -904,7 +913,7 @@
                     // page, it can be reused.
                     boolean needsLoad =
                             mTabControl.recreateWebView(appTab, urlData.mUrl);
-                    
+
                     if (current != appTab) {
                         showTab(appTab, needsLoad ? urlData : EMPTY_URL_DATA);
                     } else {
@@ -1979,6 +1988,20 @@
         final WebView main = t.getWebView();
         // Attach the main WebView.
         mContentView.addView(main, COVER_SCREEN_PARAMS);
+
+        if (mShouldShowErrorConsole) {
+            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+            if (errorConsole.numberOfErrors() == 0) {
+                errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+            } else {
+                errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+            }
+
+            mErrorConsoleContainer.addView(errorConsole,
+                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
+        }
+
         // Attach the sub window if necessary
         attachSubWindow(t);
         // Request focus on the top window.
@@ -2000,6 +2023,11 @@
     private void removeTabFromContentView(TabControl.Tab t) {
         // Remove the main WebView.
         mContentView.removeView(t.getWebView());
+
+        if (mTabControl.getCurrentErrorConsole(false) != null) {
+            mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
+        }
+
         // Remove the sub window if it exists.
         if (t.getSubWebView() != null) {
             mContentView.removeView(t.getSubWebViewContainer());
@@ -2977,6 +3005,15 @@
         public void onPageStarted(WebView view, String url, Bitmap favicon) {
             resetLockIcon(url);
             setUrlTitle(url, null);
+
+            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
+            if (errorConsole != null) {
+                errorConsole.clearErrorMessages();
+                if (mShouldShowErrorConsole) {
+                    errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+                }
+            }
+
             // Call updateIcon instead of setFavicon so the bookmark
             // database can be updated.
             updateIcon(url, favicon);
@@ -3197,9 +3234,9 @@
             if (url.startsWith("about:")) {
                 return false;
             }
-            
+
             Intent intent;
-            
+
             // perform generic parsing of the URI to turn it into an Intent.
             try {
                 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
@@ -3852,9 +3889,14 @@
          */
         @Override
         public void addMessageToConsole(String message, int lineNumber, String sourceID) {
-            Log.w(LOGTAG, "Console: " + message + " (" + sourceID + ":" + lineNumber + ")");
+            ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+            errorConsole.addErrorMessage(message, sourceID, lineNumber);
+                if (mShouldShowErrorConsole &&
+                        errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
+                    errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+                }
+            Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
         }
-
     };
 
     /**
@@ -4922,6 +4964,34 @@
         return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
     }
 
+    /* package */ void setShouldShowErrorConsole(boolean flag) {
+        if (flag == mShouldShowErrorConsole) {
+            // Nothing to do.
+            return;
+        }
+
+        mShouldShowErrorConsole = flag;
+
+        ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+
+        if (flag) {
+            // Setting the show state of the console will cause it's the layout to be inflated.
+            if (errorConsole.numberOfErrors() > 0) {
+                errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+            } else {
+                errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+            }
+
+            // Now we can add it to the main view.
+            mErrorConsoleContainer.addView(errorConsole,
+                    new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                                                  ViewGroup.LayoutParams.WRAP_CONTENT));
+        } else {
+            mErrorConsoleContainer.removeView(errorConsole);
+        }
+
+    }
+
     private final static int LOCK_ICON_UNSECURE = 0;
     private final static int LOCK_ICON_SECURE   = 1;
     private final static int LOCK_ICON_MIXED    = 2;
@@ -5073,6 +5143,9 @@
 
     private TitleBar mTitleBar;
 
+    private LinearLayout mErrorConsoleContainer = null;
+    private boolean mShouldShowErrorConsole = false;
+
     // Used during animations to prevent other animations from being triggered.
     // A count is used since the animation to and from the Window overview can
     // overlap. A count of 0 means no animation where a count of > 0 means
@@ -5146,7 +5219,7 @@
         String mEncoding;
         @Override
         boolean isEmpty() {
-            return mInlined == null || mInlined.length() == 0 || super.isEmpty(); 
+            return mInlined == null || mInlined.length() == 0 || super.isEmpty();
         }
 
         @Override
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index aa7e103..4d10fe2 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -91,6 +91,10 @@
     private boolean lightTouch = false;
     private boolean navDump = false;
 
+    // By default the error console is shown once the user navigates to about:debug.
+    // The setting can be then toggled from the settings menu.
+    private boolean showConsole = true;
+
     // Browser only settings
     private boolean doFlick = false;
 
@@ -205,6 +209,10 @@
             // Turn on Application Caches.
             s.setAppCachePath(b.appCachePath);
             s.setAppCacheEnabled(b.appCacheEnabled);
+
+            // Enable/Disable the error console.
+            b.mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+                    b.showDebugSettings && b.showConsole);
         }
     }
 
@@ -325,6 +333,15 @@
         // JS flags is loaded from DB even if showDebugSettings is false,
         // so that it can be set once and be effective all the time.
         jsFlags = p.getString("js_engine_flags", "");
+
+        // Read the setting for showing/hiding the JS Console always so that should the
+        // user enable debug settings, we already know if we should show the console.
+        // The user will never see the console unless they navigate to about:debug,
+        // regardless of the setting we read here. This setting is only used after debug
+        // is enabled.
+        showConsole = p.getBoolean("javascript_console", showConsole);
+        mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+                showDebugSettings && showConsole);
     }
 
     public String getPluginsPath() {
diff --git a/src/com/android/browser/ErrorConsoleView.java b/src/com/android/browser/ErrorConsoleView.java
new file mode 100644
index 0000000..56f663b
--- /dev/null
+++ b/src/com/android/browser/ErrorConsoleView.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.browser;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TwoLineListItem;
+
+import java.util.Vector;
+
+/* package */ class ErrorConsoleView extends LinearLayout {
+
+    /**
+     * Define some constants to describe the visibility of the error console.
+     */
+    public static final int SHOW_MINIMIZED = 0;
+    public static final int SHOW_MAXIMIZED = 1;
+    public static final int SHOW_NONE      = 2;
+
+    private TextView mConsoleHeader;
+    private ErrorConsoleListView mErrorList;
+    private LinearLayout mEvalJsViewGroup;
+    private EditText mEvalEditText;
+    private Button mEvalButton;
+    private WebView mWebView;
+    private int mCurrentShowState = SHOW_NONE;
+
+    private boolean mSetupComplete = false;
+
+    // Before we've been asked to display the console, cache any messages that should
+    // be added to the console. Then when we do display the console, add them to the view
+    // then.
+    private Vector<ErrorConsoleMessage> mErrorMessageCache;
+
+    public ErrorConsoleView(Context context) {
+        super(context);
+    }
+
+    public ErrorConsoleView(Context context, AttributeSet attributes) {
+        super(context, attributes);
+    }
+
+    private void commonSetupIfNeeded() {
+        if (mSetupComplete) {
+            return;
+        }
+
+        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.error_console, this);
+
+        // Get references to each ui element.
+        mConsoleHeader = (TextView) findViewById(R.id.error_console_header_id);
+        mErrorList = (ErrorConsoleListView) findViewById(R.id.error_console_list_id);
+        mEvalJsViewGroup = (LinearLayout) findViewById(R.id.error_console_eval_view_group_id);
+        mEvalEditText = (EditText) findViewById(R.id.error_console_eval_text_id);
+        mEvalButton = (Button) findViewById(R.id.error_console_eval_button_id);
+
+        mEvalButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // Send the javascript to be evaluated to webkit as a javascript: url
+                // TODO: Can we expose access to webkit's JS interpreter here and evaluate it that
+                // way? Note that this is called on the UI thread so we will need to post a message
+                // to the WebCore thread to implement this.
+                if (mWebView != null) {
+                    mWebView.loadUrl("javascript:" + mEvalEditText.getText());
+                }
+
+                mEvalEditText.setText("");
+            }
+        });
+
+        // Make clicking on the console title bar min/maximse it.
+        mConsoleHeader.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                if (mCurrentShowState == SHOW_MINIMIZED) {
+                    showConsole(SHOW_MAXIMIZED);
+                } else {
+                    showConsole(SHOW_MINIMIZED);
+                }
+            }
+        });
+
+        // Add any cached messages to the list now that we've assembled the view.
+        if (mErrorMessageCache != null) {
+            for (ErrorConsoleMessage msg : mErrorMessageCache) {
+                mErrorList.addErrorMessage(msg.getMessage(), msg.getSourceID(), msg.getLineNumber());
+            }
+            mErrorMessageCache.clear();
+        }
+
+        mSetupComplete = true;
+    }
+
+    /**
+     * Adds a message to the set of messages the console uses.
+     */
+    public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+        if (mSetupComplete) {
+            mErrorList.addErrorMessage(msg, sourceId, lineNumber);
+        } else {
+            if (mErrorMessageCache == null) {
+                mErrorMessageCache = new Vector<ErrorConsoleMessage>();
+            }
+            mErrorMessageCache.add(new ErrorConsoleMessage(msg, sourceId, lineNumber));
+        }
+    }
+
+    /**
+     * Removes all error messages from the console.
+     */
+    public void clearErrorMessages() {
+        if (mSetupComplete) {
+            mErrorList.clearErrorMessages();
+        } else if (mErrorMessageCache != null) {
+            mErrorMessageCache.clear();
+        }
+    }
+
+    /**
+     * Returns the current number of errors displayed in the console.
+     */
+    public int numberOfErrors() {
+        if (mSetupComplete) {
+            return mErrorList.getCount();
+        } else {
+            return (mErrorMessageCache == null) ? 0 : mErrorMessageCache.size();
+        }
+    }
+
+    /**
+     * Sets the webview that this console is associated with. Currently this is used so
+     * we can call into webkit to evaluate JS expressions in the console.
+     */
+    public void setWebView(WebView webview) {
+        mWebView = webview;
+    }
+
+    /**
+     * Sets the visibility state of the console.
+     */
+    public void showConsole(int show_state) {
+        commonSetupIfNeeded();
+        switch (show_state) {
+            case SHOW_MINIMIZED:
+                mConsoleHeader.setVisibility(View.VISIBLE);
+                mConsoleHeader.setText(R.string.error_console_header_text_minimized);
+                mErrorList.setVisibility(View.GONE);
+                mEvalJsViewGroup.setVisibility(View.GONE);
+                break;
+
+            case SHOW_MAXIMIZED:
+                mConsoleHeader.setVisibility(View.VISIBLE);
+                mConsoleHeader.setText(R.string.error_console_header_text_maximized);
+                mErrorList.setVisibility(View.VISIBLE);
+                mEvalJsViewGroup.setVisibility(View.VISIBLE);
+                break;
+
+            case SHOW_NONE:
+                mConsoleHeader.setVisibility(View.GONE);
+                mErrorList.setVisibility(View.GONE);
+                mEvalJsViewGroup.setVisibility(View.GONE);
+                break;
+        }
+        mCurrentShowState = show_state;
+    }
+
+    /**
+     * Returns the current visibility state of the console.
+     */
+    public int getShowState() {
+        if (mSetupComplete) {
+            return mCurrentShowState;
+        } else {
+            return SHOW_NONE;
+        }
+    }
+
+    /**
+     * This class extends ListView to implement the View that will actually display the set of
+     * errors encountered on the current page.
+     */
+    private static class ErrorConsoleListView extends ListView {
+        // An adapter for this View that contains a list of error messages.
+        private ErrorConsoleMessageList mConsoleMessages;
+
+        public ErrorConsoleListView(Context context, AttributeSet attributes) {
+            super(context, attributes);
+            mConsoleMessages = new ErrorConsoleMessageList(context);
+            setAdapter(mConsoleMessages);
+        }
+
+        public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+            mConsoleMessages.add(msg, sourceId, lineNumber);
+            setSelection(mConsoleMessages.getCount());
+        }
+
+        public void clearErrorMessages() {
+            mConsoleMessages.clear();
+        }
+
+        /**
+         * This class is an adapter for ErrorConsoleListView that contains the error console
+         * message data.
+         */
+        private class ErrorConsoleMessageList extends android.widget.BaseAdapter
+                implements android.widget.ListAdapter {
+
+            private Vector<ErrorConsoleMessage> mMessages;
+            private LayoutInflater mInflater;
+
+            public ErrorConsoleMessageList(Context context) {
+                mMessages = new Vector<ErrorConsoleMessage>();
+                mInflater = (LayoutInflater)context.getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+            }
+
+            /**
+             * Add a new message to the list and update the View.
+             */
+            public void add(String msg, String sourceID, int lineNumber) {
+                mMessages.add(new ErrorConsoleMessage(msg, sourceID, lineNumber));
+                notifyDataSetChanged();
+            }
+
+            /**
+             * Remove all messages from the list and update the view.
+             */
+            public void clear() {
+                mMessages.clear();
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public boolean areAllItemsEnabled() {
+                return false;
+            }
+
+            @Override
+            public boolean isEnabled(int position) {
+                return false;
+            }
+
+            public long getItemId(int position) {
+                return position;
+            }
+
+            public Object getItem(int position) {
+                return mMessages.get(position);
+            }
+
+            public int getCount() {
+                return mMessages.size();
+            }
+
+            @Override
+            public boolean hasStableIds() {
+                return true;
+            }
+
+            /**
+             * Constructs a TwoLineListItem for the error at position.
+             */
+            public View getView(int position, View convertView, ViewGroup parent) {
+                View view;
+                ErrorConsoleMessage error = mMessages.get(position);
+
+                if (error == null) {
+                    return null;
+                }
+
+                if (convertView == null) {
+                    view = mInflater.inflate(android.R.layout.two_line_list_item, parent, false);
+                } else {
+                    view = convertView;
+                }
+
+                TextView headline = (TextView) view.findViewById(android.R.id.text1);
+                TextView subText = (TextView) view.findViewById(android.R.id.text2);
+                headline.setText(error.getSourceID() + ":" + error.getLineNumber());
+                subText.setText(error.getMessage());
+                return view;
+            }
+
+        }
+    }
+
+    /**
+     * This class holds the data for a single error message in the console.
+     */
+    private static class ErrorConsoleMessage {
+        private String mMessage;
+        private String mSourceID;
+        private int mLineNumber;
+
+        public ErrorConsoleMessage(String msg, String sourceID, int lineNumber) {
+            mMessage = msg;
+            mSourceID = sourceID;
+            mLineNumber = lineNumber;
+        }
+
+        public String getMessage() {
+            return mMessage;
+        }
+
+        public String getSourceID() {
+            return mSourceID;
+        }
+
+        public int getLineNumber() {
+            return mLineNumber;
+        }
+    }
+}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 1c8b7c8..c7c3e3f 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -195,6 +195,8 @@
         // url has not changed.
         private String mOriginalUrl;
 
+        private ErrorConsoleView mErrorConsole;
+
         // Construct a new tab
         private Tab(WebView w, boolean closeOnExit, String appId, String url) {
             mMainView = w;
@@ -388,6 +390,28 @@
     }
 
     /**
+     * Return the current tab's error console. Creates the console if createIfNEcessary
+     * is true and we haven't already created the console.
+     * @param createIfNecessary Flag to indicate if the console should be created if it has
+     *                          not been already.
+     * @return The current tab's error console, or null if one has not been created and
+     *         createIfNecessary is false.
+     */
+    ErrorConsoleView getCurrentErrorConsole(boolean createIfNecessary) {
+        Tab t = getTab(mCurrentTab);
+        if (t == null) {
+            return null;
+        }
+
+        if (createIfNecessary && t.mErrorConsole == null) {
+            t.mErrorConsole = new ErrorConsoleView(mActivity);
+            t.mErrorConsole.setWebView(t.mMainView);
+        }
+
+        return t.mErrorConsole;
+    }
+
+    /**
      * Return the current tab's top-level WebView. This can return a subwindow
      * if one exists.
      * @return The top-level WebView of the current tab.