diff options
-rw-r--r-- | res/layout/custom_screen.xml | 8 | ||||
-rw-r--r-- | res/layout/error_console.xml | 67 | ||||
-rw-r--r-- | res/values/strings.xml | 6 | ||||
-rw-r--r-- | res/xml/debug_preferences.xml | 7 | ||||
-rw-r--r-- | src/com/android/browser/BrowserActivity.java | 87 | ||||
-rw-r--r-- | src/com/android/browser/BrowserSettings.java | 17 | ||||
-rw-r--r-- | src/com/android/browser/ErrorConsoleView.java | 339 | ||||
-rw-r--r-- | src/com/android/browser/TabControl.java | 24 |
8 files changed, 547 insertions, 8 deletions
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 @@ public class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity // 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 @@ public class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity 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 class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity */ @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 @@ public class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity 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 @@ public class BrowserActivity extends Activity 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 @@ class BrowserSettings extends Observable { 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 @@ class BrowserSettings extends Observable { // 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 @@ class BrowserSettings extends Observable { // 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 @@ class TabControl { // 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 @@ class TabControl { } /** + * 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. |