Bug 919709 - Make Debugger use CodeMirror. r=vporof

This commit is contained in:
Anton Kovalyov 2013-10-22 13:53:53 -07:00
parent 2dc4ce8e39
commit de1f0214e6
42 changed files with 1734 additions and 296 deletions

View File

@ -78,15 +78,18 @@ const EVENTS = {
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm");
Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
Cu.import("resource:///modules/devtools/VariablesView.jsm");
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const Editor = require("devtools/sourceeditor/editor");
const promise = require("sdk/core/promise");
const DebuggerEditor = require("devtools/sourceeditor/debugger.js");
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
"resource:///modules/devtools/Parser.jsm");
@ -608,7 +611,7 @@ StackFrames.prototype = {
* Handler for the thread client's resumed notification.
*/
_onResumed: function() {
DebuggerView.editor.setDebugLocation(-1);
DebuggerView.editor.clearDebugLocation();
// Prepare the watch expression evaluation string for the next pause.
if (!this._isWatchExpressionsEvaluation) {
@ -1434,7 +1437,6 @@ EventListeners.prototype = {
* Handles all the breakpoints in the current debugger.
*/
function Breakpoints() {
this._onEditorBreakpointChange = this._onEditorBreakpointChange.bind(this);
this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this);
this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this);
this.addBreakpoint = this.addBreakpoint.bind(this);
@ -1457,8 +1459,8 @@ Breakpoints.prototype = {
* A promise that is resolved when the breakpoints finishes initializing.
*/
initialize: function() {
DebuggerView.editor.addEventListener(
SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
DebuggerView.editor.on("breakpointAdded", this._onEditorBreakpointAdd);
DebuggerView.editor.on("breakpointRemoved", this._onEditorBreakpointRemove);
// Initialization is synchronous, for now.
return promise.resolve(null);
@ -1471,34 +1473,21 @@ Breakpoints.prototype = {
* A promise that is resolved when the breakpoints finishes destroying.
*/
destroy: function() {
DebuggerView.editor.removeEventListener(
SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
DebuggerView.editor.off("breakpointAdded", this._onEditorBreakpointAdd);
DebuggerView.editor.off("breakpointRemoved", this._onEditorBreakpointRemove);
return this.removeAllBreakpoints();
},
/**
* Event handler for breakpoint changes that happen in the editor. This
* function syncs the breakpoints in the editor to those in the debugger.
*
* @param object aEvent
* The SourceEditor.EVENTS.BREAKPOINT_CHANGE event object.
*/
_onEditorBreakpointChange: function(aEvent) {
aEvent.added.forEach(this._onEditorBreakpointAdd, this);
aEvent.removed.forEach(this._onEditorBreakpointRemove, this);
},
/**
* Event handler for new breakpoints that come from the editor.
*
* @param object aEditorBreakpoint
* The breakpoint object coming from the editor.
* @param number aLine
* Line number where breakpoint was set.
*/
_onEditorBreakpointAdd: function(aEditorBreakpoint) {
_onEditorBreakpointAdd: function(_, aLine) {
let url = DebuggerView.Sources.selectedValue;
let line = aEditorBreakpoint.line + 1;
let location = { url: url, line: line };
let location = { url: url, line: aLine + 1 };
// Initialize the breakpoint, but don't update the editor, since this
// callback is invoked because a breakpoint was added in the editor itself.
@ -1511,26 +1500,25 @@ Breakpoints.prototype = {
DebuggerView.editor.addBreakpoint(aBreakpointClient.location.line - 1);
}
// Notify that we've shown a breakpoint in the source editor.
window.emit(EVENTS.BREAKPOINT_SHOWN, aEditorBreakpoint);
window.emit(EVENTS.BREAKPOINT_SHOWN);
});
},
/**
* Event handler for breakpoints that are removed from the editor.
*
* @param object aEditorBreakpoint
* The breakpoint object that was removed from the editor.
* @param number aLine
* Line number where breakpoint was removed.
*/
_onEditorBreakpointRemove: function(aEditorBreakpoint) {
_onEditorBreakpointRemove: function(_, aLine) {
let url = DebuggerView.Sources.selectedValue;
let line = aEditorBreakpoint.line + 1;
let location = { url: url, line: line };
let location = { url: url, line: aLine + 1 };
// Destroy the breakpoint, but don't update the editor, since this callback
// is invoked because a breakpoint was removed from the editor itself.
this.removeBreakpoint(location, { noEditorUpdate: true }).then(() => {
// Notify that we've hidden a breakpoint in the source editor.
window.emit(EVENTS.BREAKPOINT_HIDDEN, aEditorBreakpoint);
window.emit(EVENTS.BREAKPOINT_HIDDEN);
});
},
@ -1644,7 +1632,7 @@ Breakpoints.prototype = {
// after the target navigated). Note that this will get out of sync
// if the source text contents change.
let line = aBreakpointClient.location.line - 1;
aBreakpointClient.text = DebuggerView.getEditorLineText(line).trim();
aBreakpointClient.text = DebuggerView.editor.getText(line).trim();
// Show the breakpoint in the editor and breakpoints pane, and resolve.
this._showBreakpoint(aBreakpointClient, aOptions);

View File

@ -14,7 +14,7 @@ function SourcesView() {
this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
this._onEditorLoad = this._onEditorLoad.bind(this);
this._onEditorUnload = this._onEditorUnload.bind(this);
this._onEditorSelection = this._onEditorSelection.bind(this);
this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
this._onSourceSelect = this._onSourceSelect.bind(this);
this._onSourceClick = this._onSourceClick.bind(this);
@ -479,7 +479,12 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
*/
_hideConditionalPopup: function() {
this._cbPanel.hidden = true;
this._cbPanel.hidePopup();
// Sometimes this._cbPanel doesn't have hidePopup method which doesn't
// break anything but simply outputs an exception to the console.
if (this._cbPanel.hidePopup) {
this._cbPanel.hidePopup();
}
},
/**
@ -645,30 +650,30 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* The load listener for the source editor.
*/
_onEditorLoad: function(aName, aEditor) {
aEditor.addEventListener(SourceEditor.EVENTS.SELECTION, this._onEditorSelection, false);
aEditor.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU, this._onEditorContextMenu, false);
aEditor.on("cursorActivity", this._onEditorCursorActivity);
aEditor.on("contextMenu", this._onEditorContextMenu);
},
/**
* The unload listener for the source editor.
*/
_onEditorUnload: function(aName, aEditor) {
aEditor.removeEventListener(SourceEditor.EVENTS.SELECTION, this._onEditorSelection, false);
aEditor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU, this._onEditorContextMenu, false);
aEditor.off("cursorActivity", this._onEditorCursorActivity);
aEditor.off("contextMenu", this._onEditorContextMenu);
},
/**
* The selection listener for the source editor.
*/
_onEditorSelection: function(e) {
let { start, end } = e.newValue;
_onEditorCursorActivity: function(e) {
let editor = DebuggerView.editor;
let start = editor.getCursor("start").line + 1;
let end = editor.getCursor().line + 1;
let url = this.selectedValue;
let url = this.selectedValue;
let lineStart = DebuggerView.editor.getLineAtOffset(start) + 1;
let lineEnd = DebuggerView.editor.getLineAtOffset(end) + 1;
let location = { url: url, line: lineStart };
let location = { url: url, line: start };
if (this.getBreakpoint(location) && lineStart == lineEnd) {
if (this.getBreakpoint(location) && start == end) {
this.highlightBreakpoint(location, { noEditorUpdate: true });
} else {
this.unhighlightBreakpoint();
@ -679,9 +684,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* The context menu listener for the source editor.
*/
_onEditorContextMenu: function({ x, y }) {
let offset = DebuggerView.editor.getOffsetAtLocation(x, y);
let line = DebuggerView.editor.getLineAtOffset(offset);
this._editorContextMenuLineNumber = line;
this._editorContextMenuLineNumber = DebuggerView.editor.getPositionFromCoords(x, y).line;
},
/**
@ -869,13 +872,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
// If this command was executed via the context menu, add the breakpoint
// on the currently hovered line in the source editor.
if (this._editorContextMenuLineNumber >= 0) {
DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
DebuggerView.editor.setCursor({ line: this._editorContextMenuLineNumber, ch: 0 });
}
// Avoid placing breakpoints incorrectly when using key shortcuts.
this._editorContextMenuLineNumber = -1;
let url = DebuggerView.Sources.selectedValue;
let line = DebuggerView.editor.getCaretPosition().line + 1;
let line = DebuggerView.editor.getCursor().line + 1;
let location = { url: url, line: line };
let breakpointItem = this.getBreakpoint(location);
@ -896,13 +900,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
// If this command was executed via the context menu, add the breakpoint
// on the currently hovered line in the source editor.
if (this._editorContextMenuLineNumber >= 0) {
DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
DebuggerView.editor.setCursor({ line: this._editorContextMenuLineNumber, ch: 0 });
}
// Avoid placing breakpoints incorrectly when using key shortcuts.
this._editorContextMenuLineNumber = -1;
let url = DebuggerView.Sources.selectedValue;
let line = DebuggerView.editor.getCaretPosition().line + 1;
let line = DebuggerView.editor.getCursor().line + 1;
let location = { url: url, line: line };
let breakpointItem = this.getBreakpoint(location);
@ -1456,7 +1461,7 @@ WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
_onCmdAddExpression: function(aText) {
// Only add a new expression if there's no pending input.
if (this.getAllStrings().indexOf("") == -1) {
this.addExpression(aText || DebuggerView.editor.getSelectedText());
this.addExpression(aText || DebuggerView.editor.getSelection());
}
},
@ -2100,12 +2105,9 @@ GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {
let url = sourceResultsItem.instance.url;
let line = lineResultsItem.instance.line;
DebuggerView.setEditorLocation(url, line + 1, { noDebug: true });
let editor = DebuggerView.editor;
let offset = editor.getCaretOffset();
let { start, length } = lineResultsItem.lineData.range;
editor.setSelection(offset + start, offset + start + length);
DebuggerView.setEditorLocation(url, line + 1, { noDebug: true });
DebuggerView.editor.extendSelection(lineResultsItem.lineData.range);
},
/**

View File

@ -861,10 +861,9 @@ FilterView.prototype = {
*/
_performLineSearch: function(aLine) {
// Make sure we're actually searching for a valid line.
if (!aLine) {
return;
if (aLine) {
DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 });
}
DebuggerView.editor.setCaretPosition(aLine - 1);
},
/**
@ -879,10 +878,8 @@ FilterView.prototype = {
if (!aToken) {
return;
}
let offset = DebuggerView.editor.find(aToken, { ignoreCase: true });
if (offset > -1) {
DebuggerView.editor.setSelection(offset, offset + aToken.length)
}
DebuggerView.editor.find(aToken);
},
/**
@ -1043,14 +1040,8 @@ FilterView.prototype = {
// Jump to the next/previous instance of the currently searched token.
if (isTokenSearch) {
let [, token] = args;
let methods = { selectNext: "findNext", selectPrev: "findPrevious" };
// Search for the token and select it.
let offset = DebuggerView.editor[methods[actionToPerform]](true);
if (offset > -1) {
DebuggerView.editor.setSelection(offset, offset + token.length)
}
let methods = { selectNext: "findNext", selectPrev: "findPrev" };
DebuggerView.editor[methods[actionToPerform]]();
return;
}
@ -1061,7 +1052,7 @@ FilterView.prototype = {
// Modify the line number and jump to it.
line += !isReturnKey ? amounts[actionToPerform] : 0;
let lineCount = DebuggerView.editor.getLineCount();
let lineCount = DebuggerView.editor.lineCount();
let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
this._doSearch(SEARCH_LINE_FLAG, lineTarget);
return;
@ -1084,7 +1075,7 @@ FilterView.prototype = {
_doSearch: function(aOperator = "", aText = "") {
this._searchbox.focus();
this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
this._searchbox.value = aOperator + (aText || DebuggerView.editor.getSelectedText());
this._searchbox.value = aOperator + (aText || DebuggerView.editor.getSelection());
},
/**

View File

@ -28,13 +28,6 @@ const SEARCH_FUNCTION_FLAG = "@";
const SEARCH_TOKEN_FLAG = "#";
const SEARCH_LINE_FLAG = ":";
const SEARCH_VARIABLE_FLAG = "*";
const DEFAULT_EDITOR_CONFIG = {
mode: SourceEditor.MODES.TEXT,
readOnly: true,
showLineNumbers: true,
showAnnotationRuler: true,
showOverviewRuler: true
};
/**
* Object defining the debugger view components.
@ -187,7 +180,7 @@ let DebuggerView = {
},
/**
* Initializes the SourceEditor instance.
* Initializes the Editor instance.
*
* @param function aCallback
* Called after the editor finishes initializing.
@ -195,11 +188,35 @@ let DebuggerView = {
_initializeEditor: function(aCallback) {
dumpn("Initializing the DebuggerView editor");
this.editor = new SourceEditor();
this.editor.init(document.getElementById("editor"), DEFAULT_EDITOR_CONFIG, () => {
// This needs to be more localizable: see bug 929234.
let extraKeys = {};
extraKeys[(Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + "F"] = (cm) => {
DebuggerView.Filtering._doTokenSearch();
};
this.editor = new Editor({
mode: Editor.modes.text,
readOnly: true,
lineNumbers: true,
showAnnotationRuler: true,
gutters: [ "breakpoints" ],
extraKeys: extraKeys,
contextMenu: "sourceEditorContextMenu"
});
this.editor.appendTo(document.getElementById("editor")).then(() => {
this.editor.extend(DebuggerEditor);
this._loadingText = L10N.getStr("loadingText");
this._onEditorLoad(aCallback);
});
this.editor.on("gutterClick", (ev, line) => {
if (this.editor.hasBreakpoint(line)) {
this.editor.removeBreakpoint(line);
} else {
this.editor.addBreakpoint(line);
}
});
},
/**
@ -219,7 +236,7 @@ let DebuggerView = {
},
/**
* Destroys the SourceEditor instance and also executes any necessary
* Destroys the Editor instance and also executes any necessary
* post-unload operations.
*
* @param function aCallback
@ -276,9 +293,9 @@ let DebuggerView = {
* The source text content.
*/
_setEditorText: function(aTextContent = "") {
this.editor.setMode(SourceEditor.MODES.TEXT);
this.editor.setMode(Editor.modes.text);
this.editor.setText(aTextContent);
this.editor.resetUndo();
this.editor.clearHistory();
},
/**
@ -294,22 +311,24 @@ let DebuggerView = {
*/
_setEditorMode: function(aUrl, aContentType = "", aTextContent = "") {
// Avoid setting the editor mode for very large files.
// Is this still necessary? See bug 929225.
if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
this.editor.setMode(SourceEditor.MODES.TEXT);
return void this.editor.setMode(Editor.modes.text);
}
// Use JS mode for files with .js and .jsm extensions.
else if (SourceUtils.isJavaScript(aUrl, aContentType)) {
this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
if (SourceUtils.isJavaScript(aUrl, aContentType)) {
return void this.editor.setMode(Editor.modes.js);
}
// Use HTML mode for files in which the first non whitespace character is
// &lt;, regardless of extension.
else if (aTextContent.match(/^\s*</)) {
this.editor.setMode(SourceEditor.MODES.HTML);
}
// Unknown languange, use plain text.
else {
this.editor.setMode(SourceEditor.MODES.TEXT);
if (aTextContent.match(/^\s*</)) {
return void this.editor.setMode(Editor.modes.html);
}
// Unknown language, use text.
this.editor.setMode(Editor.modes.text);
},
/**
@ -415,6 +434,11 @@ let DebuggerView = {
let sourceItem = this.Sources.getItemByValue(aUrl);
let sourceForm = sourceItem.attachment.source;
// Once we change the editor location, it replaces editor's contents.
// This means that the debug location information is now obsolete, so
// we need to clear it. We set a new location below, in this function.
this.editor.clearDebugLocation();
// Make sure the requested source client is shown in the editor, then
// update the source editor's caret position and debug location.
return this._setEditorSource(sourceForm, aFlags).then(() => {
@ -423,35 +447,23 @@ let DebuggerView = {
if (aLine < 1) {
return;
}
if (aFlags.charOffset) {
aLine += this.editor.getLineAtOffset(aFlags.charOffset);
aLine += this.editor.getPosition(aFlags.charOffset).line;
}
if (aFlags.lineOffset) {
aLine += aFlags.lineOffset;
}
if (!aFlags.noCaret) {
this.editor.setCaretPosition(aLine - 1, aFlags.columnOffset);
}
if (!aFlags.noDebug) {
this.editor.setDebugLocation(aLine - 1, aFlags.columnOffset);
}
});
},
/**
* Gets the text in the source editor's specified line.
*
* @param number aLine [optional]
* The line to get the text from. If unspecified, it defaults to
* the current caret position.
* @return string
* The specified line's text.
*/
getEditorLineText: function(aLine) {
let line = aLine || this.editor.getCaretPosition().line;
let start = this.editor.getLineStart(line);
let end = this.editor.getLineEnd(line);
return this.editor.getText(start, end);
if (!aFlags.noCaret) {
this.editor.setCursor({ line: aLine -1, ch: aFlags.columnOffset || 0 });
}
if (!aFlags.noDebug) {
this.editor.setDebugLocation(aLine - 1);
}
}).then(null, console.error);
},
/**
@ -599,9 +611,9 @@ let DebuggerView = {
this.EventListeners.empty();
if (this.editor) {
this.editor.setMode(SourceEditor.MODES.TEXT);
this.editor.setMode(Editor.modes.text);
this.editor.setText("");
this.editor.resetUndo();
this.editor.clearHistory();
this._editorSource = {};
}
},

View File

@ -13,7 +13,6 @@
%debuggerDTD;
]>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/devtools/source-editor-overlay.xul"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
macanimationtype="document"
@ -29,7 +28,6 @@
<script type="text/javascript" src="debugger-panes.js"/>
<commandset id="editMenuCommands"/>
<commandset id="sourceEditorCommands"/>
<commandset id="debuggerCommands">
<command id="prettyPrintCommand"
@ -86,7 +84,7 @@
<popupset id="debuggerPopupset">
<menupopup id="sourceEditorContextMenu"
onpopupshowing="goUpdateSourceEditorMenuItems()">
onpopupshowing="goUpdateGlobalEditMenuItems()">
<menuitem id="se-dbg-cMenu-addBreakpoint"
label="&debuggerUI.seMenuBreak;"
key="addBreakpointKey"
@ -100,9 +98,9 @@
key="addWatchExpressionKey"
command="addWatchExpressionCommand"/>
<menuseparator/>
<menuitem id="se-cMenu-copy"/>
<menuitem id="cMenu_copy"/>
<menuseparator/>
<menuitem id="se-cMenu-selectAll"/>
<menuitem id="cMenu_selectAll"/>
<menuseparator/>
<menuitem id="se-dbg-cMenu-findFile"
label="&debuggerUI.searchFile;"

View File

@ -35,7 +35,7 @@ function test() {
is(gEditor.getBreakpoints().length, 0,
"No breakpoints currently shown in the editor.");
gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAdd);
gEditor.on("breakpointAdded", onEditorBreakpointAdd);
gPanel.addBreakpoint({ url: gSources.selectedValue, line: 4 }).then(onBreakpointAdd);
}
@ -59,8 +59,8 @@ function test() {
maybeFinish();
}
function onEditorBreakpointAdd(aEvent) {
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAdd);
function onEditorBreakpointAdd() {
gEditor.off("breakpointAdded", onEditorBreakpointAdd);
is(gEditor.getBreakpoints().length, 1,
"There is only one breakpoint in the editor");

View File

@ -12,14 +12,13 @@ function test() {
requestLongerTimeout(2);
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gBreakpoints;
let gSources, gBreakpoints;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;

View File

@ -40,7 +40,7 @@ function test() {
function verifyView({ disabled, visible }) {
return Task.spawn(function() {
// It takes a tick for the checkbox in the SideMenuWidget and the
// gutter in the SourceEditor to get updated.
// gutter in the editor to get updated.
yield waitForTick();
let breakpointItem = gSources.getBreakpoint(gBreakpointLocation);

View File

@ -54,7 +54,7 @@ function test() {
info("Add the first breakpoint.");
let location = { url: gSources.selectedValue, line: 6 };
gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddFirst);
gEditor.once("breakpointAdded", onEditorBreakpointAddFirst);
gPanel.addBreakpoint(location).then(onBreakpointAddFirst);
}
@ -62,17 +62,12 @@ function test() {
let breakpointsRemoved = 0;
let editorBreakpointChanges = 0;
function onEditorBreakpointAddFirst(aEvent) {
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddFirst);
function onEditorBreakpointAddFirst(aEvent, aLine) {
editorBreakpointChanges++;
ok(aEvent,
"breakpoint1 added to the editor.");
is(aEvent.added.length, 1,
"One breakpoint added to the editor.");
is(aEvent.removed.length, 0,
"No breakpoint was removed from the editor.");
is(aEvent.added[0].line, 5,
is(aLine, 5,
"Editor breakpoint line is correct.");
is(gEditor.getBreakpoints().length, 1,
@ -108,21 +103,16 @@ function test() {
"The second source should be currently selected.");
info("Remove the first breakpoint.");
gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointRemoveFirst);
gEditor.once("breakpointRemoved", onEditorBreakpointRemoveFirst);
gPanel.removeBreakpoint(aBreakpointClient.location).then(onBreakpointRemoveFirst);
}
function onEditorBreakpointRemoveFirst(aEvent) {
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointRemoveFirst);
function onEditorBreakpointRemoveFirst(aEvent, aLine) {
editorBreakpointChanges++;
ok(aEvent,
"breakpoint1 removed from the editor.");
is(aEvent.added.length, 0,
"No breakpoint was added to the editor.");
is(aEvent.removed.length, 1,
"One breakpoint was removed from the editor.");
is(aEvent.removed[0].line, 5,
is(aLine, 5,
"Editor breakpoint line is correct.");
is(gEditor.getBreakpoints().length, 0,
@ -161,16 +151,14 @@ function test() {
info("Add a breakpoint to the first source, which is not selected.");
let location = { url: gSources.values[0], line: 5 };
let options = { noEditorUpdate: true };
gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddBackgroundTrap);
gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
gPanel.addBreakpoint(location, options).then(onBreakpointAddBackground);
}
function onEditorBreakpointAddBackgroundTrap(aEvent) {
function onEditorBreakpointAddBackgroundTrap() {
// Trap listener: no breakpoint must be added to the editor when a
// breakpoint is added to a source that is not currently selected.
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddBackgroundTrap);
editorBreakpointChanges++;
ok(false, "breakpoint2 must not be added to the editor.");
}
@ -203,25 +191,20 @@ function test() {
"The second source should be currently selected.");
// Remove the trap listener.
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddBackgroundTrap);
gEditor.off("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
info("Switch to the first source, which is not yet selected");
gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddSwitch);
gEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onEditorTextChanged);
gEditor.once("breakpointAdded", onEditorBreakpointAddSwitch);
gEditor.once("change", onEditorTextChanged);
gSources.selectedIndex = 0;
}
function onEditorBreakpointAddSwitch(aEvent) {
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointAddSwitch);
function onEditorBreakpointAddSwitch(aEvent, aLine) {
editorBreakpointChanges++;
ok(aEvent,
"breakpoint2 added to the editor.");
is(aEvent.added.length, 1,
"One breakpoint added to the editor.");
is(aEvent.removed.length, 0,
"No breakpoint was removed from the editor.");
is(aEvent.added[0].line, 4,
is(aLine, 4,
"Editor breakpoint line is correct.");
is(gEditor.getBreakpoints().length, 1,
@ -230,13 +213,8 @@ function test() {
function onEditorTextChanged() {
// Wait for the actual text to be shown.
if (gEditor.getText() != gDebugger.L10N.getStr("loadingText")) {
onEditorTextReallyChanged();
}
}
function onEditorTextReallyChanged() {
gEditor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onEditorTextChanged);
if (gEditor.getText() == gDebugger.L10N.getStr("loadingText"))
return void gEditor.once("change", onEditorTextChanged);
is(gEditor.getText().indexOf("debugger"), -1,
"The second source is no longer displayed.");
@ -246,15 +224,15 @@ function test() {
is(gSources.values[0], gSources.selectedValue,
"The first source should be currently selected.");
let window = gEditor.editorElement.contentWindow;
let window = gEditor.container.contentWindow;
executeSoon(() => window.mozRequestAnimationFrame(onReadyForClick));
}
function onReadyForClick() {
info("Remove the second breakpoint using the mouse.");
gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointRemoveSecond);
gEditor.once("breakpointRemoved", onEditorBreakpointRemoveSecond);
let iframe = gEditor.editorElement;
let iframe = gEditor.container;
let testWin = iframe.ownerDocument.defaultView;
// Flush the layout for the iframe.
@ -264,27 +242,20 @@ function test() {
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let lineOffset = gEditor.getLineStart(4);
let coords = gEditor.getLocationAtOffset(lineOffset);
let coords = gEditor.cursorCoords({ line: 4, ch: 0 });
let rect = iframe.getBoundingClientRect();
let left = rect.left + 10;
let top = rect.top + coords.y + 4;
let top = rect.top + coords.top + 4;
utils.sendMouseEventToWindow("mousedown", left, top, 0, 1, 0, false, 0, 0);
utils.sendMouseEventToWindow("mouseup", left, top, 0, 1, 0, false, 0, 0);
}
function onEditorBreakpointRemoveSecond(aEvent) {
gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, onEditorBreakpointRemoveSecond);
function onEditorBreakpointRemoveSecond(aEvent, aLine) {
editorBreakpointChanges++;
ok(aEvent,
"breakpoint2 removed from the editor.");
is(aEvent.added.length, 0,
"No breakpoint was added to the editor.");
is(aEvent.removed.length, 1,
"One breakpoint was removed from the editor.");
is(aEvent.removed[0].line, 4,
is(aLine, 4,
"Editor breakpoint line is correct.");
is(gEditor.getBreakpoints().length, 0,

View File

@ -181,7 +181,7 @@ function test() {
}
function setCaretPosition(aLine) {
gEditor.setCaretPosition(aLine - 1);
gEditor.setCursor({ line: aLine - 1, ch: 0 });
}
function setContextPosition(aLine) {

View File

@ -20,7 +20,7 @@ function test() {
gSources = gDebugger.DebuggerView.Sources;
gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest).then(null, info);
gDebuggee.firstCall();
});
@ -39,14 +39,14 @@ function test() {
ok(gContextMenu,
"The source editor's context menupopup is available.");
ok(gEditor.readOnly,
ok(gEditor.isReadOnly(),
"The source editor is read only.");
gEditor.focus();
gEditor.setSelection(0, 10);
gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 });
once(gContextMenu, "popupshown").then(testContextMenu);
gContextMenu.openPopup(gEditor.editorElement, "overlap", 0, 0, true, false);
once(gContextMenu, "popupshown").then(testContextMenu).then(null, info);
gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
}
function testContextMenu() {
@ -56,21 +56,6 @@ function test() {
"#editMenuCommands found.");
ok(!document.getElementById("editMenuKeys"),
"#editMenuKeys not found.");
ok(document.getElementById("sourceEditorCommands"),
"#sourceEditorCommands found.");
// Map command ids to their expected disabled state.
let commands = {"se-cmd-undo": true, "se-cmd-redo": true,
"se-cmd-cut": true, "se-cmd-paste": true,
"se-cmd-delete": true, "cmd_findAgain": true,
"cmd_findPrevious": true, "cmd_find": false,
"cmd_gotoLine": false, "cmd_copy": false,
"se-cmd-selectAll": false};
for (let id in commands) {
is(document.getElementById(id).hasAttribute("disabled"), commands[id],
"The element with id: " + id + " hasAttribute('disabled').");
}
gContextMenu.hidePopup();
resumeDebuggerThenCloseAndFinish(gPanel);

View File

@ -37,7 +37,7 @@ function testInitialSource() {
is(gSources.itemCount, 3,
"Found the expected number of sources.");
is(gEditor.getMode(), SourceEditor.MODES.TEXT,
is(gEditor.getMode().name, "text",
"Found the expected editor mode.");
is(gEditor.getText().search(/firstCall/), -1,
"The first source is not displayed.");
@ -55,7 +55,7 @@ function testSwitch1() {
is(gSources.itemCount, 3,
"Found the expected number of sources.");
is(gEditor.getMode(), SourceEditor.MODES.JAVASCRIPT,
is(gEditor.getMode().name, "javascript",
"Found the expected editor mode.");
is(gEditor.getText().search(/firstCall/), 118,
"The first source is displayed.");
@ -73,7 +73,7 @@ function testSwitch2() {
is(gSources.itemCount, 3,
"Found the expected number of sources.");
is(gEditor.getMode(), SourceEditor.MODES.HTML,
is(gEditor.getMode().name, "htmlmixed",
"Found the expected editor mode.");
is(gEditor.getText().search(/firstCall/), -1,
"The first source is not displayed.");

View File

@ -39,7 +39,7 @@ function selectContextMenuItem() {
const menuItem = gDebugger.document.getElementById("se-dbg-cMenu-prettyPrint");
menuItem.click();
});
gContextMenu.openPopup(gEditor.editorElement, "overlap", 0, 0, true, false);
gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
}
function testSourceIsPretty() {

View File

@ -24,7 +24,7 @@ function test() {
yield waitForSourceShown(gPanel, TAB_URL);
// From this point onward, the source editor's text should never change.
once(gEditor, SourceEditor.EVENTS.TEXT_CHANGED).then(() => {
gEditor.once("change", () => {
ok(false, "The source editor text shouldn't have changed.");
});

View File

@ -40,7 +40,7 @@ function test() {
yield waitForSourceShown(gPanel, JS_URL);
// From this point onward, the source editor's text should never change.
once(gEditor, SourceEditor.EVENTS.TEXT_CHANGED).then(() => {
gEditor.once("change", () => {
ok(false, "The source editor text shouldn't have changed.");
});

View File

@ -104,7 +104,7 @@ function testSwitchPaused1() {
executeSoon(() => {
ok(isCaretPos(gPanel, 1),
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), -1,
is(gEditor.getDebugLocation(), null,
"Editor debugger location is correct.");
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);

View File

@ -99,7 +99,8 @@ function testSwitchPaused1() {
executeSoon(() => {
ok(isCaretPos(gPanel, 1),
"Editor caret location is correct.");
is(gEditor.getDebugLocation(), -1,
is(gEditor.getDebugLocation(), null,
"Editor debugger location is correct.");
waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);

View File

@ -130,7 +130,6 @@ function performTest() {
ok(isCaretPos(gPanel, 26, 11 + token.length),
"The editor didn't jump to the correct token (6).");
setText(gSearchBox, ":bogus#" + token + ";");
is(gFiltering.searchData.toSource(), '["#", [":bogus", "debugger;"]]',
"The searchbox data wasn't parsed correctly.");
@ -283,7 +282,7 @@ function performTest() {
gEditor.focus();
gEditor.setSelection(1, 5);
gEditor.setSelection.apply(gEditor, gEditor.getPosition(1, 5));
ok(isCaretPos(gPanel, 1, 6),
"The editor caret position didn't update after selecting some text.");
@ -294,7 +293,7 @@ function performTest() {
"The search field has the right initial value (1).");
gEditor.focus();
gEditor.setSelection(415, 418);
gEditor.setSelection.apply(gEditor, gEditor.getPosition(415, 418));
ok(isCaretPos(gPanel, 21, 30),
"The editor caret position didn't update after selecting some number.");

View File

@ -33,11 +33,11 @@ function test() {
function testLineSearch() {
setText(gSearchBox, ":42");
ok(isCaretPos(gPanel, 1),
ok(isCaretPos(gPanel, 7),
"The editor caret position appears to be correct (1.1).");
ok(isEditorSel(gPanel, [1, 1]),
ok(isEditorSel(gPanel, [160, 160]),
"The editor selection appears to be correct (1.1).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (1.1).");
backspaceText(gSearchBox, 1);
@ -45,7 +45,7 @@ function testLineSearch() {
"The editor caret position appears to be correct (1.2).");
ok(isEditorSel(gPanel, [110, 110]),
"The editor selection appears to be correct (1.2).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (1.2).");
backspaceText(gSearchBox, 1);
@ -53,7 +53,7 @@ function testLineSearch() {
"The editor caret position appears to be correct (1.3).");
ok(isEditorSel(gPanel, [110, 110]),
"The editor selection appears to be correct (1.3).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (1.3).");
setText(gSearchBox, ":4");
@ -61,7 +61,7 @@ function testLineSearch() {
"The editor caret position appears to be correct (1.4).");
ok(isEditorSel(gPanel, [110, 110]),
"The editor selection appears to be correct (1.4).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (1.4).");
gSearchBox.select();
@ -70,7 +70,7 @@ function testLineSearch() {
"The editor caret position appears to be correct (1.5).");
ok(isEditorSel(gPanel, [110, 110]),
"The editor selection appears to be correct (1.5).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (1.5).");
is(gSearchBox.value, "",
"The searchbox should have been cleared.");
@ -82,7 +82,7 @@ function testTokenSearch() {
"The editor caret position appears to be correct (2.1).");
ok(isEditorSel(gPanel, [153, 155]),
"The editor selection appears to be correct (2.1).");
is(gEditor.getSelectedText(), ";\"",
is(gEditor.getSelection(), ";\"",
"The editor selected text appears to be correct (2.1).");
backspaceText(gSearchBox, 1);
@ -90,7 +90,7 @@ function testTokenSearch() {
"The editor caret position appears to be correct (2.2).");
ok(isEditorSel(gPanel, [153, 154]),
"The editor selection appears to be correct (2.2).");
is(gEditor.getSelectedText(), ";",
is(gEditor.getSelection(), ";",
"The editor selected text appears to be correct (2.2).");
backspaceText(gSearchBox, 1);
@ -98,7 +98,7 @@ function testTokenSearch() {
"The editor caret position appears to be correct (2.3).");
ok(isEditorSel(gPanel, [154, 154]),
"The editor selection appears to be correct (2.3).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (2.3).");
setText(gSearchBox, "#;");
@ -106,7 +106,7 @@ function testTokenSearch() {
"The editor caret position appears to be correct (2.4).");
ok(isEditorSel(gPanel, [153, 154]),
"The editor selection appears to be correct (2.4).");
is(gEditor.getSelectedText(), ";",
is(gEditor.getSelection(), ";",
"The editor selected text appears to be correct (2.4).");
gSearchBox.select();
@ -115,7 +115,7 @@ function testTokenSearch() {
"The editor caret position appears to be correct (2.5).");
ok(isEditorSel(gPanel, [154, 154]),
"The editor selection appears to be correct (2.5).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct (2.5).");
is(gSearchBox.value, "",
"The searchbox should have been cleared.");

View File

@ -87,7 +87,7 @@ function doFirstJump() {
executeSoon(() => {
ok(isCaretPos(gPanel, 5, 7),
"The editor didn't jump to the correct line (1).");
is(gEditor.getSelectedText(), "eval",
is(gEditor.getSelection(), "eval",
"The editor didn't select the correct text (1).");
deferred.resolve();
@ -115,7 +115,7 @@ function doSecondJump() {
executeSoon(() => {
ok(isCaretPos(gPanel, 6, 7),
"The editor didn't jump to the correct line (2).");
is(gEditor.getSelectedText(), "eval",
is(gEditor.getSelection(), "eval",
"The editor didn't select the correct text (2).");
deferred.resolve();
@ -143,7 +143,7 @@ function doWrapAroundJump() {
executeSoon(() => {
ok(isCaretPos(gPanel, 5, 7),
"The editor didn't jump to the correct line (3).");
is(gEditor.getSelectedText(), "eval",
is(gEditor.getSelection(), "eval",
"The editor didn't select the correct text (3).");
deferred.resolve();
@ -171,7 +171,7 @@ function doBackwardsWrapAroundJump() {
executeSoon(() => {
ok(isCaretPos(gPanel, 6, 7),
"The editor didn't jump to the correct line (4).");
is(gEditor.getSelectedText(), "eval",
is(gEditor.getSelection(), "eval",
"The editor didn't select the correct text (4).");
deferred.resolve();
@ -195,7 +195,7 @@ function testSearchTokenEmpty() {
"Not all the sources are shown after the global search (4).");
ok(isCaretPos(gPanel, 6, 7),
"The editor didn't remain at the correct line (4).");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor shouldn't keep the previous text selected (4).");
is(gSearchView.itemCount, 0,

View File

@ -100,7 +100,7 @@ function testClickLineToJump() {
ok(isCaretPos(gPanel, 1, 5),
"The editor didn't jump to the correct line (1).");
is(gEditor.getSelectedText(), "A",
is(gEditor.getSelection(), "A",
"The editor didn't select the correct text (1).");
ok(gSources.selectedValue.contains("-01.js"),
"The currently shown source is incorrect (1).");
@ -129,7 +129,7 @@ function testClickMatchToJump() {
ok(isCaretPos(gPanel, 6, 6),
"The editor didn't jump to the correct line (2).");
is(gEditor.getSelectedText(), "a",
is(gEditor.getSelection(), "a",
"The editor didn't select the correct text (2).");
ok(gSources.selectedValue.contains("-02.js"),
"The currently shown source is incorrect (2).");

View File

@ -8,7 +8,7 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
let gSources, gSearchView, gSearchBox;
function test() {
// Debug test slaves are a bit slow at this test.
@ -19,7 +19,6 @@ function test() {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gSearchView = gDebugger.DebuggerView.FilteredSources;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
@ -230,7 +229,6 @@ registerCleanupFunction(function() {
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gSources = null;
gSearchView = null;
gSearchBox = null;

View File

@ -8,7 +8,7 @@
const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources, gSourceUtils, gSearchView, gSearchBox;
let gSources, gSourceUtils, gSearchView, gSearchBox;
function test() {
// Debug test slaves are a bit slow at this test.
@ -19,7 +19,6 @@ function test() {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gSourceUtils = gDebugger.SourceUtils;
gSearchView = gDebugger.DebuggerView.FilteredSources;
@ -272,7 +271,6 @@ registerCleanupFunction(function() {
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gSources = null;
gSourceUtils = null;
gSearchView = null;

View File

@ -9,7 +9,7 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSearchBox, gSearchBoxPanel;
let gSearchBox, gSearchBoxPanel;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
@ -17,7 +17,6 @@ function test() {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel;
@ -56,7 +55,6 @@ registerCleanupFunction(function() {
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gSearchBox = null;
gSearchBoxPanel = null;
});

View File

@ -44,7 +44,7 @@ function tryShowPopup() {
"The editor caret position appears to be correct.");
ok(isEditorSel(gPanel, [125, 131]),
"The editor selection appears to be correct.");
is(gEditor.getSelectedText(), "Call()",
is(gEditor.getSelection(), "Call()",
"The editor selected text appears to be correct.");
is(gSearchBoxPanel.state, "closed",
@ -68,7 +68,7 @@ function testFocusLost() {
"The editor caret position appears to be correct after gaining focus.");
ok(isEditorSel(gPanel, [165, 165]),
"The editor selection appears to be correct after gaining focus.");
is(gEditor.getSelectedText(), "",
is(gEditor.getSelection(), "",
"The editor selected text appears to be correct after gaining focus.");
is(gSearchBoxPanel.state, "closed",

View File

@ -85,7 +85,7 @@ function test() {
let url = href + leaf;
let label = gUtils.getSourceLabel(url);
let trimmedLabel = gUtils.trimUrlLength(label);
gSources.push([trimmedLabel, url]);
gSources.push([trimmedLabel, url], { attachment: {}});
}
info("Source labels:");

View File

@ -132,7 +132,7 @@ function testAfterResume() {
"Should have no frames after resume.");
ok(isCaretPos(gPanel, 5),
"Editor caret location is correct after resume.");
is(gEditor.getDebugLocation(), -1,
is(gEditor.getDebugLocation(), null,
"Editor debug location is correct after resume.");
deferred.resolve();

View File

@ -9,7 +9,7 @@
const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gVariables, gSearchBox;
let gVariables, gSearchBox;
function test() {
// Debug test slaves are a bit slow at this test.
@ -20,7 +20,6 @@ function test() {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gVariables = gDebugger.DebuggerView.Variables;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
@ -233,7 +232,6 @@ registerCleanupFunction(function() {
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gVariables = null;
gSearchBox = null;
});

View File

@ -12,14 +12,13 @@ function test() {
requestLongerTimeout(2);
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gWatch, gVariables;
let gWatch, gVariables;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gWatch = gDebugger.DebuggerView.WatchExpressions;
gVariables = gDebugger.DebuggerView.Variables;

View File

@ -20,7 +20,6 @@ let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils
let { BrowserDebuggerProcess } = Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { SourceEditor } = Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", {});
let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
let TargetFactory = devtools.TargetFactory;
let Toolbox = devtools.Toolbox;
@ -246,9 +245,9 @@ function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) {
}
function waitForCaretUpdated(aPanel, aLine, aCol = 1) {
return waitForEditorEvents(aPanel, SourceEditor.EVENTS.SELECTION).then(() => {
let caret = aPanel.panelWin.DebuggerView.editor.getCaretPosition();
info("Caret updated: " + (caret.line + 1) + ", " + (caret.col + 1));
return waitForEditorEvents(aPanel, "cursorActivity").then(() => {
let cursor = aPanel.panelWin.DebuggerView.editor.getCursor();
info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
if (!isCaretPos(aPanel, aLine, aCol)) {
return waitForCaretUpdated(aPanel, aLine, aCol);
@ -272,16 +271,19 @@ function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) {
function isCaretPos(aPanel, aLine, aCol = 1) {
let editor = aPanel.panelWin.DebuggerView.editor;
let caret = editor.getCaretPosition();
let cursor = editor.getCursor();
// Source editor starts counting line and column numbers from 0.
info("Current editor caret position: " + (caret.line + 1) + ", " + (caret.col + 1));
return caret.line == (aLine - 1) && caret.col == (aCol - 1);
info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1);
}
function isEditorSel(aPanel, [start, end]) {
let editor = aPanel.panelWin.DebuggerView.editor;
let range = editor.getSelection();
let range = {
start: editor.getOffset(editor.getCursor("start")),
end: editor.getOffset(editor.getCursor())
};
// Source editor starts counting line and column numbers from 0.
info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1));
@ -336,12 +338,12 @@ function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) {
let editor = aPanel.panelWin.DebuggerView.editor;
let count = 0;
editor.addEventListener(aEventName, function onEvent(...aArgs) {
editor.on(aEventName, function onEvent(...aArgs) {
info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s).");
if (count == aEventRepeat) {
ok(true, "Enough '" + aEventName + "' editor events have been fired.");
editor.removeEventListener(aEventName, onEvent);
editor.off(aEventName, onEvent);
deferred.resolve.apply(deferred, aArgs);
}
});

View File

@ -32,12 +32,17 @@ browser.jar:
content/browser/devtools/codemirror/codemirror.js (sourceeditor/codemirror/codemirror.js)
content/browser/devtools/codemirror/codemirror.css (sourceeditor/codemirror/codemirror.css)
content/browser/devtools/codemirror/javascript.js (sourceeditor/codemirror/javascript.js)
content/browser/devtools/codemirror/xml.js (sourceeditor/codemirror/xml.js)
content/browser/devtools/codemirror/css.js (sourceeditor/codemirror/css.js)
content/browser/devtools/codemirror/htmlmixed.js (sourceeditor/codemirror/htmlmixed.js)
content/browser/devtools/codemirror/activeline.js (sourceeditor/codemirror/activeline.js)
content/browser/devtools/codemirror/matchbrackets.js (sourceeditor/codemirror/matchbrackets.js)
content/browser/devtools/codemirror/comment.js (sourceeditor/codemirror/comment.js)
content/browser/devtools/codemirror/searchcursor.js (sourceeditor/codemirror/search/searchcursor.js)
content/browser/devtools/codemirror/search.js (sourceeditor/codemirror/search/search.js)
content/browser/devtools/codemirror/dialog.js (sourceeditor/codemirror/dialog/dialog.js)
content/browser/devtools/codemirror/dialog.css (sourceeditor/codemirror/dialog/dialog.css)
content/browser/devtools/codemirror/mozilla.css (sourceeditor/codemirror/mozilla.css)
* content/browser/devtools/source-editor-overlay.xul (sourceeditor/source-editor-overlay.xul)
content/browser/devtools/debugger.xul (debugger/debugger.xul)
content/browser/devtools/debugger.css (debugger/debugger.css)

View File

@ -17,14 +17,13 @@ function test() {
let view = dbg.panelWin.DebuggerView;
is(view.Sources.selectedValue, data.uri, "URI is different");
is(view.editor.getCaretPosition().line, data.line - 1,
"Line is different");
is(view.editor.getCursor().line, data.line - 1, "Line is different");
// Test the case where script is already loaded.
view.editor.setCaretPosition(1);
view.editor.setCursor({ line: 1, ch: 1 });
gDevTools.showToolbox(target, "jsprofiler").then(function () {
panel.displaySource(data).then(function onOpenAgain() {
is(view.editor.getCaretPosition().line, data.line - 1,
is(view.editor.getCursor().line, data.line - 1,
"Line is different");
tearDown(tab);
});

View File

@ -102,7 +102,7 @@ function fileAImported(aStatus, aFileContent)
is(gScratchpad.getText(), gFileAContent, "the editor content is correct");
gScratchpad.editor.replaceText("new text",
gScratchpad.editor.posFromIndex(gScratchpad.getText().length));
gScratchpad.editor.getPosition(gScratchpad.getText().length));
is(gScratchpad.getText(), gFileAContent + "new text", "text updated correctly");
gScratchpad.undo();
@ -131,7 +131,7 @@ function fileBImported(aStatus, aFileContent)
"the editor content is still correct after undo");
gScratchpad.editor.replaceText("new text",
gScratchpad.editor.posFromIndex(gScratchpad.getText().length));
gScratchpad.editor.getPosition(gScratchpad.getText().length));
is(gScratchpad.getText(), gFileBContent + "new text", "text updated correctly");
gScratchpad.undo();

View File

@ -0,0 +1,40 @@
/* vim:set ts=2 sw=2 sts=2 et tw=80:
* 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/. */
(function () {
"use strict";
const WRAP_CLASS = "CodeMirror-activeline";
const BACK_CLASS = "CodeMirror-activeline-background";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
updateActiveLine(cm);
cm.on("cursorActivity", updateActiveLine);
} else if (!val && prev) {
cm.off("cursorActivity", updateActiveLine);
clearActiveLine(cm);
delete cm.state.activeLine;
}
});
function clearActiveLine(cm) {
if ("activeLine" in cm.state) {
cm.removeLineClass(cm.state.activeLine, "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLine, "background", BACK_CLASS);
}
}
function updateActiveLine(cm) {
var line = cm.getLineHandleVisualStart(cm.getCursor().line);
if (cm.state.activeLine == line) return;
clearActiveLine(cm);
cm.addLineClass(line, "wrap", WRAP_CLASS);
cm.addLineClass(line, "background", BACK_CLASS);
cm.state.activeLine = line;
}
})();

View File

@ -0,0 +1,623 @@
CodeMirror.defineMode("css", function(config) {
return CodeMirror.getMode(config, "text/css");
});
CodeMirror.defineMode("css-base", function(config, parserConfig) {
"use strict";
var indentUnit = config.indentUnit,
hooks = parserConfig.hooks || {},
atMediaTypes = parserConfig.atMediaTypes || {},
atMediaFeatures = parserConfig.atMediaFeatures || {},
propertyKeywords = parserConfig.propertyKeywords || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
allowNested = !!parserConfig.allowNested,
type = null;
function ret(style, tp) { type = tp; return style; }
function tokenBase(stream, state) {
var ch = stream.next();
if (hooks[ch]) {
// result[0] is style and result[1] is type
var result = hooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());}
else if (ch == "=") ret(null, "compare");
else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
else if (ch == "\"" || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
else if (ch == "#") {
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "hash");
}
else if (ch == "!") {
stream.match(/^\s*\w*/);
return ret("keyword", "important");
}
else if (/\d/.test(ch)) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
}
else if (ch === "-") {
if (/\d/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (stream.match(/^[^-]+-/)) {
return ret("meta", "meta");
}
}
else if (/[,+>*\/]/.test(ch)) {
return ret(null, "select-op");
}
else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
return ret("qualifier", "qualifier");
}
else if (ch == ":") {
return ret("operator", ch);
}
else if (/[;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
}
else if (ch == "u" && stream.match("rl(")) {
stream.backUp(1);
state.tokenize = tokenParenthesized;
return ret("property", "variable");
}
else {
stream.eatWhile(/[\w\\\-]/);
return ret("property", "variable");
}
}
function tokenString(quote, nonInclusive) {
return function(stream, state) {
var escaped = false, ch;
while ((ch = stream.next()) != null) {
if (ch == quote && !escaped)
break;
escaped = !escaped && ch == "\\";
}
if (!escaped) {
if (nonInclusive) stream.backUp(1);
state.tokenize = tokenBase;
}
return ret("string", "string");
};
}
function tokenParenthesized(stream, state) {
stream.next(); // Must be '('
if (!stream.match(/\s*[\"\']/, false))
state.tokenize = tokenString(")", true);
else
state.tokenize = tokenBase;
return ret(null, "(");
}
return {
startState: function(base) {
return {tokenize: tokenBase,
baseIndent: base || 0,
stack: [],
lastToken: null};
},
token: function(stream, state) {
// Use these terms when applicable (see http://www.xanthir.com/blog/b4E50)
//
// rule** or **ruleset:
// A selector + braces combo, or an at-rule.
//
// declaration block:
// A sequence of declarations.
//
// declaration:
// A property + colon + value combo.
//
// property value:
// The entire value of a property.
//
// component value:
// A single piece of a property value. Like the 5px in
// text-shadow: 0 0 5px blue;. Can also refer to things that are
// multiple terms, like the 1-4 terms that make up the background-size
// portion of the background shorthand.
//
// term:
// The basic unit of author-facing CSS, like a single number (5),
// dimension (5px), string ("foo"), or function. Officially defined
// by the CSS 2.1 grammar (look for the 'term' production)
//
//
// simple selector:
// A single atomic selector, like a type selector, an attr selector, a
// class selector, etc.
//
// compound selector:
// One or more simple selectors without a combinator. div.example is
// compound, div > .example is not.
//
// complex selector:
// One or more compound selectors chained with combinators.
//
// combinator:
// The parts of selectors that express relationships. There are four
// currently - the space (descendant combinator), the greater-than
// bracket (child combinator), the plus sign (next sibling combinator),
// and the tilda (following sibling combinator).
//
// sequence of selectors:
// One or more of the named type of selector chained with commas.
state.tokenize = state.tokenize || tokenBase;
if (state.tokenize == tokenBase && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (style && typeof style != "string") style = ret(style[0], style[1]);
// Changing style returned based on context
var context = state.stack[state.stack.length-1];
if (style == "variable") {
if (type == "variable-definition") state.stack.push("propertyValue");
return state.lastToken = "variable-2";
} else if (style == "property") {
var word = stream.current().toLowerCase();
if (context == "propertyValue") {
if (valueKeywords.hasOwnProperty(word)) {
style = "string-2";
} else if (colorKeywords.hasOwnProperty(word)) {
style = "keyword";
} else {
style = "variable-2";
}
} else if (context == "rule") {
if (!propertyKeywords.hasOwnProperty(word)) {
style += " error";
}
} else if (context == "block") {
// if a value is present in both property, value, or color, the order
// of preference is property -> color -> value
if (propertyKeywords.hasOwnProperty(word)) {
style = "property";
} else if (colorKeywords.hasOwnProperty(word)) {
style = "keyword";
} else if (valueKeywords.hasOwnProperty(word)) {
style = "string-2";
} else {
style = "tag";
}
} else if (!context || context == "@media{") {
style = "tag";
} else if (context == "@media") {
if (atMediaTypes[stream.current()]) {
style = "attribute"; // Known attribute
} else if (/^(only|not)$/.test(word)) {
style = "keyword";
} else if (word == "and") {
style = "error"; // "and" is only allowed in @mediaType
} else if (atMediaFeatures.hasOwnProperty(word)) {
style = "error"; // Known property, should be in @mediaType(
} else {
// Unknown, expecting keyword or attribute, assuming attribute
style = "attribute error";
}
} else if (context == "@mediaType") {
if (atMediaTypes.hasOwnProperty(word)) {
style = "attribute";
} else if (word == "and") {
style = "operator";
} else if (/^(only|not)$/.test(word)) {
style = "error"; // Only allowed in @media
} else {
// Unknown attribute or property, but expecting property (preceded
// by "and"). Should be in parentheses
style = "error";
}
} else if (context == "@mediaType(") {
if (propertyKeywords.hasOwnProperty(word)) {
// do nothing, remains "property"
} else if (atMediaTypes.hasOwnProperty(word)) {
style = "error"; // Known property, should be in parentheses
} else if (word == "and") {
style = "operator";
} else if (/^(only|not)$/.test(word)) {
style = "error"; // Only allowed in @media
} else {
style += " error";
}
} else if (context == "@import") {
style = "tag";
} else {
style = "error";
}
} else if (style == "atom") {
if(!context || context == "@media{" || context == "block") {
style = "builtin";
} else if (context == "propertyValue") {
if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
style += " error";
}
} else {
style = "error";
}
} else if (context == "@media" && type == "{") {
style = "error";
}
// Push/pop context stack
if (type == "{") {
if (context == "@media" || context == "@mediaType") {
state.stack[state.stack.length-1] = "@media{";
}
else {
var newContext = allowNested ? "block" : "rule";
state.stack.push(newContext);
}
}
else if (type == "}") {
if (context == "interpolation") style = "operator";
state.stack.pop();
if (context == "propertyValue") state.stack.pop();
}
else if (type == "interpolation") state.stack.push("interpolation");
else if (type == "@media") state.stack.push("@media");
else if (type == "@import") state.stack.push("@import");
else if (context == "@media" && /\b(keyword|attribute)\b/.test(style))
state.stack[state.stack.length-1] = "@mediaType";
else if (context == "@mediaType" && stream.current() == ",")
state.stack[state.stack.length-1] = "@media";
else if (type == "(") {
if (context == "@media" || context == "@mediaType") {
// Make sure @mediaType is used to avoid error on {
state.stack[state.stack.length-1] = "@mediaType";
state.stack.push("@mediaType(");
}
}
else if (type == ")") {
if (context == "propertyValue" && state.stack[state.stack.length-2] == "@mediaType(") {
// In @mediaType( without closing ; after propertyValue
state.stack.pop();
state.stack.pop();
}
else if (context == "@mediaType(") {
state.stack.pop();
}
}
else if (type == ":" && state.lastToken == "property") state.stack.push("propertyValue");
else if (context == "propertyValue" && type == ";") state.stack.pop();
else if (context == "@import" && type == ";") state.stack.pop();
return state.lastToken = style;
},
indent: function(state, textAfter) {
var n = state.stack.length;
if (/^\}/.test(textAfter))
n -= state.stack[n-1] == "propertyValue" ? 2 : 1;
return state.baseIndent + n * indentUnit;
},
electricChars: "}",
blockCommentStart: "/*",
blockCommentEnd: "*/",
fold: "brace"
};
});
(function() {
function keySet(array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
keys[array[i]] = true;
}
return keys;
}
var atMediaTypes = keySet([
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
]);
var atMediaFeatures = keySet([
"width", "min-width", "max-width", "height", "min-height", "max-height",
"device-width", "min-device-width", "max-device-width", "device-height",
"min-device-height", "max-device-height", "aspect-ratio",
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
"max-color", "color-index", "min-color-index", "max-color-index",
"monochrome", "min-monochrome", "max-monochrome", "resolution",
"min-resolution", "max-resolution", "scan", "grid"
]);
var propertyKeywords = keySet([
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-iteration-count",
"animation-name", "animation-play-state", "animation-timing-function",
"appearance", "azimuth", "backface-visibility", "background",
"background-attachment", "background-clip", "background-color",
"background-image", "background-origin", "background-position",
"background-repeat", "background-size", "baseline-shift", "binding",
"bleed", "bookmark-label", "bookmark-level", "bookmark-state",
"bookmark-target", "border", "border-bottom", "border-bottom-color",
"border-bottom-left-radius", "border-bottom-right-radius",
"border-bottom-style", "border-bottom-width", "border-collapse",
"border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color",
"border-left-style", "border-left-width", "border-radius", "border-right",
"border-right-color", "border-right-style", "border-right-width",
"border-spacing", "border-style", "border-top", "border-top-color",
"border-top-left-radius", "border-top-right-radius", "border-top-style",
"border-top-width", "border-width", "bottom", "box-decoration-break",
"box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
"caption-side", "clear", "clip", "color", "color-profile", "column-count",
"column-fill", "column-gap", "column-rule", "column-rule-color",
"column-rule-style", "column-rule-width", "column-span", "column-width",
"columns", "content", "counter-increment", "counter-reset", "crop", "cue",
"cue-after", "cue-before", "cursor", "direction", "display",
"dominant-baseline", "drop-initial-after-adjust",
"drop-initial-after-align", "drop-initial-before-adjust",
"drop-initial-before-align", "drop-initial-size", "drop-initial-value",
"elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
"flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
"float", "float-offset", "font", "font-feature-settings", "font-family",
"font-kerning", "font-language-override", "font-size", "font-size-adjust",
"font-stretch", "font-style", "font-synthesis", "font-variant",
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid-cell", "grid-column", "grid-column-align",
"grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow",
"grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span",
"grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens",
"icon", "image-orientation", "image-rendering", "image-resolution",
"inline-box-align", "justify-content", "left", "letter-spacing",
"line-break", "line-height", "line-stacking", "line-stacking-ruby",
"line-stacking-shift", "line-stacking-strategy", "list-style",
"list-style-image", "list-style-position", "list-style-type", "margin",
"margin-bottom", "margin-left", "margin-right", "margin-top",
"marker-offset", "marks", "marquee-direction", "marquee-loop",
"marquee-play-count", "marquee-speed", "marquee-style", "max-height",
"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
"nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline",
"outline-color", "outline-offset", "outline-style", "outline-width",
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
"page", "page-break-after", "page-break-before", "page-break-inside",
"page-policy", "pause", "pause-after", "pause-before", "perspective",
"perspective-origin", "pitch", "pitch-range", "play-during", "position",
"presentation-level", "punctuation-trim", "quotes", "rendering-intent",
"resize", "rest", "rest-after", "rest-before", "richness", "right",
"rotation", "rotation-point", "ruby-align", "ruby-overhang",
"ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header",
"speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
"tab-size", "table-layout", "target", "target-name", "target-new",
"target-position", "text-align", "text-align-last", "text-decoration",
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
"text-decoration-style", "text-emphasis", "text-emphasis-color",
"text-emphasis-position", "text-emphasis-style", "text-height",
"text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
"text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
"text-wrap", "top", "transform", "transform-origin", "transform-style",
"transition", "transition-delay", "transition-duration",
"transition-property", "transition-timing-function", "unicode-bidi",
"vertical-align", "visibility", "voice-balance", "voice-duration",
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
"voice-volume", "volume", "white-space", "widows", "width", "word-break",
"word-spacing", "word-wrap", "z-index", "zoom",
// SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
"color-interpolation", "color-interpolation-filters", "color-profile",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
"glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode"
]);
var colorKeywords = keySet([
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
"gold", "goldenrod", "gray", "green", "greenyellow", "honeydew",
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
"purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon",
"sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen"
]);
var valueKeywords = keySet([
"above", "absolute", "activeborder", "activecaption", "afar",
"after-white-space", "ahead", "alias", "all", "all-scroll", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
"arabic-indic", "armenian", "asterisks", "auto", "avoid", "background",
"backwards", "baseline", "below", "bidi-override", "binary", "bengali",
"blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
"both", "bottom", "break-all", "break-word", "button", "button-bevel",
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
"cell", "center", "checkbox", "circle", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
"col-resize", "collapse", "compact", "condensed", "contain", "content",
"content-box", "context-menu", "continuous", "copy", "cover", "crop",
"cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari",
"disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted",
"double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
"ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer",
"landscape", "lao", "large", "larger", "left", "level", "lighter",
"line-through", "linear", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
"lower-roman", "lowercase", "ltr", "malayalam", "match",
"media-controls-background", "media-current-time-display",
"media-fullscreen-button", "media-mute-button", "media-play-button",
"media-return-to-realtime-button", "media-rewind-button",
"media-seek-back-button", "media-seek-forward-button", "media-slider",
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button", "menulist-text",
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
"mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
"ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "overlay", "overline", "padding", "padding-box", "painted",
"paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait",
"pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button",
"radio", "read-only", "read-write", "read-write-plaintext-only", "relative",
"repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba",
"ridge", "right", "round", "row-resize", "rtl", "run-in", "running",
"s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration",
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
"single", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
"small", "small-caps", "small-caption", "smaller", "solid", "somali",
"source-atop", "source-in", "source-out", "source-over", "space", "square",
"square-button", "start", "static", "status-bar", "stretch", "stroke",
"sub", "subpixel-antialiased", "super", "sw-resize", "table",
"table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group",
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
"vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
"window", "windowframe", "windowtext", "x-large", "x-small", "xor",
"xx-large", "xx-small"
]);
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return ["comment", "comment"];
}
CodeMirror.defineMIME("text/css", {
atMediaTypes: atMediaTypes,
atMediaFeatures: atMediaFeatures,
propertyKeywords: propertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
hooks: {
"<": function(stream, state) {
function tokenSGMLComment(stream, state) {
var dashes = 0, ch;
while ((ch = stream.next()) != null) {
if (dashes >= 2 && ch == ">") {
state.tokenize = null;
break;
}
dashes = (ch == "-") ? dashes + 1 : 0;
}
return ["comment", "comment"];
}
if (stream.eat("!")) {
state.tokenize = tokenSGMLComment;
return tokenSGMLComment(stream, state);
}
},
"/": function(stream, state) {
if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
return false;
}
},
name: "css-base"
});
CodeMirror.defineMIME("text/x-scss", {
atMediaTypes: atMediaTypes,
atMediaFeatures: atMediaFeatures,
propertyKeywords: propertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
allowNested: true,
hooks: {
"$": function(stream) {
stream.match(/^[\w-]+/);
if (stream.peek() == ":") {
return ["variable", "variable-definition"];
}
return ["variable", "variable"];
},
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
"#": function(stream) {
if (stream.eat("{")) {
return ["operator", "interpolation"];
} else {
stream.eatWhile(/[\w\\\-]/);
return ["atom", "hash"];
}
}
},
name: "css-base"
});
})();

View File

@ -0,0 +1,104 @@
CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
var cssMode = CodeMirror.getMode(config, "css");
var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
mode: CodeMirror.getMode(config, "javascript")});
if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
var conf = scriptTypesConf[i];
scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
}
scriptTypes.push({matches: /./,
mode: CodeMirror.getMode(config, "text/plain")});
function html(stream, state) {
var tagName = state.htmlState.tagName;
var style = htmlMode.token(stream, state.htmlState);
if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
// Script block: mode to change to depends on type attribute
var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
scriptType = scriptType ? scriptType[1] : "";
if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
for (var i = 0; i < scriptTypes.length; ++i) {
var tp = scriptTypes[i];
if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
if (tp.mode) {
state.token = script;
state.localMode = tp.mode;
state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
}
break;
}
}
} else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
state.token = css;
state.localMode = cssMode;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
}
return style;
}
function maybeBackup(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat), m;
if (close > -1) stream.backUp(cur.length - close);
else if (m = cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur[0]);
}
return style;
}
function script(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = state.localMode = null;
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*script\s*>/,
state.localMode.token(stream, state.localState));
}
function css(stream, state) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = state.localMode = null;
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*style\s*>/,
cssMode.token(stream, state.localState));
}
return {
startState: function() {
var state = htmlMode.startState();
return {token: html, localMode: null, localState: null, htmlState: state};
},
copyState: function(state) {
if (state.localState)
var local = CodeMirror.copyState(state.localMode, state.localState);
return {token: state.token, localMode: state.localMode, localState: local,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function(stream, state) {
return state.token(stream, state);
},
indent: function(state, textAfter) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter);
else
return CodeMirror.Pass;
},
electricChars: "/{}:",
innerMode: function(state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");

View File

@ -0,0 +1,30 @@
.breakpoints {
width: 16px;
}
.breakpoint, .debugLocation, .breakpoint-debugLocation {
display: inline-block;
margin-left: 5px;
width: 14px;
height: 14px;
background-repeat: no-repeat;
background-position: center center;
background-size: 12px;
}
.breakpoint {
background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.debugLocation {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
}
.breakpoint.debugLocation {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.CodeMirror-activeline-background {
background: #e8f2ff;
}

View File

@ -0,0 +1,341 @@
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag || true;
var Kludges = parserConfig.htmlMode ? {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true
} : {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false
};
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
var tagName, type;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
} else if (stream.match("--")) {
return chain(inBlock("comment", "-->"));
} else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else {
return null;
}
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
} else {
var isClose = stream.eat("/");
tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
if (!tagName) return "error";
type = isClose ? "closeTag" : "openTag";
state.tokenize = inTag;
return "tag";
}
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
} else {
stream.eatWhile(/[^&<]/);
return null;
}
}
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag";
} else if (ch == "=") {
type = "equals";
return null;
} else if (ch == "<") {
return "error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
state.stringStartCol = stream.column();
return state.tokenize(stream, state);
} else {
stream.eatWhile(/[^\s\u00a0=<>\"\']/);
return "word";
}
}
function inAttribute(quote) {
var closure = function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
closure.isInAttribute = true;
return closure;
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
};
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
var curState, curStream, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
curState.context = {
prev: curState.context,
tagName: tagName,
indent: curState.indented,
startOfLine: startOfLine,
noIndent: noIndent
};
}
function popContext() {
if (curState.context) curState.context = curState.context.prev;
}
function element(type) {
if (type == "openTag") {
curState.tagName = tagName;
curState.tagStart = curStream.column();
return cont(attributes, endtag(curState.startOfLine));
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
if (curState.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
popContext();
}
err = !curState.context || curState.context.tagName != tagName;
}
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
var tagName = curState.tagName;
curState.tagName = curState.tagStart = null;
if (type == "selfcloseTag" ||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
maybePopContext(tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
maybePopContext(tagName.toLowerCase());
pushContext(tagName, startOfLine);
return cont();
}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err) setStyle = "error";
if (type == "endTag") { popContext(); return cont(); }
setStyle = "error";
return cont(arguments.callee);
};
}
function maybePopContext(nextTagName) {
var parentTagName;
while (true) {
if (!curState.context) {
return;
}
parentTagName = curState.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext();
}
}
function attributes(type) {
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
if (type == "endTag" || type == "selfcloseTag") return pass();
setStyle = "error";
return cont(attributes);
}
function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
if (!Kludges.allowMissing) setStyle = "error";
else if (type == "word") setStyle = "attribute";
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
}
function attvalue(type) {
if (type == "string") return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
else return pass();
}
return {
startState: function() {
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null};
},
token: function(stream, state) {
if (!state.tagName && stream.sol()) {
state.startOfLine = true;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
setStyle = type = tagName = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state; curStream = stream;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style)) break;
}
}
state.startOfLine = false;
return setStyle || style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
// Indent multi-line strings (e.g. css).
if (state.tokenize.isInAttribute) {
return state.stringStartCol + 1;
}
if ((state.tokenize != inTag && state.tokenize != inText) ||
context && context.noIndent)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
if (multilineTagIndentPastTag)
return state.tagStart + state.tagName.length + 2;
else
return state.tagStart + indentUnit * multilineTagIndentFactor;
}
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
if (context && /^<\//.test(textAfter))
context = context.prev;
while (context && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return 0;
},
electricChars: "/",
blockCommentStart: "<!--",
blockCommentEnd: "-->",
configuration: parserConfig.htmlMode ? "html" : "xml",
helperType: parserConfig.htmlMode ? "html" : "xml"
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});

View File

@ -0,0 +1,267 @@
/* 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/. */
"use strict";
const dbginfo = new WeakMap();
// Private functions
/**
* Adds a marker to the breakpoints gutter.
* Type should be either a 'breakpoint' or a 'debugLocation'.
*/
function addMarker(cm, line, type) {
let info = cm.lineInfo(line);
if (info.gutterMarkers)
return void info.gutterMarkers.breakpoints.classList.add(type);
let mark = cm.getWrapperElement().ownerDocument.createElement("div");
mark.className = type;
mark.innerHTML = "";
cm.setGutterMarker(info.line, "breakpoints", mark);
}
/**
* Removes a marker from the breakpoints gutter.
* Type should be either a 'breakpoint' or a 'debugLocation'.
*/
function removeMarker(cm, line, type) {
let info = cm.lineInfo(line);
if (!info || !info.gutterMarkers)
return;
info.gutterMarkers.breakpoints.classList.remove(type);
}
// These functions implement search within the debugger. Since
// search in the debugger is different from other components,
// we can't use search.js CodeMirror addon. This is a slightly
// modified version of that addon. Depends on searchcursor.js.
function SearchState() {
this.posFrom = this.posTo = this.query = null;
}
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
function getSearchCursor(cm, query, pos) {
// If the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos,
typeof query == "string" && query == query.toLowerCase());
}
/**
* If there's a saved search, selects the next results.
* Otherwise, creates a new search and selects the first
* result.
*/
function doSearch(cm, rev, query) {
let state = getSearchState(cm);
if (state.query)
return searchNext(cm, rev);
cm.operation(function () {
if (state.query) return;
state.query = query;
state.posFrom = state.posTo = { line: 0, ch: 0 };
searchNext(cm, rev);
});
}
/**
* Selects the next result of a saved search.
*/
function searchNext(cm, rev) {
cm.operation(function () {
let state = getSearchState(cm)
let cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
cursor = getSearchCursor(cm, state.query, rev ?
{ line: cm.lastLine(), ch: null } : { line: cm.firstLine(), ch: 0 });
if (!cursor.find(rev))
return;
}
cm.setSelection(cursor.from(), cursor.to());
state.posFrom = cursor.from();
state.posTo = cursor.to();
});
}
/**
* Clears the currently saved search.
*/
function clearSearch(cm) {
let state = getSearchState(cm);
if (!state.query)
return;
state.query = null;
}
// Exported functions
/**
* This function is called whenever Editor is extended with functions
* from this module. See Editor.extend for more info.
*/
function initialize(ctx) {
let { ed } = ctx;
dbginfo.set(ed, {
breakpoints: {},
debugLocation: null
});
}
/**
* True if editor has a visual breakpoint at that line, false
* otherwise.
*/
function hasBreakpoint(ctx, line) {
let { cm } = ctx;
let markers = cm.lineInfo(line).gutterMarkers;
return markers != null &&
markers.breakpoints.classList.contains("breakpoint");
}
/**
* Adds a visual breakpoint for a specified line. Third
* parameter 'cond' can hold any object.
*
* After adding a breakpoint, this function makes Editor to
* emit a breakpointAdded event.
*/
function addBreakpoint(ctx, line, cond) {
if (hasBreakpoint(ctx, line))
return;
let { ed, cm } = ctx;
let meta = dbginfo.get(ed);
let info = cm.lineInfo(line);
addMarker(cm, line, "breakpoint");
meta.breakpoints[line] = { condition: cond };
info.handle.on("delete", function onDelete() {
info.handle.off("delete", onDelete);
meta.breakpoints[info.line] = null;
});
ed.emit("breakpointAdded", line);
}
/**
* Removes a visual breakpoint from a specified line and
* makes Editor to emit a breakpointRemoved event.
*/
function removeBreakpoint(ctx, line) {
if (!hasBreakpoint(ctx, line))
return;
let { ed, cm } = ctx;
let meta = dbginfo.get(ed);
let info = cm.lineInfo(line);
meta.breakpoints[info.line] = null;
removeMarker(cm, info.line, "breakpoint");
ed.emit("breakpointRemoved", line);
}
/**
* Returns a list of all breakpoints in the current Editor.
*/
function getBreakpoints(ctx) {
let { ed } = ctx;
let meta = dbginfo.get(ed);
return Object.keys(meta.breakpoints).reduce((acc, line) => {
if (meta.breakpoints[line] != null)
acc.push({ line: line, condition: meta.breakpoints[line].condition });
return acc;
}, []);
}
/**
* Saves a debug location information and adds a visual anchor to
* the breakpoints gutter. This is used by the debugger UI to
* display the line on which the Debugger is currently paused.
*/
function setDebugLocation(ctx, line) {
let { ed, cm } = ctx;
let meta = dbginfo.get(ed);
meta.debugLocation = line;
addMarker(cm, line, "debugLocation");
}
/**
* Returns a line number that corresponds to the current debug
* location.
*/
function getDebugLocation(ctx) {
let { ed } = ctx;
let meta = dbginfo.get(ed);
return meta.debugLocation;
}
/**
* Clears the debug location. Clearing the debug location
* also removes a visual anchor from the breakpoints gutter.
*/
function clearDebugLocation(ctx) {
let { ed, cm } = ctx;
let meta = dbginfo.get(ed);
if (meta.debugLocation != null) {
removeMarker(cm, meta.debugLocation, "debugLocation");
meta.debugLocation = null;
}
}
/**
* Starts a new search.
*/
function find(ctx, query) {
let { cm } = ctx;
clearSearch(cm);
doSearch(cm, false, query);
}
/**
* Finds the next item based on the currently saved search.
*/
function findNext(ctx, query) {
let { cm } = ctx;
doSearch(cm, false, query);
}
/**
* Finds the previous item based on the currently saved search.
*/
function findPrev(ctx, query) {
let { cm } = ctx;
doSearch(cm, true, query);
}
// Export functions
[
initialize, hasBreakpoint, addBreakpoint, removeBreakpoint,
getBreakpoints, setDebugLocation, getDebugLocation,
clearDebugLocation, find, findNext, findPrev
].forEach(function (func) { module.exports[func.name] = func; });

View File

@ -24,7 +24,8 @@ const L10N = Services.strings.createBundle(L10N_BUNDLE);
const CM_STYLES = [
"chrome://browser/content/devtools/codemirror/codemirror.css",
"chrome://browser/content/devtools/codemirror/dialog.css"
"chrome://browser/content/devtools/codemirror/dialog.css",
"chrome://browser/content/devtools/codemirror/mozilla.css"
];
const CM_SCRIPTS = [
@ -33,7 +34,12 @@ const CM_SCRIPTS = [
"chrome://browser/content/devtools/codemirror/searchcursor.js",
"chrome://browser/content/devtools/codemirror/search.js",
"chrome://browser/content/devtools/codemirror/matchbrackets.js",
"chrome://browser/content/devtools/codemirror/comment.js"
"chrome://browser/content/devtools/codemirror/comment.js",
"chrome://browser/content/devtools/codemirror/javascript.js",
"chrome://browser/content/devtools/codemirror/xml.js",
"chrome://browser/content/devtools/codemirror/css.js",
"chrome://browser/content/devtools/codemirror/htmlmixed.js",
"chrome://browser/content/devtools/codemirror/activeline.js"
];
const CM_IFRAME =
@ -62,8 +68,9 @@ const CM_MAPPING = [
"undo",
"redo",
"clearHistory",
"posFromIndex",
"openDialog"
"openDialog",
"cursorCoords",
"lineCount"
];
const CM_JUMP_DIALOG = [
@ -75,7 +82,9 @@ const editors = new WeakMap();
Editor.modes = {
text: { name: "text" },
js: { name: "javascript", url: "chrome://browser/content/devtools/codemirror/javascript.js" }
js: { name: "javascript" },
html: { name: "htmlmixed" },
css: { name: "css" }
};
function ctrl(k) {
@ -109,14 +118,15 @@ function Editor(config) {
this.version = null;
this.config = {
value: "",
mode: Editor.modes.text,
indentUnit: tabSize,
tabSize: tabSize,
contextMenu: null,
matchBrackets: true,
extraKeys: {},
indentWithTabs: useTabs,
value: "",
mode: Editor.modes.text,
indentUnit: tabSize,
tabSize: tabSize,
contextMenu: null,
matchBrackets: true,
extraKeys: {},
indentWithTabs: useTabs,
styleActiveLine: true
};
// Overwrite default config with user-provided, if needed.
@ -151,8 +161,9 @@ function Editor(config) {
}
Editor.prototype = {
container: null,
version: null,
config: null,
config: null,
/**
* Appends the current Editor instance to the element specified by
@ -174,17 +185,13 @@ Editor.prototype = {
let onLoad = () => {
// Once the iframe is loaded, we can inject CodeMirror
// and its dependencies into its DOM.
env.removeEventListener("load", onLoad, true);
let win = env.contentWindow.wrappedJSObject;
CM_SCRIPTS.forEach((url) =>
Services.scriptloader.loadSubScript(url, win, "utf8"));
// Plain text mode doesn't need any additional files,
// all other modes (js, html, etc.) do.
if (this.config.mode.name !== "text")
Services.scriptloader.loadSubScript(this.config.mode.url, win, "utf8");
// Create a CodeMirror instance add support for context menus and
// overwrite the default controller (otherwise items in the top and
// context menus won't work).
@ -192,12 +199,17 @@ Editor.prototype = {
cm = win.CodeMirror(win.document.body, this.config);
cm.getWrapperElement().addEventListener("contextmenu", (ev) => {
ev.preventDefault();
this.emit("contextMenu");
this.showContextMenu(doc, ev.screenX, ev.screenY);
}, false);
cm.on("change", () => this.emit("change"));
cm.on("gutterClick", (cm, line) => this.emit("gutterClick", line));
cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
doc.defaultView.controllers.insertControllerAt(0, controller(this, doc.defaultView));
this.container = env;
editors.set(this, cm);
def.resolve();
};
@ -252,11 +264,13 @@ Editor.prototype = {
},
/**
* Returns text from the text area.
* Returns text from the text area. If line argument is provided
* the method returns only that line.
*/
getText: function () {
getText: function (line) {
let cm = editors.get(this);
return cm.getValue();
return line == null ?
cm.getValue() : (cm.lineInfo(line) ? cm.lineInfo(line).text : "");
},
/**
@ -268,6 +282,32 @@ Editor.prototype = {
cm.setValue(value);
},
/**
* Changes the value of a currently used highlighting mode.
* See Editor.modes for the list of all suppoert modes.
*/
setMode: function (value) {
let cm = editors.get(this);
cm.setOption("mode", value);
},
/**
* Returns the currently active highlighting mode.
* See Editor.modes for the list of all suppoert modes.
*/
getMode: function () {
let cm = editors.get(this);
return cm.getOption("mode");
},
/**
* True if the editor is in the read-only mode, false otherwise.
*/
isReadOnly: function () {
let cm = editors.get(this);
return cm.getOption("readOnly");
},
/**
* Replaces contents of a text area within the from/to {line, ch}
* range. If neither from nor to arguments are provided works
@ -340,8 +380,59 @@ Editor.prototype = {
this.setCursor({ line: line - 1, ch: 0 }));
},
/**
* Returns a {line, ch} object that corresponds to the
* left, top coordinates.
*/
getPositionFromCoords: function (left, top) {
let cm = editors.get(this);
return cm.coordsChar({ left: left, top: top });
},
/**
* Extends the current selection to the position specified
* by the provided {line, ch} object.
*/
extendSelection: function (pos) {
let cm = editors.get(this);
let cursor = cm.indexFromPos(cm.getCursor());
let anchor = cm.posFromIndex(cursor + pos.start);
let head = cm.posFromIndex(cursor + pos.start + pos.length);
cm.setSelection(anchor, head);
},
/**
* Extends an instance of the Editor object with additional
* functions. Each function will be called with context as
* the first argument. Context is a {ed, cm} object where
* 'ed' is an instance of the Editor object and 'cm' is an
* instance of the CodeMirror object. Example:
*
* function hello(ctx, name) {
* let { cm, ed } = ctx;
* cm; // CodeMirror instance
* ed; // Editor instance
* name; // 'Mozilla'
* }
*
* editor.extend({ hello: hello });
* editor.hello('Mozilla');
*/
extend: function (funcs) {
Object.keys(funcs).forEach((name) => {
let cm = editors.get(this);
let ctx = { ed: this, cm: cm };
if (name === "initialize")
return void funcs[name](ctx);
this[name] = funcs[name].bind(null, ctx);
});
},
destroy: function () {
this.config = null;
this.container = null;
this.config = null;
this.version = null;
this.emit("destroy");
}
@ -365,8 +456,6 @@ CM_MAPPING.forEach(function (name) {
function controller(ed, view) {
return {
supportsCommand: function (cmd) {
let cm = editors.get(ed);
switch (cmd) {
case "cmd_find":
case "cmd_findAgain":

View File

@ -9,6 +9,7 @@ TEST_DIRS += ['test']
JS_MODULES_PATH = 'modules/devtools/sourceeditor'
EXTRA_JS_MODULES += [
'debugger.js',
'editor.js',
'source-editor-orion.jsm',
'source-editor-ui.jsm',

View File

@ -101,7 +101,7 @@ function checkCorrectLine(aCallback)
{
let debuggerView = dbg.panelWin.DebuggerView;
if (debuggerView.editor &&
debuggerView.editor.getCaretPosition().line == line - 1) {
debuggerView.editor.getCursor().line == line - 1) {
return true;
}
return false;