From 2cd9394ca48fc43d9aa1145f25e85b67f4b14bdd Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Fri, 7 Sep 2018 08:59:34 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=86=E8=8A=82=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/ui/inflater/DynamicLayoutInflater.java | 1 - .../core/ui/inflater/inflaters/SpinnerInflater.java | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java index 47e2b6d9..f43ff511 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java @@ -175,7 +175,6 @@ public class DynamicLayoutInflater { } } - @SuppressWarnings("unchecked") public View inflate(Node node, @Nullable ViewGroup parent, boolean attachToParent) { View view = doInflation(node, parent, attachToParent); if (view != null && view instanceof ShouldCallOnFinishInflate) { diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/inflaters/SpinnerInflater.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/inflaters/SpinnerInflater.java index c7fb8724..7c6335ac 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/inflaters/SpinnerInflater.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/inflaters/SpinnerInflater.java @@ -73,15 +73,12 @@ public class SpinnerInflater extends BaseViewInflater { @Nullable @Override public ViewCreator getCreator() { - return new ViewCreator() { - @Override - public Spinner create(Context context, Map attrs) { - String mode = attrs.remove("android:spinnerMode"); - if (mode == null) { - return new Spinner(context); - } - return new Spinner(context, SPINNER_MODES.get(mode)); + return (context, attrs) -> { + String mode = attrs.remove("android:spinnerMode"); + if (mode == null) { + return new Spinner(context); } + return new Spinner(context, SPINNER_MODES.get(mode)); }; } From e5e1b567e754365758ace1ee6659883a0510b8d7 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Fri, 7 Sep 2018 12:36:13 +0800 Subject: [PATCH 2/4] refactor(ui): toolbar menu design; --- .idea/caches/build_file_checksums.ser | Bin 733 -> 733 bytes .../autojs/autojs/ui/edit/EditActivity.java | 8 + .../org/autojs/autojs/ui/edit/EditorView.java | 172 +++++++++++++----- .../ui/edit/toolbar/DebugToolbarFragment.java | 44 +++++ .../edit/toolbar/NormalToolbarFragment.java | 20 ++ .../edit/toolbar/SearchToolbarFragment.java | 31 ++++ .../ui/edit/toolbar/ToolbarFragment.java | 75 ++++++++ .../org/autojs/autojs/ui/main/task/Task.java | 44 ++--- .../autojs/autojs/ui/main/task/TaskGroup.java | 17 +- .../ui/main/task/TaskListRecyclerView.java | 54 +++--- app/src/main/res/layout/editor_view.xml | 84 +-------- .../res/layout/fragment_normal_toolbar.xml | 40 ++++ .../res/layout/fragment_search_toolbar.xml | 39 ++++ .../stardust/autojs/ScriptEngineService.java | 32 +++- .../stardust/autojs/engine/ScriptEngine.java | 17 +- .../autojs/engine/ScriptEngineProxy.java | 10 + .../execution/RunnableScriptExecution.java | 1 + .../autojs/execution/ScriptExecution.java | 29 +++ .../runtime/exception/ScriptException.java | 3 + 19 files changed, 528 insertions(+), 192 deletions(-) create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/toolbar/NormalToolbarFragment.java create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java create mode 100644 app/src/main/res/layout/fragment_normal_toolbar.xml create mode 100644 app/src/main/res/layout/fragment_search_toolbar.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index bf0552a3748826e28d512c2bc09b1d261dd17de6..8b8733f2c30dfc579eb647f514a3b5b38d166bdf 100644 GIT binary patch delta 99 zcmV-p0G$8b1>FUZmjz|9lz|43oOckED#;AeOyE2o0 F0$)cuFMt35 delta 99 zcmV-p0G$8b1>FUZmjz^~!1pqdoOcjkmD=lg2Z=s;mJoA+df#-DBLS2UYF1)gdYXM; zH;kl~lH7I(lg9yh5WrW009uZzuHD#=cTasVw3AQ*9uQ PFiles.read(file)) @@ -225,7 +231,13 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC private void setMenuItemStatus(int id, boolean enabled) { - findViewById(id).setEnabled(enabled); + ToolbarFragment fragment = (ToolbarFragment) getActivity().getSupportFragmentManager() + .findFragmentById(R.id.toolbar_menu); + if (fragment == null) { + mNormalToolbar.setMenuItemStatus(id, enabled); + } else { + fragment.setMenuItemStatus(id, enabled); + } } @@ -238,9 +250,15 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC setMenuItemStatus(R.id.save, false); mDocsWebView.getWebView().getSettings().setDisplayZoomControls(true); mDocsWebView.getWebView().loadUrl(Pref.getDocumentationUrl() + "index.html"); - Themes.getCurrent(getContext()). - observeOn(AndroidSchedulers.mainThread()) + Themes.getCurrent(getContext()) + .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::setTheme); + initNormalToolbar(); + } + + private void initNormalToolbar() { + mNormalToolbar.setOnMenuItemClickListener(this); + showNormalToolbar(); } private void setUpFunctionsKeyboard() { @@ -299,8 +317,36 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC return false; } + @Override + public void onToolbarMenuItemClick(int id) { + switch (id) { + case R.id.run: + runAndSaveFileIfNeeded(); + break; + case R.id.save: + saveFile(); + break; + case R.id.undo: + undo(); + break; + case R.id.redo: + redo(); + break; + case R.id.replace: + replace(); + break; + case R.id.find_next: + findNext(); + break; + case R.id.find_prev: + findPrev(); + break; + case R.id.cancel_search: + cancelSearch(); + break; + } + } - @Click(R.id.run) public void runAndSaveFileIfNeeded() { save().observeOn(AndroidSchedulers.mainThread()) .subscribe(s -> run()); @@ -308,17 +354,15 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC public void run() { Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show(); - mScriptExecution = Scripts.runWithBroadcastSender(mFile); + mScriptExecutionId = Scripts.runWithBroadcastSender(mFile).getId(); setMenuItemStatus(R.id.run, false); } - @Click(R.id.undo) public void undo() { mEditor.undo(); } - @Click(R.id.redo) public void redo() { mEditor.redo(); } @@ -335,32 +379,49 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC } public void forceStop() { - if (mScriptExecution != null) { - mScriptExecution.getEngine().forceStop(); + doWithCurrentEngine(ScriptEngine::forceStop); + } + + private void doWithCurrentEngine(Callback callback) { + ScriptExecution execution = AutoJs.getInstance().getScriptEngineService().getScriptExecution(mScriptExecutionId); + if (execution != null) { + ScriptEngine engine = execution.getEngine(); + if (engine != null) { + callback.call(engine); + } } } - @Click(R.id.save) public void saveFile() { save().subscribe(); } - @Click(R.id.find_next) void findNext() { mEditor.findNext(); } - @Click(R.id.find_prev) void findPrev() { mEditor.findPrev(); } - @Click(R.id.cancel) void cancelSearch() { - mToolbarSwitcher.showFirst(); + showNormalToolbar(); + } + + private void showNormalToolbar() { + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.toolbar_menu, mNormalToolbar) + .commit(); + } + + FragmentActivity getActivity() { + Context context = getContext(); + while (!(context instanceof Activity) && context instanceof ContextWrapper) { + context = ((ContextWrapper) context).getBaseContext(); + } + return (FragmentActivity) context; } - @Click(R.id.replace) void replace() { mEditor.replaceSelection(); } @@ -374,9 +435,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC } public void showConsole() { - if (mScriptExecution != null) { - ((JavaScriptEngine) mScriptExecution.getEngine()).getRuntime().console.show(); - } + doWithCurrentEngine(engine -> ((JavaScriptEngine) engine).getRuntime().console.show()); } public void openByOtherApps() { @@ -433,14 +492,22 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC public void find(String keywords, boolean usingRegex) { mEditor.find(keywords, usingRegex); - mReplaceMenuItem.setVisibility(GONE); - mToolbarSwitcher.showSecond(); + showSearchToolbar(false); + } + + private void showSearchToolbar(boolean showReplaceItem) { + SearchToolbarFragment searchToolbarFragment = SearchToolbarFragment_.builder() + .arg(SearchToolbarFragment.ARGUMENT_SHOW_REPLACE_ITEM, showReplaceItem) + .build(); + searchToolbarFragment.setOnMenuItemClickListener(this); + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.toolbar_menu, searchToolbarFragment) + .commit(); } public void replace(String keywords, String replacement, boolean usingRegex) { mEditor.replace(keywords, replacement, usingRegex); - mReplaceMenuItem.setVisibility(VISIBLE); - mToolbarSwitcher.showSecond(); + showSearchToolbar(true); } public void replaceAll(String keywords, String replacement, boolean usingRegex) { @@ -511,5 +578,26 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC } } + public int getScriptExecutionId() { + return mScriptExecutionId; + } + @Nullable + @Override + protected Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + Parcelable superData = super.onSaveInstanceState(); + bundle.putParcelable("super_data", superData); + bundle.putInt("script_execution_id", mScriptExecutionId); + return bundle; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + Bundle bundle = (Bundle) state; + Parcelable superData = bundle.getParcelable("super_data"); + mScriptExecutionId = bundle.getInt("script_execution_id", ScriptExecution.NO_ID); + super.onRestoreInstanceState(superData); + setMenuItemStatus(R.id.run, mScriptExecutionId == ScriptExecution.NO_ID); + } } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java new file mode 100644 index 00000000..535fc072 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java @@ -0,0 +1,44 @@ +package org.autojs.autojs.ui.edit.toolbar; + +import android.os.Looper; +import android.util.Log; + +import org.mozilla.javascript.ContextFactory; +import org.mozilla.javascript.tools.debugger.Dim; +import org.mozilla.javascript.tools.debugger.GuiCallback; + +public class DebugToolbarFragment implements GuiCallback { + + private Dim mDim; + + public void attachDebugger(){ + mDim.attachTo(ContextFactory.getGlobal()); + } + + public void deattchDebugger(){ + mDim.detach(); + } + + public void breakpoint(){ + mDim.setGuiCallback(this); + } + + @Override + public void updateSourceText(Dim.SourceInfo sourceInfo) { + } + + @Override + public void enterInterrupt(Dim.StackFrame stackFrame, String s, String s1) { + + } + + @Override + public boolean isGuiEventThread() { + return Looper.getMainLooper() == Looper.myLooper(); + } + + @Override + public void dispatchNextGuiEvent() throws InterruptedException { + + } +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/NormalToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/NormalToolbarFragment.java new file mode 100644 index 00000000..1f718d2f --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/NormalToolbarFragment.java @@ -0,0 +1,20 @@ +package org.autojs.autojs.ui.edit.toolbar; + +import android.support.v4.app.Fragment; + +import org.androidannotations.annotations.Click; +import org.androidannotations.annotations.EFragment; +import org.autojs.autojs.R; + +import java.util.Arrays; +import java.util.List; + +@EFragment(R.layout.fragment_normal_toolbar) +public class NormalToolbarFragment extends ToolbarFragment { + + + @Override + public List getMenuItemIds() { + return Arrays.asList(R.id.run, R.id.undo, R.id.redo, R.id.save); + } +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java new file mode 100644 index 00000000..ac947588 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/SearchToolbarFragment.java @@ -0,0 +1,31 @@ +package org.autojs.autojs.ui.edit.toolbar; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.View; + +import org.androidannotations.annotations.EFragment; +import org.autojs.autojs.R; + +import java.util.Arrays; +import java.util.List; + +@EFragment(R.layout.fragment_search_toolbar) +public class SearchToolbarFragment extends ToolbarFragment { + + public static final String ARGUMENT_SHOW_REPLACE_ITEM = "show_replace_item"; + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + boolean showReplaceItem = getArguments().getBoolean(ARGUMENT_SHOW_REPLACE_ITEM, false); + view.findViewById(R.id.replace).setVisibility(showReplaceItem ? View.VISIBLE : View.GONE); + } + + @Override + public List getMenuItemIds() { + return Arrays.asList(R.id.replace, R.id.find_next, R.id.find_prev, R.id.cancel_search); + } + +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java new file mode 100644 index 00000000..1dcd814b --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java @@ -0,0 +1,75 @@ +package org.autojs.autojs.ui.edit.toolbar; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.util.SparseBooleanArray; +import android.view.View; + +import java.util.List; + +public abstract class ToolbarFragment extends Fragment implements View.OnClickListener { + + public interface OnMenuItemClickListener { + void onToolbarMenuItemClick(int id); + } + + private OnMenuItemClickListener mOnMenuItemClickListener; + private List mMenuItemIds; + private SparseBooleanArray mMenuItemStatus = new SparseBooleanArray(); + + public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { + mOnMenuItemClickListener = listener; + } + + public abstract List getMenuItemIds(); + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + updateMenuItemStatus(); + } + + private void updateMenuItemStatus() { + View rootView = getView(); + if (rootView == null) { + return; + } + if (mMenuItemIds == null) { + mMenuItemIds = getMenuItemIds(); + } + for (int id : mMenuItemIds) { + View view = rootView.findViewById(id); + view.setOnClickListener(this); + view.setEnabled(mMenuItemStatus.get(id, view.isEnabled())); + } + } + + + @Override + public void onClick(View view) { + if (mOnMenuItemClickListener != null) { + mOnMenuItemClickListener.onToolbarMenuItemClick(view.getId()); + } + } + + public void setMenuItemStatus(int id, boolean enabled) { + if (mMenuItemIds == null) { + mMenuItemIds = getMenuItemIds(); + } + if (!mMenuItemIds.contains(id)) { + return; + } + mMenuItemStatus.put(id, enabled); + View rootView = getView(); + if (rootView == null) { + return; + } + View view = rootView.findViewById(id); + if (view == null) { + return; + } + view.setEnabled(enabled); + } + +} diff --git a/app/src/main/java/org/autojs/autojs/ui/main/task/Task.java b/app/src/main/java/org/autojs/autojs/ui/main/task/Task.java index 9e59665c..f1ae6097 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/task/Task.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/task/Task.java @@ -1,18 +1,13 @@ package org.autojs.autojs.ui.main.task; -import android.os.Parcel; -import android.os.Parcelable; - import com.stardust.app.GlobalAppContext; -import com.stardust.autojs.engine.JavaScriptEngine; import com.stardust.autojs.engine.ScriptEngine; -import com.stardust.autojs.engine.ScriptEngineFactory; +import com.stardust.autojs.execution.ScriptExecution; import com.stardust.autojs.script.AutoFileSource; import com.stardust.autojs.script.JavaScriptSource; import com.stardust.autojs.script.ScriptSource; -import com.stardust.pio.PFile; import com.stardust.pio.PFiles; -import org.autojs.autojs.App; + import org.autojs.autojs.R; import org.autojs.autojs.timing.TimedTask; import org.autojs.autojs.timing.TimedTaskManager; @@ -23,7 +18,7 @@ import org.joda.time.format.DateTimeFormat; * Created by Stardust on 2017/11/28. */ -public abstract class Task { +public abstract class Task { public abstract String getName(); @@ -78,46 +73,37 @@ public abstract class Task { } public static class RunningTask extends Task { - private final ScriptEngine mScriptEngine; + private final ScriptExecution mScriptExecution; - public RunningTask(ScriptEngine scriptEngine) { - mScriptEngine = scriptEngine; + public RunningTask(ScriptExecution scriptExecution) { + mScriptExecution = scriptExecution; } - public ScriptEngine getScriptEngine() { - return mScriptEngine; + public ScriptExecution getScriptExecution() { + return mScriptExecution; } @Override public String getName() { - ScriptSource source = (ScriptSource) mScriptEngine.getTag(ScriptEngine.TAG_SOURCE); - if (source == null) { - return null; - } - return source.getName(); + return mScriptExecution.getSource().getName(); } @Override public String getDesc() { - ScriptSource source = (ScriptSource) mScriptEngine.getTag(ScriptEngine.TAG_SOURCE); - if (source == null) { - return null; - } - return source.toString(); + return mScriptExecution.getSource().toString(); } @Override public void cancel() { - mScriptEngine.forceStop(); + ScriptEngine engine = mScriptExecution.getEngine(); + if (engine != null) { + engine.forceStop(); + } } @Override public String getEngineName() { - ScriptSource source = (ScriptSource) mScriptEngine.getTag(ScriptEngine.TAG_SOURCE); - if (source == null) { - return null; - } - return source.getEngineName(); + return mScriptExecution.getSource().getEngineName(); } } } diff --git a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java index 2ff28001..a1aef3d6 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskGroup.java @@ -4,12 +4,15 @@ import android.content.Context; import com.bignerdranch.expandablerecyclerview.model.Parent; import com.stardust.autojs.engine.ScriptEngine; +import com.stardust.autojs.execution.ScriptExecution; + import org.autojs.autojs.R; import org.autojs.autojs.autojs.AutoJs; import org.autojs.autojs.timing.TimedTask; import org.autojs.autojs.timing.TimedTaskManager; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -98,20 +101,20 @@ public abstract class TaskGroup implements Parent { @Override public void refresh() { - Set scriptEngines = AutoJs.getInstance().getScriptEngineService().getEngines(); + Collection executions = AutoJs.getInstance().getScriptEngineService().getScriptExecutions(); mTasks.clear(); - for (ScriptEngine engine : scriptEngines) { - mTasks.add(new Task.RunningTask(engine)); + for (ScriptExecution execution : executions) { + mTasks.add(new Task.RunningTask(execution)); } } - public int addTask(ScriptEngine engine) { + public int addTask(ScriptExecution engine) { int pos = mTasks.size(); mTasks.add(new Task.RunningTask(engine)); return pos; } - public int removeTask(ScriptEngine engine) { + public int removeTask(ScriptExecution engine) { int i = indexOf(engine); if (i >= 0) { mTasks.remove(i); @@ -119,9 +122,9 @@ public abstract class TaskGroup implements Parent { return i; } - public int indexOf(ScriptEngine engine) { + public int indexOf(ScriptExecution engine) { for (int i = 0; i < mTasks.size(); i++) { - if (((Task.RunningTask) mTasks.get(i)).getScriptEngine().equals(engine)) { + if (((Task.RunningTask) mTasks.get(i)).getScriptExecution().equals(engine)) { return i; } } diff --git a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java index b716490f..3c9c7fe7 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java @@ -12,6 +12,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; + import com.stardust.autojs.workground.WrapContentLinearLayoutManager; import com.bignerdranch.expandablerecyclerview.ChildViewHolder; @@ -25,6 +26,7 @@ import com.stardust.autojs.execution.ScriptExecutionListener; import com.stardust.autojs.execution.SimpleScriptExecutionListener; import com.stardust.autojs.engine.ScriptEngine; import com.stardust.autojs.script.AutoFileSource; + import org.autojs.autojs.R; import org.autojs.autojs.autojs.AutoJs; import org.autojs.autojs.storage.database.ModelChange; @@ -32,6 +34,7 @@ import org.autojs.autojs.timing.TaskReceiver; import org.autojs.autojs.timing.TimedTask; import org.autojs.autojs.timing.TimedTaskManager; import org.autojs.autojs.ui.timing.TimedTaskSettingActivity_; + import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import java.util.ArrayList; @@ -47,7 +50,7 @@ import io.reactivex.disposables.Disposable; * Created by Stardust on 2017/3/24. */ -public class TaskListRecyclerView extends ThemeColorRecyclerView implements ScriptEngineManager.EngineLifecycleCallback { +public class TaskListRecyclerView extends ThemeColorRecyclerView { private static final String LOG_TAG = "TaskListRecyclerView"; @@ -61,15 +64,26 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView implements Scri private ScriptExecutionListener mScriptExecutionListener = new SimpleScriptExecutionListener() { @Override public void onStart(final ScriptExecution execution) { - post(() -> { - int position = mRunningTaskGroup.indexOf(execution.getEngine()); - if (position >= 0) { - mAdapter.notifyChildChanged(0, position); - } else { - refresh(); - } - }); + mAdapter.notifyChildInserted(0, mRunningTaskGroup.addTask(execution)); + } + @Override + public void onSuccess(ScriptExecution execution, Object result) { + onFinish(execution); + } + + @Override + public void onException(ScriptExecution execution, Exception e) { + onFinish(execution); + } + + private void onFinish(ScriptExecution execution){ + final int i = mRunningTaskGroup.removeTask(execution); + if (i >= 0) { + mAdapter.notifyChildRemoved(0, i); + } else { + refresh(); + } } }; @@ -116,7 +130,6 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView implements Scri @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mScriptEngineService.registerEngineLifecycleCallback(this); AutoJs.getInstance().getScriptEngineService().registerGlobalScriptExecutionListener(mScriptExecutionListener); mTimedTaskChangeDisposable = TimedTaskManager.getInstance().getTimeTaskChanges() .observeOn(AndroidSchedulers.mainThread()) @@ -134,7 +147,6 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView implements Scri @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mScriptEngineService.unregisterEngineLifecycleCallback(this); AutoJs.getInstance().getScriptEngineService().unregisterGlobalScriptExecutionListener(mScriptExecutionListener); mTimedTaskChangeDisposable.dispose(); } @@ -162,26 +174,6 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView implements Scri } } - @Override - public void onEngineCreate(final ScriptEngine engine) { - post(() -> - mAdapter.notifyChildInserted(0, mRunningTaskGroup.addTask(engine)) - ); - } - - @Override - public void onEngineRemove(final ScriptEngine engine) { - post(() -> { - final int i = mRunningTaskGroup.removeTask(engine); - if (i >= 0) { - mAdapter.notifyChildRemoved(0, i); - } else { - refresh(); - } - - }); - } - private class Adapter extends ExpandableRecyclerAdapter { public Adapter(@NonNull List parentList) { diff --git a/app/src/main/res/layout/editor_view.xml b/app/src/main/res/layout/editor_view.xml index c1808bf7..29112e7c 100644 --- a/app/src/main/res/layout/editor_view.xml +++ b/app/src/main/res/layout/editor_view.xml @@ -19,89 +19,11 @@ android:title="@string/_app_name" app:popupTheme="@style/AppTheme.PopupOverlay"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_gravity="right"/> diff --git a/app/src/main/res/layout/fragment_normal_toolbar.xml b/app/src/main/res/layout/fragment_normal_toolbar.xml new file mode 100644 index 00000000..a5ca5924 --- /dev/null +++ b/app/src/main/res/layout/fragment_normal_toolbar.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_toolbar.xml b/app/src/main/res/layout/fragment_search_toolbar.xml new file mode 100644 index 00000000..7c829637 --- /dev/null +++ b/app/src/main/res/layout/fragment_search_toolbar.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/ScriptEngineService.java b/autojs/src/main/java/com/stardust/autojs/ScriptEngineService.java index ffc41460..03c50b48 100644 --- a/autojs/src/main/java/com/stardust/autojs/ScriptEngineService.java +++ b/autojs/src/main/java/com/stardust/autojs/ScriptEngineService.java @@ -1,6 +1,7 @@ package com.stardust.autojs; import android.content.Context; +import android.support.annotation.Nullable; import com.stardust.autojs.engine.JavaScriptEngine; import com.stardust.autojs.engine.ScriptEngine; @@ -30,6 +31,9 @@ import java.io.IOException; import java.io.PipedReader; import java.io.PipedWriter; import java.io.PrintWriter; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Set; @@ -83,8 +87,16 @@ public class ScriptEngineService { private UiHandler mUiHandler; private final Console mGlobalConsole; private final ScriptEngineManager mScriptEngineManager; - private final EngineLifecycleObserver mEngineLifecycleObserver = new EngineLifecycleObserver(); + private final EngineLifecycleObserver mEngineLifecycleObserver = new EngineLifecycleObserver() { + + @Override + public void onEngineRemove(ScriptEngine engine) { + mScriptExecutions.remove(engine.getId()); + super.onEngineRemove(engine); + } + }; private ScriptExecutionObserver mScriptExecutionObserver = new ScriptExecutionObserver(); + private LinkedHashMap mScriptExecutions = new LinkedHashMap<>(); ScriptEngineService(ScriptEngineServiceBuilder builder) { mUiHandler = builder.mUiHandler; @@ -119,6 +131,12 @@ public class ScriptEngineService { } public ScriptExecution execute(ScriptExecutionTask task) { + ScriptExecution execution = executeInternal(task); + mScriptExecutions.put(execution.getId(), execution); + return execution; + } + + private ScriptExecution executeInternal(ScriptExecutionTask task) { if (task.getListener() != null) { task.setExecutionListener(new ScriptExecutionObserver.Wrapper(mScriptExecutionObserver, task.getListener())); } else { @@ -185,6 +203,18 @@ public class ScriptEngineService { return mScriptEngineManager.getEngines(); } + public Collection getScriptExecutions() { + return mScriptExecutions.values(); + } + + @Nullable + public ScriptExecution getScriptExecution(int id) { + if(id == ScriptExecution.NO_ID){ + return null; + } + return mScriptExecutions.get(id); + } + private static class EngineLifecycleObserver implements ScriptEngineManager.EngineLifecycleCallback { private final Set mEngineLifecycleCallbacks = new LinkedHashSet<>(); diff --git a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java index 010a304e..ec08c4ac 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java @@ -2,11 +2,13 @@ package com.stardust.autojs.engine; import android.support.annotation.CallSuper; +import com.stardust.autojs.execution.ScriptExecution; import com.stardust.autojs.runtime.exception.ScriptException; import com.stardust.autojs.script.ScriptSource; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; /** * Created by Stardust on 2017/4/2. @@ -46,6 +48,9 @@ public interface ScriptEngine { Exception getUncaughtException(); + void setId(int id); + + int getId(); /** * @hide @@ -68,7 +73,7 @@ public interface ScriptEngine { private OnDestroyListener mOnDestroyListener; private boolean mDestroyed = false; private Exception mUncaughtException; - + private volatile AtomicInteger mId = new AtomicInteger(ScriptExecution.NO_ID); @Override public synchronized void setTag(String key, Object value) { @@ -116,5 +121,15 @@ public interface ScriptEngine { public Exception getUncaughtException() { return mUncaughtException; } + + @Override + public void setId(int id) { + mId.compareAndSet(ScriptExecution.NO_ID, id); + } + + @Override + public int getId() { + return mId.get(); + } } } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineProxy.java b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineProxy.java index d59e68ac..298b2323 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineProxy.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineProxy.java @@ -77,4 +77,14 @@ public class ScriptEngineProxy implements ScriptEngine Date: Fri, 7 Sep 2018 22:26:03 +0800 Subject: [PATCH 3/4] feat(ui): supports debugging --- .idea/caches/build_file_checksums.ser | Bin 733 -> 733 bytes app/build.gradle | 4 +- .../main/assets/editor/theme/dark_plus.json | 4 +- .../main/assets/editor/theme/light_plus.json | 2 + .../autojs/model/editor/EditorColors.java | 20 + .../org/autojs/autojs/ui/edit/EditorMenu.java | 17 + .../org/autojs/autojs/ui/edit/EditorView.java | 36 + .../autojs/ui/edit/editor/CodeEditText.java | 47 +- .../autojs/ui/edit/editor/CodeEditor.java | 43 +- .../autojs/autojs/ui/edit/theme/Theme.java | 11 + .../ui/edit/toolbar/DebugToolbarFragment.java | 124 +- .../ui/edit/toolbar/ToolbarFragment.java | 24 +- .../ui/main/task/TaskListRecyclerView.java | 16 +- app/src/main/res/drawable-xhdpi/ic_debug.png | Bin 0 -> 3196 bytes .../main/res/drawable/ic_debug_step_into.png | Bin 0 -> 1749 bytes .../main/res/drawable/ic_debug_step_out.png | Bin 0 -> 1652 bytes .../main/res/drawable/ic_debug_step_over.png | Bin 0 -> 2682 bytes .../res/layout/fragment_debug_toolbar.xml | 46 + app/src/main/res/menu/menu_editor.xml | 18 + app/src/main/res/values/strings.xml | 8 + .../main/java/com/stardust/autojs/AutoJs.java | 1 + .../engine/LoopBasedJavaScriptEngine.java | 4 + .../autojs/engine/RhinoJavaScriptEngine.java | 25 +- .../stardust/autojs/engine/ScriptEngine.java | 3 +- .../autojs/engine/ScriptEngineManager.java | 20 +- .../execution/RunnableScriptExecution.java | 3 +- .../autojs/rhino/debug/DebugCallback.java | 50 + .../com/stardust/autojs/rhino/debug/Dim.java | 1558 +++++++++++++++++ 28 files changed, 2027 insertions(+), 57 deletions(-) create mode 100644 app/src/main/res/drawable-xhdpi/ic_debug.png create mode 100644 app/src/main/res/drawable/ic_debug_step_into.png create mode 100644 app/src/main/res/drawable/ic_debug_step_out.png create mode 100644 app/src/main/res/drawable/ic_debug_step_over.png create mode 100644 app/src/main/res/layout/fragment_debug_toolbar.xml create mode 100644 autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java create mode 100644 autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 8b8733f2c30dfc579eb647f514a3b5b38d166bdf..5994ddd6575950de53e5f378d17ad52d70a088a0 100644 GIT binary patch delta 36 ucmV+<0NelF1>FUZm;|(IKdzCScM#OzQ(#GhE!D$8C2%0KfAF&-0lEPFUZm;|wufd-MBcMy~+$qdp>UZ+;#dFmwN run()); @@ -515,6 +531,26 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC } + public void launchDebugger() { + DebugToolbarFragment debugToolbarFragment = DebugToolbarFragment_.builder() + .build(); + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.toolbar_menu, debugToolbarFragment) + .commit(); + mDebugging = true; + } + + public void exitDebugging() { + FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); + Fragment fragment = fragmentManager.findFragmentById(R.id.toolbar_menu); + if (fragment instanceof DebugToolbarFragment) { + ((DebugToolbarFragment) fragment).detachDebugger(); + } + showNormalToolbar(); + mEditor.setDebuggingLine(-1); + mDebugging = false; + } + private void showErrorMessage(String msg) { Snackbar.make(EditorView.this, getResources().getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG) .setAction(R.string.text_detail, v -> LogActivity_.intent(getContext()).start()) diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditText.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditText.java index 79602b90..db8dca48 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditText.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditText.java @@ -22,7 +22,6 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; -import android.os.Parcelable; import android.support.v7.widget.AppCompatEditText; import android.text.Editable; import android.text.Layout; @@ -31,14 +30,16 @@ import android.util.Log; import android.util.TimingLogger; import android.view.Gravity; -import org.autojs.autojs.BuildConfig; import org.autojs.autojs.ui.edit.theme.Theme; import org.autojs.autojs.ui.edit.theme.TokenMapping; + import com.stardust.util.ClipboardUtil; import com.stardust.util.TextUtils; import org.mozilla.javascript.Token; +import java.util.LinkedHashMap; + import static org.autojs.autojs.ui.edit.editor.BracketMatching.UNMATCHED_BRACKET; /** @@ -62,6 +63,8 @@ public class CodeEditText extends AppCompatEditText { private int mFirstLineForDraw = -1, mLastLineForDraw; private int[] mMatchingBrackets = {-1, -1}; private int mUnmatchedBracket = -1; + private LinkedHashMap mBreakpoints = new LinkedHashMap<>(); + private int mDebuggingLine = -1; public CodeEditText(Context context) { @@ -87,6 +90,10 @@ public class CodeEditText extends AppCompatEditText { mLineHighlightPaint.setStyle(Paint.Style.FILL); } + public LinkedHashMap getBreakpoints() { + return mBreakpoints; + } + public void setTheme(Theme theme) { mTheme = theme; invalidate(); @@ -101,8 +108,8 @@ public class CodeEditText extends AppCompatEditText { updatePaddingForGutter(); updateLineRangeForDraw(canvas); - //绘制当前行高亮需要在绘制光标之前 - drawLineHighlight(canvas, mLineHighlightPaint, getCurrentLine()); + //绘制行高亮需要在绘制光标之前 + drawLineHighlights(canvas); //调用super.onDraw绘制光标和选择高亮。因为字体颜色被设置为透明因此super.onDraw()绘制的字体不显示 // TODO: 2018/2/24 优化效率。不绘制透明字体。 @@ -118,6 +125,30 @@ public class CodeEditText extends AppCompatEditText { mLogger.dumpToLog(); } + public int getDebuggingLine() { + return mDebuggingLine; + } + + public void setDebuggingLine(int debuggingLine) { + mDebuggingLine = debuggingLine; + invalidate(); + } + + private void drawLineHighlights(Canvas canvas) { + int currentLine = getCurrentLine(); + int debugHighlightLine = mDebuggingLine; + if(debugHighlightLine != currentLine){ + //绘制当前行高亮 + mLineHighlightPaint.setColor(mTheme.getLineHighlightBackgroundColor()); + drawLineHighlight(canvas, mLineHighlightPaint, getCurrentLine()); + } + if(debugHighlightLine != -1){ + mLineHighlightPaint.setColor(mTheme.getDebuggingLineBackgroundColor()); + drawLineHighlight(canvas, mLineHighlightPaint, debugHighlightLine); + } + + } + private void updateLineRangeForDraw(Canvas canvas) { Layout layout = getLayout(); if (layout == null) @@ -159,15 +190,22 @@ public class CodeEditText extends AppCompatEditText { int scrollX = Math.max(getRealScrollX() - paddingLeft, 0); Paint paint = getPaint(); int lineNumberColor = mTheme.getLineNumberColor(); + int breakPointColor = mTheme.getBreakpointColor(); if (DEBUG) Log.d(LOG_TAG, "draw line: " + (mLastLineForDraw - mFirstLineForDraw + 1)); mLogger.addSplit("before draw line"); for (int line = mFirstLineForDraw; line <= mLastLineForDraw && line < lineCount; line++) { int lineBottom = layout.getLineTop(line + 1); + int lineTop = layout.getLineTop(line); int lineBaseline = lineBottom - layout.getLineDescent(line); //drawLineNumber String lineNumberText = Integer.toString(line + 1); + // if there is a breakpoint at this line, draw highlight background for line number + if (mBreakpoints.containsKey(line)) { + paint.setColor(breakPointColor); + canvas.drawRect(0, lineTop, paddingLeft - 10, lineBottom, paint); + } paint.setColor(lineNumberColor); canvas.drawText(lineNumberText, 0, lineNumberText.length(), 10, lineBaseline, paint); @@ -225,7 +263,6 @@ public class CodeEditText extends AppCompatEditText { } int lineTop = getLayout().getLineTop(line); int lineBottom = getLayout().getLineTop(line + 1); - paint.setColor(mTheme.getLineHighlightBackgroundColor()); canvas.drawRect(0, lineTop, canvas.getWidth(), lineBottom, paint); } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java index 5f894d70..3834cda8 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java @@ -7,13 +7,15 @@ import android.util.AttributeSet; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; -import com.android.dx.util.IntList; import com.stardust.autojs.script.JsBeautifier; + import org.autojs.autojs.R; import org.autojs.autojs.ui.edit.theme.Theme; + import com.stardust.util.ClipboardUtil; import com.stardust.util.TextUtils; +import java.util.LinkedHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -53,7 +55,6 @@ public class CodeEditor extends HVScrollView { private JsBeautifier mJsBeautifier; private MaterialDialog mProcessDialog; - private CharSequence mReplacement = ""; private String mKeywords; private Matcher mMatcher; @@ -73,7 +74,7 @@ public class CodeEditor extends HVScrollView { private void init() { //setFillViewport(true); inflate(getContext(), R.layout.code_editor, this); - mCodeEditText = (CodeEditText) findViewById(R.id.code_edit_text); + mCodeEditText = findViewById(R.id.code_edit_text); mCodeEditText.addTextChangedListener(new AutoIndent(mCodeEditText)); mTextViewRedoUndo = new TextViewRedoUndo(mCodeEditText); mJavaScriptHighlighter = new JavaScriptHighlighter(mTheme, mCodeEditText); @@ -260,7 +261,7 @@ public class CodeEditor extends HVScrollView { } public void findPrev() { - if (mMatcher != null){ + if (mMatcher != null) { Toast.makeText(getContext(), R.string.error_regex_find_prev, Toast.LENGTH_SHORT).show(); return; } @@ -329,6 +330,30 @@ public class CodeEditor extends HVScrollView { mTextViewRedoUndo.markTextAsUnchanged(); } + public LinkedHashMap getBreakpoints() { + return mCodeEditText.getBreakpoints(); + } + + public void setDebuggingLine(int line){ + mCodeEditText.setDebuggingLine(line); + } + + public void addOrRemoveBreakpoint(int line) { + LinkedHashMap breakpoints = mCodeEditText.getBreakpoints(); + if(breakpoints.remove(line) == null){ + breakpoints.put(line, new Breakpoint(line)); + } + mCodeEditText.invalidate(); + } + + public void addOrRemoveBreakpointAtCurrentLine() { + int line = LayoutHelper.getLineOfChar(mCodeEditText.getLayout(), mCodeEditText.getSelectionStart()); + if (line < 0 || line >= mCodeEditText.getLayout().getLineCount()) + return; + addOrRemoveBreakpoint(line); + } + + @Override protected void onDraw(Canvas canvas) { int codeWidth = getWidth() - getPaddingLeft() - getPaddingRight(); @@ -340,4 +365,14 @@ public class CodeEditor extends HVScrollView { } super.onDraw(canvas); } + + public static class Breakpoint { + + public int line; + public boolean enabled = true; + + public Breakpoint(int line) { + this.line = line; + } + } } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/theme/Theme.java b/app/src/main/java/org/autojs/autojs/ui/edit/theme/Theme.java index 2c05413e..27e490b4 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/theme/Theme.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/theme/Theme.java @@ -25,6 +25,8 @@ public class Theme { private int mImeBarForegroundColor = Color.WHITE; private EditorTheme mEditorTheme; private int mLineHighlightBackground; + private int mBreakpointColor; + private int mDebuggingLineBackground; public Theme(EditorTheme theme) { mEditorTheme = theme; @@ -34,6 +36,8 @@ public class Theme { mImeBarBackgroundColor = parseColor(theme.getEditorColors().getImeBackgroundColor(), mImeBarBackgroundColor); mImeBarForegroundColor = parseColor(theme.getEditorColors().getImeForegroundColor(), mImeBarForegroundColor); mLineHighlightBackground = parseColor(theme.getEditorColors().getLineHighlightBackground(), mLineHighlightBackground); + mDebuggingLineBackground = parseColor(theme.getEditorColors().getDebuggingLineBackground(), mDebuggingLineBackground); + mBreakpointColor = parseColor(theme.getEditorColors().getBreakpointForeground(), mBackgroundColor); for (TokenColor tokenColor : theme.getTokenColors()) { String foregroundStr = tokenColor.getSettings().getForeground(); @@ -141,4 +145,11 @@ public class Theme { } + public int getBreakpointColor() { + return mBreakpointColor; + } + + public int getDebuggingLineBackgroundColor() { + return mDebuggingLineBackground; + } } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java index 535fc072..d2c7d640 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java @@ -1,35 +1,116 @@ package org.autojs.autojs.ui.edit.toolbar; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; import android.os.Looper; +import android.support.annotation.Nullable; import android.util.Log; +import android.view.View; + +import com.stardust.autojs.engine.RhinoJavaScriptEngine; +import com.stardust.autojs.execution.ScriptExecution; +import com.stardust.autojs.rhino.debug.Dim; +import com.stardust.autojs.rhino.debug.DebugCallback; + +import org.androidannotations.annotations.Click; +import org.androidannotations.annotations.EFragment; +import org.autojs.autojs.R; +import org.autojs.autojs.autojs.AutoJs; +import org.autojs.autojs.ui.edit.EditorView; +import org.autojs.autojs.ui.edit.editor.CodeEditor; import org.mozilla.javascript.ContextFactory; -import org.mozilla.javascript.tools.debugger.Dim; -import org.mozilla.javascript.tools.debugger.GuiCallback; -public class DebugToolbarFragment implements GuiCallback { +import java.util.Arrays; +import java.util.List; - private Dim mDim; +@EFragment(R.layout.fragment_debug_toolbar) +public class DebugToolbarFragment extends ToolbarFragment implements DebugCallback { - public void attachDebugger(){ - mDim.attachTo(ContextFactory.getGlobal()); + private static final String LOG_TAG = "DebugToolbarFragment"; + private Dim mDim = new Dim(); + private EditorView mEditorView; + private Handler mHandler; + + public DebugToolbarFragment() { + mDim.setGuiCallback(this); + mDim.setBreak(); + mDim.attachTo(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal()); + Log.d(LOG_TAG, "DebugToolbarFragment()"); } - public void deattchDebugger(){ + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHandler = new Handler(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mEditorView = findEditorView(view); + mEditorView.run(); + Log.d(LOG_TAG, "onViewCreated"); + } + + public void detachDebugger() { mDim.detach(); } - public void breakpoint(){ - mDim.setGuiCallback(this); + @Click(R.id.step_over) + void stepOver() { + mEditorView.getEditor().setDebuggingLine(-1); + mDim.setReturnValue(Dim.STEP_OVER); + } + + @Click(R.id.step_into) + void stepInto() { + mEditorView.getEditor().setDebuggingLine(-1); + mDim.setReturnValue(Dim.STEP_INTO); + } + + @Click(R.id.stop_out) + void stepOut() { + mEditorView.getEditor().setDebuggingLine(-1); + mDim.setReturnValue(Dim.STEP_OUT); + } + + @Click(R.id.stop_script) + void stopScript() { + mEditorView.forceStop(); + } + + @Click(R.id.resume_script) + void resumeScript() { + mEditorView.getEditor().setDebuggingLine(-1); + mDim.setReturnValue(Dim.GO); } @Override public void updateSourceText(Dim.SourceInfo sourceInfo) { + Log.d(LOG_TAG, "updateSourceText: url = " + sourceInfo.url() + ", source = " + sourceInfo.source()); + if (!sourceInfo.url().equals(mEditorView.getFile().toString())) { + return; + } + sourceInfo.removeAllBreakpoints(); + for (CodeEditor.Breakpoint breakpoint : mEditorView.getEditor().getBreakpoints().values()) { + int line = breakpoint.line + 1; + if (sourceInfo.breakableLine(line)) { + sourceInfo.breakpoint(line, breakpoint.enabled); + Log.d(LOG_TAG, "not breakable: " + line); + } + } } @Override - public void enterInterrupt(Dim.StackFrame stackFrame, String s, String s1) { - + public void enterInterrupt(Dim.StackFrame stackFrame, String threadName, String s1) { + Log.d(LOG_TAG, "enterInterrupt: threadName = " + threadName + ", url = " + stackFrame.getUrl() + ", line = " + stackFrame.getLineNumber()); + if (stackFrame.getUrl().equals(mEditorView.getFile().toString())) { + mEditorView.getEditor().setDebuggingLine(stackFrame.getLineNumber() - 1); + } else { + mHandler.post(this::resumeScript); + } } @Override @@ -38,7 +119,26 @@ public class DebugToolbarFragment implements GuiCallback { } @Override - public void dispatchNextGuiEvent() throws InterruptedException { + public void dispatchNextGuiEvent() { + Log.d(LOG_TAG, "dispatchNextGuiEvent"); } + + @Override + public boolean shouldAttachDebugger(RhinoJavaScriptEngine engine) { + ScriptExecution execution = AutoJs.getInstance().getScriptEngineService().getScriptExecution(mEditorView.getScriptExecutionId()); + return execution != null && execution.getId() == engine.getId(); + + } + + @Override + public List getMenuItemIds() { + return Arrays.asList(R.id.step_over, R.id.step_into, R.id.stop_out, R.id.resume_script, R.id.stop_script); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mDim.detach(); + } } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java index 1dcd814b..aed9a309 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/ToolbarFragment.java @@ -1,11 +1,14 @@ package org.autojs.autojs.ui.edit.toolbar; import android.os.Bundle; +import android.os.Parcelable; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.util.SparseBooleanArray; import android.view.View; +import org.autojs.autojs.ui.edit.EditorView; + import java.util.List; public abstract class ToolbarFragment extends Fragment implements View.OnClickListener { @@ -16,7 +19,6 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi private OnMenuItemClickListener mOnMenuItemClickListener; private List mMenuItemIds; - private SparseBooleanArray mMenuItemStatus = new SparseBooleanArray(); public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { mOnMenuItemClickListener = listener; @@ -27,21 +29,32 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - updateMenuItemStatus(); + updateMenuItemStatus(view); } - private void updateMenuItemStatus() { - View rootView = getView(); + protected EditorView findEditorView(View view) { + while (!(view instanceof EditorView) && view.getParent() != null) { + view = (View) view.getParent(); + } + if (!(view instanceof EditorView)) { + throw new IllegalStateException("cannot find EditorView from child: " + view); + } + return (EditorView) view; + } + + + private void updateMenuItemStatus(View rootView) { if (rootView == null) { return; } + EditorView editorView = findEditorView(rootView); if (mMenuItemIds == null) { mMenuItemIds = getMenuItemIds(); } for (int id : mMenuItemIds) { View view = rootView.findViewById(id); view.setOnClickListener(this); - view.setEnabled(mMenuItemStatus.get(id, view.isEnabled())); + view.setEnabled(editorView.getMenuItemStatus(id, view.isEnabled())); } } @@ -60,7 +73,6 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi if (!mMenuItemIds.contains(id)) { return; } - mMenuItemStatus.put(id, enabled); View rootView = getView(); if (rootView == null) { return; diff --git a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java index 3c9c7fe7..aba82132 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java +++ b/app/src/main/java/org/autojs/autojs/ui/main/task/TaskListRecyclerView.java @@ -64,7 +64,7 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView { private ScriptExecutionListener mScriptExecutionListener = new SimpleScriptExecutionListener() { @Override public void onStart(final ScriptExecution execution) { - mAdapter.notifyChildInserted(0, mRunningTaskGroup.addTask(execution)); + post(()-> mAdapter.notifyChildInserted(0, mRunningTaskGroup.addTask(execution))); } @Override @@ -78,12 +78,14 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView { } private void onFinish(ScriptExecution execution){ - final int i = mRunningTaskGroup.removeTask(execution); - if (i >= 0) { - mAdapter.notifyChildRemoved(0, i); - } else { - refresh(); - } + post(()->{ + final int i = mRunningTaskGroup.removeTask(execution); + if (i >= 0) { + mAdapter.notifyChildRemoved(0, i); + } else { + refresh(); + } + }); } }; diff --git a/app/src/main/res/drawable-xhdpi/ic_debug.png b/app/src/main/res/drawable-xhdpi/ic_debug.png new file mode 100644 index 0000000000000000000000000000000000000000..c766543b450800aeaa8eb94a35d6ccaed280bb9f GIT binary patch literal 3196 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 I*o5W(A6Pz@p#T5? literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_debug_step_into.png b/app/src/main/res/drawable/ic_debug_step_into.png new file mode 100644 index 0000000000000000000000000000000000000000..bf7f1a2a0bce62db89047347429f3b2f216ad774 GIT binary patch literal 1749 zcmX|?do4mj!SAaD#|h$nkiN(B84$_wODe?%9PvBEHz7$K`0bk zowvfeXNWSnHL^lWScBnJ<~{Dj+H!gMW8XiX=Q-zl&iC^@=Q-y&&o5`3PHa=uRRn-- z4)zR}&53T6Jbtq`9sRBy0H5c;uyy5lPv2vS{myE<-q;X$DkLetNxJ+WJn5_BE`cF_ zrYG(F$QKRGNMl^;?Huv#)K|BO3*lxVF&`RVXA0`}xN8`M8?;3^I!^Qq{;^o!d32&U zVtF8_loeqXI$8YJg~^TR<@MRM`xBOon}t@Q!lKkMRd7|Y1tPc(C%kD-)zOD1^_a|T zy@PZ(bCqDS-^LGxf?Fy)@l!ChFJ9Je%UAbJQ7Q7n6Vq5QZ0{OGY7?q$DoT*mP67@7 zQaVZRpy)Ew7thlX4MxXarI}%xm5GL73Yc(G8&v#EZtW=e*r>S(6A<}Q6}ItQJD zEySbWJVudspIx0?|53J}g6hB|g8k%KOXvT`Y?=Nn-1Po-`U4|rEBGJGk45J=Xw}gH z<}cz{xW8kv=)%V_B&W2nmOuX0m7&S}AGM-zDdK|=LYOm0+gni|6*x2&uPBXy8xAwm z`|ovR4j{xdBal4%HP2aJyiLSi39h|iKO;=5U}qHrPy7&OSPSnuc8e86it~dNdgQ-- zZ2rE^&fEs7wR)n2Q{PgP^hprZ9NIo|dZ8=Kvd&*Dhw@~%OV4gR{|;}f0%|Rtim%3j z$IGFa$3AVsa-SkH4;#Gh0m*~t z*n+-?fp}Z0CSY1faP4>)g=w8~#{uag?IRzSnY_s3sOlWD62H=wSjvrBi{e8@RBhJjr^YoS#tX ze5Dl!Ckkiv2FzH*DWdC+I$uE2EqRfv)?jwLrQ!N`cM0huZ$BROGwfHO=gS5NTpJ|s zPXD-Rc}p_;ceS$eCh`F+m$4R|q`CAqbnm`Ee1Dh|^{{-hMuS+#=Q>eiZ(KKMbGY7% ztBp)%3fmg)?l$(0A;CqiR1ZF@E|9CQ<(`UU=7@g4nb-Y%7}*(WCJaqK)-qzda4#*s zlk(po==vQyh}6@_*plX`O68s`Oj_uDYbb)a(XmPYlsn7n?4)g|M?c_2`2%||b`s8cbbG|S$^<+G{V+}sr zIt!c3f&3t!ybhvFnr2VX(w+0!3fWCo(240PeWG{ZmY_b;o@>dT)Y`m{db3EawUOfs z>M4wf_E8LM*-H^vM&j^1f!kKYjJp5&z|%ypb-W%SVGHL$Q=P z>!~*@eW@95_W}Wt1WPJ@rp%hXy-U024xjEa>iBhE)Y@pRmX(he73p87f$ex3N0`~)L=#at*jpPS5f>3ztat<8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_debug_step_out.png b/app/src/main/res/drawable/ic_debug_step_out.png new file mode 100644 index 0000000000000000000000000000000000000000..468e1975a4db48c788449db60747664527df996b GIT binary patch literal 1652 zcmZ{lYgm&76vxlIy}Kb}0|A8wj5#lbS4baWfFlN|T-*XL6PWWh<{+g;vL(UWL^mK5 z@P-01iXco8L+6cT5QG>^tt_V?FO#K(#KnqfAdULar#_r>e&_N(pU(4~JYifE-NDlV z0O)I03*NRzY}%+~yZ<_L>$d=8-Wowzf=V(}!Mm9p!R(mIx#%hqjd~q$)f5&+#%$Bn z%;dV0&v7*Qo2j43=gWO_tH(ZSi=0s)uPV%E-zb&~(g^OBIr-?KwppRVNO#kLZ>`yV zy~<2esO-tnzRSGKlvm9G0sBl>pSWaacp-a-zTz<`dL@+w6#a8*#&0)L{ui=;^56>$ z!0z1hfmNRff&mWrIJFM5Uwn9&nL#s_+kA<+j}syJtvg;2-Bwv)vy&T?>G~F-y}8JMn|f-k_#g%wF=vtXXw6APPQKw9@_FI%6y z$p~wN1on-*_wI1PEw3*a?M8%Ew%G7HuHaVCw-D`yq6M#p!HKA1e<=<|@!e1xaj4Sg z$AsSfIHX<*j$)V3h6_W$({f~DlR)$NT`0;D3k+hdUI7=|7%7uM;P^Zy zT(}5MS!zL#wp`-o*3_vKyRf96)08#r2;8Ti=pApJ2*sthx~Jj)ldFqUN< zf*pHL=!zv-){!t0H6JO8pLiXSducc@;GUy^%?H^$nA>(6;eWy0LDC9ear73eEj3^T&cP|n5ZJ_c>6c|SUrpZ#;3z%gVTpyFTX-nG+lr7 zOFT5YPK`T4&q`!^kwS>lJDSb5`x%NVQ#VpQ{*${+&Nu7@ij- zd3=4c_U7f@o*AVam3vwcxF^Jnv>>agrw`OQVI;SiS?S=SN!faaC=F{zeOOp7E-w8Q zHVK!tP$+C)m3z&N68&tdr>r$(FnSm^;LD>Q(x^a>CdEhc^yY=JD&c&T)>MQQ_#aN znU~TqU6KQyYL#wTJx1AC*4`~9;SyNq7d0MQa(ltUg}Y5}00n}pS-nKA&jy3Ir+ht} zrZj8`ytbsMDcLN0SGfV9*1hz$J`Ih2`(e$POGjCwgK-YDG6HsaA1=v?`CdvloFz2S zjUp>({WRzOrgw6|_YB9ykoLqc!nO`liYB5uCCgjbGItX= zS>%L?cRgk)!D895_b5@i;laud7ItOjNiXX^k_%G^@0n}8`<0Lg`!|6#5pjb0x1^fC E0To`kH~;_u literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_debug_step_over.png b/app/src/main/res/drawable/ic_debug_step_over.png new file mode 100644 index 0000000000000000000000000000000000000000..3860385c06e21a6b87714fcbdd720fc9bc9b00b5 GIT binary patch literal 2682 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 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_editor.xml b/app/src/main/res/menu/menu_editor.xml index e2456bdd..bc7ae83f 100644 --- a/app/src/main/res/menu/menu_editor.xml +++ b/app/src/main/res/menu/menu_editor.xml @@ -79,6 +79,24 @@ + + + + + + + + + + key_script_dir_path /脚本/ 发生错误: %s + 调试 + 断点 + 启动调试 + 跳出 + 进入 + 单步 + 继续 + 停止 diff --git a/autojs/src/main/java/com/stardust/autojs/AutoJs.java b/autojs/src/main/java/com/stardust/autojs/AutoJs.java index c6fc77c1..2f95ba50 100644 --- a/autojs/src/main/java/com/stardust/autojs/AutoJs.java +++ b/autojs/src/main/java/com/stardust/autojs/AutoJs.java @@ -101,6 +101,7 @@ public abstract class AutoJs { engine.setRuntime(createRuntime()); return engine; }); + LoopBasedJavaScriptEngine.initEngine(); mScriptEngineManager.registerEngine(AutoFileSource.ENGINE, () -> new RootAutomatorEngine(mContext)); } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java index 4cb03c4b..c2b6243a 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/LoopBasedJavaScriptEngine.java @@ -86,5 +86,9 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine { super.init(); } + public static void initEngine(){ + RhinoJavaScriptEngine.initEngine(); + } + } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java index d50a8e4a..dfd1b86b 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java @@ -3,6 +3,7 @@ package com.stardust.autojs.engine; import android.os.Looper; import android.util.Log; +import com.stardust.app.GlobalAppContext; import com.stardust.autojs.BuildConfig; import com.stardust.autojs.rhino.AndroidContextFactory; import com.stardust.autojs.rhino.NativeJavaClassWithPrototype; @@ -33,6 +34,7 @@ import java.io.Reader; import java.net.URI; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; @@ -43,10 +45,13 @@ import java.util.concurrent.ConcurrentHashMap; public class RhinoJavaScriptEngine extends JavaScriptEngine { + public static final String SOURCE_NAME_INIT = ""; + private static final String LOG_TAG = "RhinoJavaScriptEngine"; private static int contextCount = 0; private static StringScriptSource sInitScript; + private static final ConcurrentHashMap sContextEngineMap = new ConcurrentHashMap<>(); private Context mContext; private Scriptable mScriptable; @@ -69,7 +74,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { Reader reader = source.getNonNullScriptReader(); try { reader = preprocess(reader); - return mContext.evaluateReader(mScriptable, reader, "<" + source.getName() + ">", 1, null); + return mContext.evaluateReader(mScriptable, reader, source.toString(), 1, null); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -90,6 +95,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { public synchronized void destroy() { super.destroy(); Log.d(LOG_TAG, "on destroy"); + sContextEngineMap.remove(getContext()); Context.exit(); contextCount--; Log.d(LOG_TAG, "contextCount = " + contextCount); @@ -105,7 +111,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { mThread = Thread.currentThread(); ScriptableObject.putProperty(mScriptable, "__engine__", this); initRequireBuilder(mContext, mScriptable); - mContext.evaluateString(mScriptable, getInitScript().getScript(), "", 1, null); + mContext.evaluateString(mScriptable, getInitScript().getScript(), SOURCE_NAME_INIT, 1, null); } private JavaScriptSource getInitScript() { @@ -148,12 +154,10 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { } public Context createContext() { - if (!ContextFactory.hasExplicitGlobal()) { - ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mAndroidContext.getCacheDir(), "classes"))); - } Context context = new RhinoAndroidHelper(mAndroidContext).enterContext(); contextCount++; setupContext(context); + sContextEngineMap.put(context, this); return context; } @@ -164,6 +168,17 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { context.setWrapFactory(new WrapFactory()); } + public static void initEngine() { + if (!ContextFactory.hasExplicitGlobal()) { + android.content.Context context = GlobalAppContext.get(); + ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(context.getCacheDir(), "classes"))); + } + } + + public static RhinoJavaScriptEngine getEngineOfContext(Context context) { + return sContextEngineMap.get(context); + } + private class WrapFactory extends org.mozilla.javascript.WrapFactory { diff --git a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java index ec08c4ac..f0a2c09a 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.java @@ -3,7 +3,6 @@ package com.stardust.autojs.engine; import android.support.annotation.CallSuper; import com.stardust.autojs.execution.ScriptExecution; -import com.stardust.autojs.runtime.exception.ScriptException; import com.stardust.autojs.script.ScriptSource; import java.util.Map; @@ -14,7 +13,7 @@ import java.util.concurrent.atomic.AtomicInteger; * Created by Stardust on 2017/4/2. *

*

- * A ScriptEngine is created by {@link ScriptEngineManager#createEngine(String)} ()}, and then can be + * A ScriptEngine is created by {@link ScriptEngineManager#createEngine(String, int)} ()}, and then can be * used to execute script with {@link ScriptEngine#execute(ScriptSource)} in the **same** thread. * When the execution finish successfully, the engine should be destroy in the thread that created it. *

diff --git a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineManager.java b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineManager.java index c73a8cda..91507838 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineManager.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngineManager.java @@ -4,6 +4,7 @@ import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.stardust.autojs.execution.ScriptExecution; import com.stardust.autojs.script.ScriptSource; import com.stardust.util.Supplier; @@ -96,37 +97,34 @@ public class ScriptEngineManager { @Nullable - public ScriptEngine createEngine(String name) { + public ScriptEngine createEngine(String name, int id) { Supplier s = mEngineSuppliers.get(name); if (s == null) { return null; } ScriptEngine engine = s.get(); + engine.setId(id); putProperties(engine); addEngine(engine); return engine; } @Nullable - public ScriptEngine createEngineOfSource(ScriptSource source) { - return createEngine(source.getEngineName()); + public ScriptEngine createEngineOfSource(ScriptSource source, int id) { + return createEngine(source.getEngineName(), id); } - @NonNull - public ScriptEngine createEngineByNameOrThrow(String name) { - ScriptEngine engine = createEngine(name); + public ScriptEngine createEngineOfSourceOrThrow(ScriptSource source, int id) { + ScriptEngine engine = createEngineOfSource(source, id); if (engine == null) - throw new ScriptEngineFactory.EngineNotFoundException("name: " + name); + throw new ScriptEngineFactory.EngineNotFoundException("source: " + source.toString()); return engine; } @NonNull public ScriptEngine createEngineOfSourceOrThrow(ScriptSource source) { - ScriptEngine engine = createEngineOfSource(source); - if (engine == null) - throw new ScriptEngineFactory.EngineNotFoundException("source: " + source.toString()); - return engine; + return createEngineOfSourceOrThrow(source, ScriptExecution.NO_ID); } public void registerEngine(String name, Supplier supplier) { diff --git a/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java b/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java index f1ac849c..10e7b4b7 100644 --- a/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java +++ b/autojs/src/main/java/com/stardust/autojs/execution/RunnableScriptExecution.java @@ -28,8 +28,7 @@ public class RunnableScriptExecution extends ScriptExecution.AbstractScriptExecu } public Object execute() { - mScriptEngine = mScriptEngineManager.createEngineOfSourceOrThrow(getSource()); - mScriptEngine.setId(getId()); + mScriptEngine = mScriptEngineManager.createEngineOfSourceOrThrow(getSource(), getId()); return execute(mScriptEngine); } diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java b/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java new file mode 100644 index 00000000..3dd25922 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java @@ -0,0 +1,50 @@ +package com.stardust.autojs.rhino.debug; +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +import com.stardust.autojs.engine.RhinoJavaScriptEngine; +import com.stardust.autojs.engine.ScriptEngine; + +import org.mozilla.javascript.Context; + +/** + * Interface for communication between the debugger and its GUI. This + * should be implemented by the GUI. + */ +public interface DebugCallback { + + /** + * Called when the source text of some script has been changed. + */ + void updateSourceText(Dim.SourceInfo sourceInfo); + + /** + * Called when the interrupt loop has been entered. + */ + void enterInterrupt(Dim.StackFrame lastFrame, + String threadTitle, + String alertMessage); + + /** + * Returns whether the current thread is the GUI's event thread. + * This information is required to avoid blocking the event thread + * from the debugger. + */ + boolean isGuiEventThread(); + + /** + * Processes the next GUI event. This manual pumping of GUI events + * is necessary when the GUI event thread itself has been stopped. + */ + void dispatchNextGuiEvent() throws InterruptedException; + + /** + * + * Returns whether the debugger should attach to this engine or not. + */ + boolean shouldAttachDebugger(RhinoJavaScriptEngine engine); +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java b/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java new file mode 100644 index 00000000..58b757b6 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java @@ -0,0 +1,1558 @@ +package com.stardust.autojs.rhino.debug; + +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import com.stardust.autojs.ScriptEngineService; +import com.stardust.autojs.engine.RhinoJavaScriptEngine; +import com.stardust.autojs.engine.ScriptEngine; +import com.stardust.autojs.engine.ScriptEngineManager; + +import org.mozilla.javascript.*; +import org.mozilla.javascript.debug.*; +import org.mozilla.javascript.tools.debugger.*; + +import java.util.*; +import java.io.*; +import java.net.URL; + +/** + * Dim or Debugger Implementation for Rhino. + */ +public class Dim { + + // Constants for instructing the debugger what action to perform + // to end interruption. Used by 'returnValue'. + public static final int STEP_OVER = 0; + public static final int STEP_INTO = 1; + public static final int STEP_OUT = 2; + public static final int GO = 3; + public static final int BREAK = 4; + public static final int EXIT = 5; + + // Constants for the DimIProxy interface implementation class. + private static final int IPROXY_DEBUG = 0; + private static final int IPROXY_LISTEN = 1; + private static final int IPROXY_COMPILE_SCRIPT = 2; + private static final int IPROXY_EVAL_SCRIPT = 3; + private static final int IPROXY_STRING_IS_COMPILABLE = 4; + private static final int IPROXY_OBJECT_TO_STRING = 5; + private static final int IPROXY_OBJECT_PROPERTY = 6; + private static final int IPROXY_OBJECT_IDS = 7; + + /** + * Interface to the debugger GUI. + */ + private DebugCallback callback; + + /** + * Whether the debugger should break. + */ + private boolean breakFlag; + + /** + * The ScopeProvider object that provides the scope in which to + * evaluate script. + */ + private ScopeProvider scopeProvider; + + /** + * The SourceProvider object that provides the source of evaluated scripts. + */ + private SourceProvider sourceProvider; + + /** + * The index of the current stack frame. + */ + private int frameIndex = -1; + + /** + * Information about the current stack at the point of interruption. + */ + private volatile ContextData interruptedContextData; + + /** + * The ContextFactory to listen to for debugging information. + */ + private ContextFactory contextFactory; + + private ScriptEngineService scriptEngineService; + + /** + * Synchronization object used to allow script evaluations to + * happen when a thread is resumed. + */ + private Object monitor = new Object(); + + /** + * Synchronization object used to wait for valid + * {@link #interruptedContextData}. + */ + private Object eventThreadMonitor = new Object(); + + /** + * The action to perform to end the interruption loop. + */ + private volatile int returnValue = -1; + + /** + * Whether the debugger is inside the interruption loop. + */ + private boolean insideInterruptLoop; + + /** + * The requested script string to be evaluated when the thread + * has been resumed. + */ + private String evalRequest; + + /** + * The stack frame in which to evaluate {@link #evalRequest}. + */ + private StackFrame evalFrame; + + /** + * The result of evaluating {@link #evalRequest}. + */ + private String evalResult; + + /** + * Whether the debugger should break when a script exception is thrown. + */ + private boolean breakOnExceptions; + + /** + * Whether the debugger should break when a script function is entered. + */ + private boolean breakOnEnter; + + /** + * Whether the debugger should break when a script function is returned + * from. + */ + private boolean breakOnReturn; + + /** + * Table mapping URLs to information about the script source. + */ + private final Map urlToSourceInfo = + Collections.synchronizedMap(new HashMap()); + + /** + * Table mapping function names to information about the function. + */ + private final Map functionNames = + Collections.synchronizedMap(new HashMap()); + + /** + * Table mapping functions to information about the function. + */ + private final Map functionToSource = + Collections.synchronizedMap(new HashMap()); + + /** + * ContextFactory.Listener instance attached to {@link #contextFactory}. + */ + private DimIProxy listener; + + /** + * Sets the GuiCallback object to use. + */ + public void setGuiCallback(DebugCallback callback) { + this.callback = callback; + } + + /** + * Tells the debugger to break at the next opportunity. + */ + public void setBreak() { + this.breakFlag = true; + } + + /** + * Sets the ScopeProvider to be used. + */ + public void setScopeProvider(ScopeProvider scopeProvider) { + this.scopeProvider = scopeProvider; + } + + /** + * Sets the ScopeProvider to be used. + */ + public void setSourceProvider(final SourceProvider sourceProvider) { + this.sourceProvider = sourceProvider; + } + + /** + * Switches context to the stack frame with the given index. + */ + public void contextSwitch(int frameIndex) { + this.frameIndex = frameIndex; + } + + /** + * Sets whether the debugger should break on exceptions. + */ + public void setBreakOnExceptions(boolean breakOnExceptions) { + this.breakOnExceptions = breakOnExceptions; + } + + /** + * Sets whether the debugger should break on function entering. + */ + public void setBreakOnEnter(boolean breakOnEnter) { + this.breakOnEnter = breakOnEnter; + } + + /** + * Sets whether the debugger should break on function return. + */ + public void setBreakOnReturn(boolean breakOnReturn) { + this.breakOnReturn = breakOnReturn; + } + + /** + * Attaches the debugger to the given ContextFactory. + */ + public void attachTo(ScriptEngineService scriptEngineService, ContextFactory factory) { + detach(); + this.contextFactory = factory; + this.scriptEngineService = scriptEngineService; + this.listener = new DimIProxy(this, IPROXY_LISTEN); + scriptEngineService.registerEngineLifecycleCallback(this.listener); + } + + /** + * Detaches the debugger from the current ContextFactory. + */ + public void detach() { + if (listener != null) { + scriptEngineService.unregisterEngineLifecycleCallback(listener); + contextFactory = null; + scriptEngineService = null; + listener = null; + } + } + + /** + * Releases resources associated with this debugger. + */ + public void dispose() { + detach(); + } + + /** + * Returns the FunctionSource object for the given script or function. + */ + private FunctionSource getFunctionSource(DebuggableScript fnOrScript) { + FunctionSource fsource = functionSource(fnOrScript); + if (fsource == null) { + String url = getNormalizedUrl(fnOrScript); + SourceInfo si = sourceInfo(url); + if (si == null) { + if (!fnOrScript.isGeneratedScript()) { + // Not eval or Function, try to load it from URL + String source = loadSource(url); + if (source != null) { + DebuggableScript top = fnOrScript; + for (; ; ) { + DebuggableScript parent = top.getParent(); + if (parent == null) { + break; + } + top = parent; + } + registerTopScript(top, source); + fsource = functionSource(fnOrScript); + } + } + } + } + return fsource; + } + + /** + * Loads the script at the given URL. + */ + private String loadSource(String sourceUrl) { + String source = null; + int hash = sourceUrl.indexOf('#'); + if (hash >= 0) { + sourceUrl = sourceUrl.substring(0, hash); + } + try { + InputStream is; + openStream: + { + if (sourceUrl.indexOf(':') < 0) { + // Can be a file name + try { + if (sourceUrl.startsWith("~/")) { + String home = SecurityUtilities.getSystemProperty("user.home"); + if (home != null) { + String pathFromHome = sourceUrl.substring(2); + File f = new File(new File(home), pathFromHome); + if (f.exists()) { + is = new FileInputStream(f); + break openStream; + } + } + } + File f = new File(sourceUrl); + if (f.exists()) { + is = new FileInputStream(f); + break openStream; + } + } catch (SecurityException ex) { + } + // No existing file, assume missed http:// + if (sourceUrl.startsWith("//")) { + sourceUrl = "http:" + sourceUrl; + } else if (sourceUrl.startsWith("/")) { + sourceUrl = "http://127.0.0.1" + sourceUrl; + } else { + sourceUrl = "http://" + sourceUrl; + } + } + + is = (new URL(sourceUrl)).openStream(); + } + + try { + source = Kit.readReader(new InputStreamReader(is)); + } finally { + is.close(); + } + } catch (IOException ex) { + System.err.println + ("Failed to load source from " + sourceUrl + ": " + ex); + } + return source; + } + + /** + * Registers the given script as a top-level script in the debugger. + */ + private void registerTopScript(DebuggableScript topScript, String source) { + if (!topScript.isTopLevel()) { + throw new IllegalArgumentException(); + } + String url = getNormalizedUrl(topScript); + DebuggableScript[] functions = getAllFunctions(topScript); + if (sourceProvider != null) { + final String providedSource = sourceProvider.getSource(topScript); + if (providedSource != null) { + source = providedSource; + } + } + + final SourceInfo sourceInfo = new SourceInfo(source, functions, url); + + synchronized (urlToSourceInfo) { + SourceInfo old = urlToSourceInfo.get(url); + if (old != null) { + sourceInfo.copyBreakpointsFrom(old); + } + urlToSourceInfo.put(url, sourceInfo); + for (int i = 0; i != sourceInfo.functionSourcesTop(); ++i) { + FunctionSource fsource = sourceInfo.functionSource(i); + String name = fsource.name(); + if (name.length() != 0) { + functionNames.put(name, fsource); + } + } + } + + synchronized (functionToSource) { + for (int i = 0; i != functions.length; ++i) { + FunctionSource fsource = sourceInfo.functionSource(i); + functionToSource.put(functions[i], fsource); + } + } + + callback.updateSourceText(sourceInfo); + } + + /** + * Returns the FunctionSource object for the given function or script. + */ + private FunctionSource functionSource(DebuggableScript fnOrScript) { + return functionToSource.get(fnOrScript); + } + + /** + * Returns an array of all function names. + */ + public String[] functionNames() { + synchronized (urlToSourceInfo) { + return functionNames.keySet().toArray(new String[functionNames.size()]); + } + } + + /** + * Returns the FunctionSource object for the function with the given name. + */ + public FunctionSource functionSourceByName(String functionName) { + return functionNames.get(functionName); + } + + /** + * Returns the SourceInfo object for the given URL. + */ + public SourceInfo sourceInfo(String url) { + return urlToSourceInfo.get(url); + } + + /** + * Returns the source URL for the given script or function. + */ + private String getNormalizedUrl(DebuggableScript fnOrScript) { + String url = fnOrScript.getSourceName(); + if (url == null) { + url = ""; + } else { + // Not to produce window for eval from different lines, + // strip line numbers, i.e. replace all #[0-9]+\(eval\) by + // (eval) + // Option: similar teatment for Function? + char evalSeparator = '#'; + StringBuilder sb = null; + int urlLength = url.length(); + int cursor = 0; + for (; ; ) { + int searchStart = url.indexOf(evalSeparator, cursor); + if (searchStart < 0) { + break; + } + String replace = null; + int i = searchStart + 1; + while (i != urlLength) { + int c = url.charAt(i); + if (!('0' <= c && c <= '9')) { + break; + } + ++i; + } + if (i != searchStart + 1) { + // i points after #[0-9]+ + if ("(eval)".regionMatches(0, url, i, 6)) { + cursor = i + 6; + replace = "(eval)"; + } + } + if (replace == null) { + break; + } + if (sb == null) { + sb = new StringBuilder(); + sb.append(url.substring(0, searchStart)); + } + sb.append(replace); + } + if (sb != null) { + if (cursor != urlLength) { + sb.append(url.substring(cursor)); + } + url = sb.toString(); + } + } + return url; + } + + /** + * Returns an array of all functions in the given script. + */ + private static DebuggableScript[] getAllFunctions + (DebuggableScript function) { + ObjArray functions = new ObjArray(); + collectFunctions_r(function, functions); + DebuggableScript[] result = new DebuggableScript[functions.size()]; + functions.toArray(result); + return result; + } + + /** + * Helper function for {@link #getAllFunctions(DebuggableScript)}. + */ + private static void collectFunctions_r(DebuggableScript function, + ObjArray array) { + array.add(function); + for (int i = 0; i != function.getFunctionCount(); ++i) { + collectFunctions_r(function.getFunction(i), array); + } + } + + /** + * Clears all breakpoints. + */ + public void clearAllBreakpoints() { + for (SourceInfo si : urlToSourceInfo.values()) { + si.removeAllBreakpoints(); + } + } + + /** + * Called when a breakpoint has been hit. + */ + private void handleBreakpointHit(StackFrame frame, Context cx) { + breakFlag = false; + interrupted(cx, frame, null); + } + + /** + * Called when a script exception has been thrown. + */ + private void handleExceptionThrown(Context cx, Throwable ex, + StackFrame frame) { + if (breakOnExceptions) { + ContextData cd = frame.contextData(); + if (cd.lastProcessedException != ex) { + interrupted(cx, frame, ex); + cd.lastProcessedException = ex; + } + } + } + + /** + * Returns the current ContextData object. + */ + public ContextData currentContextData() { + return interruptedContextData; + } + + /** + * Sets the action to perform to end interruption. + */ + public void setReturnValue(int returnValue) { + synchronized (monitor) { + this.returnValue = returnValue; + monitor.notify(); + } + } + + /** + * Resumes execution of script. + */ + public void go() { + synchronized (monitor) { + this.returnValue = GO; + monitor.notifyAll(); + } + } + + /** + * Evaluates the given script. + */ + public String eval(String expr) { + String result = "undefined"; + if (expr == null) { + return result; + } + ContextData contextData = currentContextData(); + if (contextData == null || frameIndex >= contextData.frameCount()) { + return result; + } + StackFrame frame = contextData.getFrame(frameIndex); + if (contextData.eventThreadFlag) { + Context cx = Context.getCurrentContext(); + result = do_eval(cx, frame, expr); + } else { + synchronized (monitor) { + if (insideInterruptLoop) { + evalRequest = expr; + evalFrame = frame; + monitor.notify(); + do { + try { + monitor.wait(); + } catch (InterruptedException exc) { + Thread.currentThread().interrupt(); + break; + } + } while (evalRequest != null); + result = evalResult; + } + } + } + return result; + } + + /** + * Compiles the given script. + */ + public void compileScript(String url, String text) { + DimIProxy action = new DimIProxy(this, IPROXY_COMPILE_SCRIPT); + action.url = url; + action.text = text; + action.withContext(); + } + + /** + * Evaluates the given script. + */ + public void evalScript(final String url, final String text) { + DimIProxy action = new DimIProxy(this, IPROXY_EVAL_SCRIPT); + action.url = url; + action.text = text; + action.withContext(); + } + + /** + * Converts the given script object to a string. + */ + public String objectToString(Object object) { + DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_TO_STRING); + action.object = object; + action.withContext(); + return action.stringResult; + } + + /** + * Returns whether the given string is syntactically valid script. + */ + public boolean stringIsCompilableUnit(String str) { + DimIProxy action = new DimIProxy(this, IPROXY_STRING_IS_COMPILABLE); + action.text = str; + action.withContext(); + return action.booleanResult; + } + + /** + * Returns the value of a property on the given script object. + */ + public Object getObjectProperty(Object object, Object id) { + DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_PROPERTY); + action.object = object; + action.id = id; + action.withContext(); + return action.objectResult; + } + + /** + * Returns an array of the property names on the given script object. + */ + public Object[] getObjectIds(Object object) { + DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_IDS); + action.object = object; + action.withContext(); + return action.objectArrayResult; + } + + /** + * Returns the value of a property on the given script object. + */ + private Object getObjectPropertyImpl(Context cx, Object object, + Object id) { + Scriptable scriptable = (Scriptable) object; + Object result; + if (id instanceof String) { + String name = (String) id; + if (name.equals("this")) { + result = scriptable; + } else if (name.equals("__proto__")) { + result = scriptable.getPrototype(); + } else if (name.equals("__parent__")) { + result = scriptable.getParentScope(); + } else { + result = ScriptableObject.getProperty(scriptable, name); + if (result == ScriptableObject.NOT_FOUND) { + result = Undefined.instance; + } + } + } else { + int index = ((Integer) id).intValue(); + result = ScriptableObject.getProperty(scriptable, index); + if (result == ScriptableObject.NOT_FOUND) { + result = Undefined.instance; + } + } + return result; + } + + /** + * Returns an array of the property names on the given script object. + */ + private Object[] getObjectIdsImpl(Context cx, Object object) { + if (!(object instanceof Scriptable) || object == Undefined.instance) { + return Context.emptyArgs; + } + + Object[] ids; + Scriptable scriptable = (Scriptable) object; + if (scriptable instanceof DebuggableObject) { + ids = ((DebuggableObject) scriptable).getAllIds(); + } else { + ids = scriptable.getIds(); + } + + Scriptable proto = scriptable.getPrototype(); + Scriptable parent = scriptable.getParentScope(); + int extra = 0; + if (proto != null) { + ++extra; + } + if (parent != null) { + ++extra; + } + if (extra != 0) { + Object[] tmp = new Object[extra + ids.length]; + System.arraycopy(ids, 0, tmp, extra, ids.length); + ids = tmp; + extra = 0; + if (proto != null) { + ids[extra++] = "__proto__"; + } + if (parent != null) { + ids[extra++] = "__parent__"; + } + } + + return ids; + } + + /** + * Interrupts script execution. + */ + private void interrupted(Context cx, final StackFrame frame, + Throwable scriptException) { + ContextData contextData = frame.contextData(); + boolean eventThreadFlag = callback.isGuiEventThread(); + contextData.eventThreadFlag = eventThreadFlag; + + boolean recursiveEventThreadCall = false; + + interruptedCheck: + synchronized (eventThreadMonitor) { + if (eventThreadFlag) { + if (interruptedContextData != null) { + recursiveEventThreadCall = true; + break interruptedCheck; + } + } else { + while (interruptedContextData != null) { + try { + eventThreadMonitor.wait(); + } catch (InterruptedException exc) { + return; + } + } + } + interruptedContextData = contextData; + } + + if (recursiveEventThreadCall) { + // XXX: For now the following is commented out as on Linux + // too deep recursion of dispatchNextGuiEvent causes GUI lockout. + // Note: it can make GUI unresponsive if long-running script + // will be called on GUI thread while processing another interrupt + if (false) { + // Run event dispatch until gui sets a flag to exit the initial + // call to interrupted. + while (this.returnValue == -1) { + try { + callback.dispatchNextGuiEvent(); + } catch (InterruptedException exc) { + } + } + } + return; + } + + if (interruptedContextData == null) Kit.codeBug(); + + try { + do { + int frameCount = contextData.frameCount(); + this.frameIndex = frameCount - 1; + + final String threadTitle = Thread.currentThread().toString(); + final String alertMessage; + if (scriptException == null) { + alertMessage = null; + } else { + alertMessage = scriptException.toString(); + } + + int returnValue = -1; + if (!eventThreadFlag) { + synchronized (monitor) { + if (insideInterruptLoop) Kit.codeBug(); + this.insideInterruptLoop = true; + this.evalRequest = null; + this.returnValue = -1; + callback.enterInterrupt(frame, threadTitle, + alertMessage); + try { + for (; ; ) { + try { + monitor.wait(); + } catch (InterruptedException exc) { + Thread.currentThread().interrupt(); + break; + } + if (evalRequest != null) { + this.evalResult = null; + try { + evalResult = do_eval(cx, evalFrame, + evalRequest); + } finally { + evalRequest = null; + evalFrame = null; + monitor.notify(); + } + continue; + } + if (this.returnValue != -1) { + returnValue = this.returnValue; + break; + } + } + } finally { + insideInterruptLoop = false; + } + } + } else { + this.returnValue = -1; + callback.enterInterrupt(frame, threadTitle, alertMessage); + while (this.returnValue == -1) { + try { + callback.dispatchNextGuiEvent(); + } catch (InterruptedException exc) { + } + } + returnValue = this.returnValue; + } + switch (returnValue) { + case STEP_OVER: + contextData.breakNextLine = true; + contextData.stopAtFrameDepth = contextData.frameCount(); + break; + case STEP_INTO: + contextData.breakNextLine = true; + contextData.stopAtFrameDepth = -1; + break; + case STEP_OUT: + if (contextData.frameCount() > 1) { + contextData.breakNextLine = true; + contextData.stopAtFrameDepth + = contextData.frameCount() - 1; + } + break; + } + } while (false); + } finally { + synchronized (eventThreadMonitor) { + interruptedContextData = null; + eventThreadMonitor.notifyAll(); + } + } + + } + + /** + * Evaluates script in the given stack frame. + */ + private static String do_eval(Context cx, StackFrame frame, String expr) { + String resultString; + Debugger saved_debugger = cx.getDebugger(); + Object saved_data = cx.getDebuggerContextData(); + int saved_level = cx.getOptimizationLevel(); + + cx.setDebugger(null, null); + cx.setOptimizationLevel(-1); + cx.setGeneratingDebug(false); + try { + Callable script = (Callable) cx.compileString(expr, "", 0, null); + Object result = script.call(cx, frame.scope, frame.thisObj, + ScriptRuntime.emptyArgs); + if (result == Undefined.instance) { + resultString = ""; + } else { + resultString = ScriptRuntime.toString(result); + } + } catch (Exception exc) { + resultString = exc.getMessage(); + } finally { + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(saved_level); + cx.setDebugger(saved_debugger, saved_data); + } + if (resultString == null) { + resultString = "null"; + } + return resultString; + } + + /** + * Proxy class to implement debug interfaces without bloat of class + * files. + */ + private class DimIProxy + implements ContextAction, ScriptEngineManager.EngineLifecycleCallback, Debugger { + + /** + * The debugger. + */ + private Dim dim; + + /** + * The interface implementation type. One of the IPROXY_* constants + * defined in {@link Dim}. + */ + private int type; + + /** + * The URL origin of the script to compile or evaluate. + */ + private String url; + + /** + * The text of the script to compile, evaluate or test for compilation. + */ + private String text; + + /** + * The object to convert, get a property from or enumerate. + */ + private Object object; + + /** + * The property to look up in {@link #object}. + */ + private Object id; + + /** + * The boolean result of the action. + */ + private boolean booleanResult; + + /** + * The String result of the action. + */ + private String stringResult; + + /** + * The Object result of the action. + */ + private Object objectResult; + + /** + * The Object[] result of the action. + */ + private Object[] objectArrayResult; + + /** + * Creates a new DimIProxy. + */ + private DimIProxy(Dim dim, int type) { + this.dim = dim; + this.type = type; + } + + // ContextAction + + /** + * Performs the action given by {@link #type}. + */ + public Object run(Context cx) { + switch (type) { + case IPROXY_COMPILE_SCRIPT: + cx.compileString(text, url, 1, null); + break; + + case IPROXY_EVAL_SCRIPT: { + Scriptable scope = null; + if (dim.scopeProvider != null) { + scope = dim.scopeProvider.getScope(); + } + if (scope == null) { + scope = new ImporterTopLevel(cx); + } + cx.evaluateString(scope, text, url, 1, null); + } + break; + + case IPROXY_STRING_IS_COMPILABLE: + booleanResult = cx.stringIsCompilableUnit(text); + break; + + case IPROXY_OBJECT_TO_STRING: + if (object == Undefined.instance) { + stringResult = "undefined"; + } else if (object == null) { + stringResult = "null"; + } else if (object instanceof NativeCall) { + stringResult = "[object Call]"; + } else { + stringResult = Context.toString(object); + } + break; + + case IPROXY_OBJECT_PROPERTY: + objectResult = dim.getObjectPropertyImpl(cx, object, id); + break; + + case IPROXY_OBJECT_IDS: + objectArrayResult = dim.getObjectIdsImpl(cx, object); + break; + + default: + throw Kit.codeBug(); + } + return null; + } + + /** + * Performs the action given by {@link #type} with the attached + * {@link ContextFactory}. + */ + private void withContext() { + dim.contextFactory.call(this); + } + + @Override + public void onEngineCreate(ScriptEngine engine) { + if (type != IPROXY_LISTEN) Kit.codeBug(); + if (!(engine instanceof RhinoJavaScriptEngine) || + !callback.shouldAttachDebugger((RhinoJavaScriptEngine) engine)) { + return; + } + + Context cx = ((RhinoJavaScriptEngine) engine).getContext(); + ContextData contextData = new ContextData(); + Debugger debugger = new DimIProxy(dim, IPROXY_DEBUG); + cx.setDebugger(debugger, contextData); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + } + + @Override + public void onEngineRemove(ScriptEngine engine) { + if (type != IPROXY_LISTEN) Kit.codeBug(); + } + + // Debugger + + /** + * Returns a StackFrame for the given function or script. + */ + public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) { + if (type != IPROXY_DEBUG) Kit.codeBug(); + + FunctionSource item = dim.getFunctionSource(fnOrScript); + if (item == null) { + // Can not debug if source is not available + return null; + } + return new StackFrame(cx, dim, item); + } + + /** + * Called when compilation is finished. + */ + public void handleCompilationDone(Context cx, + DebuggableScript fnOrScript, + String source) { + if (type != IPROXY_DEBUG) Kit.codeBug(); + + if (!fnOrScript.isTopLevel()) { + return; + } + dim.registerTopScript(fnOrScript, source); + } + } + + /** + * Class to store information about a stack. + */ + public static class ContextData { + + /** + * The stack frames. + */ + private ObjArray frameStack = new ObjArray(); + + /** + * Whether the debugger should break at the next line in this context. + */ + private boolean breakNextLine; + + /** + * The frame depth the debugger should stop at. Used to implement + * "step over" and "step out". + */ + private int stopAtFrameDepth = -1; + + /** + * Whether this context is in the event thread. + */ + private boolean eventThreadFlag; + + /** + * The last exception that was processed. + */ + private Throwable lastProcessedException; + + /** + * Returns the ContextData for the given Context. + */ + public static ContextData get(Context cx) { + return (ContextData) cx.getDebuggerContextData(); + } + + /** + * Returns the number of stack frames. + */ + public int frameCount() { + return frameStack.size(); + } + + /** + * Returns the stack frame with the given index. + */ + public StackFrame getFrame(int frameNumber) { + int num = frameStack.size() - frameNumber - 1; + return (StackFrame) frameStack.get(num); + } + + /** + * Pushes a stack frame on to the stack. + */ + private void pushFrame(StackFrame frame) { + frameStack.push(frame); + } + + /** + * Pops a stack frame from the stack. + */ + private void popFrame() { + frameStack.pop(); + } + } + + /** + * Object to represent one stack frame. + */ + public static class StackFrame implements DebugFrame { + + /** + * The debugger. + */ + private Dim dim; + + /** + * The ContextData for the Context being debugged. + */ + private ContextData contextData; + + /** + * The scope. + */ + private Scriptable scope; + + /** + * The 'this' object. + */ + private Scriptable thisObj; + + /** + * Information about the function. + */ + private FunctionSource fsource; + + /** + * Array of breakpoint state for each source line. + */ + private boolean[] breakpoints; + + /** + * Current line number. + */ + private int lineNumber; + + /** + * Creates a new StackFrame. + */ + private StackFrame(Context cx, Dim dim, FunctionSource fsource) { + this.dim = dim; + this.contextData = ContextData.get(cx); + this.fsource = fsource; + this.breakpoints = fsource.sourceInfo().breakpoints; + this.lineNumber = fsource.firstLine(); + } + + /** + * Called when the stack frame is entered. + */ + public void onEnter(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + contextData.pushFrame(this); + this.scope = scope; + this.thisObj = thisObj; + if (dim.breakOnEnter) { + dim.handleBreakpointHit(this, cx); + } + } + + /** + * Called when the current position has changed. + */ + public void onLineChange(Context cx, int lineno) { + this.lineNumber = lineno; + + if (!breakpoints[lineno] && !dim.breakFlag) { + boolean lineBreak = contextData.breakNextLine; + if (lineBreak && contextData.stopAtFrameDepth >= 0) { + lineBreak = (contextData.frameCount() + <= contextData.stopAtFrameDepth); + } + if (!lineBreak) { + return; + } + contextData.stopAtFrameDepth = -1; + contextData.breakNextLine = false; + } + + dim.handleBreakpointHit(this, cx); + } + + /** + * Called when an exception has been thrown. + */ + public void onExceptionThrown(Context cx, Throwable exception) { + dim.handleExceptionThrown(cx, exception, this); + } + + /** + * Called when the stack frame has been left. + */ + public void onExit(Context cx, boolean byThrow, + Object resultOrException) { + if (dim.breakOnReturn && !byThrow) { + dim.handleBreakpointHit(this, cx); + } + contextData.popFrame(); + } + + /** + * Called when a 'debugger' statement is executed. + */ + public void onDebuggerStatement(Context cx) { + dim.handleBreakpointHit(this, cx); + } + + /** + * Returns the SourceInfo object for the function. + */ + public SourceInfo sourceInfo() { + return fsource.sourceInfo(); + } + + /** + * Returns the ContextData object for the Context. + */ + public ContextData contextData() { + return contextData; + } + + /** + * Returns the scope object for this frame. + */ + public Object scope() { + return scope; + } + + /** + * Returns the 'this' object for this frame. + */ + public Object thisObj() { + return thisObj; + } + + /** + * Returns the source URL. + */ + public String getUrl() { + return fsource.sourceInfo().url(); + } + + /** + * Returns the current line number. + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * Returns the current function name. + */ + public String getFunctionName() { + return fsource.name(); + } + } + + /** + * Class to store information about a function. + */ + public static class FunctionSource { + + /** + * Information about the source of the function. + */ + private SourceInfo sourceInfo; + + /** + * Line number of the first line of the function. + */ + private int firstLine; + + /** + * The function name. + */ + private String name; + + /** + * Creates a new FunctionSource. + */ + private FunctionSource(SourceInfo sourceInfo, int firstLine, + String name) { + if (name == null) throw new IllegalArgumentException(); + this.sourceInfo = sourceInfo; + this.firstLine = firstLine; + this.name = name; + } + + /** + * Returns the SourceInfo object that describes the source of the + * function. + */ + public SourceInfo sourceInfo() { + return sourceInfo; + } + + /** + * Returns the line number of the first line of the function. + */ + public int firstLine() { + return firstLine; + } + + /** + * Returns the name of the function. + */ + public String name() { + return name; + } + } + + /** + * Class to store information about a script source. + */ + public static class SourceInfo { + + /** + * An empty array of booleans. + */ + private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + + /** + * The script. + */ + private String source; + + /** + * The URL of the script. + */ + private String url; + + /** + * Array indicating which lines can have breakpoints set. + */ + private boolean[] breakableLines; + + /** + * Array indicating whether a breakpoint is set on the line. + */ + private boolean[] breakpoints; + + /** + * Array of FunctionSource objects for the functions in the script. + */ + private FunctionSource[] functionSources; + + /** + * Creates a new SourceInfo object. + */ + private SourceInfo(String source, DebuggableScript[] functions, + String normilizedUrl) { + this.source = source; + this.url = normilizedUrl; + + int N = functions.length; + int[][] lineArrays = new int[N][]; + for (int i = 0; i != N; ++i) { + lineArrays[i] = functions[i].getLineNumbers(); + } + + int minAll = 0, maxAll = -1; + int[] firstLines = new int[N]; + for (int i = 0; i != N; ++i) { + int[] lines = lineArrays[i]; + if (lines == null || lines.length == 0) { + firstLines[i] = -1; + } else { + int min, max; + min = max = lines[0]; + for (int j = 1; j != lines.length; ++j) { + int line = lines[j]; + if (line < min) { + min = line; + } else if (line > max) { + max = line; + } + } + firstLines[i] = min; + if (minAll > maxAll) { + minAll = min; + maxAll = max; + } else { + if (min < minAll) { + minAll = min; + } + if (max > maxAll) { + maxAll = max; + } + } + } + } + + if (minAll > maxAll) { + // No line information + this.breakableLines = EMPTY_BOOLEAN_ARRAY; + this.breakpoints = EMPTY_BOOLEAN_ARRAY; + } else { + if (minAll < 0) { + // Line numbers can not be negative + throw new IllegalStateException(String.valueOf(minAll)); + } + int linesTop = maxAll + 1; + this.breakableLines = new boolean[linesTop]; + this.breakpoints = new boolean[linesTop]; + for (int i = 0; i != N; ++i) { + int[] lines = lineArrays[i]; + if (lines != null && lines.length != 0) { + for (int j = 0; j != lines.length; ++j) { + int line = lines[j]; + this.breakableLines[line] = true; + } + } + } + } + this.functionSources = new FunctionSource[N]; + for (int i = 0; i != N; ++i) { + String name = functions[i].getFunctionName(); + if (name == null) { + name = ""; + } + this.functionSources[i] + = new FunctionSource(this, firstLines[i], name); + } + } + + /** + * Returns the source text. + */ + public String source() { + return this.source; + } + + /** + * Returns the script's origin URL. + */ + public String url() { + return this.url; + } + + /** + * Returns the number of FunctionSource objects stored in this object. + */ + public int functionSourcesTop() { + return functionSources.length; + } + + /** + * Returns the FunctionSource object with the given index. + */ + public FunctionSource functionSource(int i) { + return functionSources[i]; + } + + /** + * Copies the breakpoints from the given SourceInfo object into this + * one. + */ + private void copyBreakpointsFrom(SourceInfo old) { + int end = old.breakpoints.length; + if (end > this.breakpoints.length) { + end = this.breakpoints.length; + } + for (int line = 0; line != end; ++line) { + if (old.breakpoints[line]) { + this.breakpoints[line] = true; + } + } + } + + /** + * Returns whether the given line number can have a breakpoint set on + * it. + */ + public boolean breakableLine(int line) { + return (line < this.breakableLines.length) + && this.breakableLines[line]; + } + + /** + * Returns whether there is a breakpoint set on the given line. + */ + public boolean breakpoint(int line) { + if (!breakableLine(line)) { + throw new IllegalArgumentException(String.valueOf(line)); + } + return line < this.breakpoints.length && this.breakpoints[line]; + } + + /** + * Sets or clears the breakpoint flag for the given line. + */ + public boolean breakpoint(int line, boolean value) { + if (!breakableLine(line)) { + throw new IllegalArgumentException(String.valueOf(line)); + } + boolean changed; + synchronized (breakpoints) { + if (breakpoints[line] != value) { + breakpoints[line] = value; + changed = true; + } else { + changed = false; + } + } + return changed; + } + + /** + * Removes all breakpoints from the script. + */ + public void removeAllBreakpoints() { + synchronized (breakpoints) { + for (int line = 0; line != breakpoints.length; ++line) { + breakpoints[line] = false; + } + } + } + } +} \ No newline at end of file From c56157d8da2869a91039c51c3b345e9a031b1785 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Sat, 8 Sep 2018 01:53:51 +0800 Subject: [PATCH 4/4] feat(ui): fix debug mode exception on orientation change --- app/src/main/AndroidManifest.xml | 6 +- .../org/autojs/autojs/ui/edit/EditorView.java | 12 +++- .../autojs/ui/edit/editor/CodeEditText.java | 39 +++++++++++- .../autojs/ui/edit/editor/CodeEditor.java | 9 ++- .../ui/edit/toolbar/DebugToolbarFragment.java | 63 ++++++++++++------- app/src/main/res/layout/code_editor.xml | 1 + .../res/layout/fragment_debug_toolbar.xml | 2 +- .../stardust/autojs/ScriptEngineService.java | 23 +++++-- .../autojs/core/looper/TimerThread.java | 3 +- .../com/stardust/autojs/rhino/debug/Dim.java | 2 + .../autojs/runtime/ScriptRuntime.java | 2 +- common/release/output.json | 2 +- .../inrt/launch/AssetsProjectLauncher.java | 3 +- 13 files changed, 127 insertions(+), 40 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 81cdb5c1..ad608775 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,6 +73,7 @@ @@ -81,7 +82,10 @@ android:name=".ui.settings.SettingsActivity_" android:theme="@style/AppTheme.Settings"/> - + = mCodeEditText.getLayout().getLineCount() || line < 0) { + Layout layout = mCodeEditText.getLayout(); + if (line < 0 || (layout != null && line >= layout.getLineCount())) { return; } mCodeEditText.setSelection(mCodeEditText.getLayout().getLineStart(line) + col); @@ -334,13 +336,14 @@ public class CodeEditor extends HVScrollView { return mCodeEditText.getBreakpoints(); } - public void setDebuggingLine(int line){ + public void setDebuggingLine(int line) { + jumpTo(line, 0); mCodeEditText.setDebuggingLine(line); } public void addOrRemoveBreakpoint(int line) { LinkedHashMap breakpoints = mCodeEditText.getBreakpoints(); - if(breakpoints.remove(line) == null){ + if (breakpoints.remove(line) == null) { breakpoints.put(line, new Breakpoint(line)); } mCodeEditText.invalidate(); diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java index d2c7d640..21feb219 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java @@ -1,6 +1,5 @@ package org.autojs.autojs.ui.edit.toolbar; -import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -29,14 +28,11 @@ import java.util.List; public class DebugToolbarFragment extends ToolbarFragment implements DebugCallback { private static final String LOG_TAG = "DebugToolbarFragment"; - private Dim mDim = new Dim(); + private Dim mDim; private EditorView mEditorView; private Handler mHandler; public DebugToolbarFragment() { - mDim.setGuiCallback(this); - mDim.setBreak(); - mDim.attachTo(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal()); Log.d(LOG_TAG, "DebugToolbarFragment()"); } @@ -50,29 +46,54 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mEditorView = findEditorView(view); - mEditorView.run(); + ScriptExecution scriptExecution = mEditorView.getScriptExecution(); + if (scriptExecution != null) { + mDim = (Dim) scriptExecution.getEngine().getTag(Dim.TAG); + } + if (mDim == null) { + mDim = new Dim(); + mDim.setBreak(); + mDim.setBreakOnExceptions(true); + mDim.attachTo(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal()); + mDim.setGuiCallback(this); + setInterrupted(false); + mEditorView.run(); + } else { + mDim.setGuiCallback(this); + } Log.d(LOG_TAG, "onViewCreated"); } + private void setInterrupted(boolean interrupted) { + setMenuItemStatus(R.id.step_into, interrupted); + setMenuItemStatus(R.id.step_over, interrupted); + setMenuItemStatus(R.id.step_out, interrupted); + setMenuItemStatus(R.id.resume_script, interrupted); + if (!interrupted) { + mEditorView.getEditor().setDebuggingLine(-1); + } + } + public void detachDebugger() { mDim.detach(); + mDim.setGuiCallback(null); } @Click(R.id.step_over) void stepOver() { - mEditorView.getEditor().setDebuggingLine(-1); + setInterrupted(false); mDim.setReturnValue(Dim.STEP_OVER); } @Click(R.id.step_into) void stepInto() { - mEditorView.getEditor().setDebuggingLine(-1); + setInterrupted(false); mDim.setReturnValue(Dim.STEP_INTO); } - @Click(R.id.stop_out) + @Click(R.id.step_out) void stepOut() { - mEditorView.getEditor().setDebuggingLine(-1); + setInterrupted(false); mDim.setReturnValue(Dim.STEP_OUT); } @@ -83,13 +104,13 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba @Click(R.id.resume_script) void resumeScript() { - mEditorView.getEditor().setDebuggingLine(-1); + setInterrupted(false); mDim.setReturnValue(Dim.GO); } @Override public void updateSourceText(Dim.SourceInfo sourceInfo) { - Log.d(LOG_TAG, "updateSourceText: url = " + sourceInfo.url() + ", source = " + sourceInfo.source()); + Log.d(LOG_TAG, "updateSourceText: url = " + sourceInfo.url()); if (!sourceInfo.url().equals(mEditorView.getFile().toString())) { return; } @@ -107,7 +128,12 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba public void enterInterrupt(Dim.StackFrame stackFrame, String threadName, String s1) { Log.d(LOG_TAG, "enterInterrupt: threadName = " + threadName + ", url = " + stackFrame.getUrl() + ", line = " + stackFrame.getLineNumber()); if (stackFrame.getUrl().equals(mEditorView.getFile().toString())) { - mEditorView.getEditor().setDebuggingLine(stackFrame.getLineNumber() - 1); + final int line = stackFrame.getLineNumber() - 1; + mHandler.post(() -> { + mEditorView.getEditor().setDebuggingLine(line); + setInterrupted(true); + }); + } else { mHandler.post(this::resumeScript); } @@ -120,25 +146,18 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba @Override public void dispatchNextGuiEvent() { - Log.d(LOG_TAG, "dispatchNextGuiEvent"); - } @Override public boolean shouldAttachDebugger(RhinoJavaScriptEngine engine) { - ScriptExecution execution = AutoJs.getInstance().getScriptEngineService().getScriptExecution(mEditorView.getScriptExecutionId()); + ScriptExecution execution = mEditorView.getScriptExecution(); return execution != null && execution.getId() == engine.getId(); } @Override public List getMenuItemIds() { - return Arrays.asList(R.id.step_over, R.id.step_into, R.id.stop_out, R.id.resume_script, R.id.stop_script); + return Arrays.asList(R.id.step_over, R.id.step_into, R.id.step_out, R.id.resume_script, R.id.stop_script); } - @Override - public void onDestroy() { - super.onDestroy(); - mDim.detach(); - } } diff --git a/app/src/main/res/layout/code_editor.xml b/app/src/main/res/layout/code_editor.xml index 85c20411..72b09fd7 100644 --- a/app/src/main/res/layout/code_editor.xml +++ b/app/src/main/res/layout/code_editor.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollHorizontally="true" + android:imeOptions="flagNoExtractUi" android:textColor="@android:color/transparent" android:textCursorDrawable="@drawable/code_edit_text_cursor" android:textSize="15sp"/> diff --git a/app/src/main/res/layout/fragment_debug_toolbar.xml b/app/src/main/res/layout/fragment_debug_toolbar.xml index 6752d24f..0831e974 100644 --- a/app/src/main/res/layout/fragment_debug_toolbar.xml +++ b/app/src/main/res/layout/fragment_debug_toolbar.xml @@ -21,7 +21,7 @@ app:text="@string/text_debug_step_into"/>