merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2013-09-23 13:07:38 +02:00
Родитель 7adc394b80 a468d67cfd
Коммит 9e90608ff7
117 изменённых файлов: 11209 добавлений и 628 удалений

Просмотреть файл

@ -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
// &lt;, 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('&', '&amp;').replace('<', '&lt;') + '</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('&', '&amp;').replace('<', '&lt;') + '</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 (&middot;).
*
* @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('&', '&amp;').replace('<', '&lt;') +
'</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 == "<" ? "&lt;" : "&amp;"; });
}
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;
};

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше