зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central
This commit is contained in:
Коммит
9e90608ff7
|
@ -130,13 +130,13 @@ const WorkerSandbox = EventEmitter.compose({
|
|||
// Even if this principal is for a domain that is specified in the multiple
|
||||
// domain principal.
|
||||
let principals = window;
|
||||
let wantDOMConstructors = []
|
||||
let wantGlobalProperties = []
|
||||
if (EXPANDED_PRINCIPALS.length > 0 && !worker._injectInDocument) {
|
||||
principals = EXPANDED_PRINCIPALS.concat(window);
|
||||
// We have to replace XHR constructor of the content document
|
||||
// with a custom cross origin one, automagically added by platform code:
|
||||
delete proto.XMLHttpRequest;
|
||||
wantDOMConstructors.push("XMLHttpRequest");
|
||||
wantGlobalProperties.push("XMLHttpRequest");
|
||||
}
|
||||
|
||||
// Instantiate trusted code in another Sandbox in order to prevent content
|
||||
|
@ -149,7 +149,7 @@ const WorkerSandbox = EventEmitter.compose({
|
|||
let content = this._sandbox = sandbox(principals, {
|
||||
sandboxPrototype: proto,
|
||||
wantXrays: true,
|
||||
wantDOMConstructors: wantDOMConstructors,
|
||||
wantGlobalProperties: wantGlobalProperties,
|
||||
sameZoneAs: window
|
||||
});
|
||||
// We have to ensure that window.top and window.parent are the exact same
|
||||
|
|
|
@ -643,7 +643,7 @@
|
|||
for (let name in aOptions)
|
||||
options += "," + name + "=" + aOptions[name];
|
||||
|
||||
let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul", null, "chrome,all" + options);
|
||||
let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul", null, "chrome,all,dialog=no" + options);
|
||||
|
||||
otherWin.addEventListener("load", function _chatLoad(event) {
|
||||
if (event.target != otherWin.document)
|
||||
|
|
|
@ -72,7 +72,7 @@ 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/source-editor.jsm");
|
||||
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");
|
||||
|
@ -1147,7 +1147,7 @@ SourceScripts.prototype = {
|
|||
|
||||
/**
|
||||
* Pretty print a source's text. All subsequent calls to |getText| will return
|
||||
* the pretty text.
|
||||
* the pretty text. Nothing will happen for non-javascript files.
|
||||
*
|
||||
* @param Object aSource
|
||||
* The source form from the RDP.
|
||||
|
@ -1156,8 +1156,13 @@ SourceScripts.prototype = {
|
|||
* [aSource, error].
|
||||
*/
|
||||
prettyPrint: function(aSource) {
|
||||
let textPromise = this._cache.get(aSource.url);
|
||||
// Only attempt to pretty print JavaScript sources.
|
||||
if (!SourceUtils.isJavaScript(aSource.url, aSource.contentType)) {
|
||||
return promise.reject([aSource, "Can't prettify non-javascript files."]);
|
||||
}
|
||||
|
||||
// Only use the existing promise if it is pretty printed.
|
||||
let textPromise = this._cache.get(aSource.url);
|
||||
if (textPromise && textPromise.pretty) {
|
||||
return textPromise;
|
||||
}
|
||||
|
@ -1166,12 +1171,17 @@ SourceScripts.prototype = {
|
|||
this._cache.set(aSource.url, deferred.promise);
|
||||
|
||||
this.activeThread.source(aSource)
|
||||
.prettyPrint(Prefs.editorTabSize, ({ error, message, source }) => {
|
||||
.prettyPrint(Prefs.editorTabSize, ({ error, message, source: text }) => {
|
||||
if (error) {
|
||||
// Revert the rejected promise from the cache, so that the original
|
||||
// source's text may be shown when the source is selected.
|
||||
this._cache.set(aSource.url, textPromise);
|
||||
deferred.reject([aSource, message || error]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the cached source AST from the Parser, to avoid getting
|
||||
// wrong locations when searching for functions.
|
||||
DebuggerController.Parser.clearSource(aSource.url);
|
||||
|
||||
if (this.activeThread.paused) {
|
||||
|
@ -1180,7 +1190,7 @@ SourceScripts.prototype = {
|
|||
this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
|
||||
}
|
||||
|
||||
deferred.resolve([aSource, source]);
|
||||
deferred.resolve([aSource, text]);
|
||||
});
|
||||
|
||||
deferred.promise.pretty = true;
|
||||
|
@ -1218,14 +1228,14 @@ SourceScripts.prototype = {
|
|||
}
|
||||
|
||||
// Get the source text from the active thread.
|
||||
this.activeThread.source(aSource).source(aResponse => {
|
||||
this.activeThread.source(aSource).source(({ error, message, source: text }) => {
|
||||
if (aOnTimeout) {
|
||||
window.clearTimeout(fetchTimeout);
|
||||
}
|
||||
if (aResponse.error) {
|
||||
deferred.reject([aSource, aResponse.message || aResponse.error]);
|
||||
if (error) {
|
||||
deferred.reject([aSource, message || error]);
|
||||
} else {
|
||||
deferred.resolve([aSource, aResponse.source]);
|
||||
deferred.resolve([aSource, text]);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -379,19 +379,23 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
|||
* Pretty print the selected source.
|
||||
*/
|
||||
prettyPrint: function() {
|
||||
const resetEditor = () => {
|
||||
const resetEditor = ([{ url }]) => {
|
||||
// Only set the text when the source is still selected.
|
||||
if (this.selectedValue === source.url) {
|
||||
DebuggerView.setEditorLocation(source.url, 0, { force: true });
|
||||
if (url == this.selectedValue) {
|
||||
DebuggerView.setEditorLocation(url, 0, { force: true });
|
||||
}
|
||||
};
|
||||
const printError = ([{ url }, error]) => {
|
||||
let err = DevToolsUtils.safeErrorString(error);
|
||||
let msg = "Couldn't prettify source: " + url + "\n" + err;
|
||||
Cu.reportError(msg);
|
||||
dumpn(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
let { source } = this.selectedItem.attachment;
|
||||
// Reset the editor even when we fail, so that we can give the user a clue
|
||||
// as to why the source isn't pretty printed and what happened.
|
||||
DebuggerController.SourceScripts.prettyPrint(source)
|
||||
.then(resetEditor,
|
||||
resetEditor);
|
||||
let prettyPrinted = DebuggerController.SourceScripts.prettyPrint(source);
|
||||
prettyPrinted.then(resetEditor, printError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -995,6 +999,18 @@ let SourceUtils = {
|
|||
_labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
|
||||
_groupsCache: new Map(),
|
||||
|
||||
/**
|
||||
* Returns true if the specified url and/or content type are specific to
|
||||
* javascript files.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the source is likely javascript.
|
||||
*/
|
||||
isJavaScript: function(aUrl, aContentType = "") {
|
||||
return /\.jsm?$/.test(this.trimUrlQuery(aUrl)) ||
|
||||
aContentType.contains("javascript");
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the labels cache, populated by methods like
|
||||
* SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
|
||||
|
|
|
@ -240,26 +240,19 @@ let DebuggerView = {
|
|||
// Avoid setting the editor mode for very large files.
|
||||
if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
|
||||
this.editor.setMode(SourceEditor.MODES.TEXT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aContentType) {
|
||||
if (/javascript/.test(aContentType)) {
|
||||
// Use JS mode for files with .js and .jsm extensions.
|
||||
else if (SourceUtils.isJavaScript(aUrl, aContentType)) {
|
||||
this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
|
||||
} else {
|
||||
this.editor.setMode(SourceEditor.MODES.HTML);
|
||||
}
|
||||
} else if (aTextContent.match(/^\s*</)) {
|
||||
// Use HTML mode for files in which the first non whitespace character is
|
||||
// <, regardless of extension.
|
||||
else if (aTextContent.match(/^\s*</)) {
|
||||
this.editor.setMode(SourceEditor.MODES.HTML);
|
||||
} else {
|
||||
// Use JS mode for files with .js and .jsm extensions.
|
||||
if (/\.jsm?$/.test(SourceUtils.trimUrlQuery(aUrl))) {
|
||||
this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
|
||||
} else {
|
||||
this.editor.setMode(SourceEditor.MODES.TEXT);
|
||||
}
|
||||
// Unknown languange, use plain text.
|
||||
else {
|
||||
this.editor.setMode(SourceEditor.MODES.TEXT);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -325,11 +325,10 @@
|
|||
</vbox>
|
||||
<toolbar id="sources-toolbar" class="devtools-toolbar">
|
||||
<toolbarbutton id="pretty-print"
|
||||
label="{}"
|
||||
tooltiptext="&debuggerUI.sources.prettyPrint;"
|
||||
class="devtools-toolbarbutton devtools-monospace"
|
||||
command="prettyPrintCommand">
|
||||
{}
|
||||
</toolbarbutton>
|
||||
command="prettyPrintCommand"/>
|
||||
</toolbar>
|
||||
</vbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
|
|
|
@ -54,6 +54,8 @@ MOCHITEST_BROWSER_FILES = \
|
|||
browser_dbg_pretty-print-02.js \
|
||||
browser_dbg_pretty-print-03.js \
|
||||
browser_dbg_pretty-print-04.js \
|
||||
browser_dbg_pretty-print-05.js \
|
||||
browser_dbg_pretty-print-06.js \
|
||||
browser_dbg_progress-listener-bug.js \
|
||||
browser_dbg_reload-preferred-script-01.js \
|
||||
browser_dbg_reload-preferred-script-02.js \
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure that prettifying HTML sources doesn't do anything.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_included-script.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gEditor, gSources, gControllerSources;
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gEditor = gDebugger.DebuggerView.editor;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gControllerSources = gDebugger.DebuggerController.SourceScripts;
|
||||
|
||||
Task.spawn(function() {
|
||||
yield waitForSourceShown(gPanel, TAB_URL);
|
||||
|
||||
// From this point onward, the source editor's text should never change.
|
||||
once(gEditor, SourceEditor.EVENTS.TEXT_CHANGED).then(() => {
|
||||
ok(false, "The source editor text shouldn't have changed.");
|
||||
});
|
||||
|
||||
is(gSources.selectedValue, TAB_URL,
|
||||
"The correct source is currently selected.");
|
||||
ok(gEditor.getText().contains("myFunction"),
|
||||
"The source shouldn't be pretty printed yet.");
|
||||
|
||||
clickPrettyPrintButton();
|
||||
|
||||
let { source } = gSources.selectedItem.attachment;
|
||||
try {
|
||||
yield gControllerSources.prettyPrint(source);
|
||||
ok(false, "The promise for a prettified source should be rejected!");
|
||||
} catch ([source, error]) {
|
||||
is(error, "Can't prettify non-javascript files.",
|
||||
"The promise was correctly rejected with a meaningful message.");
|
||||
}
|
||||
|
||||
let [source, text] = yield gControllerSources.getText(source);
|
||||
is(gSources.selectedValue, TAB_URL,
|
||||
"The correct source is still selected.");
|
||||
ok(gEditor.getText().contains("myFunction"),
|
||||
"The displayed source hasn't changed.");
|
||||
ok(text.contains("myFunction"),
|
||||
"The cached source text wasn't altered in any way.");
|
||||
|
||||
yield closeDebuggerAndFinish(gPanel);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickPrettyPrintButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebugger.document.getElementById("pretty-print"),
|
||||
gDebugger);
|
||||
}
|
||||
|
||||
function prepareDebugger(aPanel) {
|
||||
aPanel._view.Sources.preferredSource = TAB_URL;
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gEditor = null;
|
||||
gSources = null;
|
||||
gControllerSources = null;
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure that prettifying JS sources with type errors works as expected.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_included-script.html";
|
||||
const JS_URL = EXAMPLE_URL + "code_location-changes.js";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger, gClient;
|
||||
let gEditor, gSources, gControllerSources, gPrettyPrinted;
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gClient = gDebugger.gClient;
|
||||
gEditor = gDebugger.DebuggerView.editor;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
gControllerSources = gDebugger.DebuggerController.SourceScripts;
|
||||
|
||||
// We can't feed javascript files with syntax errors to the debugger,
|
||||
// because they will never run, thus sometimes getting gc'd before the
|
||||
// debugger is opened, or even before the target finishes navigating.
|
||||
// Make the client lie about being able to parse perfectly fine code.
|
||||
gClient.request = (function (aOriginalRequestMethod) {
|
||||
return function (aPacket, aCallback) {
|
||||
if (aPacket.type == "prettyPrint") {
|
||||
gPrettyPrinted = true;
|
||||
return executeSoon(() => aCallback({ error: "prettyPrintError" }));
|
||||
}
|
||||
return aOriginalRequestMethod(aPacket, aCallback);
|
||||
};
|
||||
}(gClient.request));
|
||||
|
||||
Task.spawn(function() {
|
||||
yield waitForSourceShown(gPanel, JS_URL);
|
||||
|
||||
// From this point onward, the source editor's text should never change.
|
||||
once(gEditor, SourceEditor.EVENTS.TEXT_CHANGED).then(() => {
|
||||
ok(false, "The source editor text shouldn't have changed.");
|
||||
});
|
||||
|
||||
is(gSources.selectedValue, JS_URL,
|
||||
"The correct source is currently selected.");
|
||||
ok(gEditor.getText().contains("myFunction"),
|
||||
"The source shouldn't be pretty printed yet.");
|
||||
|
||||
clickPrettyPrintButton();
|
||||
|
||||
let { source } = gSources.selectedItem.attachment;
|
||||
try {
|
||||
yield gControllerSources.prettyPrint(source);
|
||||
ok(false, "The promise for a prettified source should be rejected!");
|
||||
} catch ([source, error]) {
|
||||
ok(error.contains("prettyPrintError"),
|
||||
"The promise was correctly rejected with a meaningful message.");
|
||||
}
|
||||
|
||||
let [source, text] = yield gControllerSources.getText(source);
|
||||
is(gSources.selectedValue, JS_URL,
|
||||
"The correct source is still selected.");
|
||||
ok(gEditor.getText().contains("myFunction"),
|
||||
"The displayed source hasn't changed.");
|
||||
ok(text.contains("myFunction"),
|
||||
"The cached source text wasn't altered in any way.");
|
||||
|
||||
is(gPrettyPrinted, true,
|
||||
"The hijacked pretty print method was executed.");
|
||||
|
||||
yield closeDebuggerAndFinish(gPanel);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickPrettyPrintButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebugger.document.getElementById("pretty-print"),
|
||||
gDebugger);
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gClient = null;
|
||||
gEditor = null;
|
||||
gSources = null;
|
||||
gControllerSources = null;
|
||||
gPrettyPrinted = null;
|
||||
});
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
|
|
|
@ -20,7 +20,7 @@ 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/source-editor.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;
|
||||
|
|
|
@ -29,6 +29,15 @@ browser.jar:
|
|||
content/browser/devtools/fontinspector/font-inspector.xhtml (fontinspector/font-inspector.xhtml)
|
||||
content/browser/devtools/fontinspector/font-inspector.css (fontinspector/font-inspector.css)
|
||||
content/browser/devtools/orion.js (sourceeditor/orion/orion.js)
|
||||
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/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/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)
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
/* Tags are organized in a UL/LI tree and indented thanks to a left padding.
|
||||
* A very large padding is used in combination with a slightly smaller margin
|
||||
* to make sure childs actually span from edge-to-edge. */
|
||||
.child {
|
||||
margin-left: -1000em;
|
||||
padding-left: 1001em;
|
||||
|
@ -33,9 +36,9 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
/* Children are indented thanks to their parent's left padding, that means they
|
||||
* are not stretching from edge to edge, which is what we want.
|
||||
* So we insert an element and make sure it covers the whole "line" */
|
||||
/* This extra element placed in each tag is positioned absolutely to cover the
|
||||
* whole tag line and is used for background styling (when a selection is made
|
||||
* or when the tag is flashing) */
|
||||
.tag-line .highlighter {
|
||||
position: absolute;
|
||||
left: -1000em;
|
||||
|
@ -86,6 +89,10 @@
|
|||
margin-right: 0;
|
||||
}
|
||||
|
||||
.highlighter.flash-out {
|
||||
transition: background .5s;
|
||||
}
|
||||
|
||||
/* Preview */
|
||||
|
||||
#previewbar {
|
||||
|
|
|
@ -13,6 +13,7 @@ const DEFAULT_MAX_CHILDREN = 100;
|
|||
const COLLAPSE_ATTRIBUTE_LENGTH = 120;
|
||||
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
|
||||
const COLLAPSE_DATA_URL_LENGTH = 60;
|
||||
const CONTAINER_FLASHING_DURATION = 500;
|
||||
|
||||
const {UndoStack} = require("devtools/shared/undo");
|
||||
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
|
||||
|
@ -357,9 +358,11 @@ MarkupView.prototype = {
|
|||
*
|
||||
* @param DOMNode aNode
|
||||
* The node in the content document.
|
||||
* @param boolean aFlashNode
|
||||
* Whether the newly imported node should be flashed
|
||||
* @returns MarkupContainer The MarkupContainer object for this element.
|
||||
*/
|
||||
importNode: function MT_importNode(aNode)
|
||||
importNode: function MT_importNode(aNode, aFlashNode)
|
||||
{
|
||||
if (!aNode) {
|
||||
return null;
|
||||
|
@ -375,6 +378,9 @@ MarkupView.prototype = {
|
|||
this._rootNode = aNode;
|
||||
} else {
|
||||
var container = new MarkupContainer(this, aNode);
|
||||
if (aFlashNode) {
|
||||
container.flashMutation();
|
||||
}
|
||||
}
|
||||
|
||||
this._containers.set(aNode, container);
|
||||
|
@ -415,14 +421,62 @@ MarkupView.prototype = {
|
|||
container.update(false);
|
||||
} else if (type === "childList") {
|
||||
container.childrenDirty = true;
|
||||
this._updateChildren(container);
|
||||
// Update the children to take care of changes in the DOM
|
||||
// Passing true as the last parameter asks for mutation flashing of the
|
||||
// new nodes
|
||||
this._updateChildren(container, {flash: true});
|
||||
}
|
||||
}
|
||||
this._waitForChildren().then(() => {
|
||||
this._flashMutatedNodes(aMutations);
|
||||
this._inspector.emit("markupmutation");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a list of mutations returned by the mutation observer, flash the
|
||||
* corresponding containers to attract attention.
|
||||
*/
|
||||
_flashMutatedNodes: function MT_flashMutatedNodes(aMutations)
|
||||
{
|
||||
let addedOrEditedContainers = new Set();
|
||||
let removedContainers = new Set();
|
||||
|
||||
for (let {type, target, added, removed} of aMutations) {
|
||||
let container = this._containers.get(target);
|
||||
|
||||
if (container) {
|
||||
if (type === "attributes" || type === "characterData") {
|
||||
addedOrEditedContainers.add(container);
|
||||
} else if (type === "childList") {
|
||||
// If there has been removals, flash the parent
|
||||
if (removed.length) {
|
||||
removedContainers.add(container);
|
||||
}
|
||||
|
||||
// If there has been additions, flash the nodes
|
||||
added.forEach(added => {
|
||||
let addedContainer = this._containers.get(added);
|
||||
addedOrEditedContainers.add(addedContainer);
|
||||
|
||||
// The node may be added as a result of an append, in which case it
|
||||
// it will have been removed from another container first, but in
|
||||
// these cases we don't want to flash both the removal and the
|
||||
// addition
|
||||
removedContainers.delete(container);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let container of removedContainers) {
|
||||
container.flashMutation();
|
||||
}
|
||||
for (let container of addedOrEditedContainers) {
|
||||
container.flashMutation();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Make sure the given node's parents are expanded and the
|
||||
* node is scrolled on to screen.
|
||||
|
@ -449,7 +503,7 @@ MarkupView.prototype = {
|
|||
*/
|
||||
_expandContainer: function MT__expandContainer(aContainer)
|
||||
{
|
||||
return this._updateChildren(aContainer, true).then(() => {
|
||||
return this._updateChildren(aContainer, {expand: true}).then(() => {
|
||||
aContainer.expanded = true;
|
||||
});
|
||||
},
|
||||
|
@ -543,7 +597,7 @@ MarkupView.prototype = {
|
|||
if (!container.elt.parentNode) {
|
||||
let parentContainer = this._containers.get(parent);
|
||||
parentContainer.childrenDirty = true;
|
||||
this._updateChildren(parentContainer, node);
|
||||
this._updateChildren(parentContainer, {expand: node});
|
||||
}
|
||||
|
||||
node = parent;
|
||||
|
@ -606,11 +660,18 @@ MarkupView.prototype = {
|
|||
* grab a subset).
|
||||
* container.childrenDirty should be set in that case too!
|
||||
*
|
||||
* This method returns a promise that will be resolved when the children
|
||||
* are ready (which may be immediately).
|
||||
* @param MarkupContainer aContainer
|
||||
* The markup container whose children need updating
|
||||
* @param Object options
|
||||
* Options are {expand:boolean,flash:boolean}
|
||||
* @return a promise that will be resolved when the children are ready
|
||||
* (which may be immediately).
|
||||
*/
|
||||
_updateChildren: function(aContainer, aExpand)
|
||||
_updateChildren: function(aContainer, options)
|
||||
{
|
||||
let expand = options && options.expand;
|
||||
let flash = options && options.flash;
|
||||
|
||||
aContainer.hasChildren = aContainer.node.hasChildren;
|
||||
|
||||
if (!this._queuedChildUpdates) {
|
||||
|
@ -636,7 +697,7 @@ MarkupView.prototype = {
|
|||
// If we're not expanded (or asked to update anyway), we're done for
|
||||
// now. Note that this will leave the childrenDirty flag set, so when
|
||||
// expanded we'll refresh the child list.
|
||||
if (!(aContainer.expanded || aExpand)) {
|
||||
if (!(aContainer.expanded || expand)) {
|
||||
return promise.resolve(aContainer);
|
||||
}
|
||||
|
||||
|
@ -657,13 +718,13 @@ MarkupView.prototype = {
|
|||
// If children are dirty, we got a change notification for this node
|
||||
// while the request was in progress, we need to do it again.
|
||||
if (aContainer.childrenDirty) {
|
||||
return this._updateChildren(aContainer, centered);
|
||||
return this._updateChildren(aContainer, {expand: centered});
|
||||
}
|
||||
|
||||
let fragment = this.doc.createDocumentFragment();
|
||||
|
||||
for (let child of children.nodes) {
|
||||
let container = this.importNode(child);
|
||||
let container = this.importNode(child, flash);
|
||||
fragment.appendChild(container.elt);
|
||||
}
|
||||
|
||||
|
@ -999,6 +1060,54 @@ MarkupContainer.prototype = {
|
|||
event.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Temporarily flash the container to attract attention.
|
||||
* Used for markup mutations.
|
||||
*/
|
||||
flashMutation: function() {
|
||||
if (!this.selected) {
|
||||
let contentWin = this.markup._frame.contentWindow;
|
||||
this.flashed = true;
|
||||
if (this._flashMutationTimer) {
|
||||
contentWin.clearTimeout(this._flashMutationTimer);
|
||||
this._flashMutationTimer = null;
|
||||
}
|
||||
this._flashMutationTimer = contentWin.setTimeout(() => {
|
||||
this.flashed = false;
|
||||
}, CONTAINER_FLASHING_DURATION);
|
||||
}
|
||||
},
|
||||
|
||||
set flashed(aValue) {
|
||||
if (aValue) {
|
||||
// Make sure the animation class is not here
|
||||
this.highlighter.classList.remove("flash-out");
|
||||
|
||||
// Change the background
|
||||
this.highlighter.classList.add("theme-bg-contrast");
|
||||
|
||||
// Change the text color
|
||||
this.editor.elt.classList.add("theme-fg-contrast");
|
||||
[].forEach.call(
|
||||
this.editor.elt.querySelectorAll("[class*=theme-fg-color]"),
|
||||
span => span.classList.add("theme-fg-contrast")
|
||||
);
|
||||
} else {
|
||||
// Add the animation class to smoothly remove the background
|
||||
this.highlighter.classList.add("flash-out");
|
||||
|
||||
// Remove the background
|
||||
this.highlighter.classList.remove("theme-bg-contrast");
|
||||
|
||||
// Remove the text color
|
||||
this.editor.elt.classList.remove("theme-fg-contrast");
|
||||
[].forEach.call(
|
||||
this.editor.elt.querySelectorAll("[class*=theme-fg-color]"),
|
||||
span => span.classList.remove("theme-fg-contrast")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_highlighted: false,
|
||||
|
||||
/**
|
||||
|
@ -1006,6 +1115,7 @@ MarkupContainer.prototype = {
|
|||
* (that is if the tag is expanded)
|
||||
*/
|
||||
set highlighted(aValue) {
|
||||
this.highlighter.classList.remove("flash-out");
|
||||
this._highlighted = aValue;
|
||||
if (aValue) {
|
||||
if (!this.selected) {
|
||||
|
@ -1039,6 +1149,7 @@ MarkupContainer.prototype = {
|
|||
},
|
||||
|
||||
set selected(aValue) {
|
||||
this.highlighter.classList.remove("flash-out");
|
||||
this._selected = aValue;
|
||||
this.editor.selected = aValue;
|
||||
if (this._selected) {
|
||||
|
@ -1361,7 +1472,7 @@ ElementEditor.prototype = {
|
|||
}
|
||||
},
|
||||
done: (aVal, aCommit) => {
|
||||
if (!aCommit) {
|
||||
if (!aCommit || aVal === initial) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ MOCHITEST_BROWSER_FILES := \
|
|||
browser_inspector_markup_navigation.js \
|
||||
browser_inspector_markup_mutation.html \
|
||||
browser_inspector_markup_mutation.js \
|
||||
browser_inspector_markup_mutation_flashing.html \
|
||||
browser_inspector_markup_mutation_flashing.js \
|
||||
browser_inspector_markup_edit.html \
|
||||
browser_inspector_markup_edit.js \
|
||||
browser_inspector_markup_subset.html \
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>mutation flashing test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<ul class="list">
|
||||
<li class="item">item</li>
|
||||
<li class="item">item</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,130 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Will hold the doc we're viewing
|
||||
let contentTab;
|
||||
let doc;
|
||||
let listElement;
|
||||
|
||||
// Holds the MarkupTool object we're testing.
|
||||
let markup;
|
||||
let inspector;
|
||||
|
||||
// Then create the actual dom we're inspecting...
|
||||
contentTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_mutation_flashing.html";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
markup = inspector.markup;
|
||||
|
||||
// Get the content UL element
|
||||
listElement = doc.querySelector(".list");
|
||||
|
||||
// Making sure children are expanded
|
||||
inspector.selection.setNode(listElement.lastElementChild);
|
||||
inspector.once("inspector-updated", () => {
|
||||
// testData contains a list of mutations to test
|
||||
// Each array item is an object with:
|
||||
// - mutate: a function that should make changes to the content DOM
|
||||
// - assert: a function that should test flashing background
|
||||
let testData = [{
|
||||
// Adding a new node should flash the new node
|
||||
mutate: () => {
|
||||
let newLi = doc.createElement("LI");
|
||||
newLi.textContent = "new list item";
|
||||
listElement.appendChild(newLi);
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement.lastElementChild);
|
||||
}
|
||||
}, {
|
||||
// Removing a node should flash its parent
|
||||
mutate: () => {
|
||||
listElement.removeChild(listElement.lastElementChild);
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}, {
|
||||
// Re-appending an existing node should only flash this node
|
||||
mutate: () => {
|
||||
listElement.appendChild(listElement.firstElementChild);
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement.lastElementChild);
|
||||
}
|
||||
}, {
|
||||
// Adding an attribute should flash the node
|
||||
mutate: () => {
|
||||
listElement.setAttribute("name-" + Date.now(), "value-" + Date.now());
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}, {
|
||||
// Editing an attribute should flash the node
|
||||
mutate: () => {
|
||||
listElement.setAttribute("class", "list value-" + Date.now());
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}, {
|
||||
// Removing an attribute should flash the node
|
||||
mutate: () => {
|
||||
listElement.removeAttribute("class");
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}];
|
||||
testMutation(testData, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function testMutation(testData, cursor) {
|
||||
if (cursor < testData.length) {
|
||||
let {mutate, assert} = testData[cursor];
|
||||
mutate();
|
||||
inspector.once("markupmutation", () => {
|
||||
assert();
|
||||
testMutation(testData, cursor + 1);
|
||||
});
|
||||
} else {
|
||||
endTests();
|
||||
}
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
gBrowser.removeTab(contentTab);
|
||||
doc = inspector = contentTab = markup = listElement = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function assertNodeFlashing(rawNode) {
|
||||
let container = getContainerForRawNode(markup, rawNode);
|
||||
|
||||
if(!container) {
|
||||
ok(false, "Node not found");
|
||||
} else {
|
||||
ok(container.highlighter.classList.contains("theme-bg-contrast"),
|
||||
"Node is flashing");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,8 +8,7 @@ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
|||
let TargetFactory = devtools.TargetFactory;
|
||||
|
||||
// Clear preferences that may be set during the course of tests.
|
||||
function clearUserPrefs()
|
||||
{
|
||||
function clearUserPrefs() {
|
||||
Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
|
||||
Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
|
||||
Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
|
||||
|
@ -27,28 +26,3 @@ function getContainerForRawNode(markupView, rawNode) {
|
|||
let container = markupView.getContainer(front);
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.debugger.log");
|
||||
});
|
||||
|
||||
function getContainerForRawNode(markupView, rawNode) {
|
||||
let front = markupView.walker.frontForRawNode(rawNode);
|
||||
let container = markupView.getContainer(front);
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.debugger.log");
|
||||
});
|
||||
|
||||
function getContainerForRawNode(markupView, rawNode) {
|
||||
let front = markupView.walker.frontForRawNode(rawNode);
|
||||
let container = markupView.getContainer(front);
|
||||
return container;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ const EVENTS = {
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
|
|
|
@ -14,19 +14,34 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let require = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
let { Cc, Ci, Cu } = require("chrome");
|
||||
let promise = require("sdk/core/promise");
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
let DevtoolsHelpers = require("devtools/shared/helpers");
|
||||
let TargetFactory = require("devtools/framework/target").TargetFactory;
|
||||
const SCRATCHPAD_CONTEXT_CONTENT = 1;
|
||||
const SCRATCHPAD_CONTEXT_BROWSER = 2;
|
||||
const BUTTON_POSITION_SAVE = 0;
|
||||
const BUTTON_POSITION_CANCEL = 1;
|
||||
const BUTTON_POSITION_DONT_SAVE = 2;
|
||||
const BUTTON_POSITION_REVERT = 0;
|
||||
|
||||
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
|
||||
const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
|
||||
const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
|
||||
|
||||
const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
|
||||
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const promise = require("sdk/core/promise");
|
||||
const Telemetry = require("devtools/shared/telemetry");
|
||||
const escodegen = require("escodegen/escodegen");
|
||||
const Editor = require("devtools/sourceeditor/editor");
|
||||
const TargetFactory = require("devtools/framework/target").TargetFactory;
|
||||
const DevtoolsHelpers = require("devtools/shared/helpers");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
|
@ -54,19 +69,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
|
|||
"resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () =>
|
||||
Services.prefs.getIntPref("devtools.debugger.remote-timeout")
|
||||
);
|
||||
|
||||
const SCRATCHPAD_CONTEXT_CONTENT = 1;
|
||||
const SCRATCHPAD_CONTEXT_BROWSER = 2;
|
||||
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
|
||||
const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
|
||||
const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
|
||||
const BUTTON_POSITION_SAVE = 0;
|
||||
const BUTTON_POSITION_CANCEL = 1;
|
||||
const BUTTON_POSITION_DONT_SAVE = 2;
|
||||
const BUTTON_POSITION_REVERT = 0;
|
||||
const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
|
||||
Services.prefs.getIntPref("devtools.debugger.remote-timeout"));
|
||||
|
||||
// Because we have no constructor / destructor where we can log metrics we need
|
||||
// to do so here.
|
||||
|
@ -79,6 +82,7 @@ telemetry.toolOpened("scratchpad");
|
|||
var Scratchpad = {
|
||||
_instanceId: null,
|
||||
_initialWindowTitle: document.title,
|
||||
_dirty: false,
|
||||
|
||||
/**
|
||||
* Check if provided string is a mode-line and, if it is, return an
|
||||
|
@ -134,6 +138,26 @@ var Scratchpad = {
|
|||
*/
|
||||
initialized: false,
|
||||
|
||||
/**
|
||||
* Returns the 'dirty' state of this Scratchpad.
|
||||
*/
|
||||
get dirty()
|
||||
{
|
||||
let clean = this.editor && this.editor.isClean();
|
||||
return this._dirty || !clean;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the 'dirty' state of this Scratchpad.
|
||||
*/
|
||||
set dirty(aValue)
|
||||
{
|
||||
this._dirty = aValue;
|
||||
if (!aValue && this.editor)
|
||||
this.editor.markClean();
|
||||
this._updateTitle();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the xul:notificationbox DOM element. It notifies the user when
|
||||
* the current code execution context is SCRATCHPAD_CONTEXT_BROWSER.
|
||||
|
@ -143,17 +167,6 @@ var Scratchpad = {
|
|||
return document.getElementById("scratchpad-notificationbox");
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the selected text from the editor.
|
||||
*
|
||||
* @return string
|
||||
* The selected text.
|
||||
*/
|
||||
get selectedText()
|
||||
{
|
||||
return this.editor.getSelectedText();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the editor content, in the given range. If no range is given you get
|
||||
* the entire editor content.
|
||||
|
@ -169,24 +182,8 @@ var Scratchpad = {
|
|||
*/
|
||||
getText: function SP_getText(aStart, aEnd)
|
||||
{
|
||||
return this.editor.getText(aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace text in the source editor with the given text, in the given range.
|
||||
*
|
||||
* @param string aText
|
||||
* The text you want to put into the editor.
|
||||
* @param number [aStart=0]
|
||||
* Optional, the start offset, zero based, from where you want to start
|
||||
* replacing text in the editor.
|
||||
* @param number [aEnd=char count]
|
||||
* Optional, the end offset, zero based, where you want to stop
|
||||
* replacing text in the editor.
|
||||
*/
|
||||
setText: function SP_setText(aText, aStart, aEnd)
|
||||
{
|
||||
this.editor.setText(aText, aStart, aEnd);
|
||||
var value = this.editor.getText();
|
||||
return value.slice(aStart || 0, aEnd || value.length);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -209,9 +206,8 @@ var Scratchpad = {
|
|||
{
|
||||
let title = this.filename || this._initialWindowTitle;
|
||||
|
||||
if (this.editor && this.editor.dirty) {
|
||||
if (this.dirty)
|
||||
title = "*" + title;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
},
|
||||
|
@ -230,7 +226,7 @@ var Scratchpad = {
|
|||
filename: this.filename,
|
||||
text: this.getText(),
|
||||
executionContext: this.executionContext,
|
||||
saved: !this.editor.dirty,
|
||||
saved: !this.dirty
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -243,19 +239,15 @@ var Scratchpad = {
|
|||
*/
|
||||
setState: function SP_setState(aState)
|
||||
{
|
||||
if (aState.filename) {
|
||||
if (aState.filename)
|
||||
this.setFilename(aState.filename);
|
||||
}
|
||||
if (this.editor) {
|
||||
this.editor.dirty = !aState.saved;
|
||||
}
|
||||
|
||||
if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER) {
|
||||
this.dirty = !aState.saved;
|
||||
|
||||
if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER)
|
||||
this.setBrowserContext();
|
||||
}
|
||||
else {
|
||||
else
|
||||
this.setContentContext();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -297,36 +289,12 @@ var Scratchpad = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Drop the editor selection.
|
||||
* Replaces context of an editor with provided value (a string).
|
||||
* Note: this method is simply a shortcut to editor.setText.
|
||||
*/
|
||||
deselect: function SP_deselect()
|
||||
setText: function SP_setText(value)
|
||||
{
|
||||
this.editor.dropSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a specific range in the Scratchpad editor.
|
||||
*
|
||||
* @param number aStart
|
||||
* Selection range start.
|
||||
* @param number aEnd
|
||||
* Selection range end.
|
||||
*/
|
||||
selectRange: function SP_selectRange(aStart, aEnd)
|
||||
{
|
||||
this.editor.setSelection(aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current selection range.
|
||||
*
|
||||
* @return object
|
||||
* An object with two properties, start and end, that give the
|
||||
* selection range (zero based offsets).
|
||||
*/
|
||||
getSelectionRange: function SP_getSelection()
|
||||
{
|
||||
return this.editor.getSelection();
|
||||
return this.editor.setText(value);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -380,7 +348,7 @@ var Scratchpad = {
|
|||
*/
|
||||
execute: function SP_execute()
|
||||
{
|
||||
let selection = this.selectedText || this.getText();
|
||||
let selection = this.editor.getSelection() || this.getText();
|
||||
return this.evaluate(selection);
|
||||
},
|
||||
|
||||
|
@ -403,7 +371,7 @@ var Scratchpad = {
|
|||
this.writeAsErrorComment(aError.exception).then(resolve, reject);
|
||||
}
|
||||
else {
|
||||
this.deselect();
|
||||
this.editor.dropSelection();
|
||||
resolve();
|
||||
}
|
||||
}, reject);
|
||||
|
@ -434,7 +402,7 @@ var Scratchpad = {
|
|||
this._writePrimitiveAsComment(aResult).then(resolve, reject);
|
||||
}
|
||||
else {
|
||||
this.deselect();
|
||||
this.editor.dropSelection();
|
||||
this.sidebar.open(aString, aResult).then(resolve, reject);
|
||||
}
|
||||
}, reject);
|
||||
|
@ -539,7 +507,7 @@ var Scratchpad = {
|
|||
}
|
||||
}
|
||||
});
|
||||
this.setText(prettyText);
|
||||
this.editor.setText(prettyText);
|
||||
} catch (e) {
|
||||
this.writeAsErrorComment(DevToolsUtils.safeErrorString(e));
|
||||
}
|
||||
|
@ -588,17 +556,21 @@ var Scratchpad = {
|
|||
*/
|
||||
writeAsComment: function SP_writeAsComment(aValue)
|
||||
{
|
||||
let selection = this.getSelectionRange();
|
||||
let insertionPoint = selection.start != selection.end ?
|
||||
selection.end : // after selected text
|
||||
this.editor.getCharCount(); // after text end
|
||||
let value = "\n/*\n" + aValue + "\n*/";
|
||||
|
||||
let newComment = "\n/*\n" + aValue + "\n*/";
|
||||
if (this.editor.somethingSelected()) {
|
||||
let from = this.editor.getCursor("end");
|
||||
this.editor.replaceSelection(this.editor.getSelection() + value);
|
||||
let to = this.editor.getPosition(this.editor.getOffset(from) + value.length);
|
||||
this.editor.setSelection(from, to);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setText(newComment, insertionPoint, insertionPoint);
|
||||
let text = this.editor.getText();
|
||||
this.editor.setText(text + value);
|
||||
|
||||
// Select the new comment.
|
||||
this.selectRange(insertionPoint, insertionPoint + newComment.length);
|
||||
let [ from, to ] = this.editor.getPosition(text.length, (text + value).length);
|
||||
this.editor.setSelection(from, to);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -814,8 +786,9 @@ var Scratchpad = {
|
|||
this.setBrowserContext();
|
||||
}
|
||||
|
||||
this.setText(content);
|
||||
this.editor.resetUndo();
|
||||
this.editor.setText(content);
|
||||
this.editor.clearHistory();
|
||||
document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
|
||||
}
|
||||
else if (!aSilentError) {
|
||||
window.alert(this.strings.GetStringFromName("openFile.failed"));
|
||||
|
@ -1090,7 +1063,8 @@ var Scratchpad = {
|
|||
|
||||
this.exportToFile(file, true, false, aStatus => {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this.editor.dirty = false;
|
||||
this.dirty = false;
|
||||
document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
|
||||
this.setRecentFile(file);
|
||||
}
|
||||
if (aCallback) {
|
||||
|
@ -1113,7 +1087,7 @@ var Scratchpad = {
|
|||
this.setFilename(fp.file.path);
|
||||
this.exportToFile(fp.file, true, false, aStatus => {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this.editor.dirty = false;
|
||||
this.dirty = false;
|
||||
this.setRecentFile(fp.file);
|
||||
}
|
||||
if (aCallback) {
|
||||
|
@ -1315,77 +1289,46 @@ var Scratchpad = {
|
|||
initialText = state.text;
|
||||
}
|
||||
|
||||
this.editor = new SourceEditor();
|
||||
this.editor = new Editor({
|
||||
mode: Editor.modes.js,
|
||||
value: initialText,
|
||||
lineNumbers: true,
|
||||
contextMenu: "scratchpad-text-popup"
|
||||
});
|
||||
|
||||
let config = {
|
||||
mode: SourceEditor.MODES.JAVASCRIPT,
|
||||
showLineNumbers: true,
|
||||
initialText: initialText,
|
||||
contextMenu: "scratchpad-text-popup",
|
||||
};
|
||||
this.editor.appendTo(document.querySelector("#scratchpad-editor")).then(() => {
|
||||
var lines = initialText.split("\n");
|
||||
|
||||
let editorPlaceholder = document.getElementById("scratchpad-editor");
|
||||
this.editor.init(editorPlaceholder, config,
|
||||
this._onEditorLoad.bind(this, state));
|
||||
},
|
||||
|
||||
/**
|
||||
* The load event handler for the source editor. This method does post-load
|
||||
* editor initialization.
|
||||
*
|
||||
* @private
|
||||
* @param object aState
|
||||
* The initial Scratchpad state object.
|
||||
*/
|
||||
_onEditorLoad: function SP__onEditorLoad(aState)
|
||||
{
|
||||
this.editor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
|
||||
this._onDirtyChanged);
|
||||
this.editor.on("change", this._onChanged);
|
||||
this.editor.focus();
|
||||
this.editor.setCaretOffset(this.editor.getCharCount());
|
||||
if (aState) {
|
||||
this.editor.dirty = !aState.saved;
|
||||
}
|
||||
this.editor.setCursor({ line: lines.length, ch: lines.pop().length });
|
||||
|
||||
if (state)
|
||||
this.dirty = !state.saved;
|
||||
|
||||
this.initialized = true;
|
||||
this._triggerObservers("Ready");
|
||||
this.populateRecentFilesMenu();
|
||||
PreferenceObserver.init();
|
||||
}).then(null, (err) => console.log(err.message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert text at the current caret location.
|
||||
*
|
||||
* @param string aText
|
||||
* The text you want to insert.
|
||||
*/
|
||||
insertTextAtCaret: function SP_insertTextAtCaret(aText)
|
||||
{
|
||||
let caretOffset = this.editor.getCaretOffset();
|
||||
this.setText(aText, caretOffset, caretOffset);
|
||||
this.editor.setCaretOffset(caretOffset + aText.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* The Source Editor DirtyChanged event handler. This function updates the
|
||||
* The Source Editor "change" event handler. This function updates the
|
||||
* Scratchpad window title to show an asterisk when there are unsaved changes.
|
||||
*
|
||||
* @private
|
||||
* @see SourceEditor.EVENTS.DIRTY_CHANGED
|
||||
* @param object aEvent
|
||||
* The DirtyChanged event object.
|
||||
*/
|
||||
_onDirtyChanged: function SP__onDirtyChanged(aEvent)
|
||||
_onChanged: function SP__onChanged()
|
||||
{
|
||||
Scratchpad._updateTitle();
|
||||
|
||||
if (Scratchpad.filename) {
|
||||
if (Scratchpad.editor.dirty) {
|
||||
if (Scratchpad.dirty)
|
||||
document.getElementById("sp-cmd-revert").removeAttribute("disabled");
|
||||
}
|
||||
else {
|
||||
else
|
||||
document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1417,21 +1360,22 @@ var Scratchpad = {
|
|||
}
|
||||
|
||||
// This event is created only after user uses 'reload and run' feature.
|
||||
if (this._reloadAndRunEvent) {
|
||||
if (this._reloadAndRunEvent && this.gBrowser) {
|
||||
this.gBrowser.selectedBrowser.removeEventListener("load",
|
||||
this._reloadAndRunEvent, true);
|
||||
}
|
||||
|
||||
this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
|
||||
this._onDirtyChanged);
|
||||
PreferenceObserver.uninit();
|
||||
|
||||
this.editor.off("change", this._onChanged);
|
||||
this.editor.destroy();
|
||||
this.editor = null;
|
||||
|
||||
if (this._sidebar) {
|
||||
this._sidebar.destroy();
|
||||
this._sidebar = null;
|
||||
}
|
||||
|
||||
this.webConsoleClient = null;
|
||||
this.debuggerClient = null;
|
||||
this.initialized = false;
|
||||
|
@ -1452,7 +1396,7 @@ var Scratchpad = {
|
|||
*/
|
||||
promptSave: function SP_promptSave(aCallback)
|
||||
{
|
||||
if (this.editor.dirty) {
|
||||
if (this.dirty) {
|
||||
let ps = Services.prompt;
|
||||
let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_SAVE +
|
||||
ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL +
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
<?xml version="1.0"?>
|
||||
#ifdef 0
|
||||
|
||||
<!-- 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/. -->
|
||||
#endif
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % scratchpadDTD SYSTEM "chrome://browser/locale/devtools/scratchpad.dtd" >
|
||||
%scratchpadDTD;
|
||||
<!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
|
||||
%editMenuStrings;
|
||||
<!ENTITY % sourceEditorStrings SYSTEM "chrome://browser/locale/devtools/sourceeditor.dtd">
|
||||
%sourceEditorStrings;
|
||||
]>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/scratchpad.css"?>
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
<?xul-overlay href="chrome://browser/content/devtools/source-editor-overlay.xul"?>
|
||||
|
||||
<window id="main-window"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
|
@ -27,8 +31,20 @@
|
|||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/devtools/scratchpad.js"/>
|
||||
|
||||
|
||||
<script type="application/javascript">
|
||||
function goUpdateSourceEditorMenuItems() {
|
||||
goUpdateGlobalEditMenuItems();
|
||||
let commands = ['cmd_undo', 'cmd_redo', 'cmd_cut', 'cmd_paste', 'cmd_delete', 'cmd_findAgain'];
|
||||
commands.forEach(goUpdateCommand);
|
||||
}
|
||||
</script>
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
<commandset id="sourceEditorCommands"/>
|
||||
|
||||
<commandset id="sourceEditorCommands">
|
||||
<command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
|
||||
</commandset>
|
||||
|
||||
<commandset id="sp-commandset">
|
||||
<command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
|
||||
|
@ -37,11 +53,6 @@
|
|||
<command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
|
||||
<command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
|
||||
<command id="sp-cmd-revert" oncommand="Scratchpad.promptRevert();" disabled="true"/>
|
||||
|
||||
<!-- TODO: bug 650340 - implement printFile()
|
||||
<command id="sp-cmd-printFile" oncommand="Scratchpad.printFile();" disabled="true"/>
|
||||
-->
|
||||
|
||||
<command id="sp-cmd-close" oncommand="Scratchpad.close();"/>
|
||||
<command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
|
||||
<command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
|
||||
|
@ -56,7 +67,7 @@
|
|||
<command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="sourceEditorKeys"/>
|
||||
<keyset id="editMenuKeys"/>
|
||||
|
||||
<keyset id="sp-keyset">
|
||||
<key id="sp-key-window"
|
||||
|
@ -75,14 +86,6 @@
|
|||
key="&closeCmd.key;"
|
||||
command="sp-cmd-close"
|
||||
modifiers="accel"/>
|
||||
|
||||
<!-- TODO: bug 650340 - implement printFile
|
||||
<key id="sp-key-printFile"
|
||||
key="&printCmd.commandkey;"
|
||||
command="sp-cmd-printFile"
|
||||
modifiers="accel"/>
|
||||
-->
|
||||
|
||||
<key id="sp-key-run"
|
||||
key="&run.key;"
|
||||
command="sp-cmd-run"
|
||||
|
@ -115,10 +118,8 @@
|
|||
command="sp-cmd-documentationLink"/>
|
||||
</keyset>
|
||||
|
||||
|
||||
<menubar id="sp-menubar">
|
||||
<menu id="sp-file-menu" label="&fileMenu.label;"
|
||||
accesskey="&fileMenu.accesskey;">
|
||||
<menu id="sp-file-menu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
|
||||
<menupopup id="sp-menu-filepopup">
|
||||
<menuitem id="sp-menu-newscratchpad"
|
||||
label="&newWindowCmd.label;"
|
||||
|
@ -126,16 +127,19 @@
|
|||
key="sp-key-window"
|
||||
command="sp-cmd-newWindow"/>
|
||||
<menuseparator/>
|
||||
|
||||
<menuitem id="sp-menu-open"
|
||||
label="&openFileCmd.label;"
|
||||
command="sp-cmd-openFile"
|
||||
key="sp-key-open"
|
||||
accesskey="&openFileCmd.accesskey;"/>
|
||||
|
||||
<menu id="sp-open_recent-menu" label="&openRecentMenu.label;"
|
||||
accesskey="&openRecentMenu.accesskey;"
|
||||
disabled="true">
|
||||
<menupopup id="sp-menu-open_recentPopup"/>
|
||||
</menu>
|
||||
|
||||
<menuitem id="sp-menu-save"
|
||||
label="&saveFileCmd.label;"
|
||||
accesskey="&saveFileCmd.accesskey;"
|
||||
|
@ -151,14 +155,6 @@
|
|||
command="sp-cmd-revert"/>
|
||||
<menuseparator/>
|
||||
|
||||
<!-- TODO: bug 650340 - implement printFile
|
||||
<menuitem id="sp-menu-print"
|
||||
label="&printCmd.label;"
|
||||
accesskey="&printCmd.accesskey;"
|
||||
command="sp-cmd-printFile"/>
|
||||
<menuseparator/>
|
||||
-->
|
||||
|
||||
<menuitem id="sp-menu-close"
|
||||
label="&closeCmd.label;"
|
||||
key="sp-key-close"
|
||||
|
@ -166,25 +162,31 @@
|
|||
command="sp-cmd-close"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="sp-edit-menu" label="&editMenu.label;"
|
||||
accesskey="&editMenu.accesskey;">
|
||||
<menupopup id="sp-menu_editpopup"
|
||||
onpopupshowing="goUpdateSourceEditorMenuItems()">
|
||||
<menuitem id="se-menu-undo"/>
|
||||
<menuitem id="se-menu-redo"/>
|
||||
<menuitem id="menu_undo"/>
|
||||
<menuitem id="menu_redo"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-menu-cut"/>
|
||||
<menuitem id="se-menu-copy"/>
|
||||
<menuitem id="se-menu-paste"/>
|
||||
<menuitem id="menu_cut"/>
|
||||
<menuitem id="menu_copy"/>
|
||||
<menuitem id="menu_paste"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-menu-selectAll"/>
|
||||
<menuitem id="menu_selectAll"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-menu-find"/>
|
||||
<menuitem id="se-menu-findAgain"/>
|
||||
<menuitem id="menu_find"/>
|
||||
<menuitem id="menu_findAgain"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-menu-gotoLine"/>
|
||||
<menuitem id="se-menu-gotoLine"
|
||||
label="&gotoLineCmd.label;"
|
||||
accesskey="&gotoLineCmd.accesskey;"
|
||||
key="key_gotoLine"
|
||||
command="cmd_gotoLine"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="sp-execute-menu" label="&executeMenu.label;"
|
||||
accesskey="&executeMenu.accesskey;">
|
||||
<menupopup id="sp-menu_executepopup">
|
||||
|
@ -288,12 +290,12 @@
|
|||
<popupset id="scratchpad-popups">
|
||||
<menupopup id="scratchpad-text-popup"
|
||||
onpopupshowing="goUpdateSourceEditorMenuItems()">
|
||||
<menuitem id="se-cMenu-cut"/>
|
||||
<menuitem id="se-cMenu-copy"/>
|
||||
<menuitem id="se-cMenu-paste"/>
|
||||
<menuitem id="se-cMenu-delete"/>
|
||||
<menuitem id="cMenu_cut"/>
|
||||
<menuitem id="cMenu_copy"/>
|
||||
<menuitem id="cMenu_paste"/>
|
||||
<menuitem id="cMenu_delete"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-cMenu-selectAll"/>
|
||||
<menuitem id="cMenu_selectAll"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="sp-text-run"
|
||||
label="&run.label;"
|
||||
|
|
|
@ -19,7 +19,6 @@ MOCHITEST_BROWSER_FILES = \
|
|||
browser_scratchpad_bug_669612_unsaved.js \
|
||||
browser_scratchpad_bug684546_reset_undo.js \
|
||||
browser_scratchpad_bug690552_display_outputs_errors.js \
|
||||
browser_scratchpad_bug650345_find_ui.js \
|
||||
browser_scratchpad_bug714942_goto_line_ui.js \
|
||||
browser_scratchpad_bug_650760_help_key.js \
|
||||
browser_scratchpad_bug_651942_recent_files.js \
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function browserLoad() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true);
|
||||
openScratchpad(runTests);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,<p>test the Find feature in Scratchpad";
|
||||
}
|
||||
|
||||
function runTests(aWindow, aScratchpad)
|
||||
{
|
||||
let editor = aScratchpad.editor;
|
||||
let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest";
|
||||
editor.setText(text);
|
||||
|
||||
let needle = "foobar";
|
||||
editor.setSelection(0, needle.length);
|
||||
|
||||
let oldPrompt = Services.prompt;
|
||||
Services.prompt = {
|
||||
prompt: function() { return true; },
|
||||
};
|
||||
|
||||
let findKey = "F";
|
||||
info("test Ctrl/Cmd-" + findKey + " (find)");
|
||||
EventUtils.synthesizeKey(findKey, {accelKey: true}, aWindow);
|
||||
let selection = editor.getSelection();
|
||||
let newIndex = text.indexOf(needle, needle.length);
|
||||
is(selection.start, newIndex, "selection.start is correct");
|
||||
is(selection.end, newIndex + needle.length, "selection.end is correct");
|
||||
|
||||
info("test cmd_find");
|
||||
aWindow.goDoCommand("cmd_find");
|
||||
selection = editor.getSelection();
|
||||
is(selection.start, 0, "selection.start is correct");
|
||||
is(selection.end, needle.length, "selection.end is correct");
|
||||
|
||||
let findNextKey = Services.appinfo.OS == "Darwin" ? "G" : "VK_F3";
|
||||
let findNextKeyOptions = Services.appinfo.OS == "Darwin" ?
|
||||
{accelKey: true} : {};
|
||||
|
||||
info("test " + findNextKey + " (findNext)");
|
||||
EventUtils.synthesizeKey(findNextKey, findNextKeyOptions, aWindow);
|
||||
selection = editor.getSelection();
|
||||
is(selection.start, newIndex, "selection.start is correct");
|
||||
is(selection.end, newIndex + needle.length, "selection.end is correct");
|
||||
|
||||
info("test cmd_findAgain");
|
||||
aWindow.goDoCommand("cmd_findAgain");
|
||||
selection = editor.getSelection();
|
||||
is(selection.start, 0, "selection.start is correct");
|
||||
is(selection.end, needle.length, "selection.end is correct");
|
||||
|
||||
let findPreviousKey = Services.appinfo.OS == "Darwin" ? "G" : "VK_F3";
|
||||
let findPreviousKeyOptions = Services.appinfo.OS == "Darwin" ?
|
||||
{accelKey: true, shiftKey: true} : {shiftKey: true};
|
||||
|
||||
info("test " + findPreviousKey + " (findPrevious)");
|
||||
EventUtils.synthesizeKey(findPreviousKey, findPreviousKeyOptions, aWindow);
|
||||
selection = editor.getSelection();
|
||||
is(selection.start, newIndex, "selection.start is correct");
|
||||
is(selection.end, newIndex + needle.length, "selection.end is correct");
|
||||
|
||||
info("test cmd_findPrevious");
|
||||
aWindow.goDoCommand("cmd_findPrevious");
|
||||
selection = editor.getSelection();
|
||||
is(selection.start, 0, "selection.start is correct");
|
||||
is(selection.end, needle.length, "selection.end is correct");
|
||||
|
||||
needle = "BAZbaz";
|
||||
newIndex = text.toLowerCase().indexOf(needle.toLowerCase());
|
||||
|
||||
Services.prompt = {
|
||||
prompt: function(aWindow, aTitle, aMessage, aValue) {
|
||||
aValue.value = needle;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
info("test Ctrl/Cmd-" + findKey + " (find) with a custom value");
|
||||
EventUtils.synthesizeKey(findKey, {accelKey: true}, aWindow);
|
||||
selection = editor.getSelection();
|
||||
is(selection.start, newIndex, "selection.start is correct");
|
||||
is(selection.end, newIndex + needle.length, "selection.end is correct");
|
||||
|
||||
Services.prompt = oldPrompt;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
|
@ -101,7 +101,8 @@ function fileAImported(aStatus, aFileContent)
|
|||
|
||||
is(gScratchpad.getText(), gFileAContent, "the editor content is correct");
|
||||
|
||||
gScratchpad.setText("new text", gScratchpad.getText().length);
|
||||
gScratchpad.editor.replaceText("new text",
|
||||
gScratchpad.editor.posFromIndex(gScratchpad.getText().length));
|
||||
|
||||
is(gScratchpad.getText(), gFileAContent + "new text", "text updated correctly");
|
||||
gScratchpad.undo();
|
||||
|
@ -129,7 +130,8 @@ function fileBImported(aStatus, aFileContent)
|
|||
is(gScratchpad.getText(), gFileBContent,
|
||||
"the editor content is still correct after undo");
|
||||
|
||||
gScratchpad.setText("new text", gScratchpad.getText().length);
|
||||
gScratchpad.editor.replaceText("new text",
|
||||
gScratchpad.editor.posFromIndex(gScratchpad.getText().length));
|
||||
is(gScratchpad.getText(), gFileBContent + "new text", "text updated correctly");
|
||||
|
||||
gScratchpad.undo();
|
||||
|
|
|
@ -20,26 +20,23 @@ function runTests(aWindow, aScratchpad)
|
|||
let editor = aScratchpad.editor;
|
||||
let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest";
|
||||
editor.setText(text);
|
||||
editor.setCaretOffset(0);
|
||||
editor.setCursor({ line: 0, ch: 0 });
|
||||
|
||||
let oldPrompt = Services.prompt;
|
||||
let desiredValue = null;
|
||||
Services.prompt = {
|
||||
prompt: function(aWindow, aTitle, aMessage, aValue) {
|
||||
aValue.value = desiredValue;
|
||||
return true;
|
||||
},
|
||||
let oldPrompt = editor.openDialog;
|
||||
let desiredValue;
|
||||
|
||||
editor.openDialog = function (text, cb) {
|
||||
cb(desiredValue);
|
||||
};
|
||||
|
||||
desiredValue = 3;
|
||||
EventUtils.synthesizeKey("J", {accelKey: true}, aWindow);
|
||||
is(editor.getCaretOffset(), 34, "caret offset is correct");
|
||||
is(editor.getCursor().line, 2, "line is correct");
|
||||
|
||||
desiredValue = 2;
|
||||
aWindow.goDoCommand("cmd_gotoLine")
|
||||
is(editor.getCaretOffset(), 17, "caret offset is correct (again)");
|
||||
|
||||
Services.prompt = oldPrompt;
|
||||
is(editor.getCursor().line, 1, "line is correct (again)");
|
||||
|
||||
editor.openDialog = oldPrompt;
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -28,30 +28,24 @@ function runTests()
|
|||
ok(sp.editor.hasFocus(), "the editor has focus");
|
||||
|
||||
sp.setText("window.foo;");
|
||||
sp.editor.setCaretOffset(0);
|
||||
sp.editor.setCursor({ line: 0, ch: 0 });
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
|
||||
|
||||
is(sp.getText(), " window.foo;", "Tab key added 5 spaces");
|
||||
|
||||
is(sp.editor.getCaretOffset(), 5, "caret location is correct");
|
||||
is(sp.editor.getCursor().line, 0, "line is correct");
|
||||
is(sp.editor.getCursor().ch, 5, "character is correct");
|
||||
|
||||
sp.editor.setCaretOffset(6);
|
||||
sp.editor.setCursor({ line: 0, ch: 6 });
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
|
||||
|
||||
is(sp.getText(), " w indow.foo;",
|
||||
"Tab key added 4 spaces");
|
||||
|
||||
is(sp.editor.getCaretOffset(), 10, "caret location is correct");
|
||||
|
||||
// Test the new insertTextAtCaret() method.
|
||||
|
||||
sp.insertTextAtCaret("omg");
|
||||
|
||||
is(sp.getText(), " w omgindow.foo;", "insertTextAtCaret() works");
|
||||
|
||||
is(sp.editor.getCaretOffset(), 13, "caret location is correct after update");
|
||||
is(sp.editor.getCursor().line, 0, "line is correct");
|
||||
is(sp.editor.getCursor().ch, 10, "character is correct");
|
||||
|
||||
gScratchpadWindow.close();
|
||||
|
||||
|
@ -66,13 +60,14 @@ function runTests2()
|
|||
let sp = gScratchpadWindow.Scratchpad;
|
||||
|
||||
sp.setText("window.foo;");
|
||||
sp.editor.setCaretOffset(0);
|
||||
sp.editor.setCursor({ line: 0, ch: 0 });
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
|
||||
|
||||
is(sp.getText(), "\twindow.foo;", "Tab key added the tab character");
|
||||
|
||||
is(sp.editor.getCaretOffset(), 1, "caret location is correct");
|
||||
is(sp.editor.getCursor().line, 0, "line is correct");
|
||||
is(sp.editor.getCursor().ch, 1, "character is correct");
|
||||
|
||||
Services.prefs.clearUserPref("devtools.editor.tabsize");
|
||||
Services.prefs.clearUserPref("devtools.editor.expandtab");
|
||||
|
|
|
@ -32,16 +32,16 @@ function testListeners()
|
|||
{
|
||||
openScratchpad(function(aWin, aScratchpad) {
|
||||
aScratchpad.setText("new text");
|
||||
ok(isStar(aWin), "show start if scratchpad text changes");
|
||||
ok(isStar(aWin), "show star if scratchpad text changes");
|
||||
|
||||
aScratchpad.editor.dirty = false;
|
||||
aScratchpad.dirty = false;
|
||||
ok(!isStar(aWin), "no star before changing text");
|
||||
|
||||
aScratchpad.setFilename("foo.js");
|
||||
aScratchpad.setText("new text2");
|
||||
ok(isStar(aWin), "shows star if scratchpad text changes");
|
||||
|
||||
aScratchpad.editor.dirty = false;
|
||||
aScratchpad.dirty = false;
|
||||
ok(!isStar(aWin), "no star if scratchpad was just saved");
|
||||
|
||||
aScratchpad.setText("new text3");
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
function test()
|
||||
|
@ -51,9 +51,9 @@ function runTests()
|
|||
|
||||
let menuPopup = editMenu.menupopup;
|
||||
ok(menuPopup, "the Edit menupopup");
|
||||
let cutItem = doc.getElementById("se-menu-cut");
|
||||
let cutItem = doc.getElementById("menu_cut");
|
||||
ok(cutItem, "the Cut menuitem");
|
||||
let pasteItem = doc.getElementById("se-menu-paste");
|
||||
let pasteItem = doc.getElementById("menu_paste");
|
||||
ok(pasteItem, "the Paste menuitem");
|
||||
|
||||
let anchor = doc.documentElement;
|
||||
|
@ -109,7 +109,7 @@ function runTests()
|
|||
};
|
||||
|
||||
let firstHide = function() {
|
||||
sp.selectRange(0, 10);
|
||||
sp.editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 10 });
|
||||
openMenu(11, 11, showAfterSelect);
|
||||
};
|
||||
|
||||
|
@ -119,9 +119,9 @@ function runTests()
|
|||
};
|
||||
|
||||
let hideAfterSelect = function() {
|
||||
sp.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onCut);
|
||||
sp.editor.on("change", onCut);
|
||||
waitForFocus(function () {
|
||||
let selectedText = sp.editor.getSelectedText();
|
||||
let selectedText = sp.editor.getSelection();
|
||||
ok(selectedText.length > 0, "non-empty selected text will be cut");
|
||||
|
||||
EventUtils.synthesizeKey("x", {accelKey: true}, gScratchpadWindow);
|
||||
|
@ -129,7 +129,7 @@ function runTests()
|
|||
};
|
||||
|
||||
let onCut = function() {
|
||||
sp.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onCut);
|
||||
sp.editor.off("change", onCut);
|
||||
openMenu(12, 12, showAfterCut);
|
||||
};
|
||||
|
||||
|
@ -140,14 +140,14 @@ function runTests()
|
|||
};
|
||||
|
||||
let hideAfterCut = function() {
|
||||
sp.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
|
||||
sp.editor.on("change", onPaste);
|
||||
waitForFocus(function () {
|
||||
EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow);
|
||||
}, gScratchpadWindow);
|
||||
};
|
||||
|
||||
let onPaste = function() {
|
||||
sp.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
|
||||
sp.editor.off("change", onPaste);
|
||||
openMenu(13, 13, showAfterPaste);
|
||||
};
|
||||
|
||||
|
@ -174,9 +174,9 @@ function runTests()
|
|||
|
||||
menuPopup = doc.getElementById("scratchpad-text-popup");
|
||||
ok(menuPopup, "the context menupopup");
|
||||
cutItem = doc.getElementById("se-cMenu-cut");
|
||||
cutItem = doc.getElementById("cMenu_cut");
|
||||
ok(cutItem, "the Cut menuitem");
|
||||
pasteItem = doc.getElementById("se-cMenu-paste");
|
||||
pasteItem = doc.getElementById("cMenu_paste");
|
||||
ok(pasteItem, "the Paste menuitem");
|
||||
|
||||
sp.setText("bug 699130: hello world! (context menu)");
|
||||
|
|
|
@ -29,7 +29,7 @@ let menu;
|
|||
function startTest()
|
||||
{
|
||||
gScratchpad = gScratchpadWindow.Scratchpad;
|
||||
menu = gScratchpadWindow.document.getElementById("sp-menu-revert");
|
||||
menu = gScratchpadWindow.document.getElementById("sp-cmd-revert");
|
||||
createAndLoadTemporaryFile();
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ function testAfterSaved() {
|
|||
ok(menu.hasAttribute("disabled"), "The revert menu entry is disabled.");
|
||||
|
||||
// chancging the text in the file
|
||||
gScratchpad.setText("\nfoo();", gLength, gLength);
|
||||
gScratchpad.setText(gScratchpad.getText() + "\nfoo();");
|
||||
// Checking the text got changed
|
||||
is(gScratchpad.getText(), gFileContent + "\nfoo();",
|
||||
"The text changed the first time.");
|
||||
|
@ -60,7 +60,7 @@ function testAfterRevert() {
|
|||
"The revert menu entry is disabled after reverting.");
|
||||
|
||||
// chancging the text in the file again
|
||||
gScratchpad.setText("\nalert(foo.toSource());", gLength, gLength);
|
||||
gScratchpad.setText(gScratchpad.getText() + "\nalert(foo.toSource());");
|
||||
// Saving the file.
|
||||
gScratchpad.saveFile(testAfterSecondSave);
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ function testAfterSecondSave() {
|
|||
"The revert menu entry is disabled after saving.");
|
||||
|
||||
// changing the text.
|
||||
gScratchpad.setText("\nfoo();", gLength + 23, gLength + 23);
|
||||
gScratchpad.setText(gScratchpad.getText() + "\nfoo();");
|
||||
|
||||
// revert menu entry should get enabled yet again.
|
||||
ok(!menu.hasAttribute("disabled"),
|
||||
|
@ -91,6 +91,7 @@ function testAfterSecondRevert() {
|
|||
gFile.remove(false);
|
||||
gFile = null;
|
||||
gScratchpad = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function createAndLoadTemporaryFile()
|
||||
|
@ -126,6 +127,8 @@ function tempFileSaved(aStatus)
|
|||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
||||
|
|
|
@ -44,7 +44,7 @@ function runTests()
|
|||
ok(!notificationBox.currentNotification,
|
||||
"there is no notification in content context");
|
||||
|
||||
sp.setText("window.foobarBug636725 = 'aloha';");
|
||||
sp.editor.setText("window.foobarBug636725 = 'aloha';");
|
||||
|
||||
ok(!content.wrappedJSObject.foobarBug636725,
|
||||
"no content.foobarBug636725");
|
||||
|
@ -71,7 +71,8 @@ function runTests()
|
|||
ok(notificationBox.currentNotification,
|
||||
"there is a notification in browser context");
|
||||
|
||||
sp.setText("2'", 31, 32);
|
||||
let [ from, to ] = sp.editor.getPosition(31, 32);
|
||||
sp.editor.replaceText("2'", from, to);
|
||||
|
||||
is(sp.getText(), "window.foobarBug636725 = 'aloha2';",
|
||||
"setText() worked");
|
||||
|
@ -87,7 +88,7 @@ function runTests()
|
|||
{
|
||||
method: "run",
|
||||
prepare: function() {
|
||||
sp.setText("gBrowser", 7);
|
||||
sp.editor.replaceText("gBrowser", sp.editor.getPosition(7));
|
||||
|
||||
is(sp.getText(), "window.gBrowser",
|
||||
"setText() worked with no end for the replace range");
|
||||
|
@ -101,7 +102,7 @@ function runTests()
|
|||
method: "run",
|
||||
prepare: function() {
|
||||
// Check that the sandbox is cached.
|
||||
sp.setText("typeof foobarBug636725cache;");
|
||||
sp.editor.setText("typeof foobarBug636725cache;");
|
||||
},
|
||||
then: function([, , result]) {
|
||||
is(result, "undefined", "global variable does not exist");
|
||||
|
@ -110,7 +111,7 @@ function runTests()
|
|||
{
|
||||
method: "run",
|
||||
prepare: function() {
|
||||
sp.setText("var foobarBug636725cache = 'foo';" +
|
||||
sp.editor.setText("var foobarBug636725cache = 'foo';" +
|
||||
"typeof foobarBug636725cache;");
|
||||
},
|
||||
then: function([, , result]) {
|
||||
|
@ -121,7 +122,7 @@ function runTests()
|
|||
{
|
||||
method: "run",
|
||||
prepare: function() {
|
||||
sp.setText("var foobarBug636725cache2 = 'foo';" +
|
||||
sp.editor.setText("var foobarBug636725cache2 = 'foo';" +
|
||||
"typeof foobarBug636725cache2;");
|
||||
},
|
||||
then: function([, , result]) {
|
||||
|
@ -137,7 +138,7 @@ function runTests()
|
|||
is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT,
|
||||
"executionContext is content");
|
||||
|
||||
sp.setText("typeof foobarBug636725cache2;");
|
||||
sp.editor.setText("typeof foobarBug636725cache2;");
|
||||
},
|
||||
then: function([, , result]) {
|
||||
is(result, "undefined",
|
||||
|
@ -147,7 +148,7 @@ function runTests()
|
|||
|
||||
runAsyncCallbackTests(sp, tests).then(() => {
|
||||
sp.setBrowserContext();
|
||||
sp.setText("delete foobarBug636725cache;" +
|
||||
sp.editor.setText("delete foobarBug636725cache;" +
|
||||
"delete foobarBug636725cache2;");
|
||||
sp.run().then(finish);
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ function runTests()
|
|||
method: "run",
|
||||
prepare: function() {
|
||||
content.wrappedJSObject.foobarBug636725 = 1;
|
||||
sp.setText("++window.foobarBug636725");
|
||||
sp.editor.setText("++window.foobarBug636725");
|
||||
},
|
||||
then: function([code, , result]) {
|
||||
is(code, sp.getText(), "code is correct");
|
||||
|
@ -47,34 +47,15 @@ function runTests()
|
|||
is(sp.getText(), "++window.foobarBug636725\n/*\n3\n*/",
|
||||
"display() shows evaluation result in the textbox");
|
||||
|
||||
is(sp.selectedText, "\n/*\n3\n*/", "selectedText is correct");
|
||||
is(sp.editor.getSelection(), "\n/*\n3\n*/", "getSelection is correct");
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "run",
|
||||
prepare: function() {
|
||||
let selection = sp.getSelectionRange();
|
||||
is(selection.start, 24, "selection.start is correct");
|
||||
is(selection.end, 32, "selection.end is correct");
|
||||
|
||||
// Test selection run() and display().
|
||||
|
||||
sp.setText("window.foobarBug636725 = 'a';\n" +
|
||||
sp.editor.setText("window.foobarBug636725 = 'a';\n" +
|
||||
"window.foobarBug636725 = 'b';");
|
||||
|
||||
sp.selectRange(1, 2);
|
||||
|
||||
selection = sp.getSelectionRange();
|
||||
|
||||
is(selection.start, 1, "selection.start is 1");
|
||||
is(selection.end, 2, "selection.end is 2");
|
||||
|
||||
sp.selectRange(0, 29);
|
||||
|
||||
selection = sp.getSelectionRange();
|
||||
|
||||
is(selection.start, 0, "selection.start is 0");
|
||||
is(selection.end, 29, "selection.end is 29");
|
||||
sp.editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 29 });
|
||||
},
|
||||
then: function([code, , result]) {
|
||||
is(code, "window.foobarBug636725 = 'a';", "code is correct");
|
||||
|
@ -91,10 +72,9 @@ function runTests()
|
|||
{
|
||||
method: "display",
|
||||
prepare: function() {
|
||||
sp.setText("window.foobarBug636725 = 'c';\n" +
|
||||
sp.editor.setText("window.foobarBug636725 = 'c';\n" +
|
||||
"window.foobarBug636725 = 'b';");
|
||||
|
||||
sp.selectRange(0, 22);
|
||||
sp.editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 22 });
|
||||
},
|
||||
then: function() {
|
||||
is(content.wrappedJSObject.foobarBug636725, "a",
|
||||
|
@ -106,27 +86,19 @@ function runTests()
|
|||
"window.foobarBug636725 = 'b';",
|
||||
"display() shows evaluation result in the textbox");
|
||||
|
||||
is(sp.selectedText, "\n/*\na\n*/", "selectedText is correct");
|
||||
is(sp.editor.getSelection(), "\n/*\na\n*/", "getSelection is correct");
|
||||
}
|
||||
}]
|
||||
|
||||
}];
|
||||
|
||||
runAsyncCallbackTests(sp, tests).then(function() {
|
||||
let selection = sp.getSelectionRange();
|
||||
is(selection.start, 22, "selection.start is correct");
|
||||
is(selection.end, 30, "selection.end is correct");
|
||||
|
||||
sp.deselect();
|
||||
|
||||
ok(!sp.selectedText, "selectedText is empty");
|
||||
|
||||
selection = sp.getSelectionRange();
|
||||
is(selection.start, selection.end, "deselect() works");
|
||||
ok(sp.editor.somethingSelected(), "something is selected");
|
||||
sp.editor.dropSelection();
|
||||
ok(!sp.editor.somethingSelected(), "something is no longer selected");
|
||||
ok(!sp.editor.getSelection(), "getSelection is empty");
|
||||
|
||||
// Test undo/redo.
|
||||
|
||||
sp.setText("foo1");
|
||||
sp.setText("foo2");
|
||||
sp.editor.setText("foo1");
|
||||
sp.editor.setText("foo2");
|
||||
is(sp.getText(), "foo2", "editor content updated");
|
||||
sp.undo();
|
||||
is(sp.getText(), "foo1", "undo() works");
|
||||
|
|
|
@ -55,7 +55,7 @@ function fileImported(aStatus, aFileContent)
|
|||
|
||||
// Save the file after changes.
|
||||
gFileContent += "// omg, saved!";
|
||||
gScratchpad.setText(gFileContent);
|
||||
gScratchpad.editor.setText(gFileContent);
|
||||
|
||||
gScratchpad.exportToFile(gFile.QueryInterface(Ci.nsILocalFile), true, true,
|
||||
fileExported);
|
||||
|
@ -70,7 +70,7 @@ function fileExported(aStatus)
|
|||
|
||||
// Attempt another file save, with confirmation which returns false.
|
||||
gFileContent += "// omg, saved twice!";
|
||||
gScratchpad.setText(gFileContent);
|
||||
gScratchpad.editor.setText(gFileContent);
|
||||
|
||||
let oldConfirm = gScratchpadWindow.confirm;
|
||||
let askedConfirmation = false;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Please note that some subdirectories of the CodeMirror distribution
|
||||
include their own LICENSE files, and are released under different
|
||||
licences.
|
|
@ -0,0 +1,59 @@
|
|||
This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
|
||||
is a JavaScript component that provides a code editor in the browser. When
|
||||
a mode is available for the language you are coding in, it will color your
|
||||
code, and optionally help with indentation.
|
||||
|
||||
# Upgrade
|
||||
|
||||
Currently used version is 3.15. To upgrade, download a new version of
|
||||
CodeMirror from the project's page [1] and replace all JavaScript and
|
||||
CSS files inside the codemirror directory [2].
|
||||
|
||||
To confirm the functionality run mochitests for the following components:
|
||||
|
||||
* sourceeditor
|
||||
* scratchpad
|
||||
* debugger
|
||||
* styleditor
|
||||
|
||||
The sourceeditor component contains imported CodeMirror tests [3]. Some
|
||||
tests were commented out because we don't use that functionality within
|
||||
Firefox (for example Ruby editing mode). Other than that, we don't have
|
||||
any Mozilla-specific patches applied to CodeMirror itself.
|
||||
|
||||
# Addons
|
||||
|
||||
To install a new CodeMirror addon add it to the codemirror directory,
|
||||
jar.mn [4] file and editor.js [5]. Also, add it to the License section
|
||||
below.
|
||||
|
||||
# License
|
||||
|
||||
The following files in this directory are licensed according to the contents
|
||||
in the LICENSE file:
|
||||
|
||||
* codemirror.css
|
||||
* codemirror.js
|
||||
* comment.js
|
||||
* dialog/dialog.css
|
||||
* dialog/dialog.js
|
||||
* javascript.js
|
||||
* matchbrackets.js
|
||||
* search/match-highlighter.js
|
||||
* search/search.js
|
||||
* search/searchcursor.js
|
||||
* test/codemirror.html
|
||||
* test/cm_comment_test.js
|
||||
* test/cm_driver.js
|
||||
* test/cm_mode_javascript_test.js
|
||||
* test/cm_mode_test.css
|
||||
* test/cm_mode_test.js
|
||||
* test/cm_test.js
|
||||
|
||||
# Footnotes
|
||||
|
||||
[1] http://codemirror.net
|
||||
[2] browser/devtools/sourceeditor/codemirror
|
||||
[3] browser/devtools/sourceeditor/test/browser_codemirror.js
|
||||
[4] browser/devtools/jar.mn
|
||||
[5] browser/devtools/sourceeditor/editor.js
|
|
@ -0,0 +1,258 @@
|
|||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
/* Set scrolling behaviour here */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
z-index: 3;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
background: #7e7;
|
||||
z-index: 1;
|
||||
}
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
|
||||
|
||||
.cm-tab { display: inline-block; }
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable {color: black;}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-property {color: black;}
|
||||
.cm-s-default .cm-operator {color: black;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px; padding-right: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actuall scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
padding-bottom: 30px;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: -32px;
|
||||
display: inline-block;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
*display:inline;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
.CodeMirror-code pre {
|
||||
border-right: 30px solid transparent;
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
.CodeMirror-wrap .CodeMirror-code pre {
|
||||
border-right: none;
|
||||
width: auto;
|
||||
}
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {
|
||||
}
|
||||
|
||||
.CodeMirror-wrap .CodeMirror-scroll {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%; height: 0px;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
.CodeMirror-focused div.CodeMirror-cursor {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
||||
.CodeMirror span { *vertical-align: text-bottom; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,145 @@
|
|||
(function() {
|
||||
"use strict";
|
||||
|
||||
var noOptions = {};
|
||||
var nonWS = /[^\s\u00a0]/;
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function firstNonWS(str) {
|
||||
var found = str.search(nonWS);
|
||||
return found == -1 ? 0 : found;
|
||||
}
|
||||
|
||||
CodeMirror.commands.toggleComment = function(cm) {
|
||||
var from = cm.getCursor("start"), to = cm.getCursor("end");
|
||||
cm.uncomment(from, to) || cm.lineComment(from, to);
|
||||
};
|
||||
|
||||
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var commentString = options.lineComment || mode.lineComment;
|
||||
if (!commentString) {
|
||||
if (options.blockCommentStart || mode.blockCommentStart) {
|
||||
options.fullLines = true;
|
||||
self.blockComment(from, to, options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var firstLine = self.getLine(from.line);
|
||||
if (firstLine == null) return;
|
||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
|
||||
var pad = options.padding == null ? " " : options.padding;
|
||||
var blankLines = options.commentBlankLines || from.line == to.line;
|
||||
|
||||
self.operation(function() {
|
||||
if (options.indent) {
|
||||
var baseString = firstLine.slice(0, firstNonWS(firstLine));
|
||||
for (var i = from.line; i < end; ++i) {
|
||||
var line = self.getLine(i), cut = baseString.length;
|
||||
if (!blankLines && !nonWS.test(line)) continue;
|
||||
if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
|
||||
self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
|
||||
}
|
||||
} else {
|
||||
for (var i = from.line; i < end; ++i) {
|
||||
if (blankLines || nonWS.test(self.getLine(i)))
|
||||
self.replaceRange(commentString + pad, Pos(i, 0));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("blockComment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||
if (!startString || !endString) {
|
||||
if ((options.lineComment || mode.lineComment) && options.fullLines != false)
|
||||
self.lineComment(from, to, options);
|
||||
return;
|
||||
}
|
||||
|
||||
var end = Math.min(to.line, self.lastLine());
|
||||
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
|
||||
|
||||
var pad = options.padding == null ? " " : options.padding;
|
||||
if (from.line > end) return;
|
||||
|
||||
self.operation(function() {
|
||||
if (options.fullLines != false) {
|
||||
var lastLineHasText = nonWS.test(self.getLine(end));
|
||||
self.replaceRange(pad + endString, Pos(end));
|
||||
self.replaceRange(startString + pad, Pos(from.line, 0));
|
||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||
if (lead != null) for (var i = from.line + 1; i <= end; ++i)
|
||||
if (i != end || lastLineHasText)
|
||||
self.replaceRange(lead + pad, Pos(i, 0));
|
||||
} else {
|
||||
self.replaceRange(endString, to);
|
||||
self.replaceRange(startString, from);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("uncomment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end);
|
||||
|
||||
// Try finding line comments
|
||||
var lineString = options.lineComment || mode.lineComment, lines = [];
|
||||
var pad = options.padding == null ? " " : options.padding, didSomething;
|
||||
lineComment: {
|
||||
if (!lineString) break lineComment;
|
||||
for (var i = start; i <= end; ++i) {
|
||||
var line = self.getLine(i);
|
||||
var found = line.indexOf(lineString);
|
||||
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
|
||||
if (i != start && found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
|
||||
lines.push(line);
|
||||
}
|
||||
self.operation(function() {
|
||||
for (var i = start; i <= end; ++i) {
|
||||
var line = lines[i - start];
|
||||
var pos = line.indexOf(lineString), endPos = pos + lineString.length;
|
||||
if (pos < 0) continue;
|
||||
if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
|
||||
didSomething = true;
|
||||
self.replaceRange("", Pos(i, pos), Pos(i, endPos));
|
||||
}
|
||||
});
|
||||
if (didSomething) return true;
|
||||
}
|
||||
|
||||
// Try block comments
|
||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||
if (!startString || !endString) return false;
|
||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||
var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
|
||||
var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
|
||||
if (close == -1 && start != end) {
|
||||
endLine = self.getLine(--end);
|
||||
close = endLine.lastIndexOf(endString);
|
||||
}
|
||||
if (open == -1 || close == -1) return false;
|
||||
|
||||
self.operation(function() {
|
||||
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
|
||||
Pos(end, close + endString.length));
|
||||
var openEnd = open + startString.length;
|
||||
if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
|
||||
self.replaceRange("", Pos(start, open), Pos(start, openEnd));
|
||||
if (lead) for (var i = start + 1; i <= end; ++i) {
|
||||
var line = self.getLine(i), found = line.indexOf(lead);
|
||||
if (found == -1 || nonWS.test(line.slice(0, found))) continue;
|
||||
var foundEnd = found + lead.length;
|
||||
if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
|
||||
self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,32 @@
|
|||
.CodeMirror-dialog {
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
background: white;
|
||||
z-index: 15;
|
||||
padding: .1em .8em;
|
||||
overflow: hidden;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-top {
|
||||
border-bottom: 1px solid #eee;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-bottom {
|
||||
border-top: 1px solid #eee;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
width: 20em;
|
||||
color: inherit;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog button {
|
||||
font-size: 70%;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// Open simple dialogs on top of an editor. Relies on dialog.css.
|
||||
|
||||
(function() {
|
||||
function dialogDiv(cm, template, bottom) {
|
||||
var wrap = cm.getWrapperElement();
|
||||
var dialog;
|
||||
dialog = wrap.appendChild(document.createElement("div"));
|
||||
if (bottom) {
|
||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
|
||||
} else {
|
||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
|
||||
}
|
||||
dialog.innerHTML = template;
|
||||
return dialog;
|
||||
}
|
||||
|
||||
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
|
||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||
var closed = false, me = this;
|
||||
function close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
dialog.parentNode.removeChild(dialog);
|
||||
}
|
||||
var inp = dialog.getElementsByTagName("input")[0], button;
|
||||
if (inp) {
|
||||
CodeMirror.on(inp, "keydown", function(e) {
|
||||
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
|
||||
if (e.keyCode == 13 || e.keyCode == 27) {
|
||||
CodeMirror.e_stop(e);
|
||||
close();
|
||||
me.focus();
|
||||
if (e.keyCode == 13) callback(inp.value);
|
||||
}
|
||||
});
|
||||
if (options && options.onKeyUp) {
|
||||
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
|
||||
}
|
||||
if (options && options.value) inp.value = options.value;
|
||||
inp.focus();
|
||||
CodeMirror.on(inp, "blur", close);
|
||||
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
||||
CodeMirror.on(button, "click", function() {
|
||||
close();
|
||||
me.focus();
|
||||
});
|
||||
button.focus();
|
||||
CodeMirror.on(button, "blur", close);
|
||||
}
|
||||
return close;
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
|
||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||
var buttons = dialog.getElementsByTagName("button");
|
||||
var closed = false, me = this, blurring = 1;
|
||||
function close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
dialog.parentNode.removeChild(dialog);
|
||||
me.focus();
|
||||
}
|
||||
buttons[0].focus();
|
||||
for (var i = 0; i < buttons.length; ++i) {
|
||||
var b = buttons[i];
|
||||
(function(callback) {
|
||||
CodeMirror.on(b, "click", function(e) {
|
||||
CodeMirror.e_preventDefault(e);
|
||||
close();
|
||||
if (callback) callback(me);
|
||||
});
|
||||
})(callbacks[i]);
|
||||
CodeMirror.on(b, "blur", function() {
|
||||
--blurring;
|
||||
setTimeout(function() { if (blurring <= 0) close(); }, 200);
|
||||
});
|
||||
CodeMirror.on(b, "focus", function() { ++blurring; });
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,479 @@
|
|||
// TODO actually recognize syntax of TypeScript constructs
|
||||
|
||||
CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
var indentUnit = config.indentUnit;
|
||||
var statementIndent = parserConfig.statementIndent;
|
||||
var jsonMode = parserConfig.json;
|
||||
var isTS = parserConfig.typescript;
|
||||
|
||||
// Tokenizer
|
||||
|
||||
var keywords = function(){
|
||||
function kw(type) {return {type: type, style: "keyword"};}
|
||||
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
|
||||
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
||||
|
||||
var jsKeywords = {
|
||||
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
||||
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
|
||||
"var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||
"function": kw("function"), "catch": kw("catch"),
|
||||
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||
"in": operator, "typeof": operator, "instanceof": operator,
|
||||
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
||||
"this": kw("this")
|
||||
};
|
||||
|
||||
// Extend the 'normal' keywords with the TypeScript language extensions
|
||||
if (isTS) {
|
||||
var type = {type: "variable", style: "variable-3"};
|
||||
var tsKeywords = {
|
||||
// object-like things
|
||||
"interface": kw("interface"),
|
||||
"class": kw("class"),
|
||||
"extends": kw("extends"),
|
||||
"constructor": kw("constructor"),
|
||||
|
||||
// scope modifiers
|
||||
"public": kw("public"),
|
||||
"private": kw("private"),
|
||||
"protected": kw("protected"),
|
||||
"static": kw("static"),
|
||||
|
||||
"super": kw("super"),
|
||||
|
||||
// types
|
||||
"string": type, "number": type, "bool": type, "any": type
|
||||
};
|
||||
|
||||
for (var attr in tsKeywords) {
|
||||
jsKeywords[attr] = tsKeywords[attr];
|
||||
}
|
||||
}
|
||||
|
||||
return jsKeywords;
|
||||
}();
|
||||
|
||||
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
|
||||
|
||||
function chain(stream, state, f) {
|
||||
state.tokenize = f;
|
||||
return f(stream, state);
|
||||
}
|
||||
|
||||
function nextUntilUnescaped(stream, end) {
|
||||
var escaped = false, next;
|
||||
while ((next = stream.next()) != null) {
|
||||
if (next == end && !escaped)
|
||||
return false;
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Used as scratch variables to communicate multiple values without
|
||||
// consing up tons of objects.
|
||||
var type, content;
|
||||
function ret(tp, style, cont) {
|
||||
type = tp; content = cont;
|
||||
return style;
|
||||
}
|
||||
|
||||
function jsTokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == '"' || ch == "'")
|
||||
return chain(stream, state, jsTokenString(ch));
|
||||
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
|
||||
return ret(ch);
|
||||
else if (ch == "0" && stream.eat(/x/i)) {
|
||||
stream.eatWhile(/[\da-f]/i);
|
||||
return ret("number", "number");
|
||||
}
|
||||
else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
|
||||
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
|
||||
return ret("number", "number");
|
||||
}
|
||||
else if (ch == "/") {
|
||||
if (stream.eat("*")) {
|
||||
return chain(stream, state, jsTokenComment);
|
||||
}
|
||||
else if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ret("comment", "comment");
|
||||
}
|
||||
else if (state.lastType == "operator" || state.lastType == "keyword c" ||
|
||||
/^[\[{}\(,;:]$/.test(state.lastType)) {
|
||||
nextUntilUnescaped(stream, "/");
|
||||
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
|
||||
return ret("regexp", "string-2");
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(isOperatorChar);
|
||||
return ret("operator", null, stream.current());
|
||||
}
|
||||
}
|
||||
else if (ch == "#") {
|
||||
stream.skipToEnd();
|
||||
return ret("error", "error");
|
||||
}
|
||||
else if (isOperatorChar.test(ch)) {
|
||||
stream.eatWhile(isOperatorChar);
|
||||
return ret("operator", null, stream.current());
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(/[\w\$_]/);
|
||||
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
|
||||
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
|
||||
ret("variable", "variable", word);
|
||||
}
|
||||
}
|
||||
|
||||
function jsTokenString(quote) {
|
||||
return function(stream, state) {
|
||||
if (!nextUntilUnescaped(stream, quote))
|
||||
state.tokenize = jsTokenBase;
|
||||
return ret("string", "string");
|
||||
};
|
||||
}
|
||||
|
||||
function jsTokenComment(stream, state) {
|
||||
var maybeEnd = false, ch;
|
||||
while (ch = stream.next()) {
|
||||
if (ch == "/" && maybeEnd) {
|
||||
state.tokenize = jsTokenBase;
|
||||
break;
|
||||
}
|
||||
maybeEnd = (ch == "*");
|
||||
}
|
||||
return ret("comment", "comment");
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
|
||||
|
||||
function JSLexical(indented, column, type, align, prev, info) {
|
||||
this.indented = indented;
|
||||
this.column = column;
|
||||
this.type = type;
|
||||
this.prev = prev;
|
||||
this.info = info;
|
||||
if (align != null) this.align = align;
|
||||
}
|
||||
|
||||
function inScope(state, varname) {
|
||||
for (var v = state.localVars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
}
|
||||
|
||||
function parseJS(state, style, type, content, stream) {
|
||||
var cc = state.cc;
|
||||
// Communicate our context to the combinators.
|
||||
// (Less wasteful than consing up a hundred closures on every call.)
|
||||
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
|
||||
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = true;
|
||||
|
||||
while(true) {
|
||||
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
||||
if (combinator(type, content)) {
|
||||
while(cc.length && cc[cc.length - 1].lex)
|
||||
cc.pop()();
|
||||
if (cx.marked) return cx.marked;
|
||||
if (type == "variable" && inScope(state, content)) return "variable-2";
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combinator utils
|
||||
|
||||
var cx = {state: null, column: null, marked: null, cc: null};
|
||||
function pass() {
|
||||
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
||||
}
|
||||
function cont() {
|
||||
pass.apply(null, arguments);
|
||||
return true;
|
||||
}
|
||||
function register(varname) {
|
||||
function inList(list) {
|
||||
for (var v = list; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
return false;
|
||||
}
|
||||
var state = cx.state;
|
||||
if (state.context) {
|
||||
cx.marked = "def";
|
||||
if (inList(state.localVars)) return;
|
||||
state.localVars = {name: varname, next: state.localVars};
|
||||
} else {
|
||||
if (inList(state.globalVars)) return;
|
||||
state.globalVars = {name: varname, next: state.globalVars};
|
||||
}
|
||||
}
|
||||
|
||||
// Combinators
|
||||
|
||||
var defaultVars = {name: "this", next: {name: "arguments"}};
|
||||
function pushcontext() {
|
||||
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
|
||||
cx.state.localVars = defaultVars;
|
||||
}
|
||||
function popcontext() {
|
||||
cx.state.localVars = cx.state.context.vars;
|
||||
cx.state.context = cx.state.context.prev;
|
||||
}
|
||||
function pushlex(type, info) {
|
||||
var result = function() {
|
||||
var state = cx.state, indent = state.indented;
|
||||
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
||||
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
||||
};
|
||||
result.lex = true;
|
||||
return result;
|
||||
}
|
||||
function poplex() {
|
||||
var state = cx.state;
|
||||
if (state.lexical.prev) {
|
||||
if (state.lexical.type == ")")
|
||||
state.indented = state.lexical.indented;
|
||||
state.lexical = state.lexical.prev;
|
||||
}
|
||||
}
|
||||
poplex.lex = true;
|
||||
|
||||
function expect(wanted) {
|
||||
return function(type) {
|
||||
if (type == wanted) return cont();
|
||||
else if (wanted == ";") return pass();
|
||||
else return cont(arguments.callee);
|
||||
};
|
||||
}
|
||||
|
||||
function statement(type) {
|
||||
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
|
||||
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
|
||||
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
||||
if (type == "{") return cont(pushlex("}"), block, poplex);
|
||||
if (type == ";") return cont();
|
||||
if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
|
||||
if (type == "function") return cont(functiondef);
|
||||
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
|
||||
poplex, statement, poplex);
|
||||
if (type == "variable") return cont(pushlex("stat"), maybelabel);
|
||||
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
|
||||
block, poplex, poplex);
|
||||
if (type == "case") return cont(expression, expect(":"));
|
||||
if (type == "default") return cont(expect(":"));
|
||||
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
|
||||
statement, poplex, popcontext);
|
||||
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
||||
}
|
||||
function expression(type) {
|
||||
return expressionInner(type, false);
|
||||
}
|
||||
function expressionNoComma(type) {
|
||||
return expressionInner(type, true);
|
||||
}
|
||||
function expressionInner(type, noComma) {
|
||||
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||
if (type == "function") return cont(functiondef);
|
||||
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
|
||||
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
||||
if (type == "operator") return cont(noComma ? expressionNoComma : expression);
|
||||
if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop);
|
||||
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop);
|
||||
return cont();
|
||||
}
|
||||
function maybeexpression(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expression);
|
||||
}
|
||||
function maybeexpressionNoComma(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expressionNoComma);
|
||||
}
|
||||
|
||||
function maybeoperatorComma(type, value) {
|
||||
if (type == ",") return cont(expression);
|
||||
return maybeoperatorNoComma(type, value, false);
|
||||
}
|
||||
function maybeoperatorNoComma(type, value, noComma) {
|
||||
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
||||
var expr = noComma == false ? expression : expressionNoComma;
|
||||
if (type == "operator") {
|
||||
if (/\+\+|--/.test(value)) return cont(me);
|
||||
if (value == "?") return cont(expression, expect(":"), expr);
|
||||
return cont(expr);
|
||||
}
|
||||
if (type == ";") return;
|
||||
if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me);
|
||||
if (type == ".") return cont(property, me);
|
||||
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
||||
}
|
||||
function maybelabel(type) {
|
||||
if (type == ":") return cont(poplex, statement);
|
||||
return pass(maybeoperatorComma, expect(";"), poplex);
|
||||
}
|
||||
function property(type) {
|
||||
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||
}
|
||||
function objprop(type, value) {
|
||||
if (type == "variable") {
|
||||
cx.marked = "property";
|
||||
if (value == "get" || value == "set") return cont(getterSetter);
|
||||
} else if (type == "number" || type == "string") {
|
||||
cx.marked = type + " property";
|
||||
}
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma);
|
||||
}
|
||||
function getterSetter(type) {
|
||||
if (type == ":") return cont(expression);
|
||||
if (type != "variable") return cont(expect(":"), expression);
|
||||
cx.marked = "property";
|
||||
return cont(functiondef);
|
||||
}
|
||||
function commasep(what, end) {
|
||||
function proceed(type) {
|
||||
if (type == ",") {
|
||||
var lex = cx.state.lexical;
|
||||
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
||||
return cont(what, proceed);
|
||||
}
|
||||
if (type == end) return cont();
|
||||
return cont(expect(end));
|
||||
}
|
||||
return function(type) {
|
||||
if (type == end) return cont();
|
||||
else return pass(what, proceed);
|
||||
};
|
||||
}
|
||||
function block(type) {
|
||||
if (type == "}") return cont();
|
||||
return pass(statement, block);
|
||||
}
|
||||
function maybetype(type) {
|
||||
if (type == ":") return cont(typedef);
|
||||
return pass();
|
||||
}
|
||||
function typedef(type) {
|
||||
if (type == "variable"){cx.marked = "variable-3"; return cont();}
|
||||
return pass();
|
||||
}
|
||||
function vardef1(type, value) {
|
||||
if (type == "variable") {
|
||||
register(value);
|
||||
return isTS ? cont(maybetype, vardef2) : cont(vardef2);
|
||||
}
|
||||
return pass();
|
||||
}
|
||||
function vardef2(type, value) {
|
||||
if (value == "=") return cont(expressionNoComma, vardef2);
|
||||
if (type == ",") return cont(vardef1);
|
||||
}
|
||||
function maybeelse(type, value) {
|
||||
if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
|
||||
}
|
||||
function forspec1(type) {
|
||||
if (type == "var") return cont(vardef1, expect(";"), forspec2);
|
||||
if (type == ";") return cont(forspec2);
|
||||
if (type == "variable") return cont(formaybein);
|
||||
return pass(expression, expect(";"), forspec2);
|
||||
}
|
||||
function formaybein(_type, value) {
|
||||
if (value == "in") return cont(expression);
|
||||
return cont(maybeoperatorComma, forspec2);
|
||||
}
|
||||
function forspec2(type, value) {
|
||||
if (type == ";") return cont(forspec3);
|
||||
if (value == "in") return cont(expression);
|
||||
return pass(expression, expect(";"), forspec3);
|
||||
}
|
||||
function forspec3(type) {
|
||||
if (type != ")") cont(expression);
|
||||
}
|
||||
function functiondef(type, value) {
|
||||
if (type == "variable") {register(value); return cont(functiondef);}
|
||||
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
|
||||
}
|
||||
function funarg(type, value) {
|
||||
if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
|
||||
}
|
||||
|
||||
// Interface
|
||||
|
||||
return {
|
||||
startState: function(basecolumn) {
|
||||
return {
|
||||
tokenize: jsTokenBase,
|
||||
lastType: null,
|
||||
cc: [],
|
||||
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||
localVars: parserConfig.localVars,
|
||||
globalVars: parserConfig.globalVars,
|
||||
context: parserConfig.localVars && {vars: parserConfig.localVars},
|
||||
indented: 0
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream.sol()) {
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = false;
|
||||
state.indented = stream.indentation();
|
||||
}
|
||||
if (state.tokenize != jsTokenComment && stream.eatSpace()) return null;
|
||||
var style = state.tokenize(stream, state);
|
||||
if (type == "comment") return style;
|
||||
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
||||
return parseJS(state, style, type, content, stream);
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
|
||||
if (state.tokenize != jsTokenBase) return 0;
|
||||
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
|
||||
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
||||
for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||
var c = state.cc[i];
|
||||
if (c == poplex) lexical = lexical.prev;
|
||||
else if (c != maybeelse || /^else\b/.test(textAfter)) break;
|
||||
}
|
||||
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
|
||||
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
||||
lexical = lexical.prev;
|
||||
var type = lexical.type, closing = firstChar == type;
|
||||
|
||||
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
|
||||
else if (type == "form" && firstChar == "{") return lexical.indented;
|
||||
else if (type == "form") return lexical.indented + indentUnit;
|
||||
else if (type == "stat")
|
||||
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
|
||||
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
||||
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
||||
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
||||
else return lexical.indented + (closing ? 0 : indentUnit);
|
||||
},
|
||||
|
||||
electricChars: ":{}",
|
||||
blockCommentStart: jsonMode ? null : "/*",
|
||||
blockCommentEnd: jsonMode ? null : "*/",
|
||||
lineComment: jsonMode ? null : "//",
|
||||
fold: "brace",
|
||||
|
||||
helperType: jsonMode ? "json" : "javascript",
|
||||
jsonMode: jsonMode
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/javascript", "javascript");
|
||||
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
||||
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
|
@ -0,0 +1,86 @@
|
|||
(function() {
|
||||
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
|
||||
(document.documentMode == null || document.documentMode < 8);
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
|
||||
function findMatchingBracket(cm, where, strict) {
|
||||
var state = cm.state.matchBrackets;
|
||||
var maxScanLen = (state && state.maxScanLineLength) || 10000;
|
||||
|
||||
var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
|
||||
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
|
||||
if (!match) return null;
|
||||
var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
|
||||
if (strict && forward != (pos == cur.ch)) return null;
|
||||
var style = cm.getTokenTypeAt(Pos(cur.line, pos + 1));
|
||||
|
||||
var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
|
||||
function scan(line, lineNo, start) {
|
||||
if (!line.text) return;
|
||||
var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
|
||||
if (line.text.length > maxScanLen) return null;
|
||||
if (start != null) pos = start + d;
|
||||
for (; pos != end; pos += d) {
|
||||
var ch = line.text.charAt(pos);
|
||||
if (re.test(ch) && cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style) {
|
||||
var match = matching[ch];
|
||||
if (match.charAt(1) == ">" == forward) stack.push(ch);
|
||||
else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
|
||||
else if (!stack.length) return {pos: pos, match: true};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
|
||||
if (i == cur.line) found = scan(line, i, pos);
|
||||
else found = scan(cm.getLineHandle(i), i);
|
||||
if (found) break;
|
||||
}
|
||||
return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos),
|
||||
match: found && found.match, forward: forward};
|
||||
}
|
||||
|
||||
function matchBrackets(cm, autoclear) {
|
||||
// Disable brace matching in long lines, since it'll cause hugely slow updates
|
||||
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
|
||||
var found = findMatchingBracket(cm);
|
||||
if (!found || cm.getLine(found.from.line).length > maxHighlightLen ||
|
||||
found.to && cm.getLine(found.to.line).length > maxHighlightLen)
|
||||
return;
|
||||
|
||||
var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
|
||||
var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
|
||||
var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
|
||||
// Kludge to work around the IE bug from issue #1193, where text
|
||||
// input stops going to the textare whever this fires.
|
||||
if (ie_lt8 && cm.state.focused) cm.display.input.focus();
|
||||
var clear = function() {
|
||||
cm.operation(function() { one.clear(); two && two.clear(); });
|
||||
};
|
||||
if (autoclear) setTimeout(clear, 800);
|
||||
else return clear;
|
||||
}
|
||||
|
||||
var currentlyHighlighted = null;
|
||||
function doMatchBrackets(cm) {
|
||||
cm.operation(function() {
|
||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
||||
if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
|
||||
});
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init)
|
||||
cm.off("cursorActivity", doMatchBrackets);
|
||||
if (val) {
|
||||
cm.state.matchBrackets = typeof val == "object" ? val : {};
|
||||
cm.on("cursorActivity", doMatchBrackets);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
|
||||
CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
|
||||
return findMatchingBracket(this, pos, strict);
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,88 @@
|
|||
// Highlighting text that matches the selection
|
||||
//
|
||||
// Defines an option highlightSelectionMatches, which, when enabled,
|
||||
// will style strings that match the selection throughout the
|
||||
// document.
|
||||
//
|
||||
// The option can be set to true to simply enable it, or to a
|
||||
// {minChars, style, showToken} object to explicitly configure it.
|
||||
// minChars is the minimum amount of characters that should be
|
||||
// selected for the behavior to occur, and style is the token style to
|
||||
// apply to the matches. This will be prefixed by "cm-" to create an
|
||||
// actual CSS class name. showToken, when enabled, will cause the
|
||||
// current token to be highlighted when nothing is selected.
|
||||
|
||||
(function() {
|
||||
var DEFAULT_MIN_CHARS = 2;
|
||||
var DEFAULT_TOKEN_STYLE = "matchhighlight";
|
||||
|
||||
function State(options) {
|
||||
if (typeof options == "object") {
|
||||
this.minChars = options.minChars;
|
||||
this.style = options.style;
|
||||
this.showToken = options.showToken;
|
||||
}
|
||||
if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
|
||||
if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
|
||||
this.overlay = this.timeout = null;
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
var over = cm.state.matchHighlighter.overlay;
|
||||
if (over) cm.removeOverlay(over);
|
||||
clearTimeout(cm.state.matchHighlighter.timeout);
|
||||
cm.state.matchHighlighter = null;
|
||||
cm.off("cursorActivity", cursorActivity);
|
||||
}
|
||||
if (val) {
|
||||
cm.state.matchHighlighter = new State(val);
|
||||
highlightMatches(cm);
|
||||
cm.on("cursorActivity", cursorActivity);
|
||||
}
|
||||
});
|
||||
|
||||
function cursorActivity(cm) {
|
||||
var state = cm.state.matchHighlighter;
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(function() {highlightMatches(cm);}, 100);
|
||||
}
|
||||
|
||||
function highlightMatches(cm) {
|
||||
cm.operation(function() {
|
||||
var state = cm.state.matchHighlighter;
|
||||
if (state.overlay) {
|
||||
cm.removeOverlay(state.overlay);
|
||||
state.overlay = null;
|
||||
}
|
||||
if (!cm.somethingSelected() && state.showToken) {
|
||||
var re = state.showToken === true ? /[\w$]/ : state.showToken;
|
||||
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
|
||||
while (start && re.test(line.charAt(start - 1))) --start;
|
||||
while (end < line.length && re.test(line.charAt(end))) ++end;
|
||||
if (start < end)
|
||||
cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
|
||||
return;
|
||||
}
|
||||
if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
|
||||
var selection = cm.getSelection().replace(/^\s+|\s+$/g, "");
|
||||
if (selection.length >= state.minChars)
|
||||
cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
|
||||
});
|
||||
}
|
||||
|
||||
function boundariesAround(stream, re) {
|
||||
return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
|
||||
(stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
|
||||
}
|
||||
|
||||
function makeOverlay(query, hasBoundary, style) {
|
||||
return {token: function(stream) {
|
||||
if (stream.match(query) &&
|
||||
(!hasBoundary || boundariesAround(stream, hasBoundary)))
|
||||
return style;
|
||||
stream.next();
|
||||
stream.skipTo(query.charAt(0)) || stream.skipToEnd();
|
||||
}};
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,131 @@
|
|||
// Define search commands. Depends on dialog.js or another
|
||||
// implementation of the openDialog method.
|
||||
|
||||
// Replace works a little oddly -- it will do the replace on the next
|
||||
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
|
||||
// replace by making sure the match is no longer selected when hitting
|
||||
// Ctrl-G.
|
||||
|
||||
(function() {
|
||||
function searchOverlay(query) {
|
||||
if (typeof query == "string") return {token: function(stream) {
|
||||
if (stream.match(query)) return "searching";
|
||||
stream.next();
|
||||
stream.skipTo(query.charAt(0)) || stream.skipToEnd();
|
||||
}};
|
||||
return {token: function(stream) {
|
||||
if (stream.match(query)) return "searching";
|
||||
while (!stream.eol()) {
|
||||
stream.next();
|
||||
if (stream.match(query, false)) break;
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
function SearchState() {
|
||||
this.posFrom = this.posTo = this.query = null;
|
||||
this.overlay = null;
|
||||
}
|
||||
function getSearchState(cm) {
|
||||
return cm.state.search || (cm.state.search = new SearchState());
|
||||
}
|
||||
function getSearchCursor(cm, query, pos) {
|
||||
// Heuristic: if the query string is all lowercase, do a case insensitive search.
|
||||
return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
|
||||
}
|
||||
function dialog(cm, text, shortText, f) {
|
||||
if (cm.openDialog) cm.openDialog(text, f);
|
||||
else f(prompt(shortText, ""));
|
||||
}
|
||||
function confirmDialog(cm, text, shortText, fs) {
|
||||
if (cm.openConfirm) cm.openConfirm(text, fs);
|
||||
else if (confirm(shortText)) fs[0]();
|
||||
}
|
||||
function parseQuery(query) {
|
||||
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
|
||||
return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
|
||||
}
|
||||
var queryDialog =
|
||||
'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
|
||||
function doSearch(cm, rev) {
|
||||
var state = getSearchState(cm);
|
||||
if (state.query) return findNext(cm, rev);
|
||||
dialog(cm, queryDialog, "Search for:", function(query) {
|
||||
cm.operation(function() {
|
||||
if (!query || state.query) return;
|
||||
state.query = parseQuery(query);
|
||||
cm.removeOverlay(state.overlay);
|
||||
state.overlay = searchOverlay(state.query);
|
||||
cm.addOverlay(state.overlay);
|
||||
state.posFrom = state.posTo = cm.getCursor();
|
||||
findNext(cm, rev);
|
||||
});
|
||||
});
|
||||
}
|
||||
function findNext(cm, rev) {cm.operation(function() {
|
||||
var state = getSearchState(cm);
|
||||
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
||||
if (!cursor.find(rev)) {
|
||||
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
|
||||
if (!cursor.find(rev)) return;
|
||||
}
|
||||
cm.setSelection(cursor.from(), cursor.to());
|
||||
state.posFrom = cursor.from(); state.posTo = cursor.to();
|
||||
});}
|
||||
function clearSearch(cm) {cm.operation(function() {
|
||||
var state = getSearchState(cm);
|
||||
if (!state.query) return;
|
||||
state.query = null;
|
||||
cm.removeOverlay(state.overlay);
|
||||
});}
|
||||
|
||||
var replaceQueryDialog =
|
||||
'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
|
||||
var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
|
||||
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
|
||||
function replace(cm, all) {
|
||||
dialog(cm, replaceQueryDialog, "Replace:", function(query) {
|
||||
if (!query) return;
|
||||
query = parseQuery(query);
|
||||
dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
|
||||
if (all) {
|
||||
cm.operation(function() {
|
||||
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
|
||||
if (typeof query != "string") {
|
||||
var match = cm.getRange(cursor.from(), cursor.to()).match(query);
|
||||
cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
|
||||
} else cursor.replace(text);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
clearSearch(cm);
|
||||
var cursor = getSearchCursor(cm, query, cm.getCursor());
|
||||
var advance = function() {
|
||||
var start = cursor.from(), match;
|
||||
if (!(match = cursor.findNext())) {
|
||||
cursor = getSearchCursor(cm, query);
|
||||
if (!(match = cursor.findNext()) ||
|
||||
(start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
|
||||
}
|
||||
cm.setSelection(cursor.from(), cursor.to());
|
||||
confirmDialog(cm, doReplaceConfirm, "Replace?",
|
||||
[function() {doReplace(match);}, advance]);
|
||||
};
|
||||
var doReplace = function(match) {
|
||||
cursor.replace(typeof query == "string" ? text :
|
||||
text.replace(/\$(\d)/, function(_, i) {return match[i];}));
|
||||
advance();
|
||||
};
|
||||
advance();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
|
||||
CodeMirror.commands.findNext = doSearch;
|
||||
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
|
||||
CodeMirror.commands.clearSearch = clearSearch;
|
||||
CodeMirror.commands.replace = replace;
|
||||
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
|
||||
})();
|
|
@ -0,0 +1,143 @@
|
|||
(function(){
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function SearchCursor(doc, query, pos, caseFold) {
|
||||
this.atOccurrence = false; this.doc = doc;
|
||||
if (caseFold == null && typeof query == "string") caseFold = false;
|
||||
|
||||
pos = pos ? doc.clipPos(pos) : Pos(0, 0);
|
||||
this.pos = {from: pos, to: pos};
|
||||
|
||||
// The matches method is filled in based on the type of query.
|
||||
// It takes a position and a direction, and returns an object
|
||||
// describing the next occurrence of the query, or null if no
|
||||
// more matches were found.
|
||||
if (typeof query != "string") { // Regexp match
|
||||
if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
|
||||
this.matches = function(reverse, pos) {
|
||||
if (reverse) {
|
||||
query.lastIndex = 0;
|
||||
var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
|
||||
for (;;) {
|
||||
query.lastIndex = cutOff;
|
||||
var newMatch = query.exec(line);
|
||||
if (!newMatch) break;
|
||||
match = newMatch;
|
||||
start = match.index;
|
||||
cutOff = match.index + (match[0].length || 1);
|
||||
if (cutOff == line.length) break;
|
||||
}
|
||||
var matchLen = (match && match[0].length) || 0;
|
||||
if (!matchLen) {
|
||||
if (start == 0 && line.length == 0) {match = undefined;}
|
||||
else if (start != doc.getLine(pos.line).length) {
|
||||
matchLen++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query.lastIndex = pos.ch;
|
||||
var line = doc.getLine(pos.line), match = query.exec(line);
|
||||
var matchLen = (match && match[0].length) || 0;
|
||||
var start = match && match.index;
|
||||
if (start + matchLen != line.length && !matchLen) matchLen = 1;
|
||||
}
|
||||
if (match && matchLen)
|
||||
return {from: Pos(pos.line, start),
|
||||
to: Pos(pos.line, start + matchLen),
|
||||
match: match};
|
||||
};
|
||||
} else { // String query
|
||||
if (caseFold) query = query.toLowerCase();
|
||||
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
|
||||
var target = query.split("\n");
|
||||
// Different methods for single-line and multi-line queries
|
||||
if (target.length == 1) {
|
||||
if (!query.length) {
|
||||
// Empty string would match anything and never progress, so
|
||||
// we define it to match nothing instead.
|
||||
this.matches = function() {};
|
||||
} else {
|
||||
this.matches = function(reverse, pos) {
|
||||
var line = fold(doc.getLine(pos.line)), len = query.length, match;
|
||||
if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
|
||||
: (match = line.indexOf(query, pos.ch)) != -1)
|
||||
return {from: Pos(pos.line, match),
|
||||
to: Pos(pos.line, match + len)};
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.matches = function(reverse, pos) {
|
||||
var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
|
||||
var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
|
||||
if (reverse ? offsetA >= pos.ch || offsetA != match.length
|
||||
: offsetA <= pos.ch || offsetA != line.length - match.length)
|
||||
return;
|
||||
for (;;) {
|
||||
if (reverse ? !ln : ln == doc.lineCount() - 1) return;
|
||||
line = fold(doc.getLine(ln += reverse ? -1 : 1));
|
||||
match = target[reverse ? --idx : ++idx];
|
||||
if (idx > 0 && idx < target.length - 1) {
|
||||
if (line != match) return;
|
||||
else continue;
|
||||
}
|
||||
var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
|
||||
if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
|
||||
return;
|
||||
var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
|
||||
return {from: reverse ? end : start, to: reverse ? start : end};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchCursor.prototype = {
|
||||
findNext: function() {return this.find(false);},
|
||||
findPrevious: function() {return this.find(true);},
|
||||
|
||||
find: function(reverse) {
|
||||
var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
|
||||
function savePosAndFail(line) {
|
||||
var pos = Pos(line, 0);
|
||||
self.pos = {from: pos, to: pos};
|
||||
self.atOccurrence = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (this.pos = this.matches(reverse, pos)) {
|
||||
if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
|
||||
this.atOccurrence = true;
|
||||
return this.pos.match || true;
|
||||
}
|
||||
if (reverse) {
|
||||
if (!pos.line) return savePosAndFail(0);
|
||||
pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
|
||||
}
|
||||
else {
|
||||
var maxLine = this.doc.lineCount();
|
||||
if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
|
||||
pos = Pos(pos.line + 1, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
from: function() {if (this.atOccurrence) return this.pos.from;},
|
||||
to: function() {if (this.atOccurrence) return this.pos.to;},
|
||||
|
||||
replace: function(newText) {
|
||||
if (!this.atOccurrence) return;
|
||||
var lines = CodeMirror.splitLines(newText);
|
||||
this.doc.replaceRange(lines, this.pos.from, this.pos.to);
|
||||
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
|
||||
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||
return new SearchCursor(this.doc, query, pos, caseFold);
|
||||
});
|
||||
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||
return new SearchCursor(this, query, pos, caseFold);
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,440 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cu, Cc, Ci, components } = require("chrome");
|
||||
|
||||
const TAB_SIZE = "devtools.editor.tabsize";
|
||||
const EXPAND_TAB = "devtools.editor.expandtab";
|
||||
const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
const promise = require("sdk/core/promise");
|
||||
const events = require("devtools/shared/event-emitter");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
const L10N = Services.strings.createBundle(L10N_BUNDLE);
|
||||
|
||||
// CM_STYLES, CM_SCRIPTS and CM_IFRAME represent the HTML,
|
||||
// JavaScript and CSS that is injected into an iframe in
|
||||
// order to initialize a CodeMirror instance.
|
||||
|
||||
const CM_STYLES = [
|
||||
"chrome://browser/content/devtools/codemirror/codemirror.css",
|
||||
"chrome://browser/content/devtools/codemirror/dialog.css"
|
||||
];
|
||||
|
||||
const CM_SCRIPTS = [
|
||||
"chrome://browser/content/devtools/codemirror/codemirror.js",
|
||||
"chrome://browser/content/devtools/codemirror/dialog.js",
|
||||
"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"
|
||||
];
|
||||
|
||||
const CM_IFRAME =
|
||||
"data:text/html;charset=utf8,<!DOCTYPE html>" +
|
||||
"<html dir='ltr'>" +
|
||||
" <head>" +
|
||||
" <style>" +
|
||||
" html, body { height: 100%; }" +
|
||||
" body { margin: 0; overflow: hidden; }" +
|
||||
" .CodeMirror { width: 100%; height: 100% !important; }" +
|
||||
" </style>" +
|
||||
[ " <link rel='stylesheet' href='" + style + "'>" for (style of CM_STYLES) ].join("\n") +
|
||||
" </head>" +
|
||||
" <body></body>" +
|
||||
"</html>";
|
||||
|
||||
const CM_MAPPING = [
|
||||
"focus",
|
||||
"hasFocus",
|
||||
"setCursor",
|
||||
"getCursor",
|
||||
"somethingSelected",
|
||||
"setSelection",
|
||||
"getSelection",
|
||||
"replaceSelection",
|
||||
"undo",
|
||||
"redo",
|
||||
"clearHistory",
|
||||
"posFromIndex",
|
||||
"openDialog"
|
||||
];
|
||||
|
||||
const CM_JUMP_DIALOG = [
|
||||
L10N.GetStringFromName("gotoLineCmd.promptTitle")
|
||||
+ " <input type=text style='width: 10em'/>"
|
||||
];
|
||||
|
||||
const editors = new WeakMap();
|
||||
|
||||
Editor.modes = {
|
||||
text: { name: "text" },
|
||||
js: { name: "javascript", url: "chrome://browser/content/devtools/codemirror/javascript.js" }
|
||||
};
|
||||
|
||||
function ctrl(k) {
|
||||
return (Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + k;
|
||||
}
|
||||
|
||||
/**
|
||||
* A very thin wrapper around CodeMirror. Provides a number
|
||||
* of helper methods to make our use of CodeMirror easier and
|
||||
* another method, appendTo, to actually create and append
|
||||
* the CodeMirror instance.
|
||||
*
|
||||
* Note that Editor doesn't expose CodeMirror instance to the
|
||||
* outside world.
|
||||
*
|
||||
* Constructor accepts one argument, config. It is very
|
||||
* similar to the CodeMirror configuration object so for most
|
||||
* properties go to CodeMirror's documentation (see below).
|
||||
*
|
||||
* Other than that, it accepts one additional and optional
|
||||
* property contextMenu. This property should be an ID of
|
||||
* an element we can use as a context menu.
|
||||
*
|
||||
* This object is also an event emitter.
|
||||
*
|
||||
* CodeMirror docs: http://codemirror.net/doc/manual.html
|
||||
*/
|
||||
function Editor(config) {
|
||||
const tabSize = Services.prefs.getIntPref(TAB_SIZE);
|
||||
const useTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
|
||||
|
||||
this.version = null;
|
||||
this.config = {
|
||||
value: "",
|
||||
mode: Editor.modes.text,
|
||||
indentUnit: tabSize,
|
||||
tabSize: tabSize,
|
||||
contextMenu: null,
|
||||
matchBrackets: true,
|
||||
extraKeys: {},
|
||||
indentWithTabs: useTabs,
|
||||
};
|
||||
|
||||
// Overwrite default config with user-provided, if needed.
|
||||
Object.keys(config).forEach((k) => this.config[k] = config[k]);
|
||||
|
||||
// Additional shortcuts.
|
||||
this.config.extraKeys[ctrl("J")] = (cm) => this.jumpToLine();
|
||||
this.config.extraKeys[ctrl("/")] = "toggleComment";
|
||||
|
||||
// Disable ctrl-[ and ctrl-] because toolbox uses those
|
||||
// shortcuts.
|
||||
this.config.extraKeys[ctrl("[")] = false;
|
||||
this.config.extraKeys[ctrl("]")] = false;
|
||||
|
||||
// Overwrite default tab behavior. If something is selected,
|
||||
// indent those lines. If nothing is selected and we're
|
||||
// indenting with tabs, insert one tab. Otherwise insert N
|
||||
// whitespaces where N == indentUnit option.
|
||||
this.config.extraKeys.Tab = (cm) => {
|
||||
if (cm.somethingSelected())
|
||||
return void cm.indentSelection("add");
|
||||
|
||||
if (this.config.indentWithTabs)
|
||||
return void cm.replaceSelection("\t", "end", "+input");
|
||||
|
||||
var num = cm.getOption("indentUnit");
|
||||
if (cm.getCursor().ch !== 0) num -= 1;
|
||||
cm.replaceSelection(" ".repeat(num), "end", "+input");
|
||||
};
|
||||
|
||||
events.decorate(this);
|
||||
}
|
||||
|
||||
Editor.prototype = {
|
||||
version: null,
|
||||
config: null,
|
||||
|
||||
/**
|
||||
* Appends the current Editor instance to the element specified by
|
||||
* the only argument 'el'. This method actually creates and loads
|
||||
* CodeMirror and all its dependencies.
|
||||
*
|
||||
* This method is asynchronous and returns a promise.
|
||||
*/
|
||||
appendTo: function (el) {
|
||||
let def = promise.defer();
|
||||
let cm = editors.get(this);
|
||||
let doc = el.ownerDocument;
|
||||
let env = doc.createElementNS(XUL_NS, "iframe");
|
||||
env.flex = 1;
|
||||
|
||||
if (cm)
|
||||
throw new Error("You can append an editor only once.");
|
||||
|
||||
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).
|
||||
|
||||
cm = win.CodeMirror(win.document.body, this.config);
|
||||
cm.getWrapperElement().addEventListener("contextmenu", (ev) => {
|
||||
ev.preventDefault();
|
||||
this.showContextMenu(doc, ev.screenX, ev.screenY);
|
||||
}, false);
|
||||
|
||||
cm.on("change", () => this.emit("change"));
|
||||
doc.defaultView.controllers.insertControllerAt(0, controller(this, doc.defaultView));
|
||||
|
||||
editors.set(this, cm);
|
||||
def.resolve();
|
||||
};
|
||||
|
||||
env.addEventListener("load", onLoad, true);
|
||||
env.setAttribute("src", CM_IFRAME);
|
||||
el.appendChild(env);
|
||||
|
||||
this.once("destroy", () => el.removeChild(env));
|
||||
return def.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if there's something to undo and false otherwise.
|
||||
*/
|
||||
canUndo: function () {
|
||||
let cm = editors.get(this);
|
||||
return cm.historySize().undo > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if there's something to redo and false otherwise.
|
||||
*/
|
||||
canRedo: function () {
|
||||
let cm = editors.get(this);
|
||||
return cm.historySize().redo > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates and returns one or more {line, ch} objects for
|
||||
* a zero-based index who's value is relative to the start of
|
||||
* the editor's text.
|
||||
*
|
||||
* If only one argument is given, this method returns a single
|
||||
* {line,ch} object. Otherwise it returns an array.
|
||||
*/
|
||||
getPosition: function (...args) {
|
||||
let cm = editors.get(this);
|
||||
let res = args.map((ind) => cm.posFromIndex(ind));
|
||||
return args.length === 1 ? res[0] : res;
|
||||
},
|
||||
|
||||
/**
|
||||
* The reverse of getPosition. Similarly to getPosition this
|
||||
* method returns a single value if only one argument was given
|
||||
* and an array otherwise.
|
||||
*/
|
||||
getOffset: function (...args) {
|
||||
let cm = editors.get(this);
|
||||
let res = args.map((pos) => cm.indexFromPos(pos));
|
||||
return args.length > 1 ? res : res[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns text from the text area.
|
||||
*/
|
||||
getText: function () {
|
||||
let cm = editors.get(this);
|
||||
return cm.getValue();
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces whatever is in the text area with the contents of
|
||||
* the 'value' argument.
|
||||
*/
|
||||
setText: function (value) {
|
||||
let cm = editors.get(this);
|
||||
cm.setValue(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces contents of a text area within the from/to {line, ch}
|
||||
* range. If neither from nor to arguments are provided works
|
||||
* exactly like setText. If only from object is provided, inserts
|
||||
* text at that point.
|
||||
*/
|
||||
replaceText: function (value, from, to) {
|
||||
let cm = editors.get(this);
|
||||
|
||||
if (!from)
|
||||
return void this.setText(value);
|
||||
|
||||
if (!to) {
|
||||
let text = cm.getRange({ line: 0, ch: 0 }, from);
|
||||
return void this.setText(text + value);
|
||||
}
|
||||
|
||||
cm.replaceRange(value, from, to);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deselects contents of the text area.
|
||||
*/
|
||||
dropSelection: function () {
|
||||
if (!this.somethingSelected())
|
||||
return;
|
||||
|
||||
this.setCursor(this.getCursor());
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks the contents as clean and returns the current
|
||||
* version number.
|
||||
*/
|
||||
markClean: function () {
|
||||
let cm = editors.get(this);
|
||||
this.version = cm.changeGeneration();
|
||||
return this.version;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if contents of the text area are
|
||||
* clean i.e. no changes were made since the last version.
|
||||
*/
|
||||
isClean: function () {
|
||||
let cm = editors.get(this);
|
||||
return cm.isClean(this.version);
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a context menu at the point x:y. The first
|
||||
* argument, container, should be a DOM node that contains
|
||||
* a context menu element specified by the ID from
|
||||
* config.contextMenu.
|
||||
*/
|
||||
showContextMenu: function (container, x, y) {
|
||||
if (this.config.contextMenu == null)
|
||||
return;
|
||||
|
||||
let popup = container.getElementById(this.config.contextMenu);
|
||||
popup.openPopupAtScreen(x, y, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* This method opens an in-editor dialog asking for a line to
|
||||
* jump to. Once given, it changes cursor to that line.
|
||||
*/
|
||||
jumpToLine: function () {
|
||||
this.openDialog(CM_JUMP_DIALOG, (line) =>
|
||||
this.setCursor({ line: line - 1, ch: 0 }));
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.config = null;
|
||||
this.version = null;
|
||||
this.emit("destroy");
|
||||
}
|
||||
};
|
||||
|
||||
// Since Editor is a thin layer over CodeMirror some methods
|
||||
// are mapped directly—without any changes.
|
||||
|
||||
CM_MAPPING.forEach(function (name) {
|
||||
Editor.prototype[name] = function (...args) {
|
||||
let cm = editors.get(this);
|
||||
return cm[name].apply(cm, args);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns a controller object that can be used for
|
||||
* editor-specific commands such as find, jump to line,
|
||||
* copy/paste, etc.
|
||||
*/
|
||||
function controller(ed, view) {
|
||||
return {
|
||||
supportsCommand: function (cmd) {
|
||||
let cm = editors.get(ed);
|
||||
|
||||
switch (cmd) {
|
||||
case "cmd_find":
|
||||
case "cmd_findAgain":
|
||||
case "cmd_findPrevious":
|
||||
case "cmd_gotoLine":
|
||||
case "cmd_undo":
|
||||
case "cmd_redo":
|
||||
case "cmd_cut":
|
||||
case "cmd_paste":
|
||||
case "cmd_delete":
|
||||
case "cmd_selectAll":
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
isCommandEnabled: function (cmd) {
|
||||
let cm = editors.get(ed);
|
||||
|
||||
switch (cmd) {
|
||||
case "cmd_find":
|
||||
case "cmd_gotoLine":
|
||||
case "cmd_selectAll":
|
||||
return true;
|
||||
case "cmd_findAgain":
|
||||
return cm.state.search != null && cm.state.search.query != null;
|
||||
case "cmd_undo":
|
||||
return ed.canUndo();
|
||||
case "cmd_redo":
|
||||
return ed.canRedo();
|
||||
case "cmd_cut":
|
||||
return cm.getOption("readOnly") !== true && ed.somethingSelected();
|
||||
case "cmd_delete":
|
||||
return ed.somethingSelected();
|
||||
case "cmd_paste":
|
||||
return cm.getOption("readOnly") !== true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
doCommand: function (cmd) {
|
||||
let cm = editors.get(ed);
|
||||
let map = {
|
||||
"cmd_selectAll": "selectAll",
|
||||
"cmd_find": "find",
|
||||
"cmd_undo": "undo",
|
||||
"cmd_redo": "redo",
|
||||
"cmd_delete": "delCharAfter",
|
||||
"cmd_findAgain": "findNext"
|
||||
};
|
||||
|
||||
if (map[cmd])
|
||||
return void cm.execCommand(map[cmd]);
|
||||
|
||||
if (cmd === "cmd_cut")
|
||||
return void view.goDoCommand("cmd_cut");
|
||||
|
||||
if (cmd === "cmdste")
|
||||
return void view.goDoCommand("cmd_paste");
|
||||
|
||||
if (cmd == "cmd_gotoLine")
|
||||
ed.jumpToLine(cm);
|
||||
},
|
||||
|
||||
onEvent: function () {}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Editor;
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
TEST_DIRS += ['test']
|
||||
|
||||
JS_MODULES_PATH = 'modules/devtools/sourceeditor'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'editor.js',
|
||||
'source-editor-orion.jsm',
|
||||
'source-editor-ui.jsm',
|
||||
'source-editor.jsm',
|
||||
|
|
|
@ -10,7 +10,7 @@ const Ci = Components.interfaces;
|
|||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/source-editor-ui.jsm");
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor-ui.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
|
|
|
@ -9,7 +9,7 @@ const Cu = Components.utils;
|
|||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/source-editor-ui.jsm");
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor-ui.jsm");
|
||||
|
||||
const PREF_EDITOR_COMPONENT = "devtools.editor.component";
|
||||
const SOURCEEDITOR_L10N = "chrome://browser/locale/devtools/sourceeditor.properties";
|
||||
|
@ -20,7 +20,7 @@ try {
|
|||
if (component == "ui") {
|
||||
throw new Error("The ui editor component is not available.");
|
||||
}
|
||||
Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor-" + component + ".jsm", obj);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
Cu.reportError("SourceEditor component failed to load: " + component);
|
||||
|
@ -30,7 +30,7 @@ try {
|
|||
|
||||
// Load the default editor component.
|
||||
component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT);
|
||||
Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor-" + component + ".jsm", obj);
|
||||
}
|
||||
|
||||
// Export the SourceEditor.
|
||||
|
|
|
@ -24,4 +24,13 @@ MOCHITEST_BROWSER_FILES = \
|
|||
browser_bug729960_block_bracket_jump.js \
|
||||
browser_bug744021_next_prev_bracket_jump.js \
|
||||
browser_bug725392_mouse_coords_char_offset.js \
|
||||
browser_codemirror.js \
|
||||
head.js \
|
||||
codemirror.html \
|
||||
cm_comment_test.js \
|
||||
cm_driver.js \
|
||||
cm_mode_test.css \
|
||||
cm_mode_test.js \
|
||||
cm_test.js \
|
||||
cm_mode_javascript_test.js \
|
||||
$(NULL)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let editor;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
function test() {
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", temp);
|
||||
let SourceEditor = temp.SourceEditor;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const HOST = 'mochi.test:8888';
|
||||
const URI = "http://" + HOST + "/browser/browser/devtools/sourceeditor/test/codemirror.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
browser.loadURI(URI);
|
||||
|
||||
function check() {
|
||||
var win = browser.contentWindow.wrappedJSObject;
|
||||
var doc = win.document;
|
||||
var out = doc.getElementById("status");
|
||||
|
||||
if (!out || !out.classList.contains("done"))
|
||||
return void setTimeout(check, 100);
|
||||
|
||||
ok(!win.failed, "CodeMirror tests all passed");
|
||||
|
||||
while (gBrowser.tabs.length > 1) gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
setTimeout(check, 100);
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
let SourceEditor = tempScope.SourceEditor;
|
||||
|
||||
let testWin;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
namespace = "comment_";
|
||||
|
||||
(function() {
|
||||
function test(name, mode, run, before, after) {
|
||||
return testCM(name, function(cm) {
|
||||
run(cm);
|
||||
eq(cm.getValue(), after);
|
||||
}, {value: before, mode: mode});
|
||||
}
|
||||
|
||||
var simpleProg = "function foo() {\n return bar;\n}";
|
||||
|
||||
test("block", "javascript", function(cm) {
|
||||
cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"});
|
||||
}, simpleProg + "\n", "/* function foo() {\n * return bar;\n * }\n */");
|
||||
|
||||
test("blockToggle", "javascript", function(cm) {
|
||||
cm.blockComment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
|
||||
cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
|
||||
}, simpleProg, simpleProg);
|
||||
|
||||
test("line", "javascript", function(cm) {
|
||||
cm.lineComment(Pos(1, 1), Pos(1, 1));
|
||||
}, simpleProg, "function foo() {\n// return bar;\n}");
|
||||
|
||||
test("lineToggle", "javascript", function(cm) {
|
||||
cm.lineComment(Pos(0, 0), Pos(2, 1));
|
||||
cm.uncomment(Pos(0, 0), Pos(2, 1));
|
||||
}, simpleProg, simpleProg);
|
||||
|
||||
// test("fallbackToBlock", "css", function(cm) {
|
||||
// cm.lineComment(Pos(0, 0), Pos(2, 1));
|
||||
// }, "html {\n border: none;\n}", "/* html {\n border: none;\n} */");
|
||||
|
||||
// test("fallbackToLine", "ruby", function(cm) {
|
||||
// cm.blockComment(Pos(0, 0), Pos(1));
|
||||
// }, "def blah()\n return hah\n", "# def blah()\n# return hah\n");
|
||||
|
||||
test("commentRange", "javascript", function(cm) {
|
||||
cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false});
|
||||
}, simpleProg, "function foo() {\n /*return bar;*/\n}");
|
||||
|
||||
test("indented", "javascript", function(cm) {
|
||||
cm.lineComment(Pos(1, 0), Pos(2), {indent: true});
|
||||
}, simpleProg, "function foo() {\n // return bar;\n // }");
|
||||
|
||||
test("singleEmptyLine", "javascript", function(cm) {
|
||||
cm.setCursor(1);
|
||||
cm.execCommand("toggleComment");
|
||||
}, "a;\n\nb;", "a;\n// \nb;");
|
||||
})();
|
|
@ -0,0 +1,139 @@
|
|||
var tests = [], debug = null, debugUsed = new Array(), allNames = [];
|
||||
|
||||
function Failure(why) {this.message = why;}
|
||||
Failure.prototype.toString = function() { return this.message; };
|
||||
|
||||
function indexOf(collection, elt) {
|
||||
if (collection.indexOf) return collection.indexOf(elt);
|
||||
for (var i = 0, e = collection.length; i < e; ++i)
|
||||
if (collection[i] == elt) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
function test(name, run, expectedFail) {
|
||||
// Force unique names
|
||||
var originalName = name;
|
||||
var i = 2; // Second function would be NAME_2
|
||||
while (indexOf(allNames, name) !== -1){
|
||||
name = originalName + "_" + i;
|
||||
i++;
|
||||
}
|
||||
allNames.push(name);
|
||||
// Add test
|
||||
tests.push({name: name, func: run, expectedFail: expectedFail});
|
||||
return name;
|
||||
}
|
||||
var namespace = "";
|
||||
function testCM(name, run, opts, expectedFail) {
|
||||
return test(namespace + name, function() {
|
||||
var place = document.getElementById("testground"), cm = window.cm = CodeMirror(place, opts);
|
||||
var successful = false;
|
||||
try {
|
||||
run(cm);
|
||||
successful = true;
|
||||
} finally {
|
||||
if ((debug && !successful) || verbose) {
|
||||
place.style.visibility = "visible";
|
||||
} else {
|
||||
place.removeChild(cm.getWrapperElement());
|
||||
}
|
||||
}
|
||||
}, expectedFail);
|
||||
}
|
||||
|
||||
function runTests(callback) {
|
||||
if (debug) {
|
||||
if (indexOf(debug, "verbose") === 0) {
|
||||
verbose = true;
|
||||
debug.splice(0, 1);
|
||||
}
|
||||
if (debug.length < 1) {
|
||||
debug = null;
|
||||
}
|
||||
}
|
||||
var totalTime = 0;
|
||||
function step(i) {
|
||||
if (i === tests.length){
|
||||
running = false;
|
||||
return callback("done");
|
||||
}
|
||||
var test = tests[i], expFail = test.expectedFail, startTime = +new Date;
|
||||
if (debug !== null) {
|
||||
var debugIndex = indexOf(debug, test.name);
|
||||
if (debugIndex !== -1) {
|
||||
// Remove from array for reporting incorrect tests later
|
||||
debug.splice(debugIndex, 1);
|
||||
} else {
|
||||
var wildcardName = test.name.split("_")[0] + "_*";
|
||||
debugIndex = indexOf(debug, wildcardName);
|
||||
if (debugIndex !== -1) {
|
||||
// Remove from array for reporting incorrect tests later
|
||||
debug.splice(debugIndex, 1);
|
||||
debugUsed.push(wildcardName);
|
||||
} else {
|
||||
debugIndex = indexOf(debugUsed, wildcardName);
|
||||
if (debugIndex == -1) return step(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
var threw = false;
|
||||
try {
|
||||
var message = test.func();
|
||||
} catch(e) {
|
||||
threw = true;
|
||||
if (expFail) callback("expected", test.name);
|
||||
else if (e instanceof Failure) callback("fail", test.name, e.message);
|
||||
else {
|
||||
var pos = /\bat .*?([^\/:]+):(\d+):/.exec(e.stack);
|
||||
callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : ""));
|
||||
}
|
||||
}
|
||||
if (!threw) {
|
||||
if (expFail) callback("fail", test.name, message || "expected failure, but succeeded");
|
||||
else callback("ok", test.name, message);
|
||||
}
|
||||
if (!quit) { // Run next test
|
||||
var delay = 0;
|
||||
totalTime += (+new Date) - startTime;
|
||||
if (totalTime > 500){
|
||||
totalTime = 0;
|
||||
delay = 50;
|
||||
}
|
||||
setTimeout(function(){step(i + 1);}, delay);
|
||||
} else { // Quit tests
|
||||
running = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
step(0);
|
||||
}
|
||||
|
||||
function label(str, msg) {
|
||||
if (msg) return str + " (" + msg + ")";
|
||||
return str;
|
||||
}
|
||||
function eq(a, b, msg) {
|
||||
if (a != b) throw new Failure(label(a + " != " + b, msg));
|
||||
}
|
||||
function eqPos(a, b, msg) {
|
||||
function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; }
|
||||
if (a == b) return;
|
||||
if (a == null) throw new Failure(label("comparing null to " + str(b), msg));
|
||||
if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg));
|
||||
if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg));
|
||||
}
|
||||
function is(a, msg) {
|
||||
if (!a) throw new Failure(label("assertion failed", msg));
|
||||
}
|
||||
|
||||
function countTests() {
|
||||
if (!debug) return tests.length;
|
||||
var sum = 0;
|
||||
for (var i = 0; i < tests.length; ++i) {
|
||||
var name = tests[i].name;
|
||||
if (indexOf(debug, name) != -1 ||
|
||||
indexOf(debug, name.split("_")[0] + "_*") != -1)
|
||||
++sum;
|
||||
}
|
||||
return sum;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
(function() {
|
||||
var mode = CodeMirror.getMode({indentUnit: 2}, "javascript");
|
||||
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
|
||||
|
||||
MT("locals",
|
||||
"[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] = [number 10]; [keyword return] [variable-2 a] + [variable-2 c] + [variable d]; }");
|
||||
|
||||
MT("comma-and-binop",
|
||||
"[keyword function](){ [keyword var] [def x] = [number 1] + [number 2], [def y]; }");
|
||||
})();
|
|
@ -0,0 +1,10 @@
|
|||
.mt-output .mt-token {
|
||||
border: 1px solid #ddd;
|
||||
white-space: pre;
|
||||
font-family: "Consolas", monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mt-output .mt-style {
|
||||
font-size: x-small;
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* Helper to test CodeMirror highlighting modes. It pretty prints output of the
|
||||
* highlighter and can check against expected styles.
|
||||
*
|
||||
* Mode tests are registered by calling test.mode(testName, mode,
|
||||
* tokens), where mode is a mode object as returned by
|
||||
* CodeMirror.getMode, and tokens is an array of lines that make up
|
||||
* the test.
|
||||
*
|
||||
* These lines are strings, in which styled stretches of code are
|
||||
* enclosed in brackets `[]`, and prefixed by their style. For
|
||||
* example, `[keyword if]`. Brackets in the code itself must be
|
||||
* duplicated to prevent them from being interpreted as token
|
||||
* boundaries. For example `a[[i]]` for `a[i]`. If a token has
|
||||
* multiple styles, the styles must be separated by ampersands, for
|
||||
* example `[tag&error </hmtl>]`.
|
||||
*
|
||||
* See the test.js files in the css, markdown, gfm, and stex mode
|
||||
* directories for examples.
|
||||
*/
|
||||
(function() {
|
||||
function findSingle(str, pos, ch) {
|
||||
for (;;) {
|
||||
var found = str.indexOf(ch, pos);
|
||||
if (found == -1) return null;
|
||||
if (str.charAt(found + 1) != ch) return found;
|
||||
pos = found + 2;
|
||||
}
|
||||
}
|
||||
|
||||
var styleName = /[\w&-_]+/g;
|
||||
function parseTokens(strs) {
|
||||
var tokens = [], plain = "";
|
||||
for (var i = 0; i < strs.length; ++i) {
|
||||
if (i) plain += "\n";
|
||||
var str = strs[i], pos = 0;
|
||||
while (pos < str.length) {
|
||||
var style = null, text;
|
||||
if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") {
|
||||
styleName.lastIndex = pos + 1;
|
||||
var m = styleName.exec(str);
|
||||
style = m[0].replace(/&/g, " ");
|
||||
var textStart = pos + style.length + 2;
|
||||
var end = findSingle(str, textStart, "]");
|
||||
if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style);
|
||||
text = str.slice(textStart, end);
|
||||
pos = end + 1;
|
||||
} else {
|
||||
var end = findSingle(str, pos, "[");
|
||||
if (end == null) end = str.length;
|
||||
text = str.slice(pos, end);
|
||||
pos = end;
|
||||
}
|
||||
text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);});
|
||||
tokens.push(style, text);
|
||||
plain += text;
|
||||
}
|
||||
}
|
||||
return {tokens: tokens, plain: plain};
|
||||
}
|
||||
|
||||
test.mode = function(name, mode, tokens, modeName) {
|
||||
var data = parseTokens(tokens);
|
||||
return test((modeName || mode.name) + "_" + name, function() {
|
||||
return compare(data.plain, data.tokens, mode);
|
||||
});
|
||||
};
|
||||
|
||||
function compare(text, expected, mode) {
|
||||
|
||||
var expectedOutput = [];
|
||||
for (var i = 0; i < expected.length; i += 2) {
|
||||
var sty = expected[i];
|
||||
if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' ');
|
||||
expectedOutput.push(sty, expected[i + 1]);
|
||||
}
|
||||
|
||||
var observedOutput = highlight(text, mode);
|
||||
|
||||
var pass, passStyle = "";
|
||||
pass = highlightOutputsEqual(expectedOutput, observedOutput);
|
||||
passStyle = pass ? 'mt-pass' : 'mt-fail';
|
||||
|
||||
var s = '';
|
||||
if (pass) {
|
||||
s += '<div class="mt-test ' + passStyle + '">';
|
||||
s += '<pre>' + text.replace('&', '&').replace('<', '<') + '</pre>';
|
||||
s += '<div class="cm-s-default">';
|
||||
s += prettyPrintOutputTable(observedOutput);
|
||||
s += '</div>';
|
||||
s += '</div>';
|
||||
return s;
|
||||
} else {
|
||||
s += '<div class="mt-test ' + passStyle + '">';
|
||||
s += '<pre>' + text.replace('&', '&').replace('<', '<') + '</pre>';
|
||||
s += '<div class="cm-s-default">';
|
||||
s += 'expected:';
|
||||
s += prettyPrintOutputTable(expectedOutput);
|
||||
s += 'observed:';
|
||||
s += prettyPrintOutputTable(observedOutput);
|
||||
s += '</div>';
|
||||
s += '</div>';
|
||||
throw s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulation of CodeMirror's internal highlight routine for testing. Multi-line
|
||||
* input is supported.
|
||||
*
|
||||
* @param string to highlight
|
||||
*
|
||||
* @param mode the mode that will do the actual highlighting
|
||||
*
|
||||
* @return array of [style, token] pairs
|
||||
*/
|
||||
function highlight(string, mode) {
|
||||
var state = mode.startState()
|
||||
|
||||
var lines = string.replace(/\r\n/g,'\n').split('\n');
|
||||
var st = [], pos = 0;
|
||||
for (var i = 0; i < lines.length; ++i) {
|
||||
var line = lines[i], newLine = true;
|
||||
var stream = new CodeMirror.StringStream(line);
|
||||
if (line == "" && mode.blankLine) mode.blankLine(state);
|
||||
/* Start copied code from CodeMirror.highlight */
|
||||
while (!stream.eol()) {
|
||||
var style = mode.token(stream, state), substr = stream.current();
|
||||
if (style && style.indexOf(" ") > -1) style = style.split(' ').sort().join(' ');
|
||||
|
||||
stream.start = stream.pos;
|
||||
if (pos && st[pos-2] == style && !newLine) {
|
||||
st[pos-1] += substr;
|
||||
} else if (substr) {
|
||||
st[pos++] = style; st[pos++] = substr;
|
||||
}
|
||||
// Give up when line is ridiculously long
|
||||
if (stream.pos > 5000) {
|
||||
st[pos++] = null; st[pos++] = this.text.slice(stream.pos);
|
||||
break;
|
||||
}
|
||||
newLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two arrays of output from highlight.
|
||||
*
|
||||
* @param o1 array of [style, token] pairs
|
||||
*
|
||||
* @param o2 array of [style, token] pairs
|
||||
*
|
||||
* @return boolean; true iff outputs equal
|
||||
*/
|
||||
function highlightOutputsEqual(o1, o2) {
|
||||
if (o1.length != o2.length) return false;
|
||||
for (var i = 0; i < o1.length; ++i)
|
||||
if (o1[i] != o2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print tokens and corresponding styles in a table. Spaces in the token are
|
||||
* replaced with 'interpunct' dots (·).
|
||||
*
|
||||
* @param output array of [style, token] pairs
|
||||
*
|
||||
* @return html string
|
||||
*/
|
||||
function prettyPrintOutputTable(output) {
|
||||
var s = '<table class="mt-output">';
|
||||
s += '<tr>';
|
||||
for (var i = 0; i < output.length; i += 2) {
|
||||
var style = output[i], val = output[i+1];
|
||||
s +=
|
||||
'<td class="mt-token">' +
|
||||
'<span class="cm-' + String(style).replace(/ +/g, " cm-") + '">' +
|
||||
val.replace(/ /g,'\xb7').replace('&', '&').replace('<', '<') +
|
||||
'</span>' +
|
||||
'</td>';
|
||||
}
|
||||
s += '</tr><tr>';
|
||||
for (var i = 0; i < output.length; i += 2) {
|
||||
s += '<td class="mt-style"><span>' + (output[i] || null) + '</span></td>';
|
||||
}
|
||||
s += '</table>';
|
||||
return s;
|
||||
}
|
||||
})();
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,200 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CodeMirror: Test Suite</title>
|
||||
<link rel="stylesheet" href="chrome://browser/content/devtools/codemirror/codemirror.css">
|
||||
<link rel="stylesheet" href="cm_mode_test.css">
|
||||
<!--<link rel="stylesheet" href="../doc/docs.css">-->
|
||||
|
||||
<script src="chrome://browser/content/devtools/codemirror/codemirror.js"></script>
|
||||
<script src="chrome://browser/content/devtools/codemirror/searchcursor.js"></script>
|
||||
<script src="chrome://browser/content/devtools/codemirror/dialog.js"></script>
|
||||
<script src="chrome://browser/content/devtools/codemirror/matchbrackets.js"></script>
|
||||
<script src="chrome://browser/content/devtools/codemirror/comment.js"></script>
|
||||
<script src="chrome://browser/content/devtools/codemirror/javascript.js"></script>
|
||||
|
||||
<!--<script src="../addon/mode/overlay.js"></script>
|
||||
<script src="../addon/mode/multiplex.js"></script>
|
||||
<script src="../mode/xml/xml.js"></script>
|
||||
<script src="../keymap/vim.js"></script>
|
||||
<script src="../keymap/emacs.js"></script>-->
|
||||
|
||||
<style type="text/css">
|
||||
.ok {color: #090;}
|
||||
.fail {color: #e00;}
|
||||
.error {color: #c90;}
|
||||
.done {font-weight: bold;}
|
||||
#progress {
|
||||
background: #45d;
|
||||
color: white;
|
||||
text-shadow: 0 0 1px #45d, 0 0 2px #45d, 0 0 3px #45d;
|
||||
font-weight: bold;
|
||||
white-space: pre;
|
||||
}
|
||||
#testground {
|
||||
visibility: hidden;
|
||||
}
|
||||
#testground.offscreen {
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
top: -10000px;
|
||||
}
|
||||
.CodeMirror { border: 1px solid black; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CodeMirror: Test Suite</h1>
|
||||
|
||||
<p>A limited set of programmatic sanity tests for CodeMirror.</p>
|
||||
|
||||
<div style="border: 1px solid black; padding: 1px; max-width: 700px;">
|
||||
<div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div>
|
||||
</div>
|
||||
<p id=status>Please enable JavaScript...</p>
|
||||
<div id=output></div>
|
||||
|
||||
<div id=testground></div>
|
||||
|
||||
<script src="cm_driver.js"></script>
|
||||
<script src="cm_test.js"></script>
|
||||
<script src="cm_comment_test.js"></script>
|
||||
<script src="cm_mode_test.js"></script>
|
||||
<script src="cm_mode_javascript_test.js"></script>
|
||||
|
||||
<!--<script src="doc_test.js"></script>
|
||||
<script src="../mode/css/css.js"></script>
|
||||
<script src="../mode/css/test.js"></script>
|
||||
<script src="../mode/css/scss_test.js"></script>
|
||||
<script src="../mode/xml/xml.js"></script>
|
||||
<script src="../mode/htmlmixed/htmlmixed.js"></script>
|
||||
<script src="../mode/ruby/ruby.js"></script>
|
||||
<script src="../mode/haml/haml.js"></script>
|
||||
<script src="../mode/haml/test.js"></script>
|
||||
<script src="../mode/markdown/markdown.js"></script>
|
||||
<script src="../mode/markdown/test.js"></script>
|
||||
<script src="../mode/gfm/gfm.js"></script>
|
||||
<script src="../mode/gfm/test.js"></script>
|
||||
<script src="../mode/stex/stex.js"></script>
|
||||
<script src="../mode/stex/test.js"></script>
|
||||
<script src="../mode/xquery/xquery.js"></script>
|
||||
<script src="../mode/xquery/test.js"></script>
|
||||
<script src="../addon/mode/multiplex_test.js"></script>
|
||||
<script src="vim_test.js"></script>
|
||||
<script src="emacs_test.js"></script>-->
|
||||
|
||||
<script>
|
||||
window.onload = runHarness;
|
||||
CodeMirror.on(window, 'hashchange', runHarness);
|
||||
|
||||
function esc(str) {
|
||||
return str.replace(/[<&]/, function(ch) { return ch == "<" ? "<" : "&"; });
|
||||
}
|
||||
|
||||
var output = document.getElementById("output"),
|
||||
progress = document.getElementById("progress"),
|
||||
progressRan = document.getElementById("progress_ran").childNodes[0],
|
||||
progressTotal = document.getElementById("progress_total").childNodes[0];
|
||||
var count = 0,
|
||||
failed = 0,
|
||||
bad = "",
|
||||
running = false, // Flag that states tests are running
|
||||
quit = false, // Flag to quit tests ASAP
|
||||
verbose = false; // Adds message for *every* test to output
|
||||
|
||||
function runHarness(){
|
||||
if (running) {
|
||||
quit = true;
|
||||
setStatus("Restarting tests...", '', true);
|
||||
setTimeout(function(){runHarness();}, 500);
|
||||
return;
|
||||
}
|
||||
if (window.location.hash.substr(1)){
|
||||
debug = window.location.hash.substr(1).split(",");
|
||||
} else {
|
||||
debug = null;
|
||||
}
|
||||
quit = false;
|
||||
running = true;
|
||||
setStatus("Loading tests...");
|
||||
count = 0;
|
||||
failed = 0;
|
||||
bad = "";
|
||||
verbose = false;
|
||||
debugUsed = Array();
|
||||
totalTests = countTests();
|
||||
progressTotal.nodeValue = " of " + totalTests;
|
||||
progressRan.nodeValue = count;
|
||||
output.innerHTML = '';
|
||||
document.getElementById("testground").innerHTML = "<form>" +
|
||||
"<textarea id=\"code\" name=\"code\"></textarea>" +
|
||||
"<input type=submit value=ok name=submit>" +
|
||||
"</form>";
|
||||
runTests(displayTest);
|
||||
}
|
||||
|
||||
function setStatus(message, className, force){
|
||||
if (quit && !force) return;
|
||||
if (!message) throw("must provide message");
|
||||
var status = document.getElementById("status").childNodes[0];
|
||||
status.nodeValue = message;
|
||||
status.parentNode.className = className;
|
||||
}
|
||||
function addOutput(name, className, code){
|
||||
var newOutput = document.createElement("dl");
|
||||
var newTitle = document.createElement("dt");
|
||||
newTitle.className = className;
|
||||
newTitle.appendChild(document.createTextNode(name));
|
||||
newOutput.appendChild(newTitle);
|
||||
var newMessage = document.createElement("dd");
|
||||
newMessage.innerHTML = code;
|
||||
newOutput.appendChild(newTitle);
|
||||
newOutput.appendChild(newMessage);
|
||||
output.appendChild(newOutput);
|
||||
}
|
||||
function displayTest(type, name, customMessage) {
|
||||
var message = "???";
|
||||
if (type != "done") ++count;
|
||||
progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px";
|
||||
progressRan.nodeValue = count;
|
||||
if (type == "ok") {
|
||||
message = "Test '" + name + "' succeeded";
|
||||
if (!verbose) customMessage = false;
|
||||
} else if (type == "expected") {
|
||||
message = "Test '" + name + "' failed as expected";
|
||||
if (!verbose) customMessage = false;
|
||||
} else if (type == "error" || type == "fail") {
|
||||
++failed;
|
||||
message = "Test '" + name + "' failed";
|
||||
} else if (type == "done") {
|
||||
if (failed) {
|
||||
type += " fail";
|
||||
message = failed + " failure" + (failed > 1 ? "s" : "");
|
||||
} else if (count < totalTests) {
|
||||
failed = totalTests - count;
|
||||
type += " fail";
|
||||
message = failed + " failure" + (failed > 1 ? "s" : "");
|
||||
} else {
|
||||
type += " ok";
|
||||
message = "All passed";
|
||||
}
|
||||
if (debug && debug.length) {
|
||||
var bogusTests = totalTests - count;
|
||||
message += " — " + bogusTests + " nonexistent test" +
|
||||
(bogusTests > 1 ? "s" : "") + " requested by location.hash: " +
|
||||
"`" + debug.join("`, `") + "`";
|
||||
} else {
|
||||
progressTotal.nodeValue = '';
|
||||
}
|
||||
customMessage = true; // Hack to avoid adding to output
|
||||
}
|
||||
if (verbose && !customMessage) customMessage = message;
|
||||
setStatus(message, type);
|
||||
if (customMessage && customMessage.length > 0) {
|
||||
addOutput(name, type, customMessage);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -139,7 +139,7 @@ function openSourceEditorWindow(aCallback, aOptions) {
|
|||
function initEditor()
|
||||
{
|
||||
let tempScope = {};
|
||||
Cu.import("resource:///modules/source-editor.jsm", tempScope);
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm", tempScope);
|
||||
|
||||
let box = testWin.document.querySelector("box");
|
||||
editor = new tempScope.SourceEditor();
|
||||
|
|
|
@ -16,7 +16,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm");
|
||||
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
||||
|
||||
|
||||
|
|
|
@ -39,15 +39,16 @@ function testGen() {
|
|||
outputNode.focus();
|
||||
|
||||
scrollBox.onscroll = () => {
|
||||
if (scrollBox.scrollTop == 0) {
|
||||
info("onscroll top " + scrollBox.scrollTop);
|
||||
if (scrollBox.scrollTop != 0) {
|
||||
// Wait for scroll to 0.
|
||||
return;
|
||||
}
|
||||
scrollBox.onscroll = null;
|
||||
isnot(scrollBox.scrollTop, 0, "scroll location updated (moved to top)");
|
||||
is(scrollBox.scrollTop, 0, "scroll location updated (moved to top)");
|
||||
testNext();
|
||||
};
|
||||
EventUtils.synthesizeKey("VK_HOME", {});
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, hud.iframeWindow);
|
||||
|
||||
yield undefined;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
}
|
||||
|
||||
#pretty-print {
|
||||
min-width: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
}
|
||||
|
||||
#pretty-print {
|
||||
min-width: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,10 @@
|
|||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.theme-bg-contrast { /* contrast bg color to attract attention on a container */
|
||||
background: #a18650;
|
||||
}
|
||||
|
||||
.theme-link { /* blue */
|
||||
color: #3689b2;
|
||||
}
|
||||
|
@ -96,3 +100,7 @@
|
|||
.theme-fg-color7 { /* Red */
|
||||
color: #bf5656;
|
||||
}
|
||||
|
||||
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
|
||||
color: black;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,10 @@
|
|||
background: #EFEFEF;
|
||||
}
|
||||
|
||||
.theme-bg-contrast { /* contrast bg color to attract attention on a container */
|
||||
background: #a18650;
|
||||
}
|
||||
|
||||
.theme-link { /* blue */
|
||||
color: hsl(208,56%,40%);
|
||||
}
|
||||
|
@ -96,3 +100,7 @@
|
|||
.theme-fg-color7 { /* Red */
|
||||
color: #bf5656;
|
||||
}
|
||||
|
||||
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
|
||||
color: black;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
}
|
||||
|
||||
#pretty-print {
|
||||
min-width: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=817284
|
|||
|
||||
/** Test for Bug 817284 **/
|
||||
var cu = Components.utils;
|
||||
var sb = cu.Sandbox("http://example.com", { wantDOMConstructors: ["XMLHttpRequest"] });
|
||||
var sb = cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] });
|
||||
|
||||
// Test event handler calls
|
||||
var xhr = cu.evalInSandbox(
|
||||
|
|
|
@ -39,6 +39,16 @@ CheckLength(ExclusiveContext *cx, size_t length)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SetSourceURL(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
|
||||
{
|
||||
if (tokenStream.hasSourceURL()) {
|
||||
if (!ss->setSourceURL(cx, tokenStream.sourceURL()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SetSourceMap(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
|
||||
{
|
||||
|
@ -357,6 +367,9 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
|
|||
if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref()))
|
||||
return NULL;
|
||||
|
||||
if (!SetSourceURL(cx, parser.tokenStream, ss))
|
||||
return NULL;
|
||||
|
||||
if (!SetSourceMap(cx, parser.tokenStream, ss))
|
||||
return NULL;
|
||||
|
||||
|
@ -578,6 +591,9 @@ CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions opt
|
|||
JS_ASSERT(IsAsmJSModuleNative(fun->native()));
|
||||
}
|
||||
|
||||
if (!SetSourceURL(cx, parser.tokenStream, ss))
|
||||
return false;
|
||||
|
||||
if (!SetSourceMap(cx, parser.tokenStream, ss))
|
||||
return false;
|
||||
|
||||
|
|
|
@ -274,6 +274,7 @@ TokenStream::TokenStream(ExclusiveContext *cx, const CompileOptions &options,
|
|||
prevLinebase(NULL),
|
||||
userbuf(cx, base - options.column, length + options.column), // See comment below
|
||||
filename(options.filename),
|
||||
sourceURL_(NULL),
|
||||
sourceMapURL_(NULL),
|
||||
tokenbuf(cx),
|
||||
cx(cx),
|
||||
|
@ -333,6 +334,7 @@ TokenStream::TokenStream(ExclusiveContext *cx, const CompileOptions &options,
|
|||
|
||||
TokenStream::~TokenStream()
|
||||
{
|
||||
js_free(sourceURL_);
|
||||
js_free(sourceMapURL_);
|
||||
|
||||
JS_ASSERT_IF(originPrincipals, originPrincipals->refcount);
|
||||
|
@ -807,31 +809,45 @@ CharsMatch(const jschar *p, const char *q) {
|
|||
}
|
||||
|
||||
bool
|
||||
TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
|
||||
TokenStream::getDirectives(bool isMultiline, bool shouldWarnDeprecated)
|
||||
{
|
||||
// Match comments of the form "//# sourceMappingURL=<url>" or
|
||||
// "/\* //# sourceMappingURL=<url> *\/"
|
||||
// Match directive comments used in debugging, such as "//# sourceURL" and
|
||||
// "//# sourceMappingURL". Use of "//@" instead of "//#" is deprecated.
|
||||
//
|
||||
// To avoid a crashing bug in IE, several JavaScript transpilers wrap single
|
||||
// line comments containing a source mapping URL inside a multiline
|
||||
// comment. To avoid potentially expensive lookahead and backtracking, we
|
||||
// only check for this case if we encounter a '#' character.
|
||||
|
||||
if (!getSourceURL(isMultiline, shouldWarnDeprecated))
|
||||
return false;
|
||||
if (!getSourceMappingURL(isMultiline, shouldWarnDeprecated))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TokenStream::getDirective(bool isMultiline, bool shouldWarnDeprecated,
|
||||
const char *directive, int directiveLength,
|
||||
const char *errorMsgPragma, jschar **destination) {
|
||||
JS_ASSERT(directiveLength <= 18);
|
||||
jschar peeked[18];
|
||||
int32_t c;
|
||||
|
||||
if (peekChars(18, peeked) && CharsMatch(peeked, " sourceMappingURL=")) {
|
||||
if (shouldWarnDeprecated && !reportWarning(JSMSG_DEPRECATED_SOURCE_MAP)) {
|
||||
if (peekChars(directiveLength, peeked) && CharsMatch(peeked, directive)) {
|
||||
if (shouldWarnDeprecated &&
|
||||
!reportWarning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma))
|
||||
return false;
|
||||
}
|
||||
|
||||
skipChars(18);
|
||||
skipChars(directiveLength);
|
||||
tokenbuf.clear();
|
||||
|
||||
while ((c = peekChar()) && c != EOF && !IsSpaceOrBOM2(c)) {
|
||||
getChar();
|
||||
// Source mapping URLs can occur in both single- and multiline
|
||||
// comments. If we're currently inside a multiline comment, we also
|
||||
// need to recognize multiline comment terminators.
|
||||
// Debugging directives can occur in both single- and multi-line
|
||||
// comments. If we're currently inside a multi-line comment, we also
|
||||
// need to recognize multi-line comment terminators.
|
||||
if (isMultiline && c == '*' && peekChar() == '/') {
|
||||
ungetChar('*');
|
||||
break;
|
||||
|
@ -840,23 +856,44 @@ TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
|
|||
}
|
||||
|
||||
if (tokenbuf.empty())
|
||||
// The source map's URL was missing, but not quite an exception that
|
||||
// we should stop and drop everything for, though.
|
||||
// The directive's URL was missing, but this is not quite an
|
||||
// exception that we should stop and drop everything for.
|
||||
return true;
|
||||
|
||||
size_t sourceMapURLLength = tokenbuf.length();
|
||||
size_t length = tokenbuf.length();
|
||||
|
||||
js_free(sourceMapURL_);
|
||||
sourceMapURL_ = cx->pod_malloc<jschar>(sourceMapURLLength + 1);
|
||||
if (!sourceMapURL_)
|
||||
js_free(*destination);
|
||||
*destination = cx->pod_malloc<jschar>(length + 1);
|
||||
if (!*destination)
|
||||
return false;
|
||||
|
||||
PodCopy(sourceMapURL_, tokenbuf.begin(), sourceMapURLLength);
|
||||
sourceMapURL_[sourceMapURLLength] = '\0';
|
||||
PodCopy(*destination, tokenbuf.begin(), length);
|
||||
(*destination)[length] = '\0';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TokenStream::getSourceURL(bool isMultiline, bool shouldWarnDeprecated)
|
||||
{
|
||||
// Match comments of the form "//# sourceURL=<url>" or
|
||||
// "/\* //# sourceURL=<url> *\/"
|
||||
|
||||
return getDirective(isMultiline, shouldWarnDeprecated, " sourceURL=", 11,
|
||||
"sourceURL", &sourceURL_);
|
||||
}
|
||||
|
||||
bool
|
||||
TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
|
||||
{
|
||||
// Match comments of the form "//# sourceMappingURL=<url>" or
|
||||
// "/\* //# sourceMappingURL=<url> *\/"
|
||||
|
||||
return getDirective(isMultiline, shouldWarnDeprecated, " sourceMappingURL=", 18,
|
||||
"sourceMappingURL", &sourceMapURL_);
|
||||
}
|
||||
|
||||
JS_ALWAYS_INLINE Token *
|
||||
TokenStream::newToken(ptrdiff_t adjust)
|
||||
{
|
||||
|
@ -1506,7 +1543,8 @@ TokenStream::getTokenInternal(Modifier modifier)
|
|||
if (matchChar('/')) {
|
||||
c = peekChar();
|
||||
if (c == '@' || c == '#') {
|
||||
if (!getSourceMappingURL(false, getChar() == '@'))
|
||||
bool shouldWarn = getChar() == '@';
|
||||
if (!getDirectives(false, shouldWarn))
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
@ -1524,7 +1562,8 @@ TokenStream::getTokenInternal(Modifier modifier)
|
|||
while ((c = getChar()) != EOF &&
|
||||
!(c == '*' && matchChar('/'))) {
|
||||
if (c == '@' || c == '#') {
|
||||
if (!getSourceMappingURL(true, c == '@'))
|
||||
bool shouldWarn = c == '@';
|
||||
if (!getDirectives(true, shouldWarn))
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -596,6 +596,14 @@ class MOZ_STACK_CLASS TokenStream
|
|||
return pos.buf - userbuf.base();
|
||||
}
|
||||
|
||||
bool hasSourceURL() const {
|
||||
return sourceURL_ != NULL;
|
||||
}
|
||||
|
||||
jschar *sourceURL() {
|
||||
return sourceURL_;
|
||||
}
|
||||
|
||||
bool hasSourceMapURL() const {
|
||||
return sourceMapURL_ != NULL;
|
||||
}
|
||||
|
@ -803,6 +811,12 @@ class MOZ_STACK_CLASS TokenStream
|
|||
bool matchUnicodeEscapeIdStart(int32_t *c);
|
||||
bool matchUnicodeEscapeIdent(int32_t *c);
|
||||
bool peekChars(int n, jschar *cp);
|
||||
|
||||
bool getDirectives(bool isMultiline, bool shouldWarnDeprecated);
|
||||
bool getDirective(bool isMultiline, bool shouldWarnDeprecated,
|
||||
const char *directive, int directiveLength,
|
||||
const char *errorMsgPragma, jschar **destination);
|
||||
bool getSourceURL(bool isMultiline, bool shouldWarnDeprecated);
|
||||
bool getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated);
|
||||
|
||||
// |expect| cannot be an EOL char.
|
||||
|
@ -843,6 +857,7 @@ class MOZ_STACK_CLASS TokenStream
|
|||
const jschar *prevLinebase; // start of previous line; NULL if on the first line
|
||||
TokenBuf userbuf; // user input buffer
|
||||
const char *filename; // input filename or null
|
||||
jschar *sourceURL_; // the user's requested source URL or null
|
||||
jschar *sourceMapURL_; // source map's filename or null
|
||||
CharBuffer tokenbuf; // current token string buffer
|
||||
bool maybeEOL[256]; // probabilistic EOL lookup table
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/* -*- Mode: javascript; js-indent-level: 4; -*- */
|
||||
// Source.prototype.sourceURL can be a string or null.
|
||||
|
||||
let g = newGlobal('new-compartment');
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
|
||||
function getDisplayURL() {
|
||||
let fw = gw.makeDebuggeeValue(g.f);
|
||||
return fw.script.source.displayURL;
|
||||
}
|
||||
|
||||
// Comment pragmas
|
||||
g.evaluate('function f() {}\n' +
|
||||
'//@ sourceURL=file:///var/quux.js');
|
||||
assertEq(getDisplayURL(), 'file:///var/quux.js');
|
||||
|
||||
g.evaluate('function f() {}\n' +
|
||||
'/*//@ sourceURL=file:///var/quux.js*/');
|
||||
assertEq(getDisplayURL(), 'file:///var/quux.js');
|
||||
|
||||
g.evaluate('function f() {}\n' +
|
||||
'/*\n' +
|
||||
'//@ sourceURL=file:///var/quux.js\n' +
|
||||
'*/');
|
||||
assertEq(getDisplayURL(), 'file:///var/quux.js');
|
|
@ -0,0 +1,71 @@
|
|||
/* -*- Mode: javascript; js-indent-level: 4; -*- */
|
||||
// Source.prototype.sourceURL can be a string or null.
|
||||
|
||||
let g = newGlobal('new-compartment');
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
|
||||
function getDisplayURL() {
|
||||
let fw = gw.makeDebuggeeValue(g.f);
|
||||
return fw.script.source.displayURL;
|
||||
}
|
||||
|
||||
// Without a source url
|
||||
g.evaluate("function f(x) { return 2*x; }");
|
||||
assertEq(getDisplayURL(), null);
|
||||
|
||||
// With a source url
|
||||
g.evaluate("function f(x) { return 2*x; }", {sourceURL: 'file:///var/foo.js'});
|
||||
assertEq(getDisplayURL(), 'file:///var/foo.js');
|
||||
|
||||
// Nested functions
|
||||
let fired = false;
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
fired = true;
|
||||
assertEq(frame.script.source.displayURL, 'file:///var/bar.js');
|
||||
};
|
||||
g.evaluate('(function () { (function () { debugger; })(); })();',
|
||||
{sourceURL: 'file:///var/bar.js'});
|
||||
assertEq(fired, true);
|
||||
|
||||
// Comment pragmas
|
||||
g.evaluate('function f() {}\n' +
|
||||
'//# sourceURL=file:///var/quux.js');
|
||||
assertEq(getDisplayURL(), 'file:///var/quux.js');
|
||||
|
||||
g.evaluate('function f() {}\n' +
|
||||
'/*//# sourceURL=file:///var/quux.js*/');
|
||||
assertEq(getDisplayURL(), 'file:///var/quux.js');
|
||||
|
||||
g.evaluate('function f() {}\n' +
|
||||
'/*\n' +
|
||||
'//# sourceURL=file:///var/quux.js\n' +
|
||||
'*/');
|
||||
assertEq(getDisplayURL(), 'file:///var/quux.js');
|
||||
|
||||
// Spaces are disallowed by the URL spec (they should have been
|
||||
// percent-encoded).
|
||||
g.evaluate('function f() {}\n' +
|
||||
'//# sourceURL=http://example.com/has illegal spaces');
|
||||
assertEq(getDisplayURL(), 'http://example.com/has');
|
||||
|
||||
// When the URL is missing, we don't set the sourceMapURL and we don't skip the
|
||||
// next line of input.
|
||||
g.evaluate('function f() {}\n' +
|
||||
'//# sourceURL=\n' +
|
||||
'function z() {}');
|
||||
assertEq(getDisplayURL(), null);
|
||||
assertEq('z' in g, true);
|
||||
|
||||
// The last comment pragma we see should be the one which sets the displayURL.
|
||||
g.evaluate('function f() {}\n' +
|
||||
'//# sourceURL=http://example.com/foo.js\n' +
|
||||
'//# sourceURL=http://example.com/bar.js');
|
||||
assertEq(getDisplayURL(), 'http://example.com/bar.js');
|
||||
|
||||
// With both a comment and the evaluate option.
|
||||
g.evaluate('function f() {}\n' +
|
||||
'//# sourceURL=http://example.com/foo.js',
|
||||
{sourceMapURL: 'http://example.com/bar.js'});
|
||||
assertEq(getDisplayURL(), 'http://example.com/foo.js');
|
||||
|
|
@ -353,7 +353,7 @@ MSG_DEF(JSMSG_REST_WITH_DEFAULT, 299, 0, JSEXN_SYNTAXERR, "rest parameter m
|
|||
MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 300, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default")
|
||||
MSG_DEF(JSMSG_YIELD_IN_DEFAULT, 301, 0, JSEXN_SYNTAXERR, "yield in default expression")
|
||||
MSG_DEF(JSMSG_INTRINSIC_NOT_DEFINED, 302, 1, JSEXN_REFERENCEERR, "no intrinsic function {0}")
|
||||
MSG_DEF(JSMSG_ALREADY_HAS_SOURCE_MAP_URL, 303, 1, JSEXN_ERR, "{0} is being assigned a source map URL, but already has one")
|
||||
MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA, 303, 2, JSEXN_ERR, "{0} is being assigned a {1}, but already has one")
|
||||
MSG_DEF(JSMSG_PAR_ARRAY_BAD_ARG, 304, 1, JSEXN_RANGEERR, "invalid ParallelArray{0} argument")
|
||||
MSG_DEF(JSMSG_PAR_ARRAY_BAD_PARTITION, 305, 0, JSEXN_ERR, "argument must be divisible by outermost dimension")
|
||||
MSG_DEF(JSMSG_PAR_ARRAY_REDUCE_EMPTY, 306, 0, JSEXN_ERR, "cannot reduce ParallelArray object whose outermost dimension is empty")
|
||||
|
@ -400,7 +400,7 @@ MSG_DEF(JSMSG_YIELD_IN_ARROW, 346, 0, JSEXN_SYNTAXERR, "arrow function m
|
|||
MSG_DEF(JSMSG_WRONG_VALUE, 347, 2, JSEXN_ERR, "expected {0} but found {1}")
|
||||
MSG_DEF(JSMSG_PAR_ARRAY_SCATTER_BAD_TARGET, 348, 1, JSEXN_ERR, "target for index {0} is not an integer")
|
||||
MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME,349, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups")
|
||||
MSG_DEF(JSMSG_DEPRECATED_SOURCE_MAP, 350, 0, JSEXN_SYNTAXERR, "Using //@ to indicate source map URL pragmas is deprecated. Use //# instead")
|
||||
MSG_DEF(JSMSG_DEPRECATED_PRAGMA, 350, 1, JSEXN_SYNTAXERR, "Using //@ to indicate {0} pragmas is deprecated. Use //# instead")
|
||||
MSG_DEF(JSMSG_BAD_DESTRUCT_ASSIGN, 351, 1, JSEXN_SYNTAXERR, "can't assign to {0} using destructuring assignment")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS, 352, 0, JSEXN_ERR, "Invalid arguments")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX, 353, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
|
|
|
@ -1208,6 +1208,7 @@ ScriptSource::destroy()
|
|||
JS_ASSERT(ready());
|
||||
adjustDataSize(0);
|
||||
js_free(filename_);
|
||||
js_free(sourceURL_);
|
||||
js_free(sourceMapURL_);
|
||||
if (originPrincipals_)
|
||||
JS_DropPrincipals(TlsPerThreadData.get()->runtimeFromMainThread(), originPrincipals_);
|
||||
|
@ -1298,6 +1299,31 @@ ScriptSource::performXDR(XDRState<mode> *xdr)
|
|||
sourceMapURL_[sourceMapURLLen] = '\0';
|
||||
}
|
||||
|
||||
uint8_t haveSourceURL = hasSourceURL();
|
||||
if (!xdr->codeUint8(&haveSourceURL))
|
||||
return false;
|
||||
|
||||
if (haveSourceURL) {
|
||||
uint32_t sourceURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(sourceURL_);
|
||||
if (!xdr->codeUint32(&sourceURLLen))
|
||||
return false;
|
||||
|
||||
if (mode == XDR_DECODE) {
|
||||
size_t byteLen = (sourceURLLen + 1) * sizeof(jschar);
|
||||
sourceURL_ = static_cast<jschar *>(xdr->cx()->malloc_(byteLen));
|
||||
if (!sourceURL_)
|
||||
return false;
|
||||
}
|
||||
if (!xdr->codeChars(sourceURL_, sourceURLLen)) {
|
||||
if (mode == XDR_DECODE) {
|
||||
js_free(sourceURL_);
|
||||
sourceURL_ = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
sourceURL_[sourceURLLen] = '\0';
|
||||
}
|
||||
|
||||
uint8_t haveFilename = !!filename_;
|
||||
if (!xdr->codeUint8(&haveFilename))
|
||||
return false;
|
||||
|
@ -1330,14 +1356,46 @@ ScriptSource::setFilename(ExclusiveContext *cx, const char *filename)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptSource::setSourceURL(ExclusiveContext *cx, const jschar *sourceURL)
|
||||
{
|
||||
JS_ASSERT(sourceURL);
|
||||
if (hasSourceURL()) {
|
||||
if (cx->isJSContext() &&
|
||||
!JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING,
|
||||
js_GetErrorMessage, NULL,
|
||||
JSMSG_ALREADY_HAS_PRAGMA, filename_,
|
||||
"//# sourceURL"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
size_t len = js_strlen(sourceURL) + 1;
|
||||
if (len == 1)
|
||||
return true;
|
||||
sourceURL_ = js_strdup(cx, sourceURL);
|
||||
if (!sourceURL_)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const jschar *
|
||||
ScriptSource::sourceURL()
|
||||
{
|
||||
JS_ASSERT(hasSourceURL());
|
||||
return sourceURL_;
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptSource::setSourceMapURL(ExclusiveContext *cx, const jschar *sourceMapURL)
|
||||
{
|
||||
JS_ASSERT(sourceMapURL);
|
||||
if (hasSourceMapURL()) {
|
||||
if (cx->isJSContext() &&
|
||||
!JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING, js_GetErrorMessage,
|
||||
NULL, JSMSG_ALREADY_HAS_SOURCE_MAP_URL, filename_))
|
||||
!JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING,
|
||||
js_GetErrorMessage, NULL,
|
||||
JSMSG_ALREADY_HAS_PRAGMA, filename_,
|
||||
"//# sourceMappingURL"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -304,6 +304,7 @@ class ScriptSource
|
|||
uint32_t length_;
|
||||
uint32_t compressedLength_;
|
||||
char *filename_;
|
||||
jschar *sourceURL_;
|
||||
jschar *sourceMapURL_;
|
||||
JSPrincipals *originPrincipals_;
|
||||
|
||||
|
@ -320,6 +321,7 @@ class ScriptSource
|
|||
length_(0),
|
||||
compressedLength_(0),
|
||||
filename_(NULL),
|
||||
sourceURL_(NULL),
|
||||
sourceMapURL_(NULL),
|
||||
originPrincipals_(originPrincipals),
|
||||
sourceRetrievable_(false),
|
||||
|
@ -367,6 +369,11 @@ class ScriptSource
|
|||
return filename_;
|
||||
}
|
||||
|
||||
// Source URLs
|
||||
bool setSourceURL(ExclusiveContext *cx, const jschar *sourceURL);
|
||||
const jschar *sourceURL();
|
||||
bool hasSourceURL() const { return sourceURL_ != NULL; }
|
||||
|
||||
// Source maps
|
||||
bool setSourceMapURL(ExclusiveContext *cx, const jschar *sourceMapURL);
|
||||
const jschar *sourceMapURL();
|
||||
|
|
|
@ -907,6 +907,7 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp)
|
|||
const char *fileName = "@evaluate";
|
||||
RootedObject element(cx);
|
||||
JSAutoByteString fileNameBytes;
|
||||
RootedString sourceURL(cx);
|
||||
RootedString sourceMapURL(cx);
|
||||
unsigned lineNumber = 1;
|
||||
RootedObject global(cx, NULL);
|
||||
|
@ -967,6 +968,14 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp)
|
|||
if (!JSVAL_IS_PRIMITIVE(v))
|
||||
element = JSVAL_TO_OBJECT(v);
|
||||
|
||||
if (!JS_GetProperty(cx, opts, "sourceURL", &v))
|
||||
return false;
|
||||
if (!JSVAL_IS_VOID(v)) {
|
||||
sourceURL = JS_ValueToString(cx, v);
|
||||
if (!sourceURL)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(cx, opts, "sourceMapURL", &v))
|
||||
return false;
|
||||
if (!JSVAL_IS_VOID(v)) {
|
||||
|
@ -1055,6 +1064,13 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp)
|
|||
if (!script)
|
||||
return false;
|
||||
|
||||
if (sourceURL && !script->scriptSource()->hasSourceURL()) {
|
||||
const jschar *surl = JS_GetStringCharsZ(cx, sourceURL);
|
||||
if (!surl)
|
||||
return false;
|
||||
if (!script->scriptSource()->setSourceURL(cx, surl))
|
||||
return false;
|
||||
}
|
||||
if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) {
|
||||
const jschar *smurl = JS_GetStringCharsZ(cx, sourceMapURL);
|
||||
if (!smurl)
|
||||
|
|
|
@ -3753,9 +3753,30 @@ DebuggerSource_getUrl(JSContext *cx, unsigned argc, Value *vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DebuggerSource_getDisplayURL(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get url)", args, obj, sourceObject);
|
||||
|
||||
ScriptSource *ss = sourceObject->source();
|
||||
JS_ASSERT(ss);
|
||||
|
||||
if (ss->hasSourceURL()) {
|
||||
JSString *str = JS_NewUCStringCopyZ(cx, ss->sourceURL());
|
||||
if (!str)
|
||||
return false;
|
||||
args.rval().setString(str);
|
||||
} else {
|
||||
args.rval().setNull();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSPropertySpec DebuggerSource_properties[] = {
|
||||
JS_PSG("text", DebuggerSource_getText, 0),
|
||||
JS_PSG("url", DebuggerSource_getUrl, 0),
|
||||
JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
|
|
|
@ -145,24 +145,6 @@ Debug(JSContext *cx, unsigned argc, jsval *vp)
|
|||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
Atob(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
if (!argc)
|
||||
return true;
|
||||
|
||||
return xpc::Base64Decode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
|
||||
}
|
||||
|
||||
static bool
|
||||
Btoa(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
if (!argc)
|
||||
return true;
|
||||
|
||||
return xpc::Base64Encode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
|
||||
}
|
||||
|
||||
static bool
|
||||
File(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
|
|
|
@ -872,7 +872,7 @@ xpc::SandboxProxyHandler::iterate(JSContext *cx, JS::Handle<JSObject*> proxy,
|
|||
}
|
||||
|
||||
bool
|
||||
xpc::SandboxOptions::DOMConstructors::Parse(JSContext* cx, JS::HandleObject obj)
|
||||
xpc::SandboxOptions::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj)
|
||||
{
|
||||
NS_ENSURE_TRUE(JS_IsArrayObject(cx, obj), false);
|
||||
|
||||
|
@ -892,8 +892,12 @@ xpc::SandboxOptions::DOMConstructors::Parse(JSContext* cx, JS::HandleObject obj)
|
|||
TextEncoder = true;
|
||||
} else if (!strcmp(name, "TextDecoder")) {
|
||||
TextDecoder = true;
|
||||
} else if (!strcmp(name, "atob")) {
|
||||
atob = true;
|
||||
} else if (!strcmp(name, "btoa")) {
|
||||
btoa = true;
|
||||
} else {
|
||||
// Reporting error, if one of the DOM constructor names is unknown.
|
||||
// Reporting error, if one of the global property names is unknown.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -901,7 +905,7 @@ xpc::SandboxOptions::DOMConstructors::Parse(JSContext* cx, JS::HandleObject obj)
|
|||
}
|
||||
|
||||
bool
|
||||
xpc::SandboxOptions::DOMConstructors::Define(JSContext* cx, JS::HandleObject obj)
|
||||
xpc::SandboxOptions::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj)
|
||||
{
|
||||
if (XMLHttpRequest &&
|
||||
!JS_DefineFunction(cx, obj, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR))
|
||||
|
@ -915,6 +919,14 @@ xpc::SandboxOptions::DOMConstructors::Define(JSContext* cx, JS::HandleObject obj
|
|||
!dom::TextDecoderBinding::GetConstructorObject(cx, obj))
|
||||
return false;
|
||||
|
||||
if (atob &&
|
||||
!JS_DefineFunction(cx, obj, "atob", Atob, 1, 0))
|
||||
return false;
|
||||
|
||||
if (btoa &&
|
||||
!JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1026,7 +1038,7 @@ xpc::CreateSandboxObject(JSContext *cx, jsval *vp, nsISupports *prinOrSop, Sandb
|
|||
!JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0)))
|
||||
return NS_ERROR_XPC_UNEXPECTED;
|
||||
|
||||
if (!options.DOMConstructors.Define(cx, sandbox))
|
||||
if (!options.GlobalProperties.Define(cx, sandbox))
|
||||
return NS_ERROR_XPC_UNEXPECTED;
|
||||
}
|
||||
|
||||
|
@ -1292,21 +1304,21 @@ GetStringPropFromOptions(JSContext *cx, HandleObject from, const char *name, nsC
|
|||
}
|
||||
|
||||
/*
|
||||
* Helper that tries to get a list of DOM constructors from the options object.
|
||||
* Helper that tries to get a list of DOM constructors and other helpers from the options object.
|
||||
*/
|
||||
static nsresult
|
||||
GetDOMConstructorsFromOptions(JSContext *cx, HandleObject from, SandboxOptions& options)
|
||||
GetGlobalPropertiesFromOptions(JSContext *cx, HandleObject from, SandboxOptions& options)
|
||||
{
|
||||
RootedValue value(cx);
|
||||
bool found;
|
||||
nsresult rv = GetPropFromOptions(cx, from, "wantDOMConstructors", &value, &found);
|
||||
nsresult rv = GetPropFromOptions(cx, from, "wantGlobalProperties", &value, &found);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!found)
|
||||
return NS_OK;
|
||||
|
||||
NS_ENSURE_TRUE(value.isObject(), NS_ERROR_INVALID_ARG);
|
||||
RootedObject ctors(cx, &value.toObject());
|
||||
bool ok = options.DOMConstructors.Parse(cx, ctors);
|
||||
bool ok = options.GlobalProperties.Parse(cx, ctors);
|
||||
NS_ENSURE_TRUE(ok, NS_ERROR_INVALID_ARG);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1343,7 +1355,7 @@ ParseOptionsObject(JSContext *cx, jsval from, SandboxOptions &options)
|
|||
"sameZoneAs", options.sameZoneAs.address());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = GetDOMConstructorsFromOptions(cx, optionsObject, options);
|
||||
rv = GetGlobalPropertiesFromOptions(cx, optionsObject, options);
|
||||
|
||||
bool found;
|
||||
rv = GetPropFromOptions(cx, optionsObject,
|
||||
|
|
|
@ -1678,6 +1678,27 @@ JS_EXPORT_API(void) DumpCompleteHeap()
|
|||
|
||||
} // extern "C"
|
||||
|
||||
namespace xpc {
|
||||
|
||||
bool
|
||||
Atob(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
if (!argc)
|
||||
return true;
|
||||
|
||||
return xpc::Base64Decode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
|
||||
}
|
||||
|
||||
bool
|
||||
Btoa(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
if (!argc)
|
||||
return true;
|
||||
|
||||
return xpc::Base64Encode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
|
||||
}
|
||||
|
||||
} // namespace xpc
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
|
|
@ -3566,6 +3566,14 @@ xpc_GetSafeJSContext()
|
|||
|
||||
namespace xpc {
|
||||
|
||||
// JSNatives to expose atob and btoa in various non-DOM XPConnect scopes.
|
||||
bool
|
||||
Atob(JSContext *cx, unsigned argc, jsval *vp);
|
||||
|
||||
bool
|
||||
Btoa(JSContext *cx, unsigned argc, jsval *vp);
|
||||
|
||||
|
||||
// Helper function that creates a JSFunction that wraps a native function that
|
||||
// forwards the call to the original 'callable'. If the 'doclone' argument is
|
||||
// set, it also structure clones non-native arguments for extra security.
|
||||
|
@ -3586,13 +3594,15 @@ bool
|
|||
IsSandbox(JSObject *obj);
|
||||
|
||||
struct SandboxOptions {
|
||||
struct DOMConstructors {
|
||||
DOMConstructors() { mozilla::PodZero(this); }
|
||||
struct GlobalProperties {
|
||||
GlobalProperties() { mozilla::PodZero(this); }
|
||||
bool Parse(JSContext* cx, JS::HandleObject obj);
|
||||
bool Define(JSContext* cx, JS::HandleObject obj);
|
||||
bool XMLHttpRequest;
|
||||
bool TextDecoder;
|
||||
bool TextEncoder;
|
||||
bool atob;
|
||||
bool btoa;
|
||||
};
|
||||
|
||||
SandboxOptions(JSContext *cx)
|
||||
|
@ -3610,7 +3620,7 @@ struct SandboxOptions {
|
|||
JS::RootedObject proto;
|
||||
nsCString sandboxName;
|
||||
JS::RootedObject sameZoneAs;
|
||||
DOMConstructors DOMConstructors;
|
||||
GlobalProperties GlobalProperties;
|
||||
JS::RootedValue metadata;
|
||||
};
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче