Merge m-c to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-04-08 15:45:08 +02:00
commit 95dd8043e0
184 changed files with 9835 additions and 3943 deletions

View File

@ -703,6 +703,8 @@
<image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="translate-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="translated-notification-icon" class="notification-anchor-icon" role="button"/>
</box>
<!-- Use onclick instead of normal popup= syntax since the popup
code fires onmousedown, and hence eats our favicon drag events.

View File

@ -878,7 +878,7 @@ var tests = [
let callbackCount = 0;
this.testNotif1 = new basicNotification();
this.testNotif1.message += " 1";
showNotification(this.testNotif1);
this.notification1 = showNotification(this.testNotif1);
this.testNotif1.options.eventCallback = function (eventName) {
info("notifyObj1.options.eventCallback: " + eventName);
if (eventName == "dismissed") {
@ -901,13 +901,16 @@ var tests = [
}
}
};
showNotification(this.testNotif2);
this.notification2 = showNotification(this.testNotif2);
},
onShown: function (popup) {
is(popup.childNodes.length, 2, "two notifications are shown");
dismissNotification(popup);
},
onHidden: function () {}
onHidden: function () {
this.notification1.remove();
this.notification2.remove();
}
},
{ // Test #30 - Popup Notifications main actions should catch exceptions from callbacks
run: function () {
@ -1012,10 +1015,10 @@ var tests = [
gBrowser.selectedTab = gBrowser.addTab("about:blank");
let notifyObj = new basicNotification();
let originalCallback = notifyObj.options.eventCallback;
notifyObj.options.eventCallback = function (eventName) {
originalCallback(eventName);
return eventName == "swapping";
};
notifyObj.options.eventCallback = function (eventName) {
originalCallback(eventName);
return eventName == "swapping";
};
showNotification(notifyObj);
let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
@ -1035,20 +1038,22 @@ var tests = [
this.notifyObj = new basicNotification();
this.notifyObj.options.hideNotNow = true;
this.notifyObj.mainAction.dismiss = true;
showNotification(this.notifyObj);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
// checkPopup verifies that the Not Now item is hidden, and that no separator is added.
checkPopup(popup, this.notifyObj);
triggerMainCommand(popup);
},
onHidden: function (popup) { }
onHidden: function (popup) {
this.notification.remove();
}
},
{ // Test #37 - the main action callback can keep the notification.
run: function () {
this.notifyObj = new basicNotification();
this.notifyObj.mainAction.dismiss = true;
showNotification(this.notifyObj);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
@ -1057,13 +1062,14 @@ var tests = [
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
this.notification.remove();
}
},
{ // Test #38 - a secondary action callback can keep the notification.
run: function () {
this.notifyObj = new basicNotification();
this.notifyObj.secondaryActions[0].dismiss = true;
showNotification(this.notifyObj);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
@ -1072,6 +1078,23 @@ var tests = [
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
this.notification.remove();
}
},
{ // Test #39 - returning true in the showing callback should dismiss the notification.
run: function() {
let notifyObj = new basicNotification();
let originalCallback = notifyObj.options.eventCallback;
notifyObj.options.eventCallback = function (eventName) {
originalCallback(eventName);
return eventName == "showing";
};
let notification = showNotification(notifyObj);
ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered");
ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered");
notification.remove();
goNext();
}
}
];

View File

@ -37,7 +37,7 @@ BRANDING_FILES := \
$(NULL)
endif
ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
ifdef MOZ_WIDGET_GTK
BRANDING_FILES := \
default16.png \
default32.png \

View File

@ -37,7 +37,7 @@ BRANDING_FILES := \
$(NULL)
endif
ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
ifdef MOZ_WIDGET_GTK
BRANDING_FILES := \
default16.png \
default32.png \

View File

@ -33,7 +33,7 @@
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
closemenu="none"
tooltiptext="&appMenuHelp.tooltip;"
oncommand="PanelUI.showHelpView(this.parentNode);"/>
oncommand="PanelUI.showHelpView(this);"/>
<toolbarseparator/>
<toolbarbutton id="PanelUI-quit"
#ifdef XP_WIN

View File

@ -3,7 +3,6 @@
* 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/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ForgetAboutSite",
"resource://gre/modules/ForgetAboutSite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
@ -58,6 +57,8 @@ InsertionPoint.prototype = {
return this._index = val;
},
promiseGUID: function () PlacesUtils.promiseItemGUID(this.itemId),
get index() {
if (this.dropNearItemId > 0) {
// If dropNearItemId is set up we must calculate the real index of
@ -136,10 +137,7 @@ PlacesController.prototype = {
case "cmd_paste":
case "placesCmd_paste":
case "placesCmd_new:folder":
case "placesCmd_new:livemark":
case "placesCmd_new:bookmark":
case "placesCmd_new:separator":
case "placesCmd_sortBy:name":
case "placesCmd_createBookmark":
return false;
}
@ -190,7 +188,6 @@ PlacesController.prototype = {
var selectedNode = this._view.selectedNode;
return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
case "placesCmd_new:folder":
case "placesCmd_new:livemark":
return this._canInsert();
case "placesCmd_new:bookmark":
return this._canInsert();
@ -224,10 +221,18 @@ PlacesController.prototype = {
doCommand: function PC_doCommand(aCommand) {
switch (aCommand) {
case "cmd_undo":
PlacesUtils.transactionManager.undoTransaction();
if (!PlacesUIUtils.useAsyncTransactions) {
PlacesUtils.transactionManager.undoTransaction();
return;
}
PlacesTransactions.undo().then(null, Cu.reportError);
break;
case "cmd_redo":
PlacesUtils.transactionManager.redoTransaction();
if (!PlacesUIUtils.useAsyncTransactions) {
PlacesUtils.transactionManager.redoTransaction();
return;
}
PlacesTransactions.redo().then(null, Cu.reportError);
break;
case "cmd_cut":
case "placesCmd_cut":
@ -273,11 +278,8 @@ PlacesController.prototype = {
case "placesCmd_new:bookmark":
this.newItem("bookmark");
break;
case "placesCmd_new:livemark":
this.newItem("livemark");
break;
case "placesCmd_new:separator":
this.newSeparator();
this.newSeparator().then(null, Cu.reportError);
break;
case "placesCmd_show:info":
this.showBookmarkPropertiesForSelection();
@ -289,7 +291,7 @@ PlacesController.prototype = {
this.reloadSelectedLivemark();
break;
case "placesCmd_sortBy:name":
this.sortFolderByName();
this.sortFolderByName().then(null, Cu.reportError);
break;
case "placesCmd_createBookmark":
let node = this._view.selectedNode;
@ -767,17 +769,28 @@ PlacesController.prototype = {
/**
* Create a new Bookmark separator somewhere.
*/
newSeparator: function PC_newSeparator() {
newSeparator: Task.async(function* () {
var ip = this._view.insertionPoint;
if (!ip)
throw Cr.NS_ERROR_NOT_AVAILABLE;
var txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index);
PlacesUtils.transactionManager.doTransaction(txn);
// select the new item
var insertedNodeId = PlacesUtils.bookmarks
.getIdForItemAt(ip.itemId, ip.index);
this._view.selectItems([insertedNodeId], false);
},
if (!PlacesUIUtils.useAsyncTransactions) {
let txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index);
PlacesUtils.transactionManager.doTransaction(txn);
// Select the new item.
let insertedNodeId = PlacesUtils.bookmarks
.getIdForItemAt(ip.itemId, ip.index);
this._view.selectItems([insertedNodeId], false);
return;
}
let txn = PlacesTransactions.NewSeparator({ parentGUID: yield ip.promiseGUID()
, index: ip.index });
let guid = yield PlacesTransactions.transact(txn);
let itemId = yield PlacesUtils.promiseItemId(guid);
// Select the new item.
this._view.selectItems([itemId], false);
}),
/**
* Opens a dialog for moving the selected nodes.
@ -791,11 +804,16 @@ PlacesController.prototype = {
/**
* Sort the selected folder by name
*/
sortFolderByName: function PC_sortFolderByName() {
var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
var txn = new PlacesSortFolderByNameTransaction(itemId);
PlacesUtils.transactionManager.doTransaction(txn);
},
sortFolderByName: Task.async(function* () {
let itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
if (!PlacesUIUtils.useAsyncTransactions) {
var txn = new PlacesSortFolderByNameTransaction(itemId);
PlacesUtils.transactionManager.doTransaction(txn);
return;
}
let guid = yield PlacesUtils.promiseItemGUID(itemId);
yield PlacesTransactions.transact(PlacesTransactions.SortByName(guid));
}),
/**
* Walk the list of folders we're removing in this delete operation, and
@ -1661,7 +1679,6 @@ function goUpdatePlacesCommands() {
updatePlacesCommand("placesCmd_open:tab");
updatePlacesCommand("placesCmd_new:folder");
updatePlacesCommand("placesCmd_new:bookmark");
updatePlacesCommand("placesCmd_new:livemark");
updatePlacesCommand("placesCmd_new:separator");
updatePlacesCommand("placesCmd_show:info");
updatePlacesCommand("placesCmd_moveBookmarks");

View File

@ -24,8 +24,13 @@
var Ci = Components.interfaces;
var Cr = Components.results;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(window,
"PlacesUIUtils", "resource:///modules/PlacesUIUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(window,
"PlacesTransactions", "resource://gre/modules/PlacesTransactions.jsm");
]]></script>
<script type="application/javascript"
src="chrome://browser/content/places/controller.js"/>
@ -54,8 +59,6 @@
<command id="placesCmd_new:bookmark"
oncommand="goDoPlacesCommand('placesCmd_new:bookmark');"/>
<command id="placesCmd_new:livemark"
oncommand="goDoPlacesCommand('placesCmd_new:livemark');"/>
<command id="placesCmd_new:folder"
oncommand="goDoPlacesCommand('placesCmd_new:folder');"/>
<command id="placesCmd_new:separator"

View File

@ -0,0 +1,134 @@
/* 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";
this.EXPORTED_SYMBOLS = ["Translation"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
/* Create an object keeping the information related to translation for
* a specific browser. This object is passed to the translation
* infobar so that it can initialize itself. The properties exposed to
* the infobar are:
* - supportedSourceLanguages, array of supported source language codes
* - supportedTargetLanguages, array of supported target language codes
* - detectedLanguage, code of the language detected on the web page.
* - defaultTargetLanguage, code of the language to use by default for
* translation.
* - state, the state in which the infobar should be displayed
* - STATE_{OFFER,TRANSLATING,TRANSLATED,ERROR} constants.
* - translatedFrom, if already translated, source language code.
* - translatedTo, if already translated, target language code.
* - translate, method starting the translation of the current page.
* - showOriginalContent, method showing the original page content.
* - showTranslatedContent, method showing the translation for an
* already translated page whose original content is shown.
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
this.Translation = function(aBrowser) {
this.browser = aBrowser;
};
this.Translation.prototype = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
_defaultTargetLanguage: "",
get defaultTargetLanguage() {
if (!this._defaultTargetLanguage) {
this._defaultTargetLanguage = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry)
.getSelectedLocale("global")
.split("-")[0];
}
return this._defaultTargetLanguage;
},
get doc() this.browser.contentDocument,
translate: function(aFrom, aTo) {
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
},
showURLBarIcon: function(aTranslated) {
let chromeWin = this.browser.ownerGlobal;
let PopupNotifications = chromeWin.PopupNotifications;
let removeId = aTranslated ? "translate" : "translated";
let notification =
PopupNotifications.getNotification(removeId, this.browser);
if (notification)
PopupNotifications.remove(notification);
let callback = aTopic => {
if (aTopic != "showing")
return false;
if (!this.notificationBox.getNotificationWithValue("translation"))
this.showTranslationInfoBar();
return true;
};
let addId = aTranslated ? "translated" : "translate";
PopupNotifications.show(this.browser, addId, null,
addId + "-notification-icon", null, null,
{dismissed: true, eventCallback: callback});
},
_state: 0,
get state() this._state,
set state(val) {
let notif = this.notificationBox.getNotificationWithValue("translation");
if (notif)
notif.state = val;
this._state = val;
},
originalShown: true,
showOriginalContent: function() {
this.showURLBarIcon();
this.originalShown = true;
},
showTranslatedContent: function() {
this.showURLBarIcon(true);
this.originalShown = false;
},
get notificationBox() this.browser.ownerGlobal.gBrowser.getNotificationBox(),
showTranslationInfoBar: function() {
let notificationBox = this.notificationBox;
let notif = notificationBox.appendNotification("", "translation", null,
notificationBox.PRIORITY_INFO_HIGH);
notif.init(this);
return notif;
},
showTranslationUI: function(aDetectedLanguage) {
this.detectedLanguage = aDetectedLanguage;
// Reset all values before showing a new translation infobar.
this.state = 0;
this.translatedFrom = "";
this.translatedTo = "";
this.originalShown = true;
this.showURLBarIcon();
return this.showTranslationInfoBar();
}
};

View File

@ -7,7 +7,8 @@ JS_MODULES_PATH = 'modules/translation'
EXTRA_JS_MODULES = [
'cld2/cld-worker.js',
'cld2/cld-worker.js.mem',
'LanguageDetector.jsm'
'LanguageDetector.jsm',
'Translation.jsm'
]
JAR_MANIFESTS += ['jar.mn']

View File

@ -4,152 +4,191 @@
// tests the translation infobar, using a fake 'Translation' implementation.
var Translation = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
defaultTargetLanguage: "en",
Components.utils.import("resource:///modules/translation/Translation.jsm");
function waitForCondition(condition, nextTest, errorMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); nextTest(); };
}
var TranslationStub = {
__proto__: Translation.prototype,
_translateFrom: "",
_translateTo: "",
_deferred: null,
translate: function(aFrom, aTo) {
this._translateFrom = aFrom;
this._translateTo = aTo;
this._deferred = Promise.defer();
return this._deferred.promise;
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
},
_reset: function() {
this._translateFrom = "";
this._translateTo = "";
this._deferred = null;
this.translatedFrom = "";
this.translatedTo = "";
},
failTranslation: function() {
this._deferred.reject();
this.state = this.STATE_ERROR;
this._reset();
},
finishTranslation: function() {
this._deferred.resolve();
this.showTranslatedContent();
this.state = this.STATE_TRANSLATED;
this._reset();
},
_showOriginalCalled: false,
showOriginalContent: function() {
this._showOriginalCalled = true;
},
_showTranslationCalled: false,
showTranslatedContent: function() {
this._showTranslationCalled = true;
},
showTranslationUI: function(aLanguage) {
let notificationBox = gBrowser.getNotificationBox();
let notif = notificationBox.appendNotification("", "translation", null,
notificationBox.PRIORITY_INFO_HIGH);
notif.init(this);
notif.detectedLanguage = aLanguage;
return notif;
}
};
function test() {
waitForExplicitFinish();
// Show an info bar saying the current page is in French
let notif = Translation.showTranslationUI("fr");
is(notif.state, notif.STATE_OFFER, "the infobar is offering translation");
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("load", function onload() {
tab.linkedBrowser.removeEventListener("load", onload, true);
TranslationStub.browser = gBrowser.selectedBrowser;
registerCleanupFunction(function () {
gBrowser.removeTab(tab);
});
run_tests(() => {
finish();
});
}, true);
content.location = "data:text/plain,test page";
}
function checkURLBarIcon(aExpectTranslated = false) {
is(!PopupNotifications.getNotification("translate"), aExpectTranslated,
"translate icon " + (aExpectTranslated ? "not " : "") + "shown");
is(!!PopupNotifications.getNotification("translated"), aExpectTranslated,
"translated icon " + (aExpectTranslated ? "" : "not ") + "shown");
}
function run_tests(aFinishCallback) {
info("Show an info bar saying the current page is in French");
let notif = TranslationStub.showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
checkURLBarIcon();
// Click the "Translate" button
info("Click the 'Translate' button");
notif._getAnonElt("translate").click();
is(notif.state, notif.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "fr", "from language correct");
is(Translation._translateTo, Translation.defaultTargetLanguage, "from language correct");
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct");
checkURLBarIcon();
// Make the translation fail and check we are in the error state.
Translation.failTranslation();
is(notif.state, notif.STATE_ERROR, "infobar in the error state");
info("Make the translation fail and check we are in the error state.");
TranslationStub.failTranslation();
is(notif.state, TranslationStub.STATE_ERROR, "infobar in the error state");
checkURLBarIcon();
// Click the try again button
info("Click the try again button");
notif._getAnonElt("tryAgain").click();
is(notif.state, notif.STATE_TRANSLATING, "infobar in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "fr", "from language correct");
is(Translation._translateTo, Translation.defaultTargetLanguage, "to language correct");
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct");
checkURLBarIcon();
// Make the translation succeed and check we are in the 'translated' state.
Translation.finishTranslation();
is(notif.state, notif.STATE_TRANSLATED, "infobar in the translated state");
info("Make the translation succeed and check we are in the 'translated' state.");
TranslationStub.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATED, "infobar in the translated state");
checkURLBarIcon(true);
// Test 'Show Original' / 'Show Translation' buttons.
info("Test 'Show original' / 'Show Translation' buttons.");
// First check 'Show Original' is visible and 'Show Translation' is hidden.
ok(!notif._getAnonElt("showOriginal").hidden, "'Show Original' button visible");
ok(notif._getAnonElt("showTranslation").hidden, "'Show Translation' button hidden");
// Click the button.
notif._getAnonElt("showOriginal").click();
// Check the correct function has been called.
ok(Translation._showOriginalCalled, "'Translation.showOriginalContent' called")
ok(!Translation._showTranslationCalled, "'Translation.showTranslatedContent' not called")
Translation._showOriginalCalled = false;
// Check that the url bar icon shows the original content is displayed.
checkURLBarIcon();
// And the 'Show Translation' button is now visible.
ok(notif._getAnonElt("showOriginal").hidden, "'Show Original' button hidden");
ok(!notif._getAnonElt("showTranslation").hidden, "'Show Translation' button visible");
// Click the 'Show Translation' button
notif._getAnonElt("showTranslation").click();
// Check the correct function has been called.
ok(!Translation._showOriginalCalled, "'Translation.showOriginalContent' not called")
ok(Translation._showTranslationCalled, "'Translation.showTranslatedContent' called")
Translation._showTranslationCalled = false;
// Check that the url bar icon shows the page is translated.
checkURLBarIcon(true);
// Check that the 'Show Original' button is visible again.
ok(!notif._getAnonElt("showOriginal").hidden, "'Show Original' button visible");
ok(notif._getAnonElt("showTranslation").hidden, "'Show Translation' button hidden");
// Check that changing the source language causes a re-translation
info("Check that changing the source language causes a re-translation");
let from = notif._getAnonElt("fromLanguage");
from.value = "es";
from.doCommand();
is(notif.state, notif.STATE_TRANSLATING, "infobar in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "es", "from language correct");
is(Translation._translateTo, Translation.defaultTargetLanguage, "to language correct");
Translation.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "to language correct");
// We want to show the 'translated' icon while re-translating,
// because we are still displaying the previous translation.
checkURLBarIcon(true);
TranslationStub.finishTranslation();
checkURLBarIcon(true);
// Check that changing the target language causes a re-translation
info("Check that changing the target language causes a re-translation");
let to = notif._getAnonElt("toLanguage");
to.value = "pl";
to.doCommand();
is(notif.state, notif.STATE_TRANSLATING, "infobar in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "es", "from language correct");
is(Translation._translateTo, "pl", "to language correct");
Translation.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, "pl", "to language correct");
checkURLBarIcon(true);
TranslationStub.finishTranslation();
checkURLBarIcon(true);
// Cleanup.
notif.close();
// Reopen the info bar to check that it's possible to override the detected language.
notif = Translation.showTranslationUI("fr");
is(notif.state, notif.STATE_OFFER, "the infobar is offering translation");
info("Reopen the info bar to check that it's possible to override the detected language.");
notif = TranslationStub.showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
// Change the language and click 'Translate'
notif._getAnonElt("detectedLanguage").value = "ja";
notif._getAnonElt("translate").click();
is(notif.state, notif.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!Translation._deferred, "Translation.translate has been called");
is(Translation._translateFrom, "ja", "from language correct");
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "ja", "from language correct");
notif.close();
// Reopen one last time to check the 'Not Now' button closes the notification.
notif = Translation.showTranslationUI("fr");
info("Reopen to check the 'Not Now' button closes the notification.");
notif = TranslationStub.showTranslationUI("fr");
let notificationBox = gBrowser.getNotificationBox();
ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
notif._getAnonElt("notNow").click();
ok(!notificationBox.getNotificationWithValue("translation"), "no 'translate' notification after clicking 'not now'");
finish();
info("Check that clicking the url bar icon reopens the info bar");
checkURLBarIcon();
// Clicking the anchor element causes a 'showing' event to be sent
// asynchronously to our callback that will then show the infobar.
PopupNotifications.getNotification("translate").anchorElement.click();
waitForCondition(() => !!notificationBox.getNotificationWithValue("translation"), () => {
ok(!!notificationBox.getNotificationWithValue("translation"),
"there's a 'translate' notification");
aFinishCallback();
}, "timeout waiting for the info bar to reappear");
}

View File

@ -86,27 +86,10 @@
</xul:hbox>
</content>
<implementation>
<field name="STATE_OFFER" readonly="true">0</field>
<field name="STATE_TRANSLATING" readonly="true">1</field>
<field name="STATE_TRANSLATED" readonly="true">2</field>
<field name="STATE_ERROR" readonly="true">3</field>
<property name="state"
onget="return this._getAnonElt('translationStates').selectedIndex;"
onset="this._getAnonElt('translationStates').selectedIndex = val;"/>
<!-- Initialize the infobar with a translation object exposing these
properties:
- supportedSourceLanguages, array of supported source language codes
- supportedTargetLanguages, array of supported target language codes
- defaultTargetLanguage, code of the language to use by default for
translation.
- translate, method starting the translation of the current page.
Returns a promise.
- showOriginalContent, method showing the original page content.
- showTranslatedContent, method showing the translation for an
already translated page whose original content is shown.
-->
<method name="init">
<parameter name="aTranslation"/>
<body>
@ -116,6 +99,7 @@
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://global/locale/languageNames.properties");
// Fill the lists of supported source languages.
let detectedLanguage = this._getAnonElt("detectedLanguage");
let fromLanguage = this._getAnonElt("fromLanguage");
for (let code of this.translation.supportedSourceLanguages) {
@ -123,10 +107,24 @@
detectedLanguage.appendItem(name, code);
fromLanguage.appendItem(name, code);
}
detectedLanguage.value = this.translation.detectedLanguage;
// translatedFrom is only set if we have already translated this page.
if (aTranslation.translatedFrom)
fromLanguage.value = aTranslation.translatedFrom;
// Fill the list of supporter target languages.
let toLanguage = this._getAnonElt("toLanguage");
for (let code of this.translation.supportedTargetLanguages)
toLanguage.appendItem(bundle.GetStringFromName(code), code);
if (aTranslation.translatedTo)
toLanguage.value = aTranslation.translatedTo;
if (aTranslation.state)
this.state = aTranslation.state;
this._handleButtonHiding(aTranslation.originalShown);
]]>
</body>
</method>
@ -138,34 +136,29 @@
</body>
</method>
<field name="_detectedLanguage">""</field>
<property name="detectedLanguage" onget="return this._detectedLanguage;">
<setter><![CDATA[
this._getAnonElt("detectedLanguage").value = val;
this._detectedLanguage = val;
return val;
]]></setter>
</property>
<method name="translate">
<body>
<![CDATA[
if (this.state == this.STATE_OFFER) {
if (this.state == this.translation.STATE_OFFER) {
this._getAnonElt("fromLanguage").value =
this._getAnonElt("detectedLanguage").value;
this._getAnonElt("toLanguage").value =
this.translation.defaultTargetLanguage;
}
this._getAnonElt("showOriginal").hidden = false;
this._getAnonElt("showTranslation").hidden = true;
this.state = this.STATE_TRANSLATING;
this._handleButtonHiding(false);
this.translation.translate(this._getAnonElt("fromLanguage").value,
this._getAnonElt("toLanguage").value)
.then(() => { this.state = this.STATE_TRANSLATED; },
() => { this.state = this.STATE_ERROR; });
this._getAnonElt("toLanguage").value);
]]>
</body>
</method>
<method name="_handleButtonHiding">
<parameter name="aOriginalShown"/>
<body>
<![CDATA[
this._getAnonElt("showOriginal").hidden = aOriginalShown;
this._getAnonElt("showTranslation").hidden = !aOriginalShown;
]]>
</body>
</method>
@ -173,9 +166,7 @@
<method name="showOriginal">
<body>
<![CDATA[
this._getAnonElt("showOriginal").hidden = true;
this._getAnonElt("showTranslation").hidden = false;
this._handleButtonHiding(true);
this.translation.showOriginalContent();
]]>
</body>
@ -184,9 +175,7 @@
<method name="showTranslation">
<body>
<![CDATA[
this._getAnonElt("showOriginal").hidden = false;
this._getAnonElt("showTranslation").hidden = true;
this._handleButtonHiding(false);
this.translation.showTranslatedContent();
]]>
</body>

View File

@ -34,7 +34,7 @@ module.exports = DeviceStore = function(connection) {
this._onTabListChanged = this._onTabListChanged.bind(this);
this._onStatusChanged();
return this;
}
};
DeviceStore.prototype = {
destroy: function() {
@ -57,8 +57,15 @@ DeviceStore.prototype = {
_onStatusChanged: function() {
if (this._connection.status == Connection.Status.CONNECTED) {
// Watch for changes to remote browser tabs
this._connection.client.addListener("tabListChanged",
this._onTabListChanged);
this._listTabs();
} else {
if (this._connection.client) {
this._connection.client.removeListener("tabListChanged",
this._onTabListChanged);
}
this._resetStore();
}
},
@ -68,6 +75,9 @@ DeviceStore.prototype = {
},
_listTabs: function() {
if (!this._connection) {
return;
}
this._connection.client.listTabs((resp) => {
if (resp.error) {
this._connection.disconnect();
@ -76,10 +86,6 @@ DeviceStore.prototype = {
this._deviceFront = getDeviceFront(this._connection.client, resp);
// Save remote browser's tabs
this.object.tabs = resp.tabs;
// Add listener to update remote browser's tabs list in app-manager
// when it changes
this._connection.client.addListener(
'tabListChanged', this._onTabListChanged);
this._feedStore();
});
},
@ -113,4 +119,4 @@ DeviceStore.prototype = {
this.object.permissions = permissionsArray;
});
}
}
};

View File

@ -3,135 +3,46 @@
// Make sure the add-on actor can see loaded JS Modules from an add-on
const ADDON5_URL = EXAMPLE_URL + "addon5.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources, gTitle;
function onMessage(event) {
try {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-title":
gTitle = json.data.value;
break;
}
} catch(e) {
DevToolsUtils.reportException("onMessage", e);
}
}
const ADDON_URL = EXAMPLE_URL + "addon5.xpi";
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
let addon = yield addAddon(ADDON_URL);
let addonDebugger = yield initAddonDebugger(ADDON_URL);
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
is(addonDebugger.title, "Debugger - Test unpacked add-on with JS Modules", "Saw the right toolbox title.");
window.addEventListener("message", onMessage);
// Check the inital list of sources is correct
let groups = yield addonDebugger.getSourceGroups();
is(groups[0].name, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON5_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
yield testSources(false);
let sources = groups[0].sources;
is(sources.length, 2, "Should be two sources");
ok(sources[0].url.endsWith("/browser_dbg_addon5@tests.mozilla.org/bootstrap.js"), "correct url for bootstrap code")
is(sources[0].label, "bootstrap.js", "correct label for bootstrap code")
is(sources[1].url, "resource://browser_dbg_addon5/test.jsm", "correct url for addon code")
is(sources[1].label, "test.jsm", "correct label for addon code")
// Load a new module and check it appears in the list of sources
Cu.import("resource://browser_dbg_addon5/test2.jsm", {});
yield testSources(true);
groups = yield addonDebugger.getSourceGroups();
is(groups[0].name, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
sources = groups[0].sources;
is(sources.length, 3, "Should be three sources");
ok(sources[0].url.endsWith("/browser_dbg_addon5@tests.mozilla.org/bootstrap.js"), "correct url for bootstrap code")
is(sources[0].label, "bootstrap.js", "correct label for bootstrap code")
is(sources[1].url, "resource://browser_dbg_addon5/test.jsm", "correct url for addon code")
is(sources[1].label, "test.jsm", "correct label for addon code")
is(sources[2].url, "resource://browser_dbg_addon5/test2.jsm", "correct url for addon code")
is(sources[2].label, "test2.jsm", "correct label for addon code")
Cu.unload("resource://browser_dbg_addon5/test2.jsm");
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
window.removeEventListener("message", onMessage);
yield addonDebugger.destroy();
yield removeAddon(addon);
finish();
});
}
function installAddon () {
return addAddon(ADDON5_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testSources(expectSecondModule) {
let deferred = promise.defer();
let foundAddonModule = false;
let foundAddonModule2 = false;
let foundAddonBootstrap = false;
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
for (let source of sources) {
let url = source.url.split(" -> ").pop();
let { label, group } = gSources.getItemByValue(source.url).attachment;
if (url.indexOf("resource://browser_dbg_addon5/test.jsm") === 0) {
is(label, "test.jsm", "correct label for addon code");
is(group, "browser_dbg_addon5@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule = true;
} else if (url.indexOf("resource://browser_dbg_addon5/test2.jsm") === 0) {
is(label, "test2.jsm", "correct label for addon code");
is(group, "browser_dbg_addon5@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule2 = true;
} else if (url.endsWith("/browser_dbg_addon5@tests.mozilla.org/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap code");
is(group, "browser_dbg_addon5@tests.mozilla.org", "addon bootstrap script is in the add-on's group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
}
}
ok(foundAddonModule, "found JS module for the addon in the list");
is(foundAddonModule2, expectSecondModule, "saw the second addon module");
ok(foundAddonBootstrap, "found bootstrap script for the addon in the list");
is(gTitle, "Debugger - Test unpacked add-on with JS Modules", "Saw the right toolbox title.");
let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
is(groups[0].value, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -3,135 +3,46 @@
// Make sure the add-on actor can see loaded JS Modules from an add-on
const ADDON4_URL = EXAMPLE_URL + "addon4.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources, gTitle;
function onMessage(event) {
try {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-title":
gTitle = json.data.value;
break;
}
} catch(e) {
DevToolsUtils.reportException("onMessage", e);
}
}
const ADDON_URL = EXAMPLE_URL + "addon4.xpi";
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
let addon = yield addAddon(ADDON_URL);
let addonDebugger = yield initAddonDebugger(ADDON_URL);
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
is(addonDebugger.title, "Debugger - Test add-on with JS Modules", "Saw the right toolbox title.");
window.addEventListener("message", onMessage);
// Check the inital list of sources is correct
let groups = yield addonDebugger.getSourceGroups();
is(groups[0].name, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON4_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
yield testSources(false);
let sources = groups[0].sources;
is(sources.length, 2, "Should be two sources");
ok(sources[0].url.endsWith("/browser_dbg_addon4@tests.mozilla.org.xpi!/bootstrap.js"), "correct url for bootstrap code")
is(sources[0].label, "bootstrap.js", "correct label for bootstrap code")
is(sources[1].url, "resource://browser_dbg_addon4/test.jsm", "correct url for addon code")
is(sources[1].label, "test.jsm", "correct label for addon code")
// Load a new module and check it appears in the list of sources
Cu.import("resource://browser_dbg_addon4/test2.jsm", {});
yield testSources(true);
groups = yield addonDebugger.getSourceGroups();
is(groups[0].name, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
sources = groups[0].sources;
is(sources.length, 3, "Should be three sources");
ok(sources[0].url.endsWith("/browser_dbg_addon4@tests.mozilla.org.xpi!/bootstrap.js"), "correct url for bootstrap code")
is(sources[0].label, "bootstrap.js", "correct label for bootstrap code")
is(sources[1].url, "resource://browser_dbg_addon4/test.jsm", "correct url for addon code")
is(sources[1].label, "test.jsm", "correct label for addon code")
is(sources[2].url, "resource://browser_dbg_addon4/test2.jsm", "correct url for addon code")
is(sources[2].label, "test2.jsm", "correct label for addon code")
Cu.unload("resource://browser_dbg_addon4/test2.jsm");
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
window.removeEventListener("message", onMessage);
yield addonDebugger.destroy();
yield removeAddon(addon);
finish();
});
}
function installAddon () {
return addAddon(ADDON4_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testSources(expectSecondModule) {
let deferred = promise.defer();
let foundAddonModule = false;
let foundAddonModule2 = false;
let foundAddonBootstrap = false;
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
for (let source of sources) {
let url = source.url.split(" -> ").pop();
let { label, group } = gSources.getItemByValue(source.url).attachment;
if (url.indexOf("resource://browser_dbg_addon4/test.jsm") === 0) {
is(label, "test.jsm", "correct label for addon code");
is(group, "browser_dbg_addon4@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule = true;
} else if (url.indexOf("resource://browser_dbg_addon4/test2.jsm") === 0) {
is(label, "test2.jsm", "correct label for addon code");
is(group, "browser_dbg_addon4@tests.mozilla.org", "addon module is in the add-on's group");
foundAddonModule2 = true;
} else if (url.endsWith("/browser_dbg_addon4@tests.mozilla.org.xpi!/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap code");
is(group, "browser_dbg_addon4@tests.mozilla.org", "addon bootstrap script is in the add-on's group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
}
}
ok(foundAddonModule, "found JS module for the addon in the list");
is(foundAddonModule2, expectSecondModule, "saw the second addon module");
ok(foundAddonBootstrap, "found bootstrap script for the addon in the list");
is(gTitle, "Debugger - Test add-on with JS Modules", "Saw the right toolbox title.");
let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
is(groups[0].value, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group");
is(groups.length, 1, "Should be only one group.");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -4,7 +4,7 @@
// Ensure that only panels that are relevant to the addon debugger
// display in the toolbox
const ADDON3_URL = EXAMPLE_URL + "addon3.xpi";
const ADDON_URL = EXAMPLE_URL + "addon3.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources;
let PREFS = [
@ -15,10 +15,8 @@ let PREFS = [
];
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
let addon = yield addAddon(ADDON_URL);
let addonDebugger = yield initAddonDebugger(ADDON_URL);
// Store and enable all optional dev tools panels
let originalPrefs = PREFS.map(pref => {
@ -27,69 +25,20 @@ function test() {
return original;
});
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
let tabs = addonDebugger.frame.contentDocument.getElementById("toolbox-tabs").children;
let expectedTabs = ["options", "jsdebugger"];
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
is(tabs.length, 2, "displaying only 2 tabs in addon debugger");
Array.forEach(tabs, (tab, i) => {
let toolName = expectedTabs[i];
is(tab.getAttribute("toolid"), toolName, "displaying " + toolName);
});
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON3_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
testPanels(iframe);
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
yield addonDebugger.destroy();
yield removeAddon(addon);
PREFS.forEach((pref, i) => Services.prefs.setBoolPref(pref, originalPrefs[i]));
finish();
});
}
function installAddon () {
return addAddon(ADDON3_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testPanels(frame) {
let tabs = frame.contentDocument.getElementById("toolbox-tabs").children;
let expectedTabs = ["options", "jsdebugger"];
is(tabs.length, 2, "displaying only 2 tabs in addon debugger");
Array.forEach(tabs, (tab, i) => {
let toolName = expectedTabs[i];
is(tab.getAttribute("toolid"), toolName, "displaying " + toolName);
});
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -4,136 +4,32 @@
// Ensure that the sources listed when debugging an addon are either from the
// addon itself, or the SDK, with proper groups and labels.
const ADDON3_URL = EXAMPLE_URL + "addon3.xpi";
let gAddon, gClient, gThreadClient, gDebugger, gSources, gTitle;
function onMessage(event) {
try {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-title":
gTitle = json.data.value;
break;
}
} catch(e) {
DevToolsUtils.reportException("onMessage", e);
}
}
const ADDON_URL = EXAMPLE_URL + "addon3.xpi";
function test() {
Task.spawn(function () {
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
let addon = yield addAddon(ADDON_URL);
let addonDebugger = yield initAddonDebugger(ADDON_URL);
gBrowser.selectedTab = gBrowser.addTab();
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
is(addonDebugger.title, "Debugger - browser_dbg_addon3", "Saw the right toolbox title.");
window.addEventListener("message", onMessage);
// Check the inital list of sources is correct
let groups = yield addonDebugger.getSourceGroups();
is(groups[0].name, "jid1-ami3akps3baaeg@jetpack", "Add-on code should be the first group");
is(groups[1].name, "Add-on SDK", "Add-on SDK should be the second group");
is(groups.length, 2, "Should be only two groups.");
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
let sources = groups[0].sources;
is(sources.length, 2, "Should be two sources");
ok(sources[0].url.endsWith("/jid1-ami3akps3baaeg@jetpack.xpi!/bootstrap.js"), "correct url for bootstrap code")
is(sources[0].label, "bootstrap.js", "correct label for bootstrap code")
is(sources[1].url, "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js", "correct url for add-on code")
is(sources[1].label, "resources/browser_dbg_addon3/lib/main.js", "correct label for add-on code")
let connected = promise.defer();
gClient.connect(connected.resolve);
yield connected.promise;
ok(groups[1].sources.length > 10, "SDK modules are listed");
yield installAddon();
let debuggerPanel = yield initAddonDebugger(gClient, ADDON3_URL, iframe);
gDebugger = debuggerPanel.panelWin;
gThreadClient = gDebugger.gThreadClient;
gSources = gDebugger.DebuggerView.Sources;
yield testSources();
yield uninstallAddon();
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
window.removeEventListener("message", onMessage);
yield addonDebugger.destroy();
yield removeAddon(addon);
finish();
});
}
function installAddon () {
return addAddon(ADDON3_URL).then(aAddon => {
gAddon = aAddon;
});
}
function testSources() {
let deferred = promise.defer();
let foundAddonModule = false;
let foundSDKModule = 0;
let foundAddonBootstrap = false;
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
for (let source of sources) {
let url = source.url.split(" -> ").pop();
info(source.url + "\n\n\n" + url);
let { label, group } = gSources.getItemByValue(source.url).attachment;
if (url.indexOf("resource://gre/modules/commonjs/") === 0) {
is(label, url.substring(32), "correct truncated label");
is(group, "Add-on SDK", "correct SDK group");
foundSDKModule++;
} else if (url.indexOf("resource://gre/modules/commonjs/method") === 0) {
is(label.indexOf("method/"), 0, "correct truncated label");
is(group, "Add-on SDK", "correct SDK group");
foundSDKModule++;
} else if (url.indexOf("resource://jid1-ami3akps3baaeg-at-jetpack") === 0) {
is(label, "resources/browser_dbg_addon3/lib/main.js", "correct label for addon code");
is(group, "jid1-ami3akps3baaeg@jetpack", "addon code is in the add-on's group");
foundAddonModule = true;
} else if (url.endsWith("/jid1-ami3akps3baaeg@jetpack.xpi!/bootstrap.js")) {
is(label, "bootstrap.js", "correct label for bootstrap script");
is(group, "jid1-ami3akps3baaeg@jetpack", "addon code is in the add-on's group");
foundAddonBootstrap = true;
} else {
ok(false, "Saw an unexpected source: " + url);
}
}
ok(foundAddonModule, "found code for the addon in the list");
ok(foundAddonBootstrap, "found bootstrap for the addon in the list");
// Be flexible in this number, as SDK changes could change the exact number of
// built-in browser SDK modules
ok(foundSDKModule > 10, "SDK modules are listed");
is(gTitle, "Debugger - browser_dbg_addon3", "Saw the right toolbox title.");
let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
is(groups[0].value, "jid1-ami3akps3baaeg@jetpack", "Add-on code should be the first group");
is(groups[1].value, "Add-on SDK", "Add-on SDK should be the second group");
is(groups.length, 2, "Should be only two groups.");
deferred.resolve();
});
return deferred.promise;
}
function uninstallAddon() {
return removeAddon(gAddon);
}
function closeConnection () {
let deferred = promise.defer();
gClient.close(deferred.resolve);
return deferred.promise;
}
registerCleanupFunction(function() {
gClient = null;
gAddon = null;
gThreadClient = null;
gDebugger = null;
gSources = null;
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});

View File

@ -471,6 +471,14 @@ function getTab(aTarget, aWindow) {
}
}
function getSources(aClient) {
let deferred = promise.defer();
aClient.getSources(({sources}) => deferred.resolve(sources));
return deferred.promise;
}
function initDebugger(aTarget, aWindow) {
info("Initializing a debugger panel.");
@ -500,32 +508,136 @@ function initDebugger(aTarget, aWindow) {
});
}
function initAddonDebugger(aClient, aUrl, aFrame) {
info("Initializing an addon debugger panel.");
// Creates an add-on debugger for a given add-on. The returned AddonDebugger
// object must be destroyed before finishing the test
function initAddonDebugger(aUrl) {
let addonDebugger = new AddonDebugger();
return addonDebugger.init(aUrl).then(() => addonDebugger);
}
function AddonDebugger() {
this._onMessage = this._onMessage.bind(this);
}
AddonDebugger.prototype = {
init: Task.async(function*(aUrl) {
info("Initializing an addon debugger panel.");
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
this.frame = document.createElement("iframe");
this.frame.setAttribute("height", 400);
document.documentElement.appendChild(this.frame);
window.addEventListener("message", this._onMessage);
let transport = DebuggerServer.connectPipe();
this.client = new DebuggerClient(transport);
let connected = promise.defer();
this.client.connect(connected.resolve);
yield connected.promise;
let addonActor = yield getAddonActorForUrl(this.client, aUrl);
return getAddonActorForUrl(aClient, aUrl).then((addonActor) => {
let targetOptions = {
form: { addonActor: addonActor.actor, title: addonActor.name },
client: aClient,
client: this.client,
chrome: true
};
let toolboxOptions = {
customIframe: aFrame
customIframe: this.frame
};
let target = devtools.TargetFactory.forTab(targetOptions);
return gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
}).then(aToolbox => {
let toolbox = yield gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
info("Addon debugger panel shown successfully.");
let debuggerPanel = aToolbox.getCurrentPanel();
this.debuggerPanel = toolbox.getCurrentPanel();
// Wait for the initial resume...
return waitForClientEvents(debuggerPanel, "resumed")
.then(() => prepareDebugger(debuggerPanel))
.then(() => debuggerPanel);
});
yield waitForClientEvents(this.debuggerPanel, "resumed");
yield prepareDebugger(this.debuggerPanel);
}),
destroy: Task.async(function*() {
let deferred = promise.defer();
this.client.close(deferred.resolve);
yield deferred.promise;
yield this.debuggerPanel._toolbox.destroy();
this.frame.remove();
window.removeEventListener("message", this._onMessage);
}),
/**
* Returns a list of the groups and sources in the UI. The returned array
* contains objects for each group with properties name and sources. The
* sources property contains an array with objects for each source for that
* group with properties label and url.
*/
getSourceGroups: Task.async(function*() {
let debuggerWin = this.debuggerPanel.panelWin;
let sources = yield getSources(debuggerWin.gThreadClient);
ok(sources.length, "retrieved sources");
// groups will be the return value, groupmap and the maps we put in it will
// be used as quick lookups to add the url information in below
let groups = [];
let groupmap = new Map();
let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group");
for (let g of uigroups) {
let name = g.querySelector(".side-menu-widget-group-title .name").value;
let group = {
name: name,
sources: []
};
groups.push(group);
let labelmap = new Map();
groupmap.set(name, labelmap);
for (let l of g.querySelectorAll(".dbg-source-item")) {
let source = {
label: l.value,
url: null
};
labelmap.set(l.value, source);
group.sources.push(source);
}
}
for (let source of sources) {
let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.url).attachment;
if (!groupmap.has(group)) {
ok(false, "Saw a source group not in the UI: " + group);
continue;
}
if (!groupmap.get(group).has(label)) {
ok(false, "Saw a source label not in the UI: " + label);
continue;
}
groupmap.get(group).get(label).url = source.url.split(" -> ").pop();
}
return groups;
}),
_onMessage: function(event) {
let json = JSON.parse(event.data);
switch (json.name) {
case "toolbox-title":
this.title = json.data.value;
break;
}
}
}
function initChromeDebugger(aOnClose) {

View File

@ -45,6 +45,7 @@ browser.jar:
content/browser/devtools/codemirror/dialog.js (sourceeditor/codemirror/dialog/dialog.js)
content/browser/devtools/codemirror/dialog.css (sourceeditor/codemirror/dialog/dialog.css)
content/browser/devtools/codemirror/emacs.js (sourceeditor/codemirror/keymap/emacs.js)
content/browser/devtools/codemirror/sublime.js (sourceeditor/codemirror/keymap/sublime.js)
content/browser/devtools/codemirror/vim.js (sourceeditor/codemirror/keymap/vim.js)
content/browser/devtools/codemirror/foldcode.js (sourceeditor/codemirror/fold/foldcode.js)
content/browser/devtools/codemirror/brace-fold.js (sourceeditor/codemirror/fold/brace-fold.js)

View File

@ -103,10 +103,12 @@ function autoComplete({ ed, cm }) {
// "backgr|" but we need to open the popup at the beginning of the character
// "b". Thus we need to calculate the width of the entered part of the token
// ("backgr" here). 4 comes from the popup's left padding.
let cursorElement = cm.display.cursorDiv.querySelector(".CodeMirror-cursor");
let left = suggestions[0].preLabel.length * cm.defaultCharWidth() + 4;
popup.hidePopup();
popup.setItems(suggestions);
popup.openPopup(cm.display.cursor, -1 * left, 0);
popup.openPopup(cursorElement, -1 * left, 0);
private.suggestionInsertedOnce = false;
// This event is used in tests.
ed.emit("after-suggest");
@ -159,6 +161,14 @@ function cycleSuggestions(ed, reverse) {
*/
function onEditorKeypress({ ed, Editor }, event) {
let private = privates.get(ed);
// Do not try to autocomplete with multiple selections.
if (ed.hasMultipleSelections()) {
private.doNotAutocomplete = true;
private.popup.hidePopup();
return;
}
switch (event.keyCode) {
case event.DOM_VK_ESCAPE:
if (private.popup.isOpen)

View File

@ -5,7 +5,7 @@ code, and optionally help with indentation.
# Upgrade
Currently used version is 3.20. To upgrade, download a new version of
Currently used version is 4.0.3. 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].
@ -51,6 +51,7 @@ in the LICENSE file:
* dialog/dialog.css
* dialog/dialog.js
* keymap/emacs.js
* keymap/sublime.js
* keymap/vim.js
* fold/foldcode.js
* fold/brace-fold.js
@ -70,26 +71,34 @@ in the LICENSE file:
* search/searchcursor.js
* test/codemirror.html
* test/cm_comment_test.js
* test/cm_doc_test.js
* test/cm_driver.js
* test/cm_mode_javascript_test.js
* test/cm_mode_test.css
* test/cm_mode_test.js
* test/cm_multi_test.js
* test/cm_search_test.js
* test/cm_test.js
* test/cm_sublime_test.js
* test/cm_vim_test.js
* test/cm_emacs_test.js
* test/cm_test.js
# Localization patches
diff --git a/browser/devtools/sourceeditor/codemirror/search/search.js b/browser/devtools/sourceeditor/codemirror/sea
index 049f72f..df4d95e 100644
diff --git a/browser/devtools/sourceeditor/codemirror/search/search.js b/browser/devtools/sourceeditor/codemirror/search/search.js
--- a/browser/devtools/sourceeditor/codemirror/search/search.js
+++ b/browser/devtools/sourceeditor/codemirror/search/search.js
@@ -58,9 +58,22 @@
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
@@ -62,19 +62,31 @@
if (isRE) {
query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
if (query.test("")) query = /x^/;
} else if (query == "") {
query = /x^/;
}
return query;
}
- var queryDialog =
- 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)<
- 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+ var queryDialog;
function doSearch(cm, rev) {
+ if (!queryDialog) {
@ -105,10 +114,14 @@ index 049f72f..df4d95e 100644
+ queryDialog.appendChild(txt);
+ queryDialog.appendChild(inp);
+ }
+
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
cm.operation(function() {
if (!query || state.query) return;
state.query = parseQuery(query);
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
# Footnotes

View File

@ -4,7 +4,14 @@
// active line's wrapping <div> the CSS class "CodeMirror-activeline",
// and gives its background <div> the class "CodeMirror-activeline-background".
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
@ -12,34 +19,48 @@
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
updateActiveLine(cm, cm.getCursor().line);
cm.state.activeLines = [];
updateActiveLines(cm, cm.listSelections());
cm.on("beforeSelectionChange", selectionChange);
} else if (!val && prev) {
cm.off("beforeSelectionChange", selectionChange);
clearActiveLine(cm);
delete cm.state.activeLine;
clearActiveLines(cm);
delete cm.state.activeLines;
}
});
function clearActiveLine(cm) {
if ("activeLine" in cm.state) {
cm.removeLineClass(cm.state.activeLine, "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLine, "background", BACK_CLASS);
function clearActiveLines(cm) {
for (var i = 0; i < cm.state.activeLines.length; i++) {
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
}
}
function updateActiveLine(cm, selectedLine) {
var line = cm.getLineHandleVisualStart(selectedLine);
if (cm.state.activeLine == line) return;
function sameArray(a, b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++)
if (a[i] != b[i]) return false;
return true;
}
function updateActiveLines(cm, ranges) {
var active = [];
for (var i = 0; i < ranges.length; i++) {
var line = cm.getLineHandleVisualStart(ranges[i].head.line);
if (active[active.length - 1] != line) active.push(line);
}
if (sameArray(cm.state.activeLines, active)) return;
cm.operation(function() {
clearActiveLine(cm);
cm.addLineClass(line, "wrap", WRAP_CLASS);
cm.addLineClass(line, "background", BACK_CLASS);
cm.state.activeLine = line;
clearActiveLines(cm);
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
cm.addLineClass(active[i], "background", BACK_CLASS);
}
cm.state.activeLines = active;
});
}
function selectionChange(cm, sel) {
updateActiveLine(cm, sel.head.line);
updateActiveLines(cm, sel.ranges);
}
})();
});

View File

@ -1,3 +1,13 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
@ -191,6 +201,30 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "meta";
}
function cpp11StringHook(stream, state) {
stream.backUp(1);
// Raw strings.
if (stream.match(/(R|u8R|uR|UR|LR)/)) {
var match = stream.match(/"(.{0,16})\(/);
if (!match) {
return false;
}
state.cpp11RawStringDelim = match[1];
state.tokenize = tokenRawString;
return tokenRawString(stream, state);
}
// Unicode strings/chars.
if (stream.match(/(u8|u|U|L)/)) {
if (stream.match(/["']/, /* eat */ false)) {
return "string";
}
return false;
}
// Ignore this hook.
stream.next();
return false;
}
// C#-style strings where "" escapes a quote.
function tokenAtString(stream, state) {
var next;
@ -203,6 +237,19 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "string";
}
// C++11 raw string literal is <prefix>"<delim>( anything )<delim>", where
// <delim> can be a string up to 16 characters long.
function tokenRawString(stream, state) {
var closingSequence = new RegExp(".*?\\)" + state.cpp11RawStringDelim + '"');
var match = stream.match(closingSequence);
if (match) {
state.tokenize = null;
} else {
stream.skipToEnd();
}
return "string";
}
function def(mimes, mode) {
var words = [];
function add(obj) {
@ -235,10 +282,17 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
"static_cast typeid catch operator template typename class friend private " +
"this using const_cast inline public throw virtual delete mutable protected " +
"wchar_t"),
"wchar_t alignas alignof constexpr decltype nullptr noexcept thread_local final " +
"static_assert override"),
blockKeywords: words("catch class do else finally for if struct switch try while"),
atoms: words("true false null"),
hooks: {"#": cppHook},
hooks: {
"#": cppHook,
"u": cpp11StringHook,
"U": cpp11StringHook,
"L": cpp11StringHook,
"R": cpp11StringHook
},
modeProps: {fold: ["brace", "include"]}
});
CodeMirror.defineMIME("text/x-java", {
@ -379,3 +433,5 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
modeProps: {fold: ["brace", "include"]}
});
}());
});

View File

@ -1,4 +1,11 @@
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var DEFAULT_BRACKETS = "()[]{}''\"\"";
var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
var SPACE_CHAR_REGEX = /\s/;
@ -28,57 +35,89 @@
var map = {
name : "autoCloseBrackets",
Backspace: function(cm) {
if (cm.somethingSelected() || cm.getOption("disableInput")) return CodeMirror.Pass;
var cur = cm.getCursor(), around = charsAround(cm, cur);
if (around && pairs.indexOf(around) % 2 == 0)
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
else
return CodeMirror.Pass;
}
}
};
var closingBrackets = "";
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
if (left != right) closingBrackets += right;
function surround(cm) {
var selection = cm.getSelection();
cm.replaceSelection(left + selection + right);
}
function maybeOverwrite(cm) {
var cur = cm.getCursor(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
if (ahead != right || cm.somethingSelected()) return CodeMirror.Pass;
else cm.execCommand("goCharRight");
}
map["'" + left + "'"] = function(cm) {
if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment" ||
cm.getOption("disableInput"))
return CodeMirror.Pass;
if (cm.somethingSelected()) return surround(cm);
if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return;
var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1);
var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : "";
if (left == right && CodeMirror.isWordChar(curChar))
return CodeMirror.Pass;
if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar))
cm.replaceSelection(left + right, {head: ahead, anchor: ahead});
else
return CodeMirror.Pass;
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), type, next;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
if (left == "'" && cm.getTokenTypeAt(cur) == "comment")
return CodeMirror.Pass;
var next = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
if (!range.empty())
curType = "surround";
else if (left == right && next == right)
curType = "skip";
else if (left == right && CodeMirror.isWordChar(next))
return CodeMirror.Pass;
else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next))
curType = "both";
else
return CodeMirror.Pass;
if (!type) type = curType;
else if (type != curType) return CodeMirror.Pass;
}
if (type == "skip") {
cm.execCommand("goCharRight");
} else if (type == "surround") {
var sels = cm.getSelections();
for (var i = 0; i < sels.length; i++)
sels[i] = left + sels[i] + right;
cm.replaceSelections(sels, "around");
} else if (type == "both") {
cm.replaceSelection(left + right, null);
cm.execCommand("goCharLeft");
}
};
if (left != right) map["'" + right + "'"] = function(cm) {
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (!range.empty() ||
cm.getRange(range.head, CodeMirror.Pos(range.head.line, range.head.ch + 1)) != right)
return CodeMirror.Pass;
}
cm.execCommand("goCharRight");
};
if (left != right) map["'" + right + "'"] = maybeOverwrite;
})(pairs.charAt(i), pairs.charAt(i + 1));
return map;
}
function buildExplodeHandler(pairs) {
return function(cm) {
var cur = cm.getCursor(), around = charsAround(cm, cur);
if (!around || pairs.indexOf(around) % 2 != 0 || cm.getOption("disableInput"))
return CodeMirror.Pass;
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
cm.operation(function() {
var newPos = CodeMirror.Pos(cur.line + 1, 0);
cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input");
cm.indentLine(cur.line + 1, null, true);
cm.indentLine(cur.line + 2, null, true);
cm.replaceSelection("\n\n", null);
cm.execCommand("goCharLeft");
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var line = ranges[i].head.line;
cm.indentLine(line, null, true);
cm.indentLine(line + 1, null, true);
}
});
};
}
})();
});

View File

@ -36,13 +36,14 @@
min-width: 20px;
text-align: right;
color: #999;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* CURSOR */
.CodeMirror div.CodeMirror-cursor {
border-left: 1px solid black;
z-index: 3;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
@ -52,16 +53,20 @@
width: auto;
border: 0;
background: #7e7;
z-index: 1;
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-tab { display: inline-block; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
.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;}
@ -114,14 +119,18 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
/* 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;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
@ -157,6 +166,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
-moz-box-sizing: content-box;
box-sizing: content-box;
padding-bottom: 30px;
margin-bottom: -32px;
@ -195,16 +205,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
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;
@ -234,11 +235,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror div.CodeMirror-cursor {
position: absolute;
visibility: hidden;
border-right: none;
width: 0;
}
.CodeMirror-focused div.CodeMirror-cursor {
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 1;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
@ -253,9 +259,12 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursor {
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,11 @@
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var noOptions = {};
@ -11,8 +18,21 @@
}
CodeMirror.commands.toggleComment = function(cm) {
var from = cm.getCursor("start"), to = cm.getCursor("end");
cm.uncomment(from, to) || cm.lineComment(from, to);
var minLine = Infinity, ranges = cm.listSelections(), mode = null;
for (var i = ranges.length - 1; i >= 0; i--) {
var from = ranges[i].from(), to = ranges[i].to();
if (from.line >= minLine) continue;
if (to.line >= minLine) to = Pos(minLine, 0);
minLine = from.line;
if (mode == null) {
if (cm.uncomment(from, to)) mode = "un";
else { cm.lineComment(from, to); mode = "line"; }
} else if (mode == "un") {
cm.uncomment(from, to);
} else {
cm.lineComment(from, to);
}
}
};
CodeMirror.defineExtension("lineComment", function(from, to, options) {
@ -146,4 +166,4 @@
});
return true;
});
})();
});

View File

@ -1,6 +1,14 @@
CodeMirror.defineMode("css", function(config, parserConfig) {
"use strict";
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("css", function(config, parserConfig) {
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
var indentUnit = config.indentUnit,
@ -140,6 +148,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return pushContext(state, stream, "media");
} else if (type == "@font-face") {
return "font_face_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
return "keyframes";
} else if (type && type.charAt(0) == "@") {
return pushContext(state, stream, "at");
} else if (type == "hash") {
@ -264,6 +274,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return "font_face";
};
states.keyframes = function(type, stream, state) {
if (type == "word") { override = "variable"; return "keyframes"; }
if (type == "{") return pushContext(state, stream, "top");
return pass(type, stream, state);
};
states.at = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state);
@ -308,6 +324,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
indent: function(state, textAfter) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.type == "prop" && ch == "}") cx = cx.prev;
if (cx.prev &&
(ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "font_face") ||
ch == ")" && (cx.type == "parens" || cx.type == "params" || cx.type == "media_parens") ||
@ -325,7 +342,6 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
};
});
(function() {
function keySet(array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
@ -353,10 +369,10 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-iteration-count",
"animation-name", "animation-play-state", "animation-timing-function",
"appearance", "azimuth", "backface-visibility", "background",
"background-attachment", "background-clip", "background-color",
"animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backface-visibility",
"background", "background-attachment", "background-clip", "background-color",
"background-image", "background-origin", "background-position",
"background-repeat", "background-size", "baseline-shift", "binding",
"bleed", "bookmark-label", "bookmark-level", "bookmark-state",
@ -387,10 +403,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"font-stretch", "font-style", "font-synthesis", "font-variant",
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid-cell", "grid-column", "grid-column-align",
"grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow",
"grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span",
"grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens",
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
"grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
"grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
"grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens",
"icon", "image-orientation", "image-rendering", "image-resolution",
"inline-box-align", "justify-content", "left", "letter-spacing",
"line-break", "line-height", "line-stacking", "line-stacking-ruby",
@ -667,7 +684,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
}
},
"@": function(stream) {
if (stream.match(/^(charset|document|font-face|import|keyframes|media|namespace|page|supports)\b/, false)) return false;
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
@ -680,4 +697,5 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
name: "css",
helperType: "less"
});
})();
});

View File

@ -1,6 +1,13 @@
// Open simple dialogs on top of an editor. Relies on dialog.css.
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
function dialogDiv(cm, template, bottom) {
var wrap = cm.getWrapperElement();
var dialog;
@ -39,6 +46,7 @@
CodeMirror.on(inp, "keydown", function(e) {
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
if (e.keyCode == 13 || e.keyCode == 27) {
inp.blur();
CodeMirror.e_stop(e);
close();
me.focus();
@ -119,4 +127,4 @@
if (duration)
doneTimer = setTimeout(close, options.duration);
});
})();
});

View File

@ -1,5 +1,18 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
var htmlMode = CodeMirror.getMode(config, {name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag});
var cssMode = CodeMirror.getMode(config, "css");
var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
@ -100,3 +113,5 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");
});

View File

@ -1,9 +1,20 @@
// TODO actually recognize syntax of TypeScript constructs
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonMode = parserConfig.json;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
// Tokenizer
@ -53,6 +64,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}();
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
@ -128,6 +140,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
@ -195,7 +211,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
@ -295,11 +311,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
poplex.lex = true;
function expect(wanted) {
return function(type) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
else return cont(exp);
};
return exp;
}
function statement(type, value) {
@ -408,7 +425,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
} else if (type == "number" || type == "string") {
cx.marked = type + " property";
cx.marked = jsonldMode ? "property" : (type + " property");
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
}
@ -565,7 +582,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
if (parserConfig.globalVars) state.globalVars = parserConfig.globalVars;
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
@ -616,6 +634,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
fold: "brace",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode
};
});
@ -626,5 +645,8 @@ 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("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

View File

@ -1,4 +1,11 @@
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var Pos = CodeMirror.Pos;
@ -174,7 +181,7 @@
if (dup > 1 && event.origin == "+input") {
var one = event.text.join("\n"), txt = "";
for (var i = 1; i < dup; ++i) txt += one;
cm.replaceSelection(txt, "end", "+input");
cm.replaceSelection(txt);
}
}
@ -197,7 +204,7 @@
function setMark(cm) {
cm.setCursor(cm.getCursor());
cm.setExtending(true);
cm.setExtending(!cm.getExtending());
cm.on("change", function() { cm.setExtending(false); });
}
@ -266,7 +273,7 @@
cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
cm.setSelection(start, cm.getCursor());
},
"Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
"Alt-Y": function(cm) {cm.replaceSelection(popFromRing(), "around", "paste");},
"Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
@ -323,7 +330,7 @@
var range = cm.getRange(from, pos);
if (range.length != 2) return;
cm.setSelection(from, pos);
cm.replaceSelection(range.charAt(1) + range.charAt(0), "end");
cm.replaceSelection(range.charAt(1) + range.charAt(0), null, "+transpose");
}),
"Alt-C": repeated(function(cm) {
@ -395,4 +402,4 @@
}
for (var i = 0; i < 10; ++i) regPrefix(String(i));
regPrefix("-");
})();
});

View File

@ -0,0 +1,505 @@
// A rough approximation of Sublime Text's keybindings
// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets"));
else if (typeof define == "function" && define.amd) // AMD
define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var map = CodeMirror.keyMap.sublime = {fallthrough: "default"};
var cmds = CodeMirror.commands;
var Pos = CodeMirror.Pos;
var ctrl = CodeMirror.keyMap["default"] == CodeMirror.keyMap.pcDefault ? "Ctrl-" : "Cmd-";
// This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
function findPosSubword(doc, start, dir) {
if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));
var line = doc.getLine(start.line);
if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));
var state = "start", type;
for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
var next = line.charAt(dir < 0 ? pos - 1 : pos);
var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o";
if (cat == "w" && next.toUpperCase() == next) cat = "W";
if (state == "start") {
if (cat != "o") { state = "in"; type = cat; }
} else if (state == "in") {
if (type != cat) {
if (type == "w" && cat == "W" && dir < 0) pos--;
if (type == "W" && cat == "w" && dir > 0) { type = "w"; continue; }
break;
}
}
}
return Pos(start.line, pos);
}
function moveSubword(cm, dir) {
cm.extendSelectionsBy(function(range) {
if (cm.display.shift || cm.doc.extend || range.empty())
return findPosSubword(cm.doc, range.head, dir);
else
return dir < 0 ? range.from() : range.to();
});
}
cmds[map["Alt-Left"] = "goSubwordLeft"] = function(cm) { moveSubword(cm, -1); };
cmds[map["Alt-Right"] = "goSubwordRight"] = function(cm) { moveSubword(cm, 1); };
cmds[map[ctrl + "Up"] = "scrollLineUp"] = function(cm) {
cm.scrollTo(null, cm.getScrollInfo().top - cm.defaultTextHeight());
};
cmds[map[ctrl + "Down"] = "scrollLineDown"] = function(cm) {
cm.scrollTo(null, cm.getScrollInfo().top + cm.defaultTextHeight());
};
cmds[map["Shift-" + ctrl + "L"] = "splitSelectionByLine"] = function(cm) {
var ranges = cm.listSelections(), lineRanges = [];
for (var i = 0; i < ranges.length; i++) {
var from = ranges[i].from(), to = ranges[i].to();
for (var line = from.line; line <= to.line; ++line)
if (!(to.line > from.line && line == to.line && to.ch == 0))
lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),
head: line == to.line ? to : Pos(line)});
}
cm.setSelections(lineRanges, 0);
};
map["Shift-Tab"] = "indentLess";
cmds[map["Esc"] = "singleSelectionTop"] = function(cm) {
var range = cm.listSelections()[0];
cm.setSelection(range.anchor, range.head, {scroll: false});
};
cmds[map[ctrl + "L"] = "selectLine"] = function(cm) {
var ranges = cm.listSelections(), extended = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
extended.push({anchor: Pos(range.from().line, 0),
head: Pos(range.to().line + 1, 0)});
}
cm.setSelections(extended);
};
map["Shift-" + ctrl + "K"] = "deleteLine";
function insertLine(cm, above) {
cm.operation(function() {
var len = cm.listSelections().length, newSelection = [], last = -1;
for (var i = 0; i < len; i++) {
var head = cm.listSelections()[i].head;
if (head.line <= last) continue;
var at = Pos(head.line + (above ? 0 : 1), 0);
cm.replaceRange("\n", at, null, "+insertLine");
cm.indentLine(at.line, null, true);
newSelection.push({head: at, anchor: at});
last = head.line + 1;
}
cm.setSelections(newSelection);
});
}
cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { insertLine(cm, false); };
cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { insertLine(cm, true); };
function wordAt(cm, pos) {
var start = pos.ch, end = start, line = cm.getLine(pos.line);
while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;
while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;
return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};
}
cmds[map[ctrl + "D"] = "selectNextOccurrence"] = function(cm) {
var from = cm.getCursor("from"), to = cm.getCursor("to");
var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;
if (CodeMirror.cmpPos(from, to) == 0) {
var word = wordAt(cm, from);
if (!word.word) return;
cm.setSelection(word.from, word.to);
fullWord = true;
} else {
var text = cm.getRange(from, to);
var query = fullWord ? new RegExp("\\b" + text + "\\b") : text;
var cur = cm.getSearchCursor(query, to);
if (cur.findNext()) {
cm.addSelection(cur.from(), cur.to());
} else {
cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0));
if (cur.findNext())
cm.addSelection(cur.from(), cur.to());
}
}
if (fullWord)
cm.state.sublimeFindFullWord = cm.doc.sel;
};
var mirror = "(){}[]";
function selectBetweenBrackets(cm) {
var pos = cm.getCursor(), opening = cm.scanForBracket(pos, -1);
if (!opening) return;
for (;;) {
var closing = cm.scanForBracket(pos, 1);
if (!closing) return;
if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) {
cm.setSelection(Pos(opening.pos.line, opening.pos.ch + 1), closing.pos, false);
return true;
}
pos = Pos(closing.pos.line, closing.pos.ch + 1);
}
}
cmds[map["Shift-" + ctrl + "Space"] = "selectScope"] = function(cm) {
selectBetweenBrackets(cm) || cm.execCommand("selectAll");
};
cmds[map["Shift-" + ctrl + "M"] = "selectBetweenBrackets"] = function(cm) {
if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;
};
cmds[map[ctrl + "M"] = "goToBracket"] = function(cm) {
cm.extendSelectionsBy(function(range) {
var next = cm.scanForBracket(range.head, 1);
if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;
var prev = cm.scanForBracket(range.head, -1);
return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;
});
};
cmds[map["Shift-" + ctrl + "Up"] = "swapLineUp"] = function(cm) {
var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], from = range.from().line - 1, to = range.to().line;
if (from > at) linesToMove.push(from, to);
else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
at = to;
}
cm.operation(function() {
for (var i = 0; i < linesToMove.length; i += 2) {
var from = linesToMove[i], to = linesToMove[i + 1];
var line = cm.getLine(from);
cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
if (to > cm.lastLine()) {
cm.replaceRange("\n" + line, Pos(cm.lastLine()), null, "+swapLine");
var sels = cm.listSelections(), last = sels[sels.length - 1];
var head = last.head.line == to ? Pos(to - 1) : last.head;
var anchor = last.anchor.line == to ? Pos(to - 1) : last.anchor;
cm.setSelections(sels.slice(0, sels.length - 1).concat([{head: head, anchor: anchor}]));
} else {
cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
}
}
cm.scrollIntoView();
});
};
cmds[map["Shift-" + ctrl + "Down"] = "swapLineDown"] = function(cm) {
var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
for (var i = ranges.length - 1; i >= 0; i--) {
var range = ranges[i], from = range.to().line + 1, to = range.from().line;
if (from < at) linesToMove.push(from, to);
else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
at = to;
}
cm.operation(function() {
for (var i = linesToMove.length - 2; i >= 0; i -= 2) {
var from = linesToMove[i], to = linesToMove[i + 1];
var line = cm.getLine(from);
if (from == cm.lastLine())
cm.replaceRange("", Pos(from - 1), Pos(from), "+swapLine");
else
cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
}
cm.scrollIntoView();
});
};
map[ctrl + "/"] = "toggleComment";
cmds[map[ctrl + "J"] = "joinLines"] = function(cm) {
var ranges = cm.listSelections(), joined = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], from = range.from();
var start = from.line, end = range.to().line;
while (i < ranges.length - 1 && ranges[i + 1].from().line == end)
end = ranges[++i].to().line;
joined.push({start: start, end: end, anchor: !range.empty() && from});
}
cm.operation(function() {
var offset = 0, ranges = [];
for (var i = 0; i < joined.length; i++) {
var obj = joined[i];
var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head;
for (var line = obj.start; line <= obj.end; line++) {
var actual = line - offset;
if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1);
if (actual < cm.lastLine()) {
cm.replaceRange(" ", Pos(actual), Pos(actual + 1, /^\s*/.exec(cm.getLine(actual + 1))[0].length));
++offset;
}
}
ranges.push({anchor: anchor || head, head: head});
}
cm.setSelections(ranges, 0);
});
};
cmds[map["Shift-" + ctrl + "D"] = "duplicateLine"] = function(cm) {
cm.operation(function() {
var rangeCount = cm.listSelections().length;
for (var i = 0; i < rangeCount; i++) {
var range = cm.listSelections()[i];
if (range.empty())
cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0));
else
cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());
}
cm.scrollIntoView();
});
};
map[ctrl + "T"] = "transposeChars";
function sortLines(cm, caseSensitive) {
var ranges = cm.listSelections(), toSort = [], selected;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (range.empty()) continue;
var from = range.from().line, to = range.to().line;
while (i < ranges.length - 1 && ranges[i + 1].from().line == to)
to = range[++i].to().line;
toSort.push(from, to);
}
if (toSort.length) selected = true;
else toSort.push(cm.firstLine(), cm.lastLine());
cm.operation(function() {
var ranges = [];
for (var i = 0; i < toSort.length; i += 2) {
var from = toSort[i], to = toSort[i + 1];
var start = Pos(from, 0), end = Pos(to);
var lines = cm.getRange(start, end, false);
if (caseSensitive)
lines.sort();
else
lines.sort(function(a, b) {
var au = a.toUpperCase(), bu = b.toUpperCase();
if (au != bu) { a = au; b = bu; }
return a < b ? -1 : a == b ? 0 : 1;
});
cm.replaceRange(lines, start, end);
if (selected) ranges.push({anchor: start, head: end});
}
if (selected) cm.setSelections(ranges, 0);
});
}
cmds[map["F9"] = "sortLines"] = function(cm) { sortLines(cm, true); };
cmds[map[ctrl + "F9"] = "sortLinesInsensitive"] = function(cm) { sortLines(cm, false); };
cmds[map["F2"] = "nextBookmark"] = function(cm) {
var marks = cm.state.sublimeBookmarks;
if (marks) while (marks.length) {
var current = marks.shift();
var found = current.find();
if (found) {
marks.push(current);
return cm.setSelection(found.from, found.to);
}
}
};
cmds[map["Shift-F2"] = "prevBookmark"] = function(cm) {
var marks = cm.state.sublimeBookmarks;
if (marks) while (marks.length) {
marks.unshift(marks.pop());
var found = marks[marks.length - 1].find();
if (!found)
marks.pop();
else
return cm.setSelection(found.from, found.to);
}
};
cmds[map[ctrl + "F2"] = "toggleBookmark"] = function(cm) {
var ranges = cm.listSelections();
var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);
for (var i = 0; i < ranges.length; i++) {
var from = ranges[i].from(), to = ranges[i].to();
var found = cm.findMarks(from, to);
for (var j = 0; j < found.length; j++) {
if (found[j].sublimeBookmark) {
found[j].clear();
for (var k = 0; k < marks.length; k++)
if (marks[k] == found[j])
marks.splice(k--, 1);
break;
}
}
if (j == found.length)
marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));
}
};
cmds[map["Shift-" + ctrl + "F2"] = "clearBookmarks"] = function(cm) {
var marks = cm.state.sublimeBookmarks;
if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
marks.length = 0;
};
cmds[map["Alt-F2"] = "selectBookmarks"] = function(cm) {
var marks = cm.state.sublimeBookmarks, ranges = [];
if (marks) for (var i = 0; i < marks.length; i++) {
var found = marks[i].find();
if (!found)
marks.splice(i--, 0);
else
ranges.push({anchor: found.from, head: found.to});
}
if (ranges.length)
cm.setSelections(ranges, 0);
};
map["Alt-Q"] = "wrapLines";
var mapK = CodeMirror.keyMap["sublime-Ctrl-K"] = {auto: "sublime", nofallthrough: true};
map[ctrl + "K"] = function(cm) {cm.setOption("keyMap", "sublime-Ctrl-K");};
function modifyWordOrSelection(cm, mod) {
cm.operation(function() {
var ranges = cm.listSelections(), indices = [], replacements = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (range.empty()) { indices.push(i); replacements.push(""); }
else replacements.push(mod(cm.getRange(range.from(), range.to())));
}
cm.replaceSelections(replacements, "around", "case");
for (var i = indices.length - 1, at; i >= 0; i--) {
var range = ranges[indices[i]];
if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;
var word = wordAt(cm, range.head);
at = word.from;
cm.replaceRange(mod(word.word), word.from, word.to);
}
});
}
mapK[ctrl + "Backspace"] = "delLineLeft";
cmds[mapK[ctrl + "K"] = "delLineRight"] = function(cm) {
cm.operation(function() {
var ranges = cm.listSelections();
for (var i = ranges.length - 1; i >= 0; i--)
cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete");
cm.scrollIntoView();
});
};
cmds[mapK[ctrl + "U"] = "upcaseAtCursor"] = function(cm) {
modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });
};
cmds[mapK[ctrl + "L"] = "downcaseAtCursor"] = function(cm) {
modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });
};
cmds[mapK[ctrl + "Space"] = "setSublimeMark"] = function(cm) {
if (cm.state.sublimeMark) cm.state.sublimeMark.clear();
cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
};
cmds[mapK[ctrl + "A"] = "selectToSublimeMark"] = function(cm) {
var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
if (found) cm.setSelection(cm.getCursor(), found);
};
cmds[mapK[ctrl + "W"] = "deleteToSublimeMark"] = function(cm) {
var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
if (found) {
var from = cm.getCursor(), to = found;
if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }
cm.state.sublimeKilled = cm.getRange(from, to);
cm.replaceRange("", from, to);
}
};
cmds[mapK[ctrl + "X"] = "swapWithSublimeMark"] = function(cm) {
var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
if (found) {
cm.state.sublimeMark.clear();
cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
cm.setCursor(found);
}
};
cmds[mapK[ctrl + "Y"] = "sublimeYank"] = function(cm) {
if (cm.state.sublimeKilled != null)
cm.replaceSelection(cm.state.sublimeKilled, null, "paste");
};
mapK[ctrl + "G"] = "clearBookmarks";
cmds[mapK[ctrl + "C"] = "showInCenter"] = function(cm) {
var pos = cm.cursorCoords(null, "local");
cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
};
cmds[map["Shift-Alt-Up"] = "selectLinesUpward"] = function(cm) {
cm.operation(function() {
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (range.head.line > cm.firstLine())
cm.addSelection(Pos(range.head.line - 1, range.head.ch));
}
});
};
cmds[map["Shift-Alt-Down"] = "selectLinesDownward"] = function(cm) {
cm.operation(function() {
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (range.head.line < cm.lastLine())
cm.addSelection(Pos(range.head.line + 1, range.head.ch));
}
});
};
function findAndGoTo(cm, forward) {
var from = cm.getCursor("from"), to = cm.getCursor("to");
if (CodeMirror.cmpPos(from, to) == 0) {
var word = wordAt(cm, from);
if (!word.word) return;
from = word.from;
to = word.to;
}
var query = cm.getRange(from, to);
var cur = cm.getSearchCursor(query, forward ? to : from);
if (forward ? cur.findNext() : cur.findPrevious()) {
cm.setSelection(cur.from(), cur.to());
} else {
cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)
: cm.clipPos(Pos(cm.lastLine())));
if (forward ? cur.findNext() : cur.findPrevious())
cm.setSelection(cur.from(), cur.to());
else if (word)
cm.setSelection(from, to);
}
};
cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); };
cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); };
map["Shift-" + ctrl + "["] = "fold";
map["Shift-" + ctrl + "]"] = "unfold";
mapK[ctrl + "0"] = mapK[ctrl + "j"] = "unfoldAll";
map[ctrl + "I"] = "findIncremental";
map["Shift-" + ctrl + "I"] = "findIncrementalReverse";
map[ctrl + "H"] = "replace";
map["F3"] = "findNext";
map["Shift-F3"] = "findPrev";
});

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +1,91 @@
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
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 maxScanLines = (state && state.maxScanLines) || 100;
var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
function findMatchingBracket(cm, where, strict, config) {
var line = cm.getLineHandle(where.line), pos = where.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 dir = match.charAt(1) == ">" ? 1 : -1;
if (strict && (dir > 0) != (pos == where.ch)) return null;
var style = cm.getTokenTypeAt(Pos(where.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 found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
}
function scanForBracket(cm, where, dir, style, config) {
var maxScanLen = (config && config.maxScanLineLength) || 10000;
var maxScanLines = (config && config.maxScanLines) || 500;
var stack = [], re = /[(){}[\]]/;
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
var line = cm.getLine(lineNo);
if (!line) continue;
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
if (line.length > maxScanLen) continue;
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
for (; pos != end; pos += dir) {
var ch = line.charAt(pos);
if (re.test(ch) && (style === undefined || 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};
if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}
}
}
for (var i = cur.line, found, e = forward ? Math.min(i + maxScanLines, cm.lineCount()) : Math.max(-1, i - maxScanLines); 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) {
function matchBrackets(cm, autoclear, config) {
// 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 marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
if (match && cm.getLine(match.from.line).length <= maxHighlightLen &&
match.to && cm.getLine(match.to.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
if (match.to)
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
}
}
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 textarea whenever 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;
if (marks.length) {
// 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() {
for (var i = 0; i < marks.length; i++) marks[i].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);
currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
@ -84,4 +102,7 @@
CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
return findMatchingBracket(this, pos, strict);
});
})();
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style){
return scanForBracket(this, pos, dir, style);
});
});

View File

@ -12,7 +12,16 @@
// actual CSS class name. showToken, when enabled, will cause the
// current token to be highlighted when nothing is selected.
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var DEFAULT_MIN_CHARS = 2;
var DEFAULT_TOKEN_STYLE = "matchhighlight";
var DEFAULT_DELAY = 100;
@ -68,7 +77,7 @@
return;
}
if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
var selection = cm.getSelection().replace(/^\s+|\s+$/g, "");
var selection = cm.getSelections()[0].replace(/^\s+|\s+$/g, "");
if (selection.length >= state.minChars)
cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
});
@ -88,4 +97,4 @@
stream.skipTo(query.charAt(0)) || stream.skipToEnd();
}};
}
})();
});

View File

@ -6,7 +6,15 @@
// replace by making sure the match is no longer selected when hitting
// Ctrl-G.
(function() {
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function searchOverlay(query, caseInsensitive) {
var startChar;
if (typeof query == "string") {
@ -16,16 +24,11 @@
} else {
query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : "");
}
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 (startChar)
if (startChar && !caseInsensitive)
stream.skipTo(startChar) || stream.skipToEnd();
if (stream.match(query, false)) break;
}
@ -56,7 +59,13 @@
}
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
if (isRE) {
query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
if (query.test("")) query = /x^/;
} else if (query == "") {
query = /x^/;
}
return query;
}
var queryDialog;
function doSearch(cm, rev) {
@ -73,7 +82,6 @@
queryDialog.appendChild(txt);
queryDialog.appendChild(inp);
}
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
@ -81,7 +89,7 @@
if (!query || state.query) return;
state.query = parseQuery(query);
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
state.overlay = searchOverlay(state.query);
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
cm.addOverlay(state.overlay);
state.posFrom = state.posTo = cm.getCursor();
findNext(cm, rev);
@ -120,7 +128,7 @@
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];}));
cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
} else cursor.replace(text);
}
});
@ -141,7 +149,7 @@
};
var doReplace = function(match) {
cursor.replace(typeof query == "string" ? text :
text.replace(/\$(\d)/, function(_, i) {return match[i];}));
text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
advance();
};
advance();
@ -156,4 +164,4 @@
CodeMirror.commands.clearSearch = clearSearch;
CodeMirror.commands.replace = replace;
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
})();
});

View File

@ -1,4 +1,12 @@
(function(){
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var Pos = CodeMirror.Pos;
function SearchCursor(doc, query, pos, caseFold) {
@ -164,4 +172,15 @@
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold);
});
})();
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
var ranges = [], next;
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
while (next = cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
ranges.push({anchor: cur.from(), head: cur.to()});
}
if (ranges.length)
this.setSelections(ranges, 0);
});
});

View File

@ -1,15 +1,24 @@
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
if (prev == CodeMirror.Init) prev = false;
if (prev && !val)
cm.removeOverlay("trailingspace");
else if (!prev && val)
cm.addOverlay({
token: function(stream) {
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
if (i > stream.pos) { stream.pos = i; return null; }
stream.pos = l;
return "trailingspace";
},
name: "trailingspace"
});
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
if (prev == CodeMirror.Init) prev = false;
if (prev && !val)
cm.removeOverlay("trailingspace");
else if (!prev && val)
cm.addOverlay({
token: function(stream) {
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
if (i > stream.pos) { stream.pos = i; return null; }
stream.pos = l;
return "trailingspace";
},
name: "trailingspace"
});
});
});

View File

@ -1,7 +1,18 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag || true;
var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag;
if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true;
var Kludges = parserConfig.htmlMode ? {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
@ -33,14 +44,16 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true
allowMissing: true,
caseFold: true
} : {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false
allowMissing: false,
caseFold: false
};
var alignCDATA = parserConfig.alignCDATA;
@ -76,6 +89,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
if (Kludges.caseFold) tagName = tagName.toLowerCase();
if (!tagName) return "tag error";
type = isClose ? "closeTag" : "openTag";
state.tokenize = inTag;
@ -188,7 +202,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName.toLowerCase();
parentTagName = state.context.tagName;
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
@ -206,7 +220,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
var err = false;
if (state.context) {
if (state.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName.toLowerCase()))
if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName))
popContext(state);
err = !state.context || state.context.tagName != tagName;
}
@ -219,6 +233,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return baseState;
}
}
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
@ -240,10 +255,10 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase())) {
maybePopContext(state, tagName.toLowerCase());
Kludges.autoSelfClosers.hasOwnProperty(tagName)) {
maybePopContext(state, tagName);
} else {
maybePopContext(state, tagName.toLowerCase());
maybePopContext(state, tagName);
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
@ -330,3 +345,5 @@ CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
});

View File

@ -53,6 +53,7 @@ const CM_SCRIPTS = [
"chrome://browser/content/devtools/codemirror/trailingspace.js",
"chrome://browser/content/devtools/codemirror/emacs.js",
"chrome://browser/content/devtools/codemirror/vim.js",
"chrome://browser/content/devtools/codemirror/sublime.js",
"chrome://browser/content/devtools/codemirror/foldcode.js",
"chrome://browser/content/devtools/codemirror/brace-fold.js",
"chrome://browser/content/devtools/codemirror/comment-fold.js",
@ -67,7 +68,7 @@ const CM_IFRAME =
" <style>" +
" html, body { height: 100%; }" +
" body { margin: 0; overflow: hidden; }" +
" .CodeMirror { width: 100%; height: 100% !important; line-height: normal!important}" +
" .CodeMirror { width: 100%; height: 100% !important; line-height: 1.25 !important;}" +
" </style>" +
[ " <link rel='stylesheet' href='" + style + "'>" for (style of CM_STYLES) ].join("\n") +
" </head>" +
@ -161,7 +162,7 @@ function Editor(config) {
this.config.extraKeys[Editor.keyFor("indentMore")] = false;
// If alternative keymap is provided, use it.
if (keyMap === "emacs" || keyMap === "vim")
if (keyMap === "emacs" || keyMap === "vim" || keyMap === "sublime")
this.config.keyMap = keyMap;
// Overwrite default config with user-provided, if needed.
@ -403,6 +404,14 @@ Editor.prototype = {
this.setCursor(this.getCursor());
},
/**
* Returns true if there is more than one selection in the editor.
*/
hasMultipleSelections: function () {
let cm = editors.get(this);
return cm.listSelections().length > 1;
},
/**
* Gets the first visible line number in the editor.
*/

View File

@ -2,12 +2,15 @@
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
support-files =
cm_comment_test.js
cm_doc_test.js
cm_driver.js
cm_mode_javascript_test.js
cm_emacs_test.js
cm_mode_test.css
cm_mode_test.js
cm_multi_test.js
cm_search_test.js
cm_sublime_test.js
cm_test.js
cm_emacs_test.js
cm_vim_test.js
codemirror.html
css_statemachine_testcases.css

View File

@ -29,11 +29,11 @@ namespace = "comment_";
}, simpleProg, simpleProg);
// test("fallbackToBlock", "css", function(cm) {
// cm.lineComment(Pos(0, 0), Pos(2, 1));
// 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));
// cm.blockComment(Pos(0, 0), Pos(1));
// }, "def blah()\n return hah\n", "# def blah()\n# return hah\n");
test("commentRange", "javascript", function(cm) {

View File

@ -0,0 +1,348 @@
(function() {
// A minilanguage for instantiating linked CodeMirror instances and Docs
function instantiateSpec(spec, place, opts) {
var names = {}, pos = 0, l = spec.length, editors = [];
while (spec) {
var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/);
var name = m[1], isDoc = m[2], cur;
if (m[3]) {
cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]}));
} else {
var other = m[5];
if (!names.hasOwnProperty(other)) {
names[other] = editors.length;
editors.push(CodeMirror(place, opts));
}
var doc = editors[names[other]].linkedDoc({
sharedHist: !m[4],
from: m[6] ? Number(m[6]) : null,
to: m[7] ? Number(m[7]) : null
});
cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc}));
}
names[name] = editors.length;
editors.push(cur);
spec = spec.slice(m[0].length);
}
return editors;
}
function clone(obj, props) {
if (!obj) return;
clone.prototype = obj;
var inst = new clone();
if (props) for (var n in props) if (props.hasOwnProperty(n))
inst[n] = props[n];
return inst;
}
function eqAll(val) {
var end = arguments.length, msg = null;
if (typeof arguments[end-1] == "string")
msg = arguments[--end];
if (i == end) throw new Error("No editors provided to eqAll");
for (var i = 1; i < end; ++i)
eq(arguments[i].getValue(), val, msg)
}
function testDoc(name, spec, run, opts, expectFail) {
if (!opts) opts = {};
return test("doc_" + name, function() {
var place = document.getElementById("testground");
var editors = instantiateSpec(spec, place, opts);
var successful = false;
try {
run.apply(null, editors);
successful = true;
} finally {
if (!successful || verbose) {
place.style.visibility = "visible";
} else {
for (var i = 0; i < editors.length; ++i)
if (editors[i] instanceof CodeMirror)
place.removeChild(editors[i].getWrapperElement());
}
}
}, expectFail);
}
var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
function testBasic(a, b) {
eqAll("x", a, b);
a.setValue("hey");
eqAll("hey", a, b);
b.setValue("wow");
eqAll("wow", a, b);
a.replaceRange("u\nv\nw", Pos(0, 3));
b.replaceRange("i", Pos(0, 4));
b.replaceRange("j", Pos(2, 1));
eqAll("wowui\nv\nwj", a, b);
}
testDoc("basic", "A='x' B<A", testBasic);
testDoc("basicSeparate", "A='x' B<~A", testBasic);
testDoc("sharedHist", "A='ab\ncd\nef' B<A", function(a, b) {
a.replaceRange("x", Pos(0));
b.replaceRange("y", Pos(1));
a.replaceRange("z", Pos(2));
eqAll("abx\ncdy\nefz", a, b);
a.undo();
a.undo();
eqAll("abx\ncd\nef", a, b);
a.redo();
eqAll("abx\ncdy\nef", a, b);
b.redo();
eqAll("abx\ncdy\nefz", a, b);
a.undo(); b.undo(); a.undo(); a.undo();
eqAll("ab\ncd\nef", a, b);
}, null, ie_lt8);
testDoc("undoIntact", "A='ab\ncd\nef' B<~A", function(a, b) {
a.replaceRange("x", Pos(0));
b.replaceRange("y", Pos(1));
a.replaceRange("z", Pos(2));
a.replaceRange("q", Pos(0));
eqAll("abxq\ncdy\nefz", a, b);
a.undo();
a.undo();
eqAll("abx\ncdy\nef", a, b);
b.undo();
eqAll("abx\ncd\nef", a, b);
a.redo();
eqAll("abx\ncd\nefz", a, b);
a.redo();
eqAll("abxq\ncd\nefz", a, b);
a.undo(); a.undo(); a.undo(); a.undo();
eqAll("ab\ncd\nef", a, b);
b.redo();
eqAll("ab\ncdy\nef", a, b);
});
testDoc("undoConflict", "A='ab\ncd\nef' B<~A", function(a, b) {
a.replaceRange("x", Pos(0));
a.replaceRange("z", Pos(2));
// This should clear the first undo event in a, but not the second
b.replaceRange("y", Pos(0));
a.undo(); a.undo();
eqAll("abxy\ncd\nef", a, b);
a.replaceRange("u", Pos(2));
a.replaceRange("v", Pos(0));
// This should clear both events in a
b.replaceRange("w", Pos(0));
a.undo(); a.undo();
eqAll("abxyvw\ncd\nefu", a, b);
});
testDoc("doubleRebase", "A='ab\ncd\nef\ng' B<~A C<B", function(a, b, c) {
c.replaceRange("u", Pos(3));
a.replaceRange("", Pos(0, 0), Pos(1, 0));
c.undo();
eqAll("cd\nef\ng", a, b, c);
});
testDoc("undoUpdate", "A='ab\ncd\nef' B<~A", function(a, b) {
a.replaceRange("x", Pos(2));
b.replaceRange("u\nv\nw\n", Pos(0, 0));
a.undo();
eqAll("u\nv\nw\nab\ncd\nef", a, b);
a.redo();
eqAll("u\nv\nw\nab\ncd\nefx", a, b);
a.undo();
eqAll("u\nv\nw\nab\ncd\nef", a, b);
b.undo();
a.redo();
eqAll("ab\ncd\nefx", a, b);
a.undo();
eqAll("ab\ncd\nef", a, b);
});
testDoc("undoKeepRanges", "A='abcdefg' B<A", function(a, b) {
var m = a.markText(Pos(0, 1), Pos(0, 3), {className: "foo"});
b.replaceRange("x", Pos(0, 0));
eqPos(m.find().from, Pos(0, 2));
b.replaceRange("yzzy", Pos(0, 1), Pos(0));
eq(m.find(), null);
b.undo();
eqPos(m.find().from, Pos(0, 2));
b.undo();
eqPos(m.find().from, Pos(0, 1));
});
testDoc("longChain", "A='uv' B<A C<B D<C", function(a, b, c, d) {
a.replaceSelection("X");
eqAll("Xuv", a, b, c, d);
d.replaceRange("Y", Pos(0));
eqAll("XuvY", a, b, c, d);
});
testDoc("broadCast", "B<A C<A D<A E<A", function(a, b, c, d, e) {
b.setValue("uu");
eqAll("uu", a, b, c, d, e);
a.replaceRange("v", Pos(0, 1));
eqAll("uvu", a, b, c, d, e);
});
// A and B share a history, C and D share a separate one
testDoc("islands", "A='x\ny\nz' B<A C<~A D<C", function(a, b, c, d) {
a.replaceRange("u", Pos(0));
d.replaceRange("v", Pos(2));
b.undo();
eqAll("x\ny\nzv", a, b, c, d);
c.undo();
eqAll("x\ny\nz", a, b, c, d);
a.redo();
eqAll("xu\ny\nz", a, b, c, d);
d.redo();
eqAll("xu\ny\nzv", a, b, c, d);
});
testDoc("unlink", "B<A C<A D<B", function(a, b, c, d) {
a.setValue("hi");
b.unlinkDoc(a);
d.setValue("aye");
eqAll("hi", a, c);
eqAll("aye", b, d);
a.setValue("oo");
eqAll("oo", a, c);
eqAll("aye", b, d);
});
testDoc("bareDoc", "A*='foo' B*<A C<B", function(a, b, c) {
is(a instanceof CodeMirror.Doc);
is(b instanceof CodeMirror.Doc);
is(c instanceof CodeMirror);
eqAll("foo", a, b, c);
a.replaceRange("hey", Pos(0, 0), Pos(0));
c.replaceRange("!", Pos(0));
eqAll("hey!", a, b, c);
b.unlinkDoc(a);
b.setValue("x");
eqAll("x", b, c);
eqAll("hey!", a);
});
testDoc("swapDoc", "A='a' B*='b' C<A", function(a, b, c) {
var d = a.swapDoc(b);
d.setValue("x");
eqAll("x", c, d);
eqAll("b", a, b);
});
testDoc("docKeepsScroll", "A='x' B*='y'", function(a, b) {
addDoc(a, 200, 200);
a.scrollIntoView(Pos(199, 200));
var c = a.swapDoc(b);
a.swapDoc(c);
var pos = a.getScrollInfo();
is(pos.left > 0, "not at left");
is(pos.top > 0, "not at top");
});
testDoc("copyDoc", "A='u'", function(a) {
var copy = a.getDoc().copy(true);
a.setValue("foo");
copy.setValue("bar");
var old = a.swapDoc(copy);
eq(a.getValue(), "bar");
a.undo();
eq(a.getValue(), "u");
a.swapDoc(old);
eq(a.getValue(), "foo");
eq(old.historySize().undo, 1);
eq(old.copy(false).historySize().undo, 0);
});
testDoc("docKeepsMode", "A='1+1'", function(a) {
var other = CodeMirror.Doc("hi", "text/x-markdown");
a.setOption("mode", "text/javascript");
var old = a.swapDoc(other);
eq(a.getOption("mode"), "text/x-markdown");
eq(a.getMode().name, "markdown");
a.swapDoc(old);
eq(a.getOption("mode"), "text/javascript");
eq(a.getMode().name, "javascript");
});
testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) {
eq(b.getValue(), "2\n3");
eq(b.firstLine(), 1);
b.setCursor(Pos(4));
eqPos(b.getCursor(), Pos(2, 1));
a.replaceRange("-1\n0\n", Pos(0, 0));
eq(b.firstLine(), 3);
eqPos(b.getCursor(), Pos(4, 1));
a.undo();
eqPos(b.getCursor(), Pos(2, 1));
b.replaceRange("oyoy\n", Pos(2, 0));
eq(a.getValue(), "1\n2\noyoy\n3\n4\n5");
b.undo();
eq(a.getValue(), "1\n2\n3\n4\n5");
});
testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) {
a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1));
eq(b.firstLine(), 2);
eq(b.lineCount(), 2);
eq(b.getValue(), "z3\n44");
a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1));
eq(b.firstLine(), 2);
eq(b.getValue(), "z3\n4q");
eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5");
a.execCommand("selectAll");
a.replaceSelection("!");
eqAll("!", a, b);
});
testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
var mark = b.markText(Pos(0, 1), Pos(3, 1),
{className: "cm-searching", shared: true});
var found = a.findMarksAt(Pos(0, 2));
eq(found.length, 1);
eq(found[0], mark);
eq(c.findMarksAt(Pos(1, 1)).length, 1);
eqPos(mark.find().from, Pos(0, 1));
eqPos(mark.find().to, Pos(3, 1));
b.replaceRange("x\ny\n", Pos(0, 0));
eqPos(mark.find().from, Pos(2, 1));
eqPos(mark.find().to, Pos(5, 1));
var cleared = 0;
CodeMirror.on(mark, "clear", function() {++cleared;});
b.operation(function(){mark.clear();});
eq(a.findMarksAt(Pos(3, 1)).length, 0);
eq(b.findMarksAt(Pos(3, 1)).length, 0);
eq(c.findMarksAt(Pos(3, 1)).length, 0);
eq(mark.find(), null);
eq(cleared, 1);
});
testDoc("sharedBookmark", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
var mark = b.setBookmark(Pos(1, 1), {shared: true});
var found = a.findMarksAt(Pos(1, 1));
eq(found.length, 1);
eq(found[0], mark);
eq(c.findMarksAt(Pos(1, 1)).length, 1);
eqPos(mark.find(), Pos(1, 1));
b.replaceRange("x\ny\n", Pos(0, 0));
eqPos(mark.find(), Pos(3, 1));
var cleared = 0;
CodeMirror.on(mark, "clear", function() {++cleared;});
b.operation(function() {mark.clear();});
eq(a.findMarks(Pos(0, 0), Pos(5)).length, 0);
eq(b.findMarks(Pos(0, 0), Pos(5)).length, 0);
eq(c.findMarks(Pos(0, 0), Pos(5)).length, 0);
eq(mark.find(), null);
eq(cleared, 1);
});
testDoc("undoInSubview", "A='line 0\nline 1\nline 2\nline 3\nline 4' B<A/1-4", function(a, b) {
b.replaceRange("x", Pos(2, 0));
a.undo();
eq(a.getValue(), "line 0\nline 1\nline 2\nline 3\nline 4");
eq(b.getValue(), "line 1\nline 2\nline 3");
});
})();

View File

@ -44,23 +44,25 @@ function testCM(name, run, opts, expectedFail) {
function runTests(callback) {
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 (filters.length) {
for (var j = 0; j < filters.length; j++) {
if (test.name.match(filters[j])) {
break;
}
for (;;) {
if (i === tests.length) {
running = false;
return callback("done");
}
if (j == filters.length) {
var test = tests[i], skip = false;
if (filters.length) {
skip = true;
for (var j = 0; j < filters.length; j++)
if (test.name.match(filters[j])) skip = false;
}
if (skip) {
callback("skipped", test.name, message);
return step(i + 1);
i++;
} else {
break;
}
}
var threw = false;
var expFail = test.expectedFail, startTime = +new Date, threw = false;
try {
var message = test.func();
} catch(e) {
@ -69,6 +71,7 @@ function runTests(callback) {
else if (e instanceof Failure) callback("fail", test.name, e.message);
else {
var pos = /(?:\bat |@).*?([^\/:]+):(\d+)/.exec(e.stack);
if (pos) console["log"](e.stack);
callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : ""));
}
}
@ -99,6 +102,10 @@ function label(str, msg) {
function eq(a, b, msg) {
if (a != b) throw new Failure(label(a + " != " + b, msg));
}
function near(a, b, margin, msg) {
if (Math.abs(a - b) > margin)
throw new Failure(label(a + " is not close to " + b + " (" + margin + ")", msg));
}
function eqPos(a, b, msg) {
function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; }
if (a == b) return;

View File

@ -110,4 +110,42 @@
MT("scary_regexp",
"[string-2 /foo[[/]]bar/];");
var jsonld_mode = CodeMirror.getMode(
{indentUnit: 2},
{name: "javascript", jsonld: true}
);
function LD(name) {
test.mode(name, jsonld_mode, Array.prototype.slice.call(arguments, 1));
}
LD("json_ld_keywords",
'{',
' [meta "@context"]: {',
' [meta "@base"]: [string "http://example.com"],',
' [meta "@vocab"]: [string "http://xmlns.com/foaf/0.1/"],',
' [property "likesFlavor"]: {',
' [meta "@container"]: [meta "@list"]',
' [meta "@reverse"]: [string "@beFavoriteOf"]',
' },',
' [property "nick"]: { [meta "@container"]: [meta "@set"] },',
' [property "nick"]: { [meta "@container"]: [meta "@index"] }',
' },',
' [meta "@graph"]: [[ {',
' [meta "@id"]: [string "http://dbpedia.org/resource/John_Lennon"],',
' [property "name"]: [string "John Lennon"],',
' [property "modified"]: {',
' [meta "@value"]: [string "2010-05-29T14:17:39+02:00"],',
' [meta "@type"]: [string "http://www.w3.org/2001/XMLSchema#dateTime"]',
' }',
' } ]]',
'}');
LD("json_ld_fake",
'{',
' [property "@fake"]: [string "@fake"],',
' [property "@contextual"]: [string "@identifier"],',
' [property "user@domain.com"]: [string "@graphical"],',
' [property "@ID"]: [string "@@ID"]',
'}');
})();

View File

@ -0,0 +1,285 @@
(function() {
namespace = "multi_";
function hasSelections(cm) {
var sels = cm.listSelections();
var given = (arguments.length - 1) / 4;
if (sels.length != given)
throw new Failure("expected " + given + " selections, found " + sels.length);
for (var i = 0, p = 1; i < given; i++, p += 4) {
var anchor = Pos(arguments[p], arguments[p + 1]);
var head = Pos(arguments[p + 2], arguments[p + 3]);
eqPos(sels[i].anchor, anchor, "anchor of selection " + i);
eqPos(sels[i].head, head, "head of selection " + i);
}
}
function hasCursors(cm) {
var sels = cm.listSelections();
var given = (arguments.length - 1) / 2;
if (sels.length != given)
throw new Failure("expected " + given + " selections, found " + sels.length);
for (var i = 0, p = 1; i < given; i++, p += 2) {
eqPos(sels[i].anchor, sels[i].head, "something selected for " + i);
var head = Pos(arguments[p], arguments[p + 1]);
eqPos(sels[i].head, head, "selection " + i);
}
}
testCM("getSelection", function(cm) {
select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, {anchor: Pos(2, 2), head: Pos(2, 0)});
eq(cm.getSelection(), "1234\n56\n90");
eq(cm.getSelection(false).join("|"), "1234|56|90");
eq(cm.getSelections().join("|"), "1234\n56|90");
}, {value: "1234\n5678\n90"});
testCM("setSelection", function(cm) {
select(cm, Pos(3, 0), Pos(0, 0), {anchor: Pos(2, 5), head: Pos(1, 0)});
hasSelections(cm, 0, 0, 0, 0,
2, 5, 1, 0,
3, 0, 3, 0);
cm.setSelection(Pos(1, 2), Pos(1, 1));
hasSelections(cm, 1, 2, 1, 1);
select(cm, {anchor: Pos(1, 1), head: Pos(2, 4)},
{anchor: Pos(0, 0), head: Pos(1, 3)},
Pos(3, 0), Pos(2, 2));
hasSelections(cm, 0, 0, 2, 4,
3, 0, 3, 0);
cm.setSelections([{anchor: Pos(0, 1), head: Pos(0, 2)},
{anchor: Pos(1, 1), head: Pos(1, 2)},
{anchor: Pos(2, 1), head: Pos(2, 2)}], 1);
eqPos(cm.getCursor("head"), Pos(1, 2));
eqPos(cm.getCursor("anchor"), Pos(1, 1));
eqPos(cm.getCursor("from"), Pos(1, 1));
eqPos(cm.getCursor("to"), Pos(1, 2));
cm.setCursor(Pos(1, 1));
hasCursors(cm, 1, 1);
}, {value: "abcde\nabcde\nabcde\n"});
testCM("somethingSelected", function(cm) {
select(cm, Pos(0, 1), {anchor: Pos(0, 3), head: Pos(0, 5)});
eq(cm.somethingSelected(), true);
select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5));
eq(cm.somethingSelected(), false);
}, {value: "123456789"});
testCM("extendSelection", function(cm) {
select(cm, Pos(0, 1), Pos(1, 1), Pos(2, 1));
cm.setExtending(true);
cm.extendSelections([Pos(0, 2), Pos(1, 0), Pos(2, 3)]);
hasSelections(cm, 0, 1, 0, 2,
1, 1, 1, 0,
2, 1, 2, 3);
cm.extendSelection(Pos(2, 4), Pos(2, 0));
hasSelections(cm, 2, 4, 2, 0);
}, {value: "1234\n1234\n1234"});
testCM("addSelection", function(cm) {
select(cm, Pos(0, 1), Pos(1, 1));
cm.addSelection(Pos(0, 0), Pos(0, 4));
hasSelections(cm, 0, 0, 0, 4,
1, 1, 1, 1);
cm.addSelection(Pos(2, 2));
hasSelections(cm, 0, 0, 0, 4,
1, 1, 1, 1,
2, 2, 2, 2);
}, {value: "1234\n1234\n1234"});
testCM("replaceSelection", function(cm) {
var selections = [{anchor: Pos(0, 0), head: Pos(0, 1)},
{anchor: Pos(0, 2), head: Pos(0, 3)},
{anchor: Pos(0, 4), head: Pos(0, 5)},
{anchor: Pos(2, 1), head: Pos(2, 4)},
{anchor: Pos(2, 5), head: Pos(2, 6)}];
var val = "123456\n123456\n123456";
cm.setValue(val);
cm.setSelections(selections);
cm.replaceSelection("ab", "around");
eq(cm.getValue(), "ab2ab4ab6\n123456\n1ab5ab");
hasSelections(cm, 0, 0, 0, 2,
0, 3, 0, 5,
0, 6, 0, 8,
2, 1, 2, 3,
2, 4, 2, 6);
cm.setValue(val);
cm.setSelections(selections);
cm.replaceSelection("", "around");
eq(cm.getValue(), "246\n123456\n15");
hasSelections(cm, 0, 0, 0, 0,
0, 1, 0, 1,
0, 2, 0, 2,
2, 1, 2, 1,
2, 2, 2, 2);
cm.setValue(val);
cm.setSelections(selections);
cm.replaceSelection("X\nY\nZ", "around");
hasSelections(cm, 0, 0, 2, 1,
2, 2, 4, 1,
4, 2, 6, 1,
8, 1, 10, 1,
10, 2, 12, 1);
cm.replaceSelection("a", "around");
hasSelections(cm, 0, 0, 0, 1,
0, 2, 0, 3,
0, 4, 0, 5,
2, 1, 2, 2,
2, 3, 2, 4);
cm.replaceSelection("xy", "start");
hasSelections(cm, 0, 0, 0, 0,
0, 3, 0, 3,
0, 6, 0, 6,
2, 1, 2, 1,
2, 4, 2, 4);
cm.replaceSelection("z\nf");
hasSelections(cm, 1, 1, 1, 1,
2, 1, 2, 1,
3, 1, 3, 1,
6, 1, 6, 1,
7, 1, 7, 1);
eq(cm.getValue(), "z\nfxy2z\nfxy4z\nfxy6\n123456\n1z\nfxy5z\nfxy");
});
function select(cm) {
var sels = [];
for (var i = 1; i < arguments.length; i++) {
var arg = arguments[i];
if (arg.head) sels.push(arg);
else sels.push({head: arg, anchor: arg});
}
cm.setSelections(sels, sels.length - 1);
}
testCM("indentSelection", function(cm) {
select(cm, Pos(0, 1), Pos(1, 1));
cm.indentSelection(4);
eq(cm.getValue(), " foo\n bar\nbaz");
select(cm, Pos(0, 2), Pos(0, 3), Pos(0, 4));
cm.indentSelection(-2);
eq(cm.getValue(), " foo\n bar\nbaz");
select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)},
{anchor: Pos(1, 3), head: Pos(2, 0)});
cm.indentSelection(-2);
eq(cm.getValue(), "foo\n bar\nbaz");
}, {value: "foo\nbar\nbaz"});
testCM("killLine", function(cm) {
select(cm, Pos(0, 1), Pos(0, 2), Pos(1, 1));
cm.execCommand("killLine");
eq(cm.getValue(), "f\nb\nbaz");
cm.execCommand("killLine");
eq(cm.getValue(), "fbbaz");
cm.setValue("foo\nbar\nbaz");
select(cm, Pos(0, 1), {anchor: Pos(0, 2), head: Pos(2, 1)});
cm.execCommand("killLine");
eq(cm.getValue(), "faz");
}, {value: "foo\nbar\nbaz"});
testCM("deleteLine", function(cm) {
select(cm, Pos(0, 0),
{head: Pos(0, 1), anchor: Pos(2, 0)},
Pos(4, 0));
cm.execCommand("deleteLine");
eq(cm.getValue(), "4\n6\n7");
select(cm, Pos(2, 1));
cm.execCommand("deleteLine");
eq(cm.getValue(), "4\n6\n");
}, {value: "1\n2\n3\n4\n5\n6\n7"});
testCM("deleteH", function(cm) {
select(cm, Pos(0, 4), {anchor: Pos(1, 4), head: Pos(1, 5)});
cm.execCommand("delWordAfter");
eq(cm.getValue(), "foo bar baz\nabc ef ghi\n");
cm.execCommand("delWordAfter");
eq(cm.getValue(), "foo baz\nabc ghi\n");
cm.execCommand("delCharBefore");
cm.execCommand("delCharBefore");
eq(cm.getValue(), "fo baz\nab ghi\n");
select(cm, Pos(0, 3), Pos(0, 4), Pos(0, 5));
cm.execCommand("delWordAfter");
eq(cm.getValue(), "fo \nab ghi\n");
}, {value: "foo bar baz\nabc def ghi\n"});
testCM("goLineStart", function(cm) {
select(cm, Pos(0, 2), Pos(0, 3), Pos(1, 1));
cm.execCommand("goLineStart");
hasCursors(cm, 0, 0, 1, 0);
select(cm, Pos(1, 1), Pos(0, 1));
cm.setExtending(true);
cm.execCommand("goLineStart");
hasSelections(cm, 0, 1, 0, 0,
1, 1, 1, 0);
}, {value: "foo\nbar\nbaz"});
testCM("moveV", function(cm) {
select(cm, Pos(0, 2), Pos(1, 2));
cm.execCommand("goLineDown");
hasCursors(cm, 1, 2, 2, 2);
cm.execCommand("goLineUp");
hasCursors(cm, 0, 2, 1, 2);
cm.execCommand("goLineUp");
hasCursors(cm, 0, 0, 0, 2);
cm.execCommand("goLineUp");
hasCursors(cm, 0, 0);
select(cm, Pos(0, 2), Pos(1, 2));
cm.setExtending(true);
cm.execCommand("goLineDown");
hasSelections(cm, 0, 2, 2, 2);
}, {value: "12345\n12345\n12345"});
testCM("moveH", function(cm) {
select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5), Pos(2, 3));
cm.execCommand("goCharRight");
hasCursors(cm, 0, 2, 0, 4, 1, 0, 2, 4);
cm.execCommand("goCharLeft");
hasCursors(cm, 0, 1, 0, 3, 0, 5, 2, 3);
for (var i = 0; i < 15; i++)
cm.execCommand("goCharRight");
hasCursors(cm, 2, 4, 2, 5);
}, {value: "12345\n12345\n12345"});
testCM("newlineAndIndent", function(cm) {
select(cm, Pos(0, 5), Pos(1, 5));
cm.execCommand("newlineAndIndent");
hasCursors(cm, 1, 2, 3, 2);
eq(cm.getValue(), "x = [\n 1];\ny = [\n 2];");
cm.undo();
eq(cm.getValue(), "x = [1];\ny = [2];");
hasCursors(cm, 0, 5, 1, 5);
select(cm, Pos(0, 5), Pos(0, 6));
cm.execCommand("newlineAndIndent");
hasCursors(cm, 1, 2, 2, 0);
eq(cm.getValue(), "x = [\n 1\n];\ny = [2];");
}, {value: "x = [1];\ny = [2];", mode: "javascript"});
testCM("goDocStartEnd", function(cm) {
select(cm, Pos(0, 1), Pos(1, 1));
cm.execCommand("goDocStart");
hasCursors(cm, 0, 0);
select(cm, Pos(0, 1), Pos(1, 1));
cm.execCommand("goDocEnd");
hasCursors(cm, 1, 3);
select(cm, Pos(0, 1), Pos(1, 1));
cm.setExtending(true);
cm.execCommand("goDocEnd");
hasSelections(cm, 1, 1, 1, 3);
}, {value: "abc\ndef"});
testCM("selectionHistory", function(cm) {
for (var i = 0; i < 3; ++i)
cm.addSelection(Pos(0, i * 2), Pos(0, i * 2 + 1));
cm.execCommand("undoSelection");
eq(cm.getSelection(), "1\n2");
cm.execCommand("undoSelection");
eq(cm.getSelection(), "1");
cm.execCommand("undoSelection");
eq(cm.getSelection(), "");
eqPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("redoSelection");
eq(cm.getSelection(), "1");
cm.execCommand("redoSelection");
eq(cm.getSelection(), "1\n2");
cm.execCommand("redoSelection");
eq(cm.getSelection(), "1\n2\n3");
}, {value: "1 2 3"});
})();

View File

@ -0,0 +1,62 @@
(function() {
"use strict";
function test(name) {
var text = Array.prototype.slice.call(arguments, 1, arguments.length - 1).join("\n");
var body = arguments[arguments.length - 1];
return window.test("search_" + name, function() {
body(new CodeMirror.Doc(text));
});
}
function run(doc, query, insensitive) {
var cursor = doc.getSearchCursor(query, null, insensitive);
for (var i = 3; i < arguments.length; i += 4) {
var found = cursor.findNext();
is(found, "not enough results (forward)");
eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, forward, " + (i - 3) / 4);
eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, forward, " + (i - 3) / 4);
}
is(!cursor.findNext(), "too many matches (forward)");
for (var i = arguments.length - 4; i >= 3; i -= 4) {
var found = cursor.findPrevious();
is(found, "not enough results (backwards)");
eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, backwards, " + (i - 3) / 4);
eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, backwards, " + (i - 3) / 4);
}
is(!cursor.findPrevious(), "too many matches (backwards)");
}
test("simple", "abcdefg", "abcdefg", function(doc) {
run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5);
});
test("multiline", "hallo", "goodbye", function(doc) {
run(doc, "llo\ngoo", false, 0, 2, 1, 3);
run(doc, "blah\nhall", false);
run(doc, "bye\neye", false);
});
test("regexp", "abcde", "abcde", function(doc) {
run(doc, /bcd/, false, 0, 1, 0, 4, 1, 1, 1, 4);
run(doc, /BCD/, false);
run(doc, /BCD/i, false, 0, 1, 0, 4, 1, 1, 1, 4);
});
test("insensitive", "hallo", "HALLO", "oink", "hAllO", function(doc) {
run(doc, "All", false, 3, 1, 3, 4);
run(doc, "All", true, 0, 1, 0, 4, 1, 1, 1, 4, 3, 1, 3, 4);
});
test("multilineInsensitive", "zie ginds komT", "De Stoomboot", "uit Spanje weer aan", function(doc) {
run(doc, "komt\nde stoomboot\nuit", false);
run(doc, "komt\nde stoomboot\nuit", true, 0, 10, 2, 3);
run(doc, "kOMt\ndE stOOmboot\nuiT", true, 0, 10, 2, 3);
});
test("expandingCaseFold", "<b>İİ İİ</b>", "<b>uu uu</b>", function(doc) {
if (phantom) return; // A Phantom bug makes this hang
run(doc, "</b>", true, 0, 8, 0, 12, 1, 8, 1, 12);
run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8);
});
})();

View File

@ -0,0 +1,297 @@
(function() {
"use strict";
var Pos = CodeMirror.Pos;
namespace = "sublime_";
function stTest(name) {
var actions = Array.prototype.slice.call(arguments, 1);
testCM(name, function(cm) {
for (var i = 0; i < actions.length; i++) {
var action = actions[i];
if (typeof action == "string" && i == 0)
cm.setValue(action);
else if (typeof action == "string")
cm.execCommand(action);
else if (action instanceof Pos)
cm.setCursor(action);
else
action(cm);
}
});
}
function at(line, ch, msg) {
return function(cm) {
eq(cm.listSelections().length, 1);
eqPos(cm.getCursor("head"), Pos(line, ch), msg);
eqPos(cm.getCursor("anchor"), Pos(line, ch), msg);
};
}
function val(content, msg) {
return function(cm) { eq(cm.getValue(), content, msg); };
}
function argsToRanges(args) {
if (args.length % 4) throw new Error("Wrong number of arguments for ranges.");
var ranges = [];
for (var i = 0; i < args.length; i += 4)
ranges.push({anchor: Pos(args[i], args[i + 1]),
head: Pos(args[i + 2], args[i + 3])});
return ranges;
}
function setSel() {
var ranges = argsToRanges(arguments);
return function(cm) { cm.setSelections(ranges, 0); };
}
function hasSel() {
var ranges = argsToRanges(arguments);
return function(cm) {
var sels = cm.listSelections();
if (sels.length != ranges.length)
throw new Failure("Expected " + ranges.length + " selections, but found " + sels.length);
for (var i = 0; i < sels.length; i++) {
eqPos(sels[i].anchor, ranges[i].anchor, "anchor " + i);
eqPos(sels[i].head, ranges[i].head, "head " + i);
}
};
}
stTest("bySubword", "the foo_bar DooDahBah \n a",
"goSubwordLeft", at(0, 0),
"goSubwordRight", at(0, 3),
"goSubwordRight", at(0, 7),
"goSubwordRight", at(0, 11),
"goSubwordRight", at(0, 15),
"goSubwordRight", at(0, 18),
"goSubwordRight", at(0, 21),
"goSubwordRight", at(0, 22),
"goSubwordRight", at(1, 0),
"goSubwordRight", at(1, 2),
"goSubwordRight", at(1, 2),
"goSubwordLeft", at(1, 1),
"goSubwordLeft", at(1, 0),
"goSubwordLeft", at(0, 22),
"goSubwordLeft", at(0, 18),
"goSubwordLeft", at(0, 15),
"goSubwordLeft", at(0, 12),
"goSubwordLeft", at(0, 8),
"goSubwordLeft", at(0, 4),
"goSubwordLeft", at(0, 0));
stTest("splitSelectionByLine", "abc\ndef\nghi",
setSel(0, 1, 2, 2),
"splitSelectionByLine",
hasSel(0, 1, 0, 3,
1, 0, 1, 3,
2, 0, 2, 2));
stTest("splitSelectionByLineMulti", "abc\ndef\nghi\njkl",
setSel(0, 1, 1, 1,
1, 2, 3, 2,
3, 3, 3, 3),
"splitSelectionByLine",
hasSel(0, 1, 0, 3,
1, 0, 1, 1,
1, 2, 1, 3,
2, 0, 2, 3,
3, 0, 3, 2,
3, 3, 3, 3));
stTest("selectLine", "abc\ndef\nghi",
setSel(0, 1, 0, 1,
2, 0, 2, 1),
"selectLine",
hasSel(0, 0, 1, 0,
2, 0, 2, 3),
setSel(0, 1, 1, 0),
"selectLine",
hasSel(0, 0, 2, 0));
stTest("insertLineAfter", "abcde\nfghijkl\nmn",
setSel(0, 1, 0, 1,
0, 3, 0, 3,
1, 2, 1, 2,
1, 3, 1, 5), "insertLineAfter",
hasSel(1, 0, 1, 0,
3, 0, 3, 0), val("abcde\n\nfghijkl\n\nmn"));
stTest("insertLineBefore", "abcde\nfghijkl\nmn",
setSel(0, 1, 0, 1,
0, 3, 0, 3,
1, 2, 1, 2,
1, 3, 1, 5), "insertLineBefore",
hasSel(0, 0, 0, 0,
2, 0, 2, 0), val("\nabcde\n\nfghijkl\nmn"));
stTest("selectNextOccurrence", "a foo bar\nfoobar foo",
setSel(0, 2, 0, 5),
"selectNextOccurrence", hasSel(0, 2, 0, 5,
1, 0, 1, 3),
"selectNextOccurrence", hasSel(0, 2, 0, 5,
1, 0, 1, 3,
1, 7, 1, 10),
"selectNextOccurrence", hasSel(0, 2, 0, 5,
1, 0, 1, 3,
1, 7, 1, 10),
Pos(0, 3), "selectNextOccurrence", hasSel(0, 2, 0, 5),
"selectNextOccurrence", hasSel(0, 2, 0, 5,
1, 7, 1, 10),
setSel(0, 6, 0, 9),
"selectNextOccurrence", hasSel(0, 6, 0, 9,
1, 3, 1, 6));
stTest("selectScope", "foo(a) {\n bar[1, 2];\n}",
"selectScope", hasSel(0, 0, 2, 1),
Pos(0, 4), "selectScope", hasSel(0, 4, 0, 5),
Pos(0, 5), "selectScope", hasSel(0, 4, 0, 5),
Pos(0, 6), "selectScope", hasSel(0, 0, 2, 1),
Pos(0, 8), "selectScope", hasSel(0, 8, 2, 0),
Pos(1, 2), "selectScope", hasSel(0, 8, 2, 0),
Pos(1, 6), "selectScope", hasSel(1, 6, 1, 10),
Pos(1, 9), "selectScope", hasSel(1, 6, 1, 10));
stTest("goToBracket", "foo(a) {\n bar[1, 2];\n}",
Pos(0, 0), "goToBracket", at(0, 0),
Pos(0, 4), "goToBracket", at(0, 5), "goToBracket", at(0, 4),
Pos(0, 8), "goToBracket", at(2, 0), "goToBracket", at(0, 8),
Pos(1, 2), "goToBracket", at(2, 0),
Pos(1, 7), "goToBracket", at(1, 10), "goToBracket", at(1, 6));
stTest("swapLine", "1\n2\n3---\n4\n5",
"swapLineDown", val("2\n1\n3---\n4\n5"),
"swapLineUp", val("1\n2\n3---\n4\n5"),
"swapLineUp", val("1\n2\n3---\n4\n5"),
Pos(4, 1), "swapLineDown", val("1\n2\n3---\n4\n5"),
setSel(0, 1, 0, 1,
1, 0, 2, 0,
2, 2, 2, 2),
"swapLineDown", val("4\n1\n2\n3---\n5"),
hasSel(1, 1, 1, 1,
2, 0, 3, 0,
3, 2, 3, 2),
"swapLineUp", val("1\n2\n3---\n4\n5"),
hasSel(0, 1, 0, 1,
1, 0, 2, 0,
2, 2, 2, 2));
stTest("swapLineUpFromEnd", "a\nb\nc",
Pos(2, 1), "swapLineUp",
hasSel(1, 1, 1, 1), val("a\nc\nb"));
stTest("joinLines", "abc\ndef\nghi\njkl",
"joinLines", val("abc def\nghi\njkl"), at(0, 4),
"undo",
setSel(0, 2, 1, 1), "joinLines",
val("abc def ghi\njkl"), hasSel(0, 2, 0, 8),
"undo",
setSel(0, 1, 0, 1,
1, 1, 1, 1,
3, 1, 3, 1), "joinLines",
val("abc def ghi\njkl"), hasSel(0, 4, 0, 4,
0, 8, 0, 8,
1, 3, 1, 3));
stTest("duplicateLine", "abc\ndef\nghi",
Pos(1, 0), "duplicateLine", val("abc\ndef\ndef\nghi"), at(2, 0),
"undo",
setSel(0, 1, 0, 1,
1, 1, 1, 1,
2, 1, 2, 1), "duplicateLine",
val("abc\nabc\ndef\ndef\nghi\nghi"), hasSel(1, 1, 1, 1,
3, 1, 3, 1,
5, 1, 5, 1));
stTest("duplicateLineSelection", "abcdef",
setSel(0, 1, 0, 1,
0, 2, 0, 4,
0, 5, 0, 5),
"duplicateLine",
val("abcdef\nabcdcdef\nabcdcdef"), hasSel(2, 1, 2, 1,
2, 4, 2, 6,
2, 7, 2, 7));
stTest("selectLinesUpward", "123\n345\n789\n012",
setSel(0, 1, 0, 1,
1, 1, 1, 3,
2, 0, 2, 0,
3, 0, 3, 0),
"selectLinesUpward",
hasSel(0, 1, 0, 1,
0, 3, 0, 3,
1, 0, 1, 0,
1, 1, 1, 3,
2, 0, 2, 0,
3, 0, 3, 0));
stTest("selectLinesDownward", "123\n345\n789\n012",
setSel(0, 1, 0, 1,
1, 1, 1, 3,
2, 0, 2, 0,
3, 0, 3, 0),
"selectLinesDownward",
hasSel(0, 1, 0, 1,
1, 1, 1, 3,
2, 0, 2, 0,
2, 3, 2, 3,
3, 0, 3, 0));
stTest("sortLines", "c\nb\na\nC\nB\nA",
"sortLines", val("A\nB\nC\na\nb\nc"),
"undo",
setSel(0, 0, 2, 0,
3, 0, 5, 0),
"sortLines", val("a\nb\nc\nA\nB\nC"),
hasSel(0, 0, 2, 1,
3, 0, 5, 1),
"undo",
setSel(1, 0, 4, 0), "sortLinesInsensitive", val("c\na\nB\nb\nC\nA"));
stTest("bookmarks", "abc\ndef\nghi\njkl",
Pos(0, 1), "toggleBookmark",
setSel(1, 1, 1, 2), "toggleBookmark",
setSel(2, 1, 2, 2), "toggleBookmark",
"nextBookmark", hasSel(0, 1, 0, 1),
"nextBookmark", hasSel(1, 1, 1, 2),
"nextBookmark", hasSel(2, 1, 2, 2),
"prevBookmark", hasSel(1, 1, 1, 2),
"prevBookmark", hasSel(0, 1, 0, 1),
"prevBookmark", hasSel(2, 1, 2, 2),
"prevBookmark", hasSel(1, 1, 1, 2),
"toggleBookmark",
"prevBookmark", hasSel(2, 1, 2, 2),
"prevBookmark", hasSel(0, 1, 0, 1),
"selectBookmarks", hasSel(0, 1, 0, 1,
2, 1, 2, 2),
"clearBookmarks",
Pos(0, 0), "selectBookmarks", at(0, 0));
stTest("upAndDowncaseAtCursor", "abc\ndef x\nghI",
setSel(0, 1, 0, 3,
1, 1, 1, 1,
1, 4, 1, 4), "upcaseAtCursor",
val("aBC\nDEF x\nghI"), hasSel(0, 1, 0, 3,
1, 3, 1, 3,
1, 4, 1, 4),
"downcaseAtCursor",
val("abc\ndef x\nghI"), hasSel(0, 1, 0, 3,
1, 3, 1, 3,
1, 4, 1, 4));
stTest("mark", "abc\ndef\nghi",
Pos(1, 1), "setSublimeMark",
Pos(2, 1), "selectToSublimeMark", hasSel(2, 1, 1, 1),
Pos(0, 1), "swapWithSublimeMark", at(1, 1), "swapWithSublimeMark", at(0, 1),
"deleteToSublimeMark", val("aef\nghi"),
"sublimeYank", val("abc\ndef\nghi"), at(1, 1));
stTest("findUnder", "foo foobar a",
"findUnder", hasSel(0, 4, 0, 7),
"findUnder", hasSel(0, 0, 0, 3),
"findUnderPrevious", hasSel(0, 4, 0, 7),
"findUnderPrevious", hasSel(0, 0, 0, 3),
Pos(0, 4), "findUnder", hasSel(0, 4, 0, 10),
Pos(0, 11), "findUnder", hasSel(0, 11, 0, 11));
})();

View File

@ -27,6 +27,7 @@ function byClassName(elt, cls) {
}
var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
var mac = /Mac/.test(navigator.platform);
var phantom = /PhantomJS/.test(navigator.userAgent);
var opera = /Opera\/\./.test(navigator.userAgent);
@ -87,7 +88,7 @@ testCM("selection", function(cm) {
is(!cm.somethingSelected());
eq(cm.getSelection(), "");
eqPos(cm.getCursor(true), Pos(1, 0));
cm.replaceSelection("abc");
cm.replaceSelection("abc", "around");
eq(cm.getSelection(), "abc");
eq(cm.getValue(), "111111\nabc222222\n333333");
cm.replaceSelection("def", "end");
@ -132,16 +133,16 @@ testCM("extendSelection", function(cm) {
eqPos(cm.getCursor("anchor"), Pos(4, 5));
cm.setExtending(false);
cm.extendSelection(Pos(0, 3), Pos(0, 4));
eqPos(cm.getCursor("head"), Pos(0, 4));
eqPos(cm.getCursor("anchor"), Pos(0, 3));
eqPos(cm.getCursor("head"), Pos(0, 3));
eqPos(cm.getCursor("anchor"), Pos(0, 4));
});
testCM("lines", function(cm) {
eq(cm.getLine(0), "111111");
eq(cm.getLine(1), "222222");
eq(cm.getLine(-1), null);
cm.removeLine(1);
cm.setLine(1, "abc");
cm.replaceRange("", Pos(1, 0), Pos(2, 0))
cm.replaceRange("abc", Pos(1, 0), Pos(1));
eq(cm.getValue(), "111111\nabc");
}, {value: "111111\n222222\n333333"});
@ -172,7 +173,7 @@ test("core_defaults", function() {
for (var opt in defs) defsCopy[opt] = defs[opt];
defs.indentUnit = 5;
defs.value = "uu";
defs.enterMode = "keep";
defs.indentWithTabs = true;
defs.tabindex = 55;
var place = document.getElementById("testground"), cm = CodeMirror(place);
try {
@ -180,7 +181,7 @@ test("core_defaults", function() {
cm.setOption("indentUnit", 10);
eq(defs.indentUnit, 5);
eq(cm.getValue(), "uu");
eq(cm.getOption("enterMode"), "keep");
eq(cm.getOption("indentWithTabs"), true);
eq(cm.getInputField().tabIndex, 55);
}
finally {
@ -264,7 +265,7 @@ testCM("posFromIndex", function(cm) {
});
testCM("undo", function(cm) {
cm.setLine(0, "def");
cm.replaceRange("def", Pos(0, 0), Pos(0));
eq(cm.historySize().undo, 1);
cm.undo();
eq(cm.getValue(), "abc");
@ -294,7 +295,7 @@ testCM("undoDepth", function(cm) {
cm.replaceRange("f", Pos(0));
cm.undo(); cm.undo(); cm.undo();
eq(cm.getValue(), "abcd");
}, {value: "abc", undoDepth: 2});
}, {value: "abc", undoDepth: 4});
testCM("undoDoesntClearValue", function(cm) {
cm.undo();
@ -351,6 +352,13 @@ testCM("undoSelection", function(cm) {
eqPos(cm.getCursor(false), Pos(0, 2));
}, {value: "abcdefgh\n"});
testCM("undoSelectionAsBefore", function(cm) {
cm.replaceSelection("abc", "around");
cm.undo();
cm.redo();
eq(cm.getSelection(), "abc");
});
testCM("markTextSingleLine", function(cm) {
forEach([{a: 0, b: 1, c: "", f: 2, t: 5},
{a: 0, b: 4, c: "", f: 0, t: 2},
@ -541,10 +549,14 @@ testCM("bookmarkCursor", function(cm) {
cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")});
var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)),
new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0));
is(new01.left == pos01.left && new01.top == pos01.top, "at left, middle of line");
is(new11.left > pos11.left && new11.top == pos11.top, "at right, middle of line");
is(new20.left == pos20.left && new20.top == pos20.top, "at left, empty line");
is(new30.left > pos30.left && new30.top == pos30.top, "at right, empty line");
near(new01.left, pos01.left, 1);
near(new01.top, pos01.top, 1);
is(new11.left > pos11.left, "at right, middle of line");
near(new11.top == pos11.top, 1);
near(new20.left, pos20.left, 1);
near(new20.top, pos20.top, 1);
is(new30.left > pos30.left, "at right, empty line");
near(new30.top, pos30, 1);
cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")});
is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug");
}, {value: "foo\nbar\n\n\nx\ny"});
@ -561,10 +573,10 @@ testCM("multiBookmarkCursor", function(cm) {
}
var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left;
add(true);
is(Math.abs(base1 - cm.cursorCoords(Pos(0, 1)).left) < .1);
near(base1, cm.cursorCoords(Pos(0, 1)).left, 1);
while (m = ms.pop()) m.clear();
add(false);
is(Math.abs(base4 - cm.cursorCoords(Pos(0, 1)).left) < .1);
near(base4, cm.cursorCoords(Pos(0, 1)).left, 1);
}, {value: "abcdefg"});
testCM("getAllMarks", function(cm) {
@ -604,20 +616,40 @@ testCM("scrollSnap", function(cm) {
testCM("scrollIntoView", function(cm) {
if (phantom) return;
var outer = cm.getWrapperElement().getBoundingClientRect();
function test(line, ch) {
function test(line, ch, msg) {
var pos = Pos(line, ch);
cm.scrollIntoView(pos);
var box = cm.charCoords(pos, "window");
is(box.left >= outer.left && box.right <= outer.right &&
box.top >= outer.top && box.bottom <= outer.bottom);
is(box.left >= outer.left, msg + " (left)");
is(box.right <= outer.right, msg + " (right)");
is(box.top >= outer.top, msg + " (top)");
is(box.bottom <= outer.bottom, msg + " (bottom)");
}
addDoc(cm, 200, 200);
test(199, 199);
test(0, 0);
test(100, 100);
test(199, 0);
test(0, 199);
test(100, 100);
test(199, 199, "bottom right");
test(0, 0, "top left");
test(100, 100, "center");
test(199, 0, "bottom left");
test(0, 199, "top right");
test(100, 100, "center again");
});
testCM("scrollBackAndForth", function(cm) {
addDoc(cm, 1, 200);
cm.operation(function() {
cm.scrollIntoView(Pos(199, 0));
cm.scrollIntoView(Pos(4, 0));
});
is(cm.getScrollInfo().top > 0);
});
testCM("selectAllNoScroll", function(cm) {
addDoc(cm, 1, 200);
cm.execCommand("selectAll");
eq(cm.getScrollInfo().top, 0);
cm.setCursor(199);
cm.execCommand("selectAll");
is(cm.getScrollInfo().top > 0);
});
testCM("selectionPos", function(cm) {
@ -657,8 +689,8 @@ testCM("selectionPos", function(cm) {
testCM("restoreHistory", function(cm) {
cm.setValue("abc\ndef");
cm.setLine(1, "hello");
cm.setLine(0, "goop");
cm.replaceRange("hello", Pos(1, 0), Pos(1));
cm.replaceRange("goop", Pos(0, 0), Pos(0));
cm.undo();
var storedVal = cm.getValue(), storedHist = cm.getHistory();
if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist));
@ -723,11 +755,11 @@ testCM("collapsedLines", function(cm) {
cm.setCursor(Pos(3, 0));
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), Pos(5, 0));
cm.setLine(3, "abcdefg");
cm.replaceRange("abcdefg", Pos(3, 0), Pos(3));
cm.setCursor(Pos(3, 6));
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), Pos(5, 4));
cm.setLine(3, "ab");
cm.replaceRange("ab", Pos(3, 0), Pos(3));
cm.setCursor(Pos(3, 2));
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), Pos(5, 2));
@ -750,6 +782,31 @@ testCM("collapsedRangeCoordsChar", function(cm) {
eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
}, {value: "123456\nabcdef\nghijkl\nmnopqr\n"});
testCM("collapsedRangeBetweenLinesSelected", function(cm) {
var widget = document.createElement("span");
widget.textContent = "\u2194";
cm.markText(Pos(0, 3), Pos(1, 0), {replacedWith: widget});
cm.setSelection(Pos(0, 3), Pos(1, 0));
var selElts = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
for (var i = 0, w = 0; i < selElts.length; i++)
w += selElts[i].offsetWidth;
is(w > 0);
}, {value: "one\ntwo"});
testCM("randomCollapsedRanges", function(cm) {
addDoc(cm, 20, 500);
cm.operation(function() {
for (var i = 0; i < 200; i++) {
var start = Pos(Math.floor(Math.random() * 500), Math.floor(Math.random() * 20));
if (i % 4)
try { cm.markText(start, Pos(start.line + 2, 1), {collapsed: true}); }
catch(e) { if (!/overlapping/.test(String(e))) throw e; }
else
cm.markText(start, Pos(start.line, start.ch + 4), {"className": "foo"});
}
});
});
testCM("hiddenLinesAutoUnfold", function(cm) {
var range = foldLines(cm, 1, 3, true), cleared = 0;
CodeMirror.on(range, "clear", function() {cleared++;});
@ -888,6 +945,15 @@ testCM("nestedFoldOnSide", function(cm) {
is(caught && /overlap/i.test(caught.message));
}, {value: "ab\ncd\ef"});
testCM("editInFold", function(cm) {
addDoc(cm, 4, 6);
var m = cm.markText(Pos(1, 2), Pos(3, 2), {collapsed: true});
cm.replaceRange("", Pos(0, 0), Pos(1, 3));
cm.replaceRange("", Pos(2, 1), Pos(3, 3));
cm.replaceRange("a\nb\nc\nd", Pos(0, 1), Pos(1, 0));
cm.cursorCoords(Pos(0, 0));
});
testCM("wrappingInlineWidget", function(cm) {
cm.setSize("11em");
var w = document.createElement("span");
@ -904,6 +970,7 @@ testCM("wrappingInlineWidget", function(cm) {
eq(curR.bottom, cur1.bottom);
cm.replaceRange("", Pos(0, 9), Pos(0));
curR = cm.cursorCoords(Pos(0, 9));
if (phantom) return;
eq(curR.top, cur1.top);
eq(curR.bottom, cur1.bottom);
}, {value: "1 2 3 xxx 4", lineWrapping: true});
@ -1048,15 +1115,15 @@ testCM("verticalScroll", function(cm) {
cm.setSize(100, 200);
cm.setValue("foo\nbar\nbaz\n");
var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth;
cm.setLine(0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah");
cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
is(sc.scrollWidth > baseWidth, "scrollbar present");
cm.setLine(0, "foo");
cm.replaceRange("foo", Pos(0, 0), Pos(0));
if (!phantom) eq(sc.scrollWidth, baseWidth, "scrollbar gone");
cm.setLine(0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah");
cm.setLine(1, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh");
cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
cm.replaceRange("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh", Pos(1, 0), Pos(1));
is(sc.scrollWidth > baseWidth, "present again");
var curWidth = sc.scrollWidth;
cm.setLine(0, "foo");
cm.replaceRange("foo", Pos(0, 0), Pos(0));
is(sc.scrollWidth < curWidth, "scrollbar smaller");
is(sc.scrollWidth > baseWidth, "but still present");
});
@ -1133,13 +1200,32 @@ testCM("groupMovementCommands", function(cm) {
cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
eqPos(cm.getCursor(), Pos(0, 20));
cm.execCommand("goGroupRight");
eqPos(cm.getCursor(), Pos(1, 0));
cm.execCommand("goGroupRight");
eqPos(cm.getCursor(), Pos(1, 2));
cm.execCommand("goGroupRight");
eqPos(cm.getCursor(), Pos(1, 5));
cm.execCommand("goGroupLeft"); cm.execCommand("goGroupLeft");
eqPos(cm.getCursor(), Pos(1, 0));
cm.execCommand("goGroupLeft");
eqPos(cm.getCursor(), Pos(0, 20));
cm.execCommand("goGroupLeft");
eqPos(cm.getCursor(), Pos(0, 16));
}, {value: "booo ba---quux. ffff\n abc d"});
testCM("groupsAndWhitespace", function(cm) {
var positions = [Pos(0, 0), Pos(0, 2), Pos(0, 5), Pos(0, 9), Pos(0, 11),
Pos(1, 0), Pos(1, 2), Pos(1, 5)];
for (var i = 1; i < positions.length; i++) {
cm.execCommand("goGroupRight");
eqPos(cm.getCursor(), positions[i]);
}
for (var i = positions.length - 2; i >= 0; i--) {
cm.execCommand("goGroupLeft");
eqPos(cm.getCursor(), i == 2 ? Pos(0, 6) : positions[i]);
}
}, {value: " foo +++ \n bar"});
testCM("charMovementCommands", function(cm) {
cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft");
eqPos(cm.getCursor(), Pos(0, 0));
@ -1210,23 +1296,26 @@ testCM("rtlMovement", function(cm) {
"<img src=\"/בדיקה3.jpg\">"], function(line) {
var inv = line.charAt(0) == "خ";
cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart");
var cursor = byClassName(cm.getWrapperElement(), "CodeMirror-cursor")[0];
var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0];
var cursor = cursors.firstChild;
var prevX = cursor.offsetLeft, prevY = cursor.offsetTop;
for (var i = 0; i <= line.length; ++i) {
cm.execCommand("goCharRight");
cursor = cursors.firstChild;
if (i == line.length) is(cursor.offsetTop > prevY, "next line");
else is(cursor.offsetLeft > prevX, "moved right");
prevX = cursor.offsetLeft; prevY = cursor.offsetTop;
}
cm.setCursor(0, 0); cm.execCommand(inv ? "goLineStart" : "goLineEnd");
prevX = cursor.offsetLeft;
prevX = cursors.firstChild.offsetLeft;
for (var i = 0; i < line.length; ++i) {
cm.execCommand("goCharLeft");
cursor = cursors.firstChild;
is(cursor.offsetLeft < prevX, "moved left");
prevX = cursor.offsetLeft;
}
});
});
}, null, ie_lt9);
// Verify that updating a line clears its bidi ordering
testCM("bidiUpdate", function(cm) {
@ -1237,7 +1326,7 @@ testCM("bidiUpdate", function(cm) {
}, {value: "abcd\n"});
testCM("movebyTextUnit", function(cm) {
cm.setValue("בְּרֵאשִ\ńéée\n");
cm.setValue("בְּרֵאשִ\nééé́\n");
cm.execCommand("goLineEnd");
for (var i = 0; i < 4; ++i) cm.execCommand("goCharRight");
eqPos(cm.getCursor(), Pos(0, 0));
@ -1245,10 +1334,9 @@ testCM("movebyTextUnit", function(cm) {
eqPos(cm.getCursor(), Pos(1, 0));
cm.execCommand("goCharRight");
cm.execCommand("goCharRight");
eqPos(cm.getCursor(), Pos(1, 3));
eqPos(cm.getCursor(), Pos(1, 4));
cm.execCommand("goCharRight");
cm.execCommand("goCharRight");
eqPos(cm.getCursor(), Pos(1, 6));
eqPos(cm.getCursor(), Pos(1, 7));
});
testCM("lineChangeEvents", function(cm) {
@ -1309,6 +1397,39 @@ testCM("lineWidgetFocus", function(cm) {
}
});
testCM("lineWidgetCautiousRedraw", function(cm) {
var node = document.createElement("div");
node.innerHTML = "hahah";
var w = cm.addLineWidget(0, node);
var redrawn = false;
w.on("redraw", function() { redrawn = true; });
cm.replaceSelection("0");
is(!redrawn);
}, {value: "123\n456"});
testCM("lineWidgetChanged", function(cm) {
addDoc(cm, 2, 300);
cm.setSize(null, cm.defaultTextHeight() * 50);
cm.scrollTo(null, cm.heightAtLine(125, "local"));
function w() {
var node = document.createElement("div");
node.style.cssText = "background: yellow; height: 50px;";
return node;
}
var info0 = cm.getScrollInfo();
var w0 = cm.addLineWidget(0, w());
var w150 = cm.addLineWidget(150, w());
var w300 = cm.addLineWidget(300, w());
var info1 = cm.getScrollInfo();
eq(info0.height + 150, info1.height);
eq(info0.top + 50, info1.top);
w0.node.style.height = w150.node.style.height = w300.node.style.height = "10px";
w0.changed(); w150.changed(); w300.changed();
var info2 = cm.getScrollInfo();
eq(info0.height + 30, info2.height);
eq(info0.top + 10, info2.top);
});
testCM("getLineNumber", function(cm) {
addDoc(cm, 2, 20);
var h1 = cm.getLineHandle(1);
@ -1320,9 +1441,10 @@ testCM("getLineNumber", function(cm) {
});
testCM("jumpTheGap", function(cm) {
if (phantom) return;
var longLine = "abcdef ghiklmnop qrstuvw xyz ";
longLine += longLine; longLine += longLine; longLine += longLine;
cm.setLine(2, longLine);
cm.replaceRange(longLine, Pos(2, 0), Pos(2));
cm.setSize("200px", null);
cm.getWrapperElement().style.lineHeight = 2;
cm.refresh();
@ -1437,14 +1559,14 @@ testCM("readOnlyMarker", function(cm) {
eqPos(cm.getCursor(), Pos(0, 2));
eq(cm.getLine(0), "abcde");
cm.execCommand("selectAll");
cm.replaceSelection("oops");
cm.replaceSelection("oops", "around");
eq(cm.getValue(), "oopsbcd");
cm.undo();
eqPos(m.find().from, Pos(0, 1));
eqPos(m.find().to, Pos(0, 4));
m.clear();
cm.setCursor(Pos(0, 2));
cm.replaceSelection("hi");
cm.replaceSelection("hi", "around");
eq(cm.getLine(0), "abhicde");
eqPos(cm.getCursor(), Pos(0, 4));
m = mark(0, 2, 2, 2, true);
@ -1456,19 +1578,19 @@ testCM("readOnlyMarker", function(cm) {
cm.execCommand("goCharLeft");
eqPos(cm.getCursor(), Pos(0, 2));
cm.setSelection(Pos(0, 1), Pos(0, 3));
cm.replaceSelection("xx");
cm.replaceSelection("xx", "around");
eqPos(cm.getCursor(), Pos(0, 3));
eq(cm.getLine(0), "axxhicde");
}, {value: "abcde\nfghij\nklmno\n"});
testCM("dirtyBit", function(cm) {
eq(cm.isClean(), true);
cm.replaceSelection("boo");
cm.replaceSelection("boo", null, "test");
eq(cm.isClean(), false);
cm.undo();
eq(cm.isClean(), true);
cm.replaceSelection("boo");
cm.replaceSelection("baz");
cm.replaceSelection("boo", null, "test");
cm.replaceSelection("baz", null, "test");
cm.undo();
eq(cm.isClean(), false);
cm.markClean();
@ -1480,15 +1602,15 @@ testCM("dirtyBit", function(cm) {
});
testCM("changeGeneration", function(cm) {
cm.replaceSelection("x", null, "+insert");
cm.replaceSelection("x");
var softGen = cm.changeGeneration();
cm.replaceSelection("x", null, "+insert");
cm.replaceSelection("x");
cm.undo();
eq(cm.getValue(), "");
is(!cm.isClean(softGen));
cm.replaceSelection("x", null, "+insert");
cm.replaceSelection("x");
var hardGen = cm.changeGeneration(true);
cm.replaceSelection("x", null, "+insert");
cm.replaceSelection("x");
cm.undo();
eq(cm.getValue(), "x");
is(cm.isClean(hardGen));
@ -1566,8 +1688,8 @@ testCM("beforeChange", function(cm) {
}, {value: "abcdefghijk"});
testCM("beforeChangeUndo", function(cm) {
cm.setLine(0, "hi");
cm.setLine(0, "bye");
cm.replaceRange("hi", Pos(0, 0), Pos(0));
cm.replaceRange("bye", Pos(0, 0), Pos(0));
eq(cm.historySize().undo, 2);
cm.on("beforeChange", function(cm, change) {
is(!change.update);
@ -1584,9 +1706,9 @@ testCM("beforeSelectionChange", function(cm) {
if (!len || pos.ch == len) return Pos(pos.line, pos.ch - 1);
return pos;
}
cm.on("beforeSelectionChange", function(cm, sel) {
sel.head = notAtEnd(cm, sel.head);
sel.anchor = notAtEnd(cm, sel.anchor);
cm.on("beforeSelectionChange", function(cm, obj) {
obj.update([{anchor: notAtEnd(cm, obj.ranges[0].anchor),
head: notAtEnd(cm, obj.ranges[0].head)}]);
});
addDoc(cm, 10, 10);
@ -1600,9 +1722,9 @@ testCM("beforeSelectionChange", function(cm) {
testCM("change_removedText", function(cm) {
cm.setValue("abc\ndef");
var removedText;
var removedText = [];
cm.on("change", function(cm, change) {
removedText = [change.removed, change.next && change.next.removed];
removedText.push(change.removed);
});
cm.operation(function() {
@ -1610,14 +1732,19 @@ testCM("change_removedText", function(cm) {
cm.replaceRange("123", Pos(0,0));
});
eq(removedText.length, 2);
eq(removedText[0].join("\n"), "abc\nd");
eq(removedText[1].join("\n"), "");
var removedText = [];
cm.undo();
eq(removedText.length, 2);
eq(removedText[0].join("\n"), "123");
eq(removedText[1].join("\n"), "xyz");
var removedText = [];
cm.redo();
eq(removedText.length, 2);
eq(removedText[0].join("\n"), "abc\nd");
eq(removedText[1].join("\n"), "");
});
@ -1625,21 +1752,29 @@ testCM("change_removedText", function(cm) {
testCM("lineStyleFromMode", function(cm) {
CodeMirror.defineMode("test_mode", function() {
return {token: function(stream) {
if (stream.match(/^\[[^\]]*\]/)) return "line-brackets";
if (stream.match(/^\([^\]]*\)/)) return "line-background-parens";
if (stream.match(/^\[[^\]]*\]/)) return " line-brackets ";
if (stream.match(/^\([^\)]*\)/)) return " line-background-parens ";
if (stream.match(/^<[^>]*>/)) return " span line-line line-background-bg ";
stream.match(/^\s+|^\S+/);
}};
});
cm.setOption("mode", "test_mode");
var bracketElts = byClassName(cm.getWrapperElement(), "brackets");
eq(bracketElts.length, 1);
eq(bracketElts.length, 1, "brackets count");
eq(bracketElts[0].nodeName, "PRE");
is(!/brackets.*brackets/.test(bracketElts[0].className));
var parenElts = byClassName(cm.getWrapperElement(), "parens");
eq(parenElts.length, 1);
eq(parenElts.length, 1, "parens count");
eq(parenElts[0].nodeName, "DIV");
is(!/parens.*parens/.test(parenElts[0].className));
}, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: nothing"});
eq(parenElts[0].parentElement.nodeName, "DIV");
eq(byClassName(cm.getWrapperElement(), "bg").length, 1);
eq(byClassName(cm.getWrapperElement(), "line").length, 1);
var spanElts = byClassName(cm.getWrapperElement(), "cm-span");
eq(spanElts.length, 2);
is(/^\s*cm-span\s*$/.test(spanElts[0].className));
}, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: <tag> <tag>"});
CodeMirror.registerHelper("xxx", "a", "A");
CodeMirror.registerHelper("xxx", "b", "B");
@ -1659,3 +1794,85 @@ testCM("helpers", function(cm) {
cm.setOption("mode", "javascript");
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "");
});
testCM("selectionHistory", function(cm) {
for (var i = 0; i < 3; i++) {
cm.setExtending(true);
cm.execCommand("goCharRight");
cm.setExtending(false);
cm.execCommand("goCharRight");
cm.execCommand("goCharRight");
}
cm.execCommand("undoSelection");
eq(cm.getSelection(), "c");
cm.execCommand("undoSelection");
eq(cm.getSelection(), "");
eqPos(cm.getCursor(), Pos(0, 4));
cm.execCommand("undoSelection");
eq(cm.getSelection(), "b");
cm.execCommand("redoSelection");
eq(cm.getSelection(), "");
eqPos(cm.getCursor(), Pos(0, 4));
cm.execCommand("redoSelection");
eq(cm.getSelection(), "c");
cm.execCommand("redoSelection");
eq(cm.getSelection(), "");
eqPos(cm.getCursor(), Pos(0, 6));
}, {value: "a b c d"});
testCM("selectionChangeReducesRedo", function(cm) {
cm.replaceSelection("X");
cm.execCommand("goCharRight");
cm.undoSelection();
cm.execCommand("selectAll");
cm.undoSelection();
eq(cm.getValue(), "Xabc");
eqPos(cm.getCursor(), Pos(0, 1));
cm.undoSelection();
eq(cm.getValue(), "abc");
}, {value: "abc"});
testCM("selectionHistoryNonOverlapping", function(cm) {
cm.setSelection(Pos(0, 0), Pos(0, 1));
cm.setSelection(Pos(0, 2), Pos(0, 3));
cm.execCommand("undoSelection");
eqPos(cm.getCursor("anchor"), Pos(0, 0));
eqPos(cm.getCursor("head"), Pos(0, 1));
}, {value: "1234"});
testCM("cursorMotionSplitsHistory", function(cm) {
cm.replaceSelection("a");
cm.execCommand("goCharRight");
cm.replaceSelection("b");
cm.replaceSelection("c");
cm.undo();
eq(cm.getValue(), "a1234");
eqPos(cm.getCursor(), Pos(0, 2));
cm.undo();
eq(cm.getValue(), "1234");
eqPos(cm.getCursor(), Pos(0, 0));
}, {value: "1234"});
testCM("selChangeInOperationDoesNotSplit", function(cm) {
for (var i = 0; i < 4; i++) {
cm.operation(function() {
cm.replaceSelection("x");
cm.setCursor(Pos(0, cm.getCursor().ch - 1));
});
}
eqPos(cm.getCursor(), Pos(0, 0));
eq(cm.getValue(), "xxxxa");
cm.undo();
eq(cm.getValue(), "a");
}, {value: "a"});
testCM("alwaysMergeSelEventWithChangeOrigin", function(cm) {
cm.replaceSelection("U", null, "foo");
cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "foo"});
cm.undoSelection();
eq(cm.getValue(), "a");
cm.replaceSelection("V", null, "foo");
cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "bar"});
cm.undoSelection();
eq(cm.getValue(), "Va");
}, {value: "a"});

View File

@ -176,6 +176,11 @@ function testVim(name, run, opts, expectedFail) {
return callback(result);
}
}
function fakeOpenNotification(matcher) {
return function(text) {
matcher(text);
}
}
var helpers = {
doKeys: doKeysFn(cm),
// Warning: Only emulates keymap events, not character insertions. Use
@ -185,16 +190,19 @@ function testVim(name, run, opts, expectedFail) {
doEx: doExFn(cm),
assertCursorAt: assertCursorAtFn(cm),
fakeOpenDialog: fakeOpenDialog,
fakeOpenNotification: fakeOpenNotification,
getRegisterController: function() {
return CodeMirror.Vim.getRegisterController();
}
}
CodeMirror.Vim.resetVimGlobalState_();
var successful = false;
var savedOpenNotification = cm.openNotification;
try {
run(cm, vim, helpers);
successful = true;
} finally {
cm.openNotification = savedOpenNotification;
if (!successful || verbose) {
place.style.visibility = "visible";
} else {
@ -267,6 +275,7 @@ testJumplist('jumplist_skip_delted_mark<c-o>',
testJumplist('jumplist_skip_delted_mark<c-i>',
['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-i>', '<C-i>'],
[1,0], [0,2]);
/**
* @param name Name of the test
* @param keys An array of keys or a string with a single key to simulate.
@ -501,7 +510,7 @@ testVim('dl', function(cm, vim, helpers) {
helpers.doKeys('d', 'l');
eq('word1 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq(' ', register.text);
eq(' ', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1 ' });
@ -510,7 +519,7 @@ testVim('dl_eol', function(cm, vim, helpers) {
helpers.doKeys('d', 'l');
eq(' word1', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq(' ', register.text);
eq(' ', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 5);
}, { value: ' word1 ' });
@ -520,7 +529,7 @@ testVim('dl_repeat', function(cm, vim, helpers) {
helpers.doKeys('2', 'd', 'l');
eq('ord1 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq(' w', register.text);
eq(' w', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1 ' });
@ -530,7 +539,7 @@ testVim('dh', function(cm, vim, helpers) {
helpers.doKeys('d', 'h');
eq(' wrd1 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('o', register.text);
eq('o', register.toString());
is(!register.linewise);
eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor());
}, { value: ' word1 ' });
@ -540,7 +549,7 @@ testVim('dj', function(cm, vim, helpers) {
helpers.doKeys('d', 'j');
eq(' word3', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq(' word1\nword2\n', register.text);
eq(' word1\nword2\n', register.toString());
is(register.linewise);
helpers.assertCursorAt(0, 1);
}, { value: ' word1\nword2\n word3' });
@ -550,7 +559,7 @@ testVim('dj_end_of_document', function(cm, vim, helpers) {
helpers.doKeys('d', 'j');
eq(' word1 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 3);
}, { value: ' word1 ' });
@ -560,7 +569,7 @@ testVim('dk', function(cm, vim, helpers) {
helpers.doKeys('d', 'k');
eq(' word3', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq(' word1\nword2\n', register.text);
eq(' word1\nword2\n', register.toString());
is(register.linewise);
helpers.assertCursorAt(0, 1);
}, { value: ' word1\nword2\n word3' });
@ -570,7 +579,7 @@ testVim('dk_start_of_document', function(cm, vim, helpers) {
helpers.doKeys('d', 'k');
eq(' word1 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 3);
}, { value: ' word1 ' });
@ -580,7 +589,7 @@ testVim('dw_space', function(cm, vim, helpers) {
helpers.doKeys('d', 'w');
eq('word1 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq(' ', register.text);
eq(' ', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1 ' });
@ -590,7 +599,7 @@ testVim('dw_word', function(cm, vim, helpers) {
helpers.doKeys('d', 'w');
eq(' word2', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1 ', register.text);
eq('word1 ', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1 word2' });
@ -601,7 +610,7 @@ testVim('dw_only_word', function(cm, vim, helpers) {
helpers.doKeys('d', 'w');
eq(' ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1 ', register.text);
eq('word1 ', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 0);
}, { value: ' word1 ' });
@ -612,7 +621,7 @@ testVim('dw_eol', function(cm, vim, helpers) {
helpers.doKeys('d', 'w');
eq(' \nword2', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1', register.text);
eq('word1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 0);
}, { value: ' word1\nword2' });
@ -623,7 +632,7 @@ testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) {
helpers.doKeys('d', 'w');
eq(' \n\nword2', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1', register.text);
eq('word1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 0);
}, { value: ' word1\n\nword2' });
@ -669,7 +678,7 @@ testVim('dw_repeat', function(cm, vim, helpers) {
helpers.doKeys('d', '2', 'w');
eq(' ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1\nword2', register.text);
eq('word1\nword2', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 0);
}, { value: ' word1\nword2' });
@ -750,7 +759,7 @@ testVim('d_inclusive', function(cm, vim, helpers) {
helpers.doKeys('d', 'e');
eq(' ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1', register.text);
eq('word1', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1 ' });
@ -760,7 +769,7 @@ testVim('d_reverse', function(cm, vim, helpers) {
helpers.doKeys('d', 'b');
eq(' word2 ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1\n', register.text);
eq('word1\n', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
}, { value: ' word1\nword2 ' });
@ -772,7 +781,7 @@ testVim('dd', function(cm, vim, helpers) {
helpers.doKeys('d', 'd');
eq(expectedLineCount, cm.lineCount());
var register = helpers.getRegisterController().getRegister();
eq(expectedBuffer, register.text);
eq(expectedBuffer, register.toString());
is(register.linewise);
helpers.assertCursorAt(0, lines[1].textStart);
});
@ -784,7 +793,7 @@ testVim('dd_prefix_repeat', function(cm, vim, helpers) {
helpers.doKeys('2', 'd', 'd');
eq(expectedLineCount, cm.lineCount());
var register = helpers.getRegisterController().getRegister();
eq(expectedBuffer, register.text);
eq(expectedBuffer, register.toString());
is(register.linewise);
helpers.assertCursorAt(0, lines[2].textStart);
});
@ -796,7 +805,7 @@ testVim('dd_motion_repeat', function(cm, vim, helpers) {
helpers.doKeys('d', '2', 'd');
eq(expectedLineCount, cm.lineCount());
var register = helpers.getRegisterController().getRegister();
eq(expectedBuffer, register.text);
eq(expectedBuffer, register.toString());
is(register.linewise);
helpers.assertCursorAt(0, lines[2].textStart);
});
@ -808,7 +817,7 @@ testVim('dd_multiply_repeat', function(cm, vim, helpers) {
helpers.doKeys('2', 'd', '3', 'd');
eq(expectedLineCount, cm.lineCount());
var register = helpers.getRegisterController().getRegister();
eq(expectedBuffer, register.text);
eq(expectedBuffer, register.toString());
is(register.linewise);
helpers.assertCursorAt(0, lines[6].textStart);
});
@ -829,7 +838,7 @@ testVim('yw_repeat', function(cm, vim, helpers) {
helpers.doKeys('y', '2', 'w');
eq(' word1\nword2', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1\nword2', register.text);
eq('word1\nword2', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1\nword2' });
@ -842,7 +851,7 @@ testVim('yy_multiply_repeat', function(cm, vim, helpers) {
helpers.doKeys('2', 'y', '3', 'y');
eq(expectedLineCount, cm.lineCount());
var register = helpers.getRegisterController().getRegister();
eq(expectedBuffer, register.text);
eq(expectedBuffer, register.toString());
is(register.linewise);
eqPos(curStart, cm.getCursor());
});
@ -863,7 +872,7 @@ testVim('cw_repeat', function(cm, vim, helpers) {
helpers.doKeys('c', '2', 'w');
eq(' ', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('word1\nword2', register.text);
eq('word1\nword2', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
eq('vim-insert', cm.getOption('keyMap'));
@ -876,7 +885,7 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) {
helpers.doKeys('2', 'c', '3', 'c');
eq(expectedLineCount, cm.lineCount());
var register = helpers.getRegisterController().getRegister();
eq(expectedBuffer, register.text);
eq(expectedBuffer, register.toString());
is(register.linewise);
eq('vim-insert', cm.getOption('keyMap'));
});
@ -895,7 +904,7 @@ testVim('g~w_repeat', function(cm, vim, helpers) {
helpers.doKeys('g', '~', '2', 'w');
eq(' WORD1\nWORD2', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1\nword2' });
@ -907,7 +916,7 @@ testVim('g~g~', function(cm, vim, helpers) {
helpers.doKeys('2', 'g', '~', '3', 'g', '~');
eq(expectedValue, cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' });
@ -918,7 +927,7 @@ testVim('>{motion}', function(cm, vim, helpers) {
helpers.doKeys('>', 'k');
eq(expectedValue, cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 3);
}, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
@ -929,7 +938,7 @@ testVim('>>', function(cm, vim, helpers) {
helpers.doKeys('2', '>', '>');
eq(expectedValue, cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 3);
}, { value: ' word1\nword2\nword3 ', indentUnit: 2 });
@ -940,7 +949,7 @@ testVim('<{motion}', function(cm, vim, helpers) {
helpers.doKeys('<', 'k');
eq(expectedValue, cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
}, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
@ -951,7 +960,7 @@ testVim('<<', function(cm, vim, helpers) {
helpers.doKeys('2', '<', '<');
eq(expectedValue, cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('', register.text);
eq('', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
}, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
@ -959,7 +968,12 @@ testVim('<<', function(cm, vim, helpers) {
// Edit tests
function testEdit(name, before, pos, edit, after) {
return testVim(name, function(cm, vim, helpers) {
cm.setCursor(0, before.search(pos));
var ch = before.search(pos)
var line = before.substring(0, ch).split('\n').length - 1;
if (line) {
ch = before.substring(0, ch).split('\n').pop().length;
}
cm.setCursor(line, ch);
helpers.doKeys.apply(this, edit.split(''));
eq(after, cm.getValue());
}, {value: before});
@ -997,6 +1011,40 @@ testEdit('diW_end_spc', 'foo \tbAr', /A/, 'diW', 'foo \t');
testEdit('daW_end_spc', 'foo \tbAr', /A/, 'daW', 'foo');
testEdit('diW_end_punct', 'foo \tbAr.', /A/, 'diW', 'foo \t');
testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo');
// Deleting text objects
// Open and close on same line
testEdit('di(_open_spc', 'foo (bAr) baz', /\(/, 'di(', 'foo () baz');
testEdit('di)_open_spc', 'foo (bAr) baz', /\(/, 'di)', 'foo () baz');
testEdit('da(_open_spc', 'foo (bAr) baz', /\(/, 'da(', 'foo baz');
testEdit('da)_open_spc', 'foo (bAr) baz', /\(/, 'da)', 'foo baz');
testEdit('di(_middle_spc', 'foo (bAr) baz', /A/, 'di(', 'foo () baz');
testEdit('di)_middle_spc', 'foo (bAr) baz', /A/, 'di)', 'foo () baz');
testEdit('da(_middle_spc', 'foo (bAr) baz', /A/, 'da(', 'foo baz');
testEdit('da)_middle_spc', 'foo (bAr) baz', /A/, 'da)', 'foo baz');
testEdit('di(_close_spc', 'foo (bAr) baz', /\)/, 'di(', 'foo () baz');
testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz');
testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz');
testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz');
// Open and close on different lines, equally indented
testEdit('di{_middle_spc', 'a{\n\tbar\n}b', /r/, 'di{', 'a{}b');
testEdit('di}_middle_spc', 'a{\n\tbar\n}b', /r/, 'di}', 'a{}b');
testEdit('da{_middle_spc', 'a{\n\tbar\n}b', /r/, 'da{', 'ab');
testEdit('da}_middle_spc', 'a{\n\tbar\n}b', /r/, 'da}', 'ab');
// open and close on diff lines, open indented less than close
testEdit('di{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di{', 'a{}b');
testEdit('di}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di}', 'a{}b');
testEdit('da{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da{', 'ab');
testEdit('da}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da}', 'ab');
// open and close on diff lines, open indented more than close
testEdit('di[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di[', 'a\t[]b');
testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b');
testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb');
testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb');
// Operator-motion tests
testVim('D', function(cm, vim, helpers) {
@ -1004,7 +1052,7 @@ testVim('D', function(cm, vim, helpers) {
helpers.doKeys('D');
eq(' wo\nword2\n word3', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('rd1', register.text);
eq('rd1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 2);
}, { value: ' word1\nword2\n word3' });
@ -1014,7 +1062,7 @@ testVim('C', function(cm, vim, helpers) {
helpers.doKeys('C');
eq(' wo\nword2\n word3', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('rd1', register.text);
eq('rd1', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
eq('vim-insert', cm.getOption('keyMap'));
@ -1025,7 +1073,7 @@ testVim('Y', function(cm, vim, helpers) {
helpers.doKeys('Y');
eq(' word1\nword2\n word3', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('rd1', register.text);
eq('rd1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 3);
}, { value: ' word1\nword2\n word3' });
@ -1162,14 +1210,14 @@ testVim('p', function(cm, vim, helpers) {
}, { value: '___' });
testVim('p_register', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.getRegisterController().getRegister('a').set('abc\ndef', false);
helpers.getRegisterController().getRegister('a').setText('abc\ndef', false);
helpers.doKeys('"', 'a', 'p');
eq('__abc\ndef_', cm.getValue());
helpers.assertCursorAt(1, 2);
}, { value: '___' });
testVim('p_wrong_register', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.getRegisterController().getRegister('a').set('abc\ndef', false);
helpers.getRegisterController().getRegister('a').setText('abc\ndef', false);
helpers.doKeys('p');
eq('___', cm.getValue());
helpers.assertCursorAt(0, 1);
@ -1492,6 +1540,17 @@ testVim('visual_blank', function(cm, vim, helpers) {
helpers.doKeys('v', 'k');
eq(vim.visualMode, true);
}, { value: '\n' });
testVim('reselect_visual', function(cm, vim, helpers) {
helpers.doKeys('l', 'v', 'l', 'l', 'y', 'g', 'v');
helpers.assertCursorAt(0, 3);
eqPos(makeCursor(0, 1), cm.getCursor('anchor'));
helpers.doKeys('d');
eq('15', cm.getValue());
}, { value: '12345' });
testVim('reselect_visual_line', function(cm, vim, helpers) {
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'V', 'g', 'v', 'd');
eq(' 4\n 5', cm.getValue());
}, { value: ' 1\n 2\n 3\n 4\n 5' });
testVim('s_normal', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('s');
@ -1506,6 +1565,19 @@ testVim('s_visual', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 0);
eq('ac', cm.getValue());
}, { value: 'abc'});
testVim('o_visual', function(cm,vim,helpers) {
cm.setCursor(0,0);
helpers.doKeys('v','l','l','l','o');
helpers.assertCursorAt(0,0);
helpers.doKeys('v','v','j','j','j','o');
helpers.assertCursorAt(0,0);
helpers.doKeys('o');
helpers.doKeys('l','l')
helpers.assertCursorAt(3,2);
helpers.doKeys('d');
eq('p',cm.getValue());
}, { value: 'abcd\nefgh\nijkl\nmnop'});
testVim('S_normal', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('j', 'S');
@ -1520,6 +1592,7 @@ testVim('S_visual', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 0);
eq('\ncc', cm.getValue());
}, { value: 'aa\nbb\ncc'});
testVim('/ and n/N', function(cm, vim, helpers) {
cm.openDialog = helpers.fakeOpenDialog('match');
helpers.doKeys('/');
@ -1538,6 +1611,22 @@ testVim('/_case', function(cm, vim, helpers) {
helpers.doKeys('/');
helpers.assertCursorAt(1, 6);
}, { value: 'match nope match \n nope Match' });
testVim('/_2_pcre', function(cm, vim, helpers) {
CodeMirror.Vim.setOption('pcre', true);
cm.openDialog = helpers.fakeOpenDialog('(word){2}');
helpers.doKeys('/');
helpers.assertCursorAt(1, 9);
helpers.doKeys('n');
helpers.assertCursorAt(2, 1);
}, { value: 'word\n another wordword\n wordwordword\n' });
testVim('/_2_nopcre', function(cm, vim, helpers) {
CodeMirror.Vim.setOption('pcre', false);
cm.openDialog = helpers.fakeOpenDialog('\\(word\\)\\{2}');
helpers.doKeys('/');
helpers.assertCursorAt(1, 9);
helpers.doKeys('n');
helpers.assertCursorAt(2, 1);
}, { value: 'word\n another wordword\n wordwordword\n' });
testVim('/_nongreedy', function(cm, vim, helpers) {
cm.openDialog = helpers.fakeOpenDialog('aa');
helpers.doKeys('/');
@ -1661,6 +1750,100 @@ testVim('#', function(cm, vim, helpers) {
helpers.doKeys('#');
helpers.assertCursorAt(1, 8);
}, { value: ' := match nomatch match \nnomatch Match' });
testVim('macro_insert', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', '0', 'i');
cm.replaceRange('foo', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('q', '@', 'a');
eq('foofoo', cm.getValue());
}, { value: ''});
testVim('macro_space', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<Space>', '<Space>');
helpers.assertCursorAt(0, 2);
helpers.doKeys('q', 'a', '<Space>', '<Space>', 'q');
helpers.assertCursorAt(0, 4);
helpers.doKeys('@', 'a');
helpers.assertCursorAt(0, 6);
helpers.doKeys('@', 'a');
helpers.assertCursorAt(0, 8);
}, { value: 'one line of text.'});
testVim('macro_parens', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'z', 'i');
cm.replaceRange('(', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('e', 'a');
cm.replaceRange(')', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('q');
helpers.doKeys('w', '@', 'z');
helpers.doKeys('w', '@', 'z');
eq('(see) (spot) (run)', cm.getValue());
}, { value: 'see spot run'});
testVim('macro_overwrite', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'z', '0', 'i');
cm.replaceRange('I ', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('q');
helpers.doKeys('e');
// Now replace the macro with something else.
helpers.doKeys('q', 'z', 'a');
cm.replaceRange('.', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('q');
helpers.doKeys('e', '@', 'z');
helpers.doKeys('e', '@', 'z');
eq('I see. spot. run.', cm.getValue());
}, { value: 'see spot run'});
testVim('macro_search_f', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', 'f', ' ');
helpers.assertCursorAt(0,3);
helpers.doKeys('q', '0');
helpers.assertCursorAt(0,0);
helpers.doKeys('@', 'a');
helpers.assertCursorAt(0,3);
}, { value: 'The quick brown fox jumped over the lazy dog.'});
testVim('macro_search_2f', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', '2', 'f', ' ');
helpers.assertCursorAt(0,9);
helpers.doKeys('q', '0');
helpers.assertCursorAt(0,0);
helpers.doKeys('@', 'a');
helpers.assertCursorAt(0,9);
}, { value: 'The quick brown fox jumped over the lazy dog.'});
testVim('yank_register', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('"', 'a', 'y', 'y');
helpers.doKeys('j', '"', 'b', 'y', 'y');
cm.openDialog = helpers.fakeOpenDialog('registers');
cm.openNotification = helpers.fakeOpenNotification(function(text) {
is(/a\s+foo/.test(text));
is(/b\s+bar/.test(text));
});
helpers.doKeys(':');
}, { value: 'foo\nbar'});
testVim('macro_register', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', 'i');
cm.replaceRange('gangnam', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('q');
helpers.doKeys('q', 'b', 'o');
cm.replaceRange('style', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('q');
cm.openDialog = helpers.fakeOpenDialog('registers');
cm.openNotification = helpers.fakeOpenNotification(function(text) {
is(/a\s+i/.test(text));
is(/b\s+o/.test(text));
});
helpers.doKeys(':');
}, { value: ''});
testVim('.', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('2', 'd', 'w');
@ -2003,6 +2186,54 @@ testVim('zt==z<CR>', function(cm, vim, helpers){
eq(zVals[2], zVals[5]);
});
var moveTillCharacterSandbox =
'The quick brown fox \n'
'jumped over the lazy dog.'
testVim('moveTillCharacter', function(cm, vim, helpers){
cm.setCursor(0, 0);
// Search for the 'q'.
cm.openDialog = helpers.fakeOpenDialog('q');
helpers.doKeys('/');
eq(4, cm.getCursor().ch);
// Jump to just before the first o in the list.
helpers.doKeys('t');
helpers.doKeys('o');
eq('The quick brown fox \n', cm.getValue());
// Delete that one character.
helpers.doKeys('d');
helpers.doKeys('t');
helpers.doKeys('o');
eq('The quick bown fox \n', cm.getValue());
// Delete everything until the next 'o'.
helpers.doKeys('.');
eq('The quick box \n', cm.getValue());
// An unmatched character should have no effect.
helpers.doKeys('d');
helpers.doKeys('t');
helpers.doKeys('q');
eq('The quick box \n', cm.getValue());
// Matches should only be possible on single lines.
helpers.doKeys('d');
helpers.doKeys('t');
helpers.doKeys('z');
eq('The quick box \n', cm.getValue());
// After all that, the search for 'q' should still be active, so the 'N' command
// can run it again in reverse. Use that to delete everything back to the 'q'.
helpers.doKeys('d');
helpers.doKeys('N');
eq('The ox \n', cm.getValue());
eq(4, cm.getCursor().ch);
}, { value: moveTillCharacterSandbox});
testVim('searchForPipe', function(cm, vim, helpers){
CodeMirror.Vim.setOption('pcre', false);
cm.setCursor(0, 0);
// Search for the '|'.
cm.openDialog = helpers.fakeOpenDialog('|');
helpers.doKeys('/');
eq(4, cm.getCursor().ch);
}, { value: 'this|that'});
var scrollMotionSandbox =
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'
@ -2017,7 +2248,7 @@ testVim('scrollMotion', function(cm, vim, helpers){
prevScrollInfo = cm.getScrollInfo();
helpers.doKeys('<C-e>');
eq(1, cm.getCursor().line);
eq(true, prevScrollInfo.top < cm.getScrollInfo().top);
is(prevScrollInfo.top < cm.getScrollInfo().top);
// Jump to the end of the sandbox.
cm.setCursor(1000, 0);
prevCursor = cm.getCursor();
@ -2027,7 +2258,7 @@ testVim('scrollMotion', function(cm, vim, helpers){
prevScrollInfo = cm.getScrollInfo();
helpers.doKeys('<C-y>');
eq(prevCursor.line - 1, cm.getCursor().line);
eq(true, prevScrollInfo.top > cm.getScrollInfo().top);
is(prevScrollInfo.top > cm.getScrollInfo().top);
}, { value: scrollMotionSandbox});
var squareBracketMotionSandbox = ''+
@ -2225,6 +2456,8 @@ testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) {
helpers.doEx('sort! d');
eq('a3\nb2\nc1\nz\ny', cm.getValue());
}, { value: 'a3\nz\nc1\ny\nb2'});
// Basic substitute tests.
testVim('ex_substitute_same_line', function(cm, vim, helpers) {
cm.setCursor(1, 0);
helpers.doEx('s/one/two');
@ -2247,11 +2480,6 @@ testVim('ex_substitute_visual_range', function(cm, vim, helpers) {
helpers.doEx('\'<,\'>s/\\d/0');
eq('1\n0\n0\n0\n5', cm.getValue());
}, { value: '1\n2\n3\n4\n5' });
testVim('ex_substitute_capture', function(cm, vim, helpers) {
cm.setCursor(1, 0);
helpers.doEx('s/(\\d+)/$1$1/')
eq('a1111 a1212 a1313', cm.getValue());
}, { value: 'a11 a12 a13' });
testVim('ex_substitute_empty_query', function(cm, vim, helpers) {
// If the query is empty, use last query.
cm.setCursor(1, 0);
@ -2260,16 +2488,127 @@ testVim('ex_substitute_empty_query', function(cm, vim, helpers) {
helpers.doEx('s//b');
eq('abb ab2 ab3', cm.getValue());
}, { value: 'a11 a12 a13' });
testVim('ex_substitute_count', function(cm, vim, helpers) {
testVim('ex_substitute_javascript', function(cm, vim, helpers) {
CodeMirror.Vim.setOption('pcre', false);
cm.setCursor(1, 0);
helpers.doEx('s/\\d/0/i 2');
eq('1\n0\n0\n4', cm.getValue());
}, { value: '1\n2\n3\n4' });
testVim('ex_substitute_count_with_range', function(cm, vim, helpers) {
cm.setCursor(1, 0);
helpers.doEx('1,3s/\\d/0/ 3');
eq('1\n2\n0\n0', cm.getValue());
}, { value: '1\n2\n3\n4' });
// Throw all the things that javascript likes to treat as special values
// into the replace part. All should be literal (this is VIM).
helpers.doEx('s/\\(\\d+\\)/$$ $\' $` $& \\1/')
eq('a $$ $\' $` $& 0 b', cm.getValue());
}, { value: 'a 0 b' });
// More complex substitute tests that test both pcre and nopcre options.
function testSubstitute(name, options) {
testVim(name + '_pcre', function(cm, vim, helpers) {
cm.setCursor(1, 0);
CodeMirror.Vim.setOption('pcre', true);
helpers.doEx(options.expr);
eq(options.expectedValue, cm.getValue());
}, options);
// If no noPcreExpr is defined, assume that it's the same as the expr.
var noPcreExpr = options.noPcreExpr ? options.noPcreExpr : options.expr;
testVim(name + '_nopcre', function(cm, vim, helpers) {
cm.setCursor(1, 0);
CodeMirror.Vim.setOption('pcre', false);
helpers.doEx(noPcreExpr);
eq(options.expectedValue, cm.getValue());
}, options);
}
testSubstitute('ex_substitute_capture', {
value: 'a11 a12 a13',
expectedValue: 'a1111 a1212 a1313',
// $n is a backreference
expr: 's/(\\d+)/$1$1/',
// \n is a backreference.
noPcreExpr: 's/\\(\\d+\\)/\\1\\1/'});
testSubstitute('ex_substitute_capture2', {
value: 'a 0 b',
expectedValue: 'a $00 b',
expr: 's/(\\d+)/$$$1$1/',
noPcreExpr: 's/\\(\\d+\\)/$\\1\\1/'});
testSubstitute('ex_substitute_nocapture', {
value: 'a11 a12 a13',
expectedValue: 'a$1$1 a$1$1 a$1$1',
expr: 's/(\\d+)/$$1$$1',
noPcreExpr: 's/\\(\\d+\\)/$1$1/'});
testSubstitute('ex_substitute_nocapture2', {
value: 'a 0 b',
expectedValue: 'a $10 b',
expr: 's/(\\d+)/$$1$1',
noPcreExpr: 's/\\(\\d+\\)/\\$1\\1/'});
testSubstitute('ex_substitute_nocapture', {
value: 'a b c',
expectedValue: 'a $ c',
expr: 's/b/$$/',
noPcreExpr: 's/b/$/'});
testSubstitute('ex_substitute_slash_regex', {
value: 'one/two \n three/four',
expectedValue: 'one|two \n three|four',
expr: '%s/\\//|'});
testSubstitute('ex_substitute_pipe_regex', {
value: 'one|two \n three|four',
expectedValue: 'one,two \n three,four',
expr: '%s/\\|/,/',
noPcreExpr: '%s/|/,/'});
testSubstitute('ex_substitute_or_regex', {
value: 'one|two \n three|four',
expectedValue: 'ana|twa \n thraa|faar',
expr: '%s/o|e|u/a',
noPcreExpr: '%s/o\\|e\\|u/a'});
testSubstitute('ex_substitute_or_word_regex', {
value: 'one|two \n three|four',
expectedValue: 'five|five \n three|four',
expr: '%s/(one|two)/five/',
noPcreExpr: '%s/\\(one\\|two\\)/five'});
testSubstitute('ex_substitute_backslashslash_regex', {
value: 'one\\two \n three\\four',
expectedValue: 'one,two \n three,four',
expr: '%s/\\\\/,'});
testSubstitute('ex_substitute_slash_replacement', {
value: 'one,two \n three,four',
expectedValue: 'one/two \n three/four',
expr: '%s/,/\\/'});
testSubstitute('ex_substitute_backslash_replacement', {
value: 'one,two \n three,four',
expectedValue: 'one\\two \n three\\four',
expr: '%s/,/\\\\/g'});
testSubstitute('ex_substitute_multibackslash_replacement', {
value: 'one,two \n three,four',
expectedValue: 'one\\\\\\\\two \n three\\\\\\\\four', // 2*8 backslashes.
expr: '%s/,/\\\\\\\\\\\\\\\\/g'}); // 16 backslashes.
testSubstitute('ex_substitute_braces_word', {
value: 'ababab abb ab{2}',
expectedValue: 'ab abb ab{2}',
expr: '%s/(ab){2}//g',
noPcreExpr: '%s/\\(ab\\)\\{2\\}//g'});
testSubstitute('ex_substitute_braces_range', {
value: 'a aa aaa aaaa',
expectedValue: 'a a',
expr: '%s/a{2,3}//g',
noPcreExpr: '%s/a\\{2,3\\}//g'});
testSubstitute('ex_substitute_braces_literal', {
value: 'ababab abb ab{2}',
expectedValue: 'ababab abb ',
expr: '%s/ab\\{2\\}//g',
noPcreExpr: '%s/ab{2}//g'});
testSubstitute('ex_substitute_braces_char', {
value: 'ababab abb ab{2}',
expectedValue: 'ababab ab{2}',
expr: '%s/ab{2}//g',
noPcreExpr: '%s/ab\\{2\\}//g'});
testSubstitute('ex_substitute_braces_no_escape', {
value: 'ababab abb ab{2}',
expectedValue: 'ababab ab{2}',
expr: '%s/ab{2}//g',
noPcreExpr: '%s/ab\\{2}//g'});
testSubstitute('ex_substitute_count', {
value: '1\n2\n3\n4',
expectedValue: '1\n0\n0\n4',
expr: 's/\\d/0/i 2'});
testSubstitute('ex_substitute_count_with_range', {
value: '1\n2\n3\n4',
expectedValue: '1\n2\n0\n0',
expr: '1,3s/\\d/0/ 3'});
function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) {
testVim(name, function(cm, vim, helpers) {
var savedOpenDialog = cm.openDialog;
@ -2351,6 +2690,63 @@ testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) {
helpers.doKeys('n');
helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting');
}, { value: 'match nope match \n nope Match' });
testVim('set_boolean', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', true, 'boolean');
// Test default value is set.
is(CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set to non-boolean
CodeMirror.Vim.setOption('testoption', '5');
fail();
} catch (expected) {};
// Test setOption
CodeMirror.Vim.setOption('testoption', false);
is(!CodeMirror.Vim.getOption('testoption'));
});
testVim('ex_set_boolean', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', true, 'boolean');
// Test default value is set.
is(CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set to non-boolean
helpers.doEx('set testoption=22');
fail();
} catch (expected) {};
// Test setOption
helpers.doEx('set notestoption');
is(!CodeMirror.Vim.getOption('testoption'));
});
testVim('set_string', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', 'a', 'string');
// Test default value is set.
eq('a', CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set non-string.
CodeMirror.Vim.setOption('testoption', true);
fail();
} catch (expected) {};
try {
// Test fail to set 'notestoption'
CodeMirror.Vim.setOption('notestoption', 'b');
fail();
} catch (expected) {};
// Test setOption
CodeMirror.Vim.setOption('testoption', 'c');
eq('c', CodeMirror.Vim.getOption('testoption'));
});
testVim('ex_set_string', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', 'a', 'string');
// Test default value is set.
eq('a', CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set 'notestoption'
helpers.doEx('set notestoption=b');
fail();
} catch (expected) {};
// Test setOption
helpers.doEx('set testoption=c')
eq('c', CodeMirror.Vim.getOption('testoption'));
});
// TODO: Reset key maps after each test.
testVim('ex_map_key2key', function(cm, vim, helpers) {
helpers.doEx('map a x');
@ -2358,6 +2754,19 @@ testVim('ex_map_key2key', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 0);
eq('bc', cm.getValue());
}, { value: 'abc' });
testVim('ex_unmap_key2key', function(cm, vim, helpers) {
helpers.doEx('unmap a');
helpers.doKeys('a');
eq('vim-insert', cm.getOption('keyMap'));
}, { value: 'abc' });
testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) {
try {
helpers.doEx('unmap a');
fail();
} catch (expected) {}
helpers.doKeys('a');
eq('vim-insert', cm.getOption('keyMap'));
}, { value: 'abc' });
testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
helpers.doEx('map ; :');
var dialogOpened = false;
@ -2442,3 +2851,9 @@ testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 0);
eq('bc', cm.getValue());
}, { value: 'abc' });
// Test event handlers
testVim('beforeSelectionChange', function(cm, vim, helpers) {
cm.setCursor(0, 100);
eqPos(cm.getCursor('head'), cm.getCursor('anchor'));
}, { value: 'abc' });

View File

@ -15,6 +15,7 @@
<script src="chrome://browser/content/devtools/codemirror/javascript.js"></script>
<script src="chrome://browser/content/devtools/codemirror/vim.js"></script>
<script src="chrome://browser/content/devtools/codemirror/emacs.js"></script>
<script src="chrome://browser/content/devtools/codemirror/sublime.js"></script>
<!--<script src="../addon/mode/overlay.js"></script>
<script src="../addon/mode/multiplex.js"></script>
@ -60,10 +61,16 @@
<script src="cm_driver.js"></script>
<script src="cm_test.js"></script>
<script src="cm_comment_test.js"></script>
<script src="cm_doc_test.js"></script>
<script src="cm_driver.js"></script>
<script src="cm_emacs_test.js"></script>
<script src="cm_mode_test.js"></script>
<script src="cm_mode_javascript_test.js"></script>
<script src="cm_multi_test.js"></script>
<script src="cm_search_test.js"></script>
<!-- VIM and Emacs mode tests are in vimemacs.html
<script src="cm_sublime_test.js"></script>
<script src="cm_vim_test.js"></script>
<script src="cm_emacs_test.js"></script>
-->

View File

@ -14,6 +14,7 @@
<script src="chrome://browser/content/devtools/codemirror/comment.js"></script>
<script src="chrome://browser/content/devtools/codemirror/javascript.js"></script>
<script src="chrome://browser/content/devtools/codemirror/vim.js"></script>
<script src="chrome://browser/content/devtools/codemirror/sublime.js"></script>
<script src="chrome://browser/content/devtools/codemirror/emacs.js"></script>
<!--<script src="../addon/mode/overlay.js"></script>
@ -58,14 +59,21 @@
<div id=testground></div>
<script src="cm_driver.js"></script>
<script src="cm_sublime_test.js"></script>
<script src="cm_vim_test.js"></script>
<script src="cm_emacs_test.js"></script>
<!-- Basic tests are in codemirror.html
<script src="cm_driver.js"></script>
<script src="cm_test.js"></script>
<script src="cm_comment_test.js"></script>
<script src="cm_doc_test.js"></script>
<script src="cm_driver.js"></script>
<script src="cm_emacs_test.js"></script>
<script src="cm_mode_test.js"></script>
<script src="cm_mode_javascript_test.js"></script>
<script src="cm_multi_test.js"></script>
<script src="cm_search_test.js"></script>
-->
<!-- These modes/addons are not used by Editor

View File

@ -488,16 +488,21 @@ Rule.prototype = {
* for this rule's style sheet.
*
* @return {Promise}
* Promise which resolves with location as a string.
* Promise which resolves with location as an object containing
* both the full and short version of the source string.
*/
getOriginalSourceString: function() {
if (this._originalSourceString) {
return promise.resolve(this._originalSourceString);
getOriginalSourceStrings: function() {
if (this._originalSourceStrings) {
return promise.resolve(this._originalSourceStrings);
}
return this.domRule.getOriginalLocation().then(({href, line}) => {
let string = CssLogic.shortSource({href: href}) + ":" + line;
this._originalSourceString = string;
return string;
let sourceStrings = {
full: href + ":" + line,
short: CssLogic.shortSource({href: href}) + ":" + line
};
this._originalSourceStrings = sourceStrings;
return sourceStrings;
});
},
@ -1748,13 +1753,17 @@ RuleEditor.prototype = {
{
let sourceLabel = this.element.querySelector(".source-link-label");
sourceLabel.setAttribute("value", this.rule.title);
sourceLabel.setAttribute("tooltiptext", this.rule.title);
let sourceHref = (this.rule.sheet && this.rule.sheet.href) ?
this.rule.sheet.href : this.rule.title;
sourceLabel.setAttribute("tooltiptext", sourceHref);
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
if (showOrig && this.rule.domRule.type != ELEMENT_STYLE) {
this.rule.getOriginalSourceString().then((string) => {
sourceLabel.setAttribute("value", string);
sourceLabel.setAttribute("tooltiptext", string);
this.rule.getOriginalSourceStrings().then((strings) => {
sourceLabel.setAttribute("value", strings.short);
sourceLabel.setAttribute("tooltiptext", strings.full);
})
}
},

View File

@ -15,6 +15,8 @@ skip-if = true # awaiting promise-based init
[browser_bug_692400_element_style.js]
[browser_csslogic_inherited.js]
[browser_ruleview_734259_style_editor_link.js]
support-files =
browser_ruleview_734259_style_editor_link.css
[browser_ruleview_editor.js]
[browser_ruleview_editor_changedvalues.js]
[browser_ruleview_copy.js]

View File

@ -0,0 +1,3 @@
div {
opacity: 1;
}

View File

@ -17,6 +17,9 @@ const STYLESHEET_URL = "data:text/css,"+encodeURIComponent(
"color: blue",
"}"].join("\n"));
const EXTERNAL_STYLESHEET_FILE_NAME = "browser_ruleview_734259_style_editor_link.css"
const EXTERNAL_STYLESHEET_URL = TEST_BASE_HTTP + EXTERNAL_STYLESHEET_FILE_NAME;
const DOCUMENT_URL = "data:text/html,"+encodeURIComponent(
['<html>' +
'<head>' +
@ -28,6 +31,7 @@ const DOCUMENT_URL = "data:text/html,"+encodeURIComponent(
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">',
'</style>',
'<link rel="stylesheet" type="text/css" href="'+STYLESHEET_URL+'">',
'<link rel="stylesheet" type="text/css" href="'+EXTERNAL_STYLESHEET_URL+'">',
'</head>',
'<body>',
'<h1>Some header text</h1>',
@ -111,7 +115,7 @@ function testInlineStyleSheet()
});
});
let link = getLinkByIndex(2);
let link = getLinkByIndex(4);
link.scrollIntoView();
link.click();
}
@ -127,12 +131,26 @@ function testExternalStyleSheet(toolbox) {
});
toolbox.selectTool("inspector").then(function () {
testRuleViewLinkLabel();
let link = getLinkByIndex(1);
link.scrollIntoView();
link.click();
});
}
function testRuleViewLinkLabel()
{
let link = getLinkByIndex(2);
let labelElem = link.querySelector(".source-link-label");
let value = labelElem.getAttribute("value");
let tooltipText = labelElem.getAttribute("tooltiptext");
is(value, EXTERNAL_STYLESHEET_FILE_NAME + ":1",
"rule view stylesheet display value matches filename and line number");
is(tooltipText, EXTERNAL_STYLESHEET_URL,
"rule view stylesheet tooltip text matches the full URI path");
}
function validateStyleEditorSheet(aEditor, aExpectedSheetIndex)
{
info("validating style editor stylesheet");

View File

@ -258,6 +258,7 @@ run-if = os == "mac"
[browser_webconsole_property_provider.js]
[browser_webconsole_scratchpad_panel_link.js]
[browser_webconsole_split.js]
[browser_webconsole_split_escape_key.js]
[browser_webconsole_view_source.js]
[browser_webconsole_reflow.js]
[browser_webconsole_log_file_filter.js]

View File

@ -222,7 +222,7 @@ function test()
checkToolboxUI();
testDestroy();
toolbox.switchHost(Toolbox.HostType.BOTTOM).then(testDestroy);
}
function checkHostType(hostType)

View File

@ -0,0 +1,171 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function test() {
info("Test various cases where the escape key should hide the split console.");
let toolbox;
let hud;
let jsterm;
let hudMessages;
let variablesView;
Task.spawn(runner).then(finish);
function* runner() {
let {tab} = yield loadTab("data:text/html;charset=utf-8,<p>Web Console test for splitting");
let target = TargetFactory.forTab(tab);
toolbox = yield gDevTools.showToolbox(target, "inspector");
yield testCreateSplitConsoleAfterEscape();
yield showAutoCompletePopoup();
yield testHideAutoCompletePopupAfterEscape();
yield executeJS();
yield clickMessageAndShowVariablesView();
jsterm.inputNode.focus();
yield testHideVariablesViewAfterEscape();
yield clickMessageAndShowVariablesView();
yield startPropertyEditor();
yield testCancelPropertyEditorAfterEscape();
yield testHideVariablesViewAfterEscape();
yield testHideSplitConsoleAfterEscape();
}
function testCreateSplitConsoleAfterEscape() {
let result = toolbox.once("webconsole-ready", () => {
hud = toolbox.getPanel("webconsole").hud;
jsterm = hud.jsterm;
ok(toolbox.splitConsole, "Split console is created.");
});
let contentWindow = toolbox.frame.contentWindow;
contentWindow.focus();
EventUtils.sendKey("ESCAPE", contentWindow);
return result;
}
function testShowSplitConsoleAfterEscape() {
let result = toolbox.once("split-console", () => {
ok(toolbox.splitConsole, "Split console is shown.");
});
EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
return result;
}
function testHideSplitConsoleAfterEscape() {
let result = toolbox.once("split-console", () => {
ok(!toolbox.splitConsole, "Split console is hidden.");
});
EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
return result;
}
function testHideVariablesViewAfterEscape() {
let result = jsterm.once("sidebar-closed", () => {
ok(!hud.ui.jsterm.sidebar,
"Variables view is hidden.");
ok(toolbox.splitConsole,
"Split console is open after hiding the variables view.");
});
EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
return result;
}
function testHideAutoCompletePopupAfterEscape() {
let deferred = promise.defer();
let popup = jsterm.autocompletePopup;
popup._panel.addEventListener("popuphidden", function popupHidden() {
popup._panel.removeEventListener("popuphidden", popupHidden, false);
ok(!popup.isOpen,
"Auto complete popup is hidden.");
ok(toolbox.splitConsole,
"Split console is open after hiding the autocomplete popup.");
deferred.resolve();
}, false);
EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
return deferred.promise;
}
function testCancelPropertyEditorAfterEscape() {
EventUtils.sendKey("ESCAPE", variablesView.window);
ok(hud.ui.jsterm.sidebar,
"Variables view is open after canceling property editor.");
ok(toolbox.splitConsole,
"Split console is open after editing.");
}
function executeJS() {
jsterm.execute("var foo = { bar: \"baz\" }; foo;");
hudMessages = yield waitForMessages({
webconsole: hud,
messages: [{
text: "Object { bar: \"baz\" }",
category: CATEGORY_OUTPUT,
objects: true
}],
});
}
function clickMessageAndShowVariablesView() {
let result = jsterm.once("variablesview-fetched", (event, vview) => {
variablesView = vview;
});
let clickable = hudMessages[0].clickableElements[0];
EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
return result;
}
function startPropertyEditor() {
let results = yield findVariableViewProperties(variablesView, [
{name: "bar", value: "baz"}
], {webconsole: hud});
results[0].matchedProp.focus();
EventUtils.synthesizeKey("VK_RETURN", variablesView.window);
}
function showAutoCompletePopoup() {
let deferred = promise.defer();
let popupPanel = jsterm.autocompletePopup._panel;
popupPanel.addEventListener("popupshown", function popupShown() {
popupPanel.removeEventListener("popupshown", popupShown, false);
deferred.resolve();
}, false);
jsterm.inputNode.focus();
jsterm.setInputValue("document.location.");
EventUtils.sendKey("TAB", hud.iframeWindow);
return deferred.promise;
}
function finish() {
toolbox.destroy().then(() => {
toolbox = null;
hud = null;
jsterm = null;
hudMessages = null;
variablesView = null;
finishTest();
});
}
}

View File

@ -3489,6 +3489,7 @@ JSTerm.prototype = {
this._sidebarDestroy();
this.inputNode.focus();
aEvent.stopPropagation();
},
/**
@ -3922,10 +3923,12 @@ JSTerm.prototype = {
if (this.autocompletePopup.isOpen) {
this.clearCompletion();
aEvent.preventDefault();
aEvent.stopPropagation();
}
else if (this.sidebar) {
this._sidebarDestroy();
aEvent.preventDefault();
aEvent.stopPropagation();
}
break;

View File

@ -98,6 +98,10 @@ let gPolicyCounter = 0;
let gExperimentsCounter = 0;
let gExperimentEntryCounter = 0;
// Tracks active AddonInstall we know about so we can deny external
// installs.
let gActiveInstallURLs = new Set();
let gLogger;
let gLogDumping = false;
@ -208,6 +212,10 @@ function uninstallAddons(addons) {
AddonManager.addAddonListener(listener);
for (let addon of addons) {
// Disabling the add-on before uninstalling is necessary to cause tests to
// pass. This might be indicative of a bug in XPIProvider.
// TODO follow up in bug 992396.
addon.userDisabled = true;
addon.uninstall();
}
@ -359,7 +367,7 @@ Experiments.Experiments.prototype = {
AsyncShutdown.profileBeforeChange.addBlocker("Experiments.jsm shutdown",
this.uninit.bind(this));
AddonManager.addAddonListener(this);
this._startWatchingAddons();
this._loadTask = Task.spawn(this._loadFromCache.bind(this));
this._loadTask.then(
@ -380,7 +388,7 @@ Experiments.Experiments.prototype = {
*/
uninit: function () {
if (!this._shutdown) {
AddonManager.removeAddonListener(this);
this._stopWatchingAddons();
gPrefs.ignore(PREF_LOGGING, configureLogging);
gPrefs.ignore(PREF_MANIFEST_URI, this.updateManifest, this);
@ -400,6 +408,16 @@ Experiments.Experiments.prototype = {
return Promise.resolve();
},
_startWatchingAddons: function () {
AddonManager.addAddonListener(this);
AddonManager.addInstallListener(this);
},
_stopWatchingAddons: function () {
AddonManager.removeInstallListener(this);
AddonManager.removeAddonListener(this);
},
/**
* Throws an exception if we've already shut down.
*/
@ -644,6 +662,42 @@ Experiments.Experiments.prototype = {
this.disableExperiment();
},
onInstallStarted: function (install) {
if (install.addon.type != "experiment") {
return;
}
// We want to be in control of all experiment add-ons: reject installs
// for add-ons that we don't know about.
// We have a race condition of sorts to worry about here. We have 2
// onInstallStarted listeners. This one (the global one) and the one
// created as part of ExperimentEntry._installAddon. Because of the order
// they are registered in, this one likely executes first. Unfortunately,
// this means that the add-on ID is not yet set on the ExperimentEntry.
// So, we can't just look at this._trackedAddonIds because the new experiment
// will have its add-on ID set to null. We work around this by storing a
// identifying field - the source URL of the install - in a module-level
// variable (so multiple Experiments instances doesn't cancel each other
// out).
if (this._trackedAddonIds.has(install.addon.id)) {
this._log.info("onInstallStarted allowing install because add-on ID " +
"tracked by us.");
return;
}
if (gActiveInstallURLs.has(install.sourceURI.spec)) {
this._log.info("onInstallStarted allowing install because install " +
"tracked by us.");
return;
}
this._log.warn("onInstallStarted cancelling install of unknown " +
"experiment add-on: " + install.addon.id);
return false;
},
// END OF ADD-ON LISTENERS.
_getExperimentByAddonId: function (addonId) {
@ -851,6 +905,17 @@ Experiments.Experiments.prototype = {
return this._run();
},
/**
* The Set of add-on IDs that we know about from manifests.
*/
get _trackedAddonIds() {
if (!this._experiments) {
return new Set();
}
return new Set([e._addonId for ([,e] of this._experiments) if (e._addonId)]);
},
/*
* Task function to check applicability of experiments, disable the active
* experiment if needed and activate the first applicable candidate.
@ -875,7 +940,7 @@ Experiments.Experiments.prototype = {
// should have some record of it. In the end, we decide to discard all
// knowledge for these unknown experiment add-ons.
let installedExperiments = yield installedExperimentAddons();
let expectedAddonIds = new Set([e._addonId for ([,e] of this._experiments)]);
let expectedAddonIds = this._trackedAddonIds;
let unknownAddons = [a for (a of installedExperiments) if (!expectedAddonIds.has(a.id))];
if (unknownAddons.length) {
this._log.warn("_evaluateExperiments() - unknown add-ons in AddonManager: " +
@ -917,6 +982,8 @@ Experiments.Experiments.prototype = {
}
this._dirty = true;
activeChanged = true;
} else {
yield activeExperiment.ensureActive();
}
} finally {
this._pendingUninstall = null;
@ -1402,11 +1469,14 @@ Experiments.ExperimentEntry.prototype = {
let install = yield addonInstallForURL(this._manifestData.xpiURL,
this._manifestData.xpiHash);
gActiveInstallURLs.add(install.sourceURI.spec);
let failureHandler = (install, handler) => {
let message = "AddonInstall " + handler + " for " + this.id + ", state=" +
(install.state || "?") + ", error=" + install.error;
this._log.error("_installAddon() - " + message);
this._failedStart = true;
gActiveInstallURLs.delete(install.sourceURI.spec);
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
[TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
@ -1415,6 +1485,8 @@ Experiments.ExperimentEntry.prototype = {
};
let listener = {
_expectedID: null,
onDownloadEnded: install => {
this._log.trace("_installAddon() - onDownloadEnded for " + this.id);
@ -1439,13 +1511,12 @@ Experiments.ExperimentEntry.prototype = {
this._log.error("_installAddon() - onInstallStarted, wrong addon type");
return false;
}
// Experiment add-ons default to userDisabled = true.
install.addon.userDisabled = false;
},
onInstallEnded: install => {
this._log.trace("_installAddon() - install ended for " + this.id);
gActiveInstallURLs.delete(install.sourceURI.spec);
this._lastChangedDate = this._policy.now();
this._startDate = this._policy.now();
this._enabled = true;
@ -1459,6 +1530,26 @@ Experiments.ExperimentEntry.prototype = {
this._description = addon.description || "";
this._homepageURL = addon.homepageURL || "";
// Experiment add-ons default to userDisabled=true. Enable if needed.
if (addon.userDisabled) {
this._log.trace("Add-on is disabled. Enabling.");
listener._expectedID = addon.id;
AddonManager.addAddonListener(listener);
addon.userDisabled = false;
} else {
this._log.trace("Add-on is enabled. start() completed.");
deferred.resolve();
}
},
onEnabled: addon => {
this._log.info("onEnabled() for " + addon.id);
if (addon.id != listener._expectedID) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve();
},
};
@ -1496,7 +1587,7 @@ Experiments.ExperimentEntry.prototype = {
this._endDate = now;
};
AddonManager.getAddonByID(this._addonId, addon => {
this._getAddon().then((addon) => {
if (!addon) {
let message = "could not get Addon for " + this.id;
this._log.warn("stop() - " + message);
@ -1513,6 +1604,61 @@ Experiments.ExperimentEntry.prototype = {
return deferred.promise;
},
/**
* Try to ensure this experiment is active.
*
* The returned promise will be resolved if the experiment is active
* in the Addon Manager or rejected if it isn't.
*
* @return Promise<>
*/
ensureActive: Task.async(function* () {
this._log.trace("ensureActive() for " + this.id);
let addon = yield this._getAddon();
if (!addon) {
this._log.warn("Experiment is not installed: " + this._addonId);
throw new Error("Experiment is not installed: " + this._addonId);
}
// User disabled likely means the experiment is disabled at startup,
// since the permissions don't allow it to be disabled by the user.
if (!addon.userDisabled) {
return;
}
let deferred = Promise.defer();
let listener = {
onEnabled: enabledAddon => {
if (enabledAddon.id != addon.id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve();
},
};
this._log.info("Activating add-on: " + addon.id);
AddonManager.addAddonListener(listener);
addon.userDisabled = false;
yield deferred.promise;
}),
/**
* Obtain the underlying Addon from the Addon Manager.
*
* @return Promise<Addon|null>
*/
_getAddon: function () {
let deferred = Promise.defer();
AddonManager.getAddonByID(this._addonId, deferred.resolve);
return deferred.promise;
},
_logTermination: function (terminationKind, terminationReason) {
if (terminationKind === undefined) {
return;

View File

@ -147,61 +147,6 @@ function loadAddonManager() {
startupManager();
}
// Install addon and return a Promise<boolean> that is
// resolve with true on success, false otherwise.
function installAddon(url, hash) {
let deferred = Promise.defer();
let success = () => deferred.resolve(true);
let fail = () => deferred.resolve(false);
let listener = {
onDownloadCancelled: fail,
onDownloadFailed: fail,
onInstallCancelled: fail,
onInstallFailed: fail,
onInstallEnded: success,
};
let installCallback = install => {
install.addListener(listener);
install.install();
};
AddonManager.getInstallForURL(url, installCallback,
"application/x-xpinstall", hash);
return deferred.promise;
}
// Uninstall addon and return a Promise<boolean> that is
// resolve with true on success, false otherwise.
function uninstallAddon(id) {
let deferred = Promise.defer();
AddonManager.getAddonByID(id, addon => {
if (!addon) {
deferred.resolve(false);
}
let listener = {};
let handler = addon => {
if (addon.id !== id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve(true);
};
listener.onUninstalled = handler;
listener.onDisabled = handler;
AddonManager.addAddonListener(listener);
addon.uninstall();
});
return deferred.promise;
}
function getExperimentAddons() {
let deferred = Promise.defer();

View File

@ -86,6 +86,12 @@ add_task(function* test_startStop() {
});
let experiment = new Experiments.ExperimentEntry(gPolicy);
experiment.initFromManifestData(manifestData);
// We need to associate it with the singleton so the onInstallStarted
// Addon Manager listener will know about it.
Experiments.instance()._experiments = new Map();
Experiments.instance()._experiments.set(experiment.id, experiment);
let result;
defineNow(gPolicy, baseDate);
@ -93,25 +99,43 @@ add_task(function* test_startStop() {
Assert.equal(result.applicable, false, "Experiment should not be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
result = yield isApplicable(experiment);
Assert.equal(result.applicable, true, "Experiment should now be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
yield experiment.start();
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
yield experiment.stop();
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
yield experiment.start();
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
let result = yield experiment._shouldStop();
Assert.equal(result.shouldStop, false, "shouldStop should be false.");
let maybeStop = yield experiment.maybeStop();
Assert.equal(maybeStop, false, "Experiment should not have been stopped.");
Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
Assert.ok(addons[0].isActive, "The add-on is still active.");
defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
result = yield experiment._shouldStop();
@ -119,4 +143,6 @@ add_task(function* test_startStop() {
maybeStop = yield experiment.maybeStop();
Assert.equal(maybeStop, true, "Experiment should have been stopped.");
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
});

View File

@ -4,6 +4,8 @@
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
@ -151,6 +153,8 @@ add_task(function* test_getExperiments() {
"Experiments observer should not have been called yet.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
// Trigger update, clock set for experiment 1 to start.
@ -164,6 +168,8 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "An experiment add-on was installed.");
experimentListData[1].active = true;
experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
@ -187,6 +193,8 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
experimentListData[1].active = false;
experimentListData[1].endDate = now.getTime();
@ -211,6 +219,8 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "An experiment add-on is installed.");
experimentListData[0].active = true;
experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
@ -236,6 +246,8 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiments add-ons are installed.");
experimentListData[0].active = false;
experimentListData[0].endDate = now.getTime();
@ -301,11 +313,6 @@ add_task(function* test_addonAlreadyInstalled() {
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Install conflicting addon.
let installed = yield installAddon(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
Assert.ok(installed, "Addon should have been installed.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
@ -320,6 +327,19 @@ add_task(function* test_addonAlreadyInstalled() {
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "1 add-on is installed.");
// Install conflicting addon.
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "1 add-on is installed.");
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
// Cleanup.
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
@ -1326,9 +1346,8 @@ add_task(function* test_unexpectedUninstall() {
// Uninstall the addon through the addon manager instead of stopping it through
// the experiments API.
let success = yield uninstallAddon(EXPERIMENT1_ID);
yield AddonTestUtils.uninstallAddonByID(EXPERIMENT1_ID);
yield experiments._mainTask;
Assert.ok(success, "Addon should have been uninstalled.");
yield experiments.notify();
@ -1351,7 +1370,12 @@ add_task(function* testUnknownExperimentsUninstalled() {
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are present.");
yield installAddon(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
// Simulate us not listening.
experiments._stopWatchingAddons();
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
experiments._startWatchingAddons();
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment 1 installed via AddonManager");
@ -1372,3 +1396,80 @@ add_task(function* testUnknownExperimentsUninstalled() {
yield experiments.uninit();
yield removeCacheFile();
});
// If someone else installs an experiment add-on, we detect and stop that.
add_task(function* testForeignExperimentInstall() {
let experiments = new Experiments.Experiments(gPolicy);
gManifestObject = {
"version": 1,
experiments: [],
};
yield experiments.init();
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present.");
let failed;
try {
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
} catch (ex) {
failed = true;
}
Assert.ok(failed, "Add-on install should not have completed successfully");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Add-on install should have been cancelled.");
yield experiments.uninit();
yield removeCacheFile();
});
// Experiment add-ons will be disabled after Addon Manager restarts. Ensure
// we enable them automatically.
add_task(function* testEnabledAfterRestart() {
let experiments = new Experiments.Experiments(gPolicy);
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
startTime: gPolicy.now().getTime() / 1000 - 60,
endTime: gPolicy.now().getTime() / 1000 + 60,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
],
};
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experimenta add-ons installed.");
yield experiments.updateManifest();
let fromManifest = yield experiments.getExperiments();
Assert.equal(fromManifest.length, 1, "A single experiment is known.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
Assert.ok(addons[0].isActive, "That experiment is active.");
dump("Restarting Addon Manager\n");
experiments._stopWatchingAddons();
restartManager();
experiments._startWatchingAddons();
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "The experiment is still there after restart.");
Assert.ok(addons[0].userDisabled, "But it is disabled.");
Assert.equal(addons[0].isActive, false, "And not active.");
yield experiments.updateManifest();
Assert.ok(addons[0].isActive, "It activates when the manifest is evaluated.");
yield experiments.uninit();
yield removeCacheFile();
});

View File

@ -38,7 +38,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ColorUtils",
"resource:///modules/colorUtils.jsm");

View File

@ -22,7 +22,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");

View File

@ -53,13 +53,13 @@ gTests.push({
is(Elements.findbar.isShowing, false, "Find bar is still hidden");
EventUtils.synthesizeKey("f", { accelKey: true });
yield Promise.all(waitForEvent(Elements.navbar, "transitionend"),
waitForEvent(Elements.findbar, "transitionend"));
yield Promise.all([waitForEvent(Elements.navbar, "transitionend"),
waitForEvent(Elements.findbar, "transitionend")]);
is(ContextUI.navbarVisible, false, "Navbar is hidden");
is(Elements.findbar.isShowing, true, "Findbar is visible");
yield Promise.all(showNavBar(),
waitForEvent(Elements.findbar, "transitionend"));
yield Promise.all([showNavBar(),
waitForEvent(Elements.findbar, "transitionend")]);
is(ContextUI.navbarVisible, true, "Navbar is visible again");
is(Elements.findbar.isShowing, false, "Find bar is hidden again");

View File

@ -6,7 +6,7 @@
/*=============================================================================
Globals
=============================================================================*/
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
/*=============================================================================

View File

@ -4,7 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Components.utils.import("resource://gre/modules/Promise.jsm");
const ColorAnalyzer = Components.classes["@mozilla.org/places/colorAnalyzer;1"]
.getService(Components.interfaces.mozIColorAnalyzer);

View File

@ -247,7 +247,7 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
};
return true;
return false;
}
};

View File

@ -1428,6 +1428,18 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
margin: 0px;
}
.translate-notification-icon,
#translate-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16.png);
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
.translated-notification-icon,
#translated-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16.png);
-moz-image-region: rect(0px, 32px, 16px, 16px);
}
#treecolAutoCompleteImage {
max-width : 36px;
}

View File

@ -173,6 +173,7 @@ browser.jar:
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -3603,6 +3603,31 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
.translate-notification-icon,
#translate-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16.png);
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
@media (min-resolution: 2dppx) {
.translate-notification-icon,
#translate-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16@2x.png);
-moz-image-region: rect(0px, 32px, 32px, 0px);
}
}
.translated-notification-icon,
#translated-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16.png);
-moz-image-region: rect(0px, 32px, 16px, 16px);
}
@media (min-resolution: 2dppx) {
.translated-notification-icon,
#translated-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16@2x.png);
-moz-image-region: rect(0px, 64px, 32px, 32px);
}
}
.popup-notification-icon {

View File

@ -5,15 +5,17 @@
%include ../../shared/customizableui/panelUIOverlay.inc.css
@media (min-resolution: 2dppx) {
toolbarbutton[panel-multiview-anchor=true] {
#PanelUI-help[panel-multiview-anchor="true"]::after,
toolbarbutton[panel-multiview-anchor="true"] {
background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted@2x.png),
linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
background-size: 16px, auto;
}
toolbarbutton[panel-multiview-anchor=true]:-moz-locale-dir(rtl) {
#PanelUI-help[panel-multiview-anchor="true"]:-moz-locale-dir(rtl)::after,
toolbarbutton[panel-multiview-anchor="true"]:-moz-locale-dir(rtl) {
background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl@2x.png),
linear-gradient(rgba(255,255,255,0), rgba(255,255,255,0.3));
linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
}
#PanelUI-fxa-status {
@ -59,6 +61,11 @@
-moz-image-region: rect(0, 96px, 32px, 64px);
}
#PanelUI-help[panel-multiview-anchor="true"] {
-moz-image-region: rect(0, 128px, 32px, 96px);
background-size: auto;
}
.subviewbutton[checked="true"] {
background-image: url("chrome://global/skin/menu/shared-menu-check@2x.png");
}

View File

@ -293,6 +293,8 @@ browser.jar:
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -346,7 +346,10 @@ toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] > iframe {
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .panel-wide-item,
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .toolbarbutton-1:not([panel-multiview-anchor="true"]),
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer {
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-fxa-status,
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > toolbarseparator,
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-customize,
#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-help:not([panel-multiview-anchor="true"]) {
opacity: .5;
}
@ -438,6 +441,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
#PanelUI-footer-inner {
display: flex;
border-top: 1px solid hsla(210,4%,10%,.14);
position: relative;
}
#PanelUI-footer-inner > toolbarseparator {
@ -460,7 +464,6 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
min-height: 2em;
-moz-appearance: none;
box-shadow: none;
background-image: none;
border: none;
border-radius: 0;
transition: background-color;
@ -552,6 +555,10 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
-moz-image-region: rect(0, 48px, 16px, 32px);
}
#PanelUI-help[panel-multiview-anchor="true"] {
-moz-image-region: rect(0, 64px, 16px, 48px);
}
#PanelUI-help[disabled],
#PanelUI-quit[disabled] {
opacity: 0.4;
@ -834,31 +841,47 @@ menupopup[placespopup=true][singleitempopup=true] > hbox > .popup-internal-box >
height: 16px;
}
#PanelUI-footer > #PanelUI-footer-inner[panel-multiview-anchor=true],
toolbarbutton[panel-multiview-anchor=true] {
toolbarbutton[panel-multiview-anchor="true"],
toolbarbutton[panel-multiview-anchor="true"] > .toolbarbutton-menubutton-button {
color: HighlightText;
background-color: Highlight;
background-image: linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
}
toolbarbutton[panel-multiview-anchor=true] {
#PanelUI-help[panel-multiview-anchor="true"] + toolbarseparator {
display: none;
}
#PanelUI-help[panel-multiview-anchor="true"] {
background-image: linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
background-position: 0;
}
#PanelUI-help[panel-multiview-anchor="true"]::after {
content: "";
position: absolute;
top: 0;
height: 100%;
width: @exitSubviewGutterWidth@;
background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted.png),
linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
background-repeat: no-repeat;
background-color: Highlight;
background-position: left 10px center, 0; /* this doesn't need to be changed for RTL */
}
toolbarbutton[panel-multiview-anchor="true"] {
background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted.png),
linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
background-position: right 5px center;
background-repeat: no-repeat;
background-repeat: no-repeat, repeat;
}
toolbarbutton[panel-multiview-anchor=true]:-moz-locale-dir(rtl) {
toolbarbutton[panel-multiview-anchor="true"]:-moz-locale-dir(rtl) {
background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl.png),
linear-gradient(rgba(255,255,255,0), rgba(255,255,255,0.3));
linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0));
background-position: left 5px center;
}
#PanelUI-footer > #PanelUI-footer-inner[panel-multiview-anchor=true],
toolbarbutton[panel-multiview-anchor=true],
toolbarbutton[panel-multiview-anchor=true] > .toolbarbutton-menubutton-button {
color: HighlightText;
}
toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker,
#bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menubutton-dropmarker {
display: none;

View File

@ -585,11 +585,6 @@ filefield {
-moz-padding-start: 36px;
}
#chooseFolder {
margin-top: 5px;
margin-bottom: 5px;
}
/* Applications Pane Styles */
#applications-content {
@ -699,17 +694,6 @@ description > html|a {
min-width: 5.5em;
}
/* Security Pane */
/* Add margins to this buttons to unsqueeze the checkboxed in same hbox */
#addonExceptions,
#cookieExceptions,
#passwordExceptions,
#changeMasterPassword {
margin-top: 4px;
margin-bottom: 5px;
}
/* Sync Pane */
#syncEnginesList {

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -591,6 +591,7 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-lwtheme-bri
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
padding: 8px 2px;
-moz-box-pack: center;
-moz-box-align: stretch;
}
#nav-bar #PanelUI-menu-button {
@ -2384,6 +2385,18 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
margin: 0px;
}
.translate-notification-icon,
#translate-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16.png);
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
.translated-notification-icon,
#translated-notification-icon {
list-style-image: url(chrome://browser/skin/translation-16.png);
-moz-image-region: rect(0px, 32px, 16px, 16px);
}
/* Bookmarks roots menu-items */
#subscribeToPageMenuitem:not([disabled]),
#subscribeToPageMenupopup,

View File

@ -206,6 +206,7 @@ browser.jar:
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png)
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
@ -367,6 +368,7 @@ browser.jar:
skin/classic/aero/browser/aboutSyncTabs.css
#endif
skin/classic/aero/browser/aboutTabCrashed.css
skin/classic/aero/browser/aboutWelcomeBack.css (../shared/aboutWelcomeBack.css)
skin/classic/aero/browser/actionicon-tab.png
* skin/classic/aero/browser/browser.css (browser-aero.css)
* skin/classic/aero/browser/browser-lightweightTheme.css
@ -554,6 +556,7 @@ browser.jar:
skin/classic/aero/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/aero/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png)
skin/classic/aero/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/aero/browser/translation-16.png (../shared/translation/translation-16.png)
* skin/classic/aero/browser/devtools/common.css (../shared/devtools/common.css)
* skin/classic/aero/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
* skin/classic/aero/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -4,18 +4,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MessagePort.h"
#include "MessageEvent.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/MessagePortList.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "nsGlobalWindow.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsPresContext.h"
#include "nsIDocument.h"
#include "nsIDOMFile.h"
#include "nsIDOMFileList.h"
#include "nsIDOMMessageEvent.h"
#include "nsIPresShell.h"
namespace mozilla {
@ -91,6 +92,7 @@ struct StructuredCloneInfo
{
PostMessageRunnable* mEvent;
MessagePort* mPort;
nsRefPtrHashtable<nsRefPtrHashKey<MessagePortBase>, MessagePortBase> mPorts;
};
static JSObject*
@ -100,9 +102,6 @@ PostMessageReadStructuredClone(JSContext* cx,
uint32_t data,
void* closure)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(closure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) {
NS_ASSERTION(!data, "Data should be empty");
@ -119,22 +118,6 @@ PostMessageReadStructuredClone(JSContext* cx,
}
}
if (tag == SCTAG_DOM_MESSAGEPORT) {
NS_ASSERTION(!data, "Data should be empty");
MessagePort* port;
if (JS_ReadBytes(reader, &port, sizeof(port))) {
JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
if (global) {
JS::Rooted<JSObject*> obj(cx, port->WrapObject(cx, global));
if (JS_WrapObject(cx, &obj)) {
port->BindToOwner(scInfo->mPort->GetOwner());
return obj;
}
}
}
}
const JSStructuredCloneCallbacks* runtimeCallbacks =
js::GetContextStructuredCloneCallbacks(cx);
@ -178,20 +161,6 @@ PostMessageWriteStructuredClone(JSContext* cx,
}
}
MessagePortBase* port = nullptr;
nsresult rv = UNWRAP_OBJECT(MessagePort, obj, port);
if (NS_SUCCEEDED(rv)) {
nsRefPtr<MessagePortBase> newPort = port->Clone();
if (!newPort) {
return false;
}
return JS_WriteUint32Pair(writer, SCTAG_DOM_MESSAGEPORT, 0) &&
JS_WriteBytes(writer, &newPort, sizeof(newPort)) &&
scInfo->mEvent->StoreISupports(newPort);
}
const JSStructuredCloneCallbacks* runtimeCallbacks =
js::GetContextStructuredCloneCallbacks(cx);
@ -202,14 +171,108 @@ PostMessageWriteStructuredClone(JSContext* cx,
return false;
}
static bool
PostMessageReadTransferStructuredClone(JSContext* aCx,
JSStructuredCloneReader* reader,
uint32_t tag, void* data,
uint64_t unused,
void* aClosure,
JS::MutableHandle<JSObject*> returnObject)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (tag == SCTAG_DOM_MAP_MESSAGEPORT) {
MessagePort* port = static_cast<MessagePort*>(data);
port->BindToOwner(scInfo->mPort->GetOwner());
scInfo->mPorts.Put(port, nullptr);
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
if (global) {
JS::Rooted<JSObject*> obj(aCx, port->WrapObject(aCx, global));
if (JS_WrapObject(aCx, &obj)) {
MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner());
returnObject.set(obj);
}
}
return true;
}
return false;
}
static bool
PostMessageTransferStructuredClone(JSContext* aCx,
JS::Handle<JSObject*> aObj,
void* aClosure,
uint32_t* aTag,
JS::TransferableOwnership* aOwnership,
void** aContent,
uint64_t *aExtraData)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
NS_ASSERTION(scInfo, "Must have scInfo!");
MessagePortBase *port = nullptr;
nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
if (NS_SUCCEEDED(rv)) {
nsRefPtr<MessagePortBase> newPort;
if (scInfo->mPorts.Get(port, getter_AddRefs(newPort))) {
// No duplicate.
return false;
}
newPort = port->Clone();
scInfo->mPorts.Put(port, newPort);
*aTag = SCTAG_DOM_MAP_MESSAGEPORT;
*aOwnership = JS::SCTAG_TMO_CUSTOM;
*aContent = newPort;
*aExtraData = 0;
return true;
}
return false;
}
static void
PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership,
void* aData,
uint64_t aExtraData,
void* aClosure)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
MOZ_ASSERT(aOwnership == JS::SCTAG_TMO_CUSTOM);
nsRefPtr<MessagePort> port(static_cast<MessagePort*>(aData));
scInfo->mPorts.Remove(port);
}
}
JSStructuredCloneCallbacks kPostMessageCallbacks = {
PostMessageReadStructuredClone,
PostMessageWriteStructuredClone,
nullptr
nullptr,
PostMessageReadTransferStructuredClone,
PostMessageTransferStructuredClone,
PostMessageFreeTransferStructuredClone
};
} // anonymous namespace
static PLDHashOperator
PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure)
{
nsTArray<nsRefPtr<MessagePortBase> > *array =
static_cast<nsTArray<nsRefPtr<MessagePortBase> > *>(aClosure);
array->AppendElement(aKey);
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
PostMessageRunnable::Run()
{
@ -226,45 +289,32 @@ PostMessageRunnable::Run()
// Deserialize the structured clone data
JS::Rooted<JS::Value> messageData(cx);
{
StructuredCloneInfo scInfo;
scInfo.mEvent = this;
scInfo.mPort = mPort;
StructuredCloneInfo scInfo;
scInfo.mEvent = this;
scInfo.mPort = mPort;
if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
// Create the event
nsIDocument* doc = mPort->GetOwner()->GetExtantDoc();
if (!doc) {
return NS_OK;
}
nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
do_QueryInterface(mPort->GetOwner());
nsRefPtr<MessageEvent> event =
new MessageEvent(eventTarget, nullptr, nullptr);
ErrorResult error;
nsRefPtr<Event> event =
doc->CreateEvent(NS_LITERAL_STRING("MessageEvent"), error);
if (error.Failed()) {
return NS_OK;
}
event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */,
false /* cancelable */, messageData, EmptyString(),
EmptyString(), nullptr);
event->SetTrusted(true);
event->SetSource(mPort);
nsCOMPtr<nsIDOMMessageEvent> message = do_QueryInterface(event);
nsresult rv = message->InitMessageEvent(NS_LITERAL_STRING("message"),
false /* non-bubbling */,
true /* cancelable */,
messageData,
EmptyString(),
EmptyString(),
mPort->GetOwner());
if (NS_FAILED(rv)) {
return NS_OK;
}
message->SetTrusted(true);
nsTArray<nsRefPtr<MessagePortBase> > ports;
scInfo.mPorts.EnumerateRead(PopulateMessagePortList, &ports);
event->SetPorts(new MessagePortList(static_cast<dom::Event*>(event.get()), ports));
bool status;
mPort->DispatchEvent(event, &status);
mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &status);
return status ? NS_OK : NS_ERROR_FAILURE;
}

View File

@ -28,7 +28,7 @@ enum StructuredCloneTags {
// These tags are used for both main thread and workers.
SCTAG_DOM_IMAGEDATA,
SCTAG_DOM_MESSAGEPORT,
SCTAG_DOM_MAP_MESSAGEPORT,
SCTAG_DOM_FUNCTION,

View File

@ -56,6 +56,7 @@
#include "nsLayoutStatics.h"
#include "nsCCUncollectableMarker.h"
#include "mozilla/dom/workers/Workers.h"
#include "mozilla/dom/MessagePortList.h"
#include "nsJSPrincipals.h"
#include "mozilla/Attributes.h"
#include "mozilla/Debug.h"
@ -63,6 +64,7 @@
#include "mozilla/EventStates.h"
#include "mozilla/MouseEvents.h"
#include "AudioChannelService.h"
#include "MessageEvent.h"
// Interfaces Needed
#include "nsIFrame.h"
@ -82,7 +84,6 @@
#include "nsIDOMDocument.h"
#include "nsIDOMElement.h"
#include "nsIDOMEvent.h"
#include "nsIDOMMessageEvent.h"
#include "nsIDOMPopupBlockedEvent.h"
#include "nsIDOMPopStateEvent.h"
#include "nsIDOMHashChangeEvent.h"
@ -7645,6 +7646,7 @@ struct StructuredCloneInfo {
PostMessageEvent* event;
bool subsumes;
nsPIDOMWindow* window;
nsRefPtrHashtable<nsRefPtrHashKey<MessagePortBase>, MessagePortBase> ports;
};
static JSObject*
@ -7654,9 +7656,6 @@ PostMessageReadStructuredClone(JSContext* cx,
uint32_t data,
void* closure)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(closure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) {
NS_ASSERTION(!data, "Data should be empty");
@ -7673,22 +7672,6 @@ PostMessageReadStructuredClone(JSContext* cx,
}
}
if (MessageChannel::PrefEnabled() && tag == SCTAG_DOM_MESSAGEPORT) {
NS_ASSERTION(!data, "Data should be empty");
MessagePortBase* port;
if (JS_ReadBytes(reader, &port, sizeof(port))) {
JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
if (global) {
JS::Rooted<JSObject*> obj(cx, port->WrapObject(cx, global));
if (JS_WrapObject(cx, &obj)) {
port->BindToOwner(scInfo->window);
return obj;
}
}
}
}
const JSStructuredCloneCallbacks* runtimeCallbacks =
js::GetContextStructuredCloneCallbacks(cx);
@ -7729,22 +7712,6 @@ PostMessageWriteStructuredClone(JSContext* cx,
scInfo->event->StoreISupports(supports);
}
if (MessageChannel::PrefEnabled()) {
MessagePortBase* port = nullptr;
nsresult rv = UNWRAP_OBJECT(MessagePort, obj, port);
if (NS_SUCCEEDED(rv) && scInfo->subsumes) {
nsRefPtr<MessagePortBase> newPort = port->Clone();
if (!newPort) {
return false;
}
return JS_WriteUint32Pair(writer, SCTAG_DOM_MESSAGEPORT, 0) &&
JS_WriteBytes(writer, &newPort, sizeof(newPort)) &&
scInfo->event->StoreISupports(newPort);
}
}
const JSStructuredCloneCallbacks* runtimeCallbacks =
js::GetContextStructuredCloneCallbacks(cx);
@ -7755,14 +7722,108 @@ PostMessageWriteStructuredClone(JSContext* cx,
return false;
}
static bool
PostMessageReadTransferStructuredClone(JSContext* aCx,
JSStructuredCloneReader* reader,
uint32_t tag, void* aData,
uint64_t aExtraData,
void* aClosure,
JS::MutableHandle<JSObject*> returnObject)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (MessageChannel::PrefEnabled() && tag == SCTAG_DOM_MAP_MESSAGEPORT) {
MessagePort* port = static_cast<MessagePort*>(aData);
port->BindToOwner(scInfo->window);
scInfo->ports.Put(port, nullptr);
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
if (global) {
JS::Rooted<JSObject*> obj(aCx, port->WrapObject(aCx, global));
if (JS_WrapObject(aCx, &obj)) {
MOZ_ASSERT(port->GetOwner() == scInfo->window);
returnObject.set(obj);
}
}
return true;
}
return false;
}
static bool
PostMessageTransferStructuredClone(JSContext* aCx,
JS::Handle<JSObject*> aObj,
void* aClosure,
uint32_t* aTag,
JS::TransferableOwnership* aOwnership,
void** aContent,
uint64_t* aExtraData)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (MessageChannel::PrefEnabled()) {
MessagePortBase* port = nullptr;
nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
if (NS_SUCCEEDED(rv)) {
nsRefPtr<MessagePortBase> newPort;
if (scInfo->ports.Get(port, getter_AddRefs(newPort))) {
// No duplicate.
return false;
}
newPort = port->Clone();
scInfo->ports.Put(port, newPort);
*aTag = SCTAG_DOM_MAP_MESSAGEPORT;
*aOwnership = JS::SCTAG_TMO_CUSTOM;
*aContent = newPort;
*aExtraData = 0;
return true;
}
}
return false;
}
void
PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership,
void *aContent, uint64_t aExtraData, void* aClosure)
{
StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
NS_ASSERTION(scInfo, "Must have scInfo!");
if (MessageChannel::PrefEnabled() && aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
nsRefPtr<MessagePortBase> port(static_cast<MessagePort*>(aContent));
scInfo->ports.Remove(port);
}
}
JSStructuredCloneCallbacks kPostMessageCallbacks = {
PostMessageReadStructuredClone,
PostMessageWriteStructuredClone,
nullptr
nullptr,
PostMessageReadTransferStructuredClone,
PostMessageTransferStructuredClone,
PostMessageFreeTransferStructuredClone
};
} // anonymous namespace
static PLDHashOperator
PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure)
{
nsTArray<nsRefPtr<MessagePortBase> > *array =
static_cast<nsTArray<nsRefPtr<MessagePortBase> > *>(aClosure);
array->AppendElement(aKey);
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
PostMessageEvent::Run()
{
@ -7817,38 +7878,27 @@ PostMessageEvent::Run()
// Deserialize the structured clone data
JS::Rooted<JS::Value> messageData(cx);
{
StructuredCloneInfo scInfo;
scInfo.event = this;
scInfo.window = targetWindow;
StructuredCloneInfo scInfo;
scInfo.event = this;
scInfo.window = targetWindow;
if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
// Create the event
nsIDocument* doc = targetWindow->mDoc;
if (!doc)
return NS_OK;
nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
do_QueryInterface(static_cast<nsPIDOMWindow*>(targetWindow.get()));
nsRefPtr<MessageEvent> event =
new MessageEvent(eventTarget, nullptr, nullptr);
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
nsCOMPtr<nsIDOMEvent> event;
domDoc->CreateEvent(NS_LITERAL_STRING("MessageEvent"), getter_AddRefs(event));
if (!event)
return NS_OK;
nsCOMPtr<nsIDOMMessageEvent> message = do_QueryInterface(event);
nsresult rv = message->InitMessageEvent(NS_LITERAL_STRING("message"),
false /* non-bubbling */,
true /* cancelable */,
messageData,
mCallerOrigin,
EmptyString(),
mSource);
if (NS_FAILED(rv))
return NS_OK;
event->InitMessageEvent(NS_LITERAL_STRING("message"), false /*non-bubbling */,
false /*cancelable */, messageData, mCallerOrigin,
EmptyString(), mSource);
nsTArray<nsRefPtr<MessagePortBase> > ports;
scInfo.ports.EnumerateRead(PopulateMessagePortList, &ports);
event->SetPorts(new MessagePortList(static_cast<dom::Event*>(event.get()), ports));
// We can't simply call dispatchEvent on the window because doing so ends
// up flipping the trusted bit on the event, and we don't want that to
@ -7860,14 +7910,14 @@ PostMessageEvent::Run()
if (shell)
presContext = shell->GetPresContext();
message->SetTrusted(mTrustedCaller);
WidgetEvent* internalEvent = message->GetInternalNSEvent();
event->SetTrusted(mTrustedCaller);
WidgetEvent* internalEvent = event->GetInternalNSEvent();
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(static_cast<nsPIDOMWindow*>(mTargetWindow),
presContext,
internalEvent,
message,
static_cast<dom::Event*>(event.get()),
&status);
return NS_OK;
}

View File

@ -2881,7 +2881,10 @@ nsJSContext::EnsureStatics()
static JSStructuredCloneCallbacks cloneCallbacks = {
NS_DOMReadStructuredClone,
NS_DOMWriteStructuredClone,
NS_DOMStructuredCloneError
NS_DOMStructuredCloneError,
nullptr,
nullptr,
nullptr
};
JS_SetStructuredCloneCallbacks(sRuntime, &cloneCallbacks);

View File

@ -64,7 +64,7 @@ nsStructuredCloneContainer::InitFromJSVal(JS::Handle<JS::Value> aData,
mSize = 0;
mVersion = 0;
JS_ClearStructuredClone(jsBytes, mSize);
JS_ClearStructuredClone(jsBytes, mSize, nullptr, nullptr);
return NS_ERROR_FAILURE;
}
else {
@ -73,7 +73,7 @@ nsStructuredCloneContainer::InitFromJSVal(JS::Handle<JS::Value> aData,
memcpy(mData, jsBytes, mSize);
JS_ClearStructuredClone(jsBytes, mSize);
JS_ClearStructuredClone(jsBytes, mSize, nullptr, nullptr);
return NS_OK;
}

View File

@ -13,7 +13,7 @@
ok (evt.data.port instanceof MessagePort, "Data contains a MessagePort");
var a = new MessageChannel();
window.parent.postMessage({ status: "FINISH", port: a.port2 }, '*');
window.parent.postMessage({ status: "FINISH", port: a.port2 }, '*', [a.port2]);
}
</script>

View File

@ -16,7 +16,7 @@
if (counter++ == 0) {
ok(!(evt.data % 2), "The number " + evt.data + " has been received correctly by the iframe");
window.parent.postMessage({ type: 'PORT', port: port }, '*');
window.parent.postMessage({ type: 'PORT', port: port }, '*', [port]);
}
else {
ok(false, "Wrong message!");

View File

@ -33,7 +33,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=913761
}
}
transportChannel.port2.postMessage({ port: serviceChannel.port2} /* TODO: [serviceChannel.port2] */);
transportChannel.port2.postMessage({ port: serviceChannel.port2}, [serviceChannel.port2]);
}
SimpleTest.waitForExplicitFinish();

View File

@ -45,7 +45,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
div.appendChild(ifr);
function iframeLoaded() {
ifr.contentWindow.postMessage({ port: a.port2 }, '*');
ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]);
}
}

View File

@ -40,7 +40,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
ok(evt.data % 2, "The number " + evt.data + " has been received correctly by the main window");
if (evt.data < MAX - 1) {
ifr.contentWindow.postMessage({ type: 'PORT', port: port }, '*');
ifr.contentWindow.postMessage({ type: 'PORT', port: port }, '*', [port]);
} else {
SimpleTest.finish();
}
@ -66,7 +66,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
div.appendChild(ifr);
function iframeLoaded() {
ifr.contentWindow.postMessage({ type: 'PORT', port: a.port2 }, '*');
ifr.contentWindow.postMessage({ type: 'PORT', port: a.port2 }, '*', [a.port2]);
}
}

View File

@ -38,7 +38,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
div.appendChild(ifr);
function iframeLoaded() {
ifr.contentWindow.postMessage({ port: a.port2 }, '*');
ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]);
}
var tests = [ 42,

View File

@ -213,7 +213,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
}
}
postMessage(a.port2, '*');
postMessage(a.port2, '*', [a.port2]);
}
var tests = [

View File

@ -38,7 +38,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
div.appendChild(ifr);
function iframeLoaded() {
ifr.contentWindow.postMessage({ port: a.port2 }, '*');
ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]);
}
a.port1.addEventListener('message', receivePortMessage, false);

View File

@ -0,0 +1,115 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=912456
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 912456 - port cloning</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912456">Mozilla Bug 912456</a>
<script type="application/javascript">
function testTransfer() {
var a = new MessageChannel();
ok(a, "MessageChannel created");
window.addEventListener('message', receiveMessage, false);
function receiveMessage(evt) {
ok(evt.data.port, "Port has been received!");
var a = new MessageChannel();
ok(a, "MessageChannel created");
try {
evt.data.port.postMessage({port: a.port2});
ok(false, "PostMessage should throw! - no transfered port");
} catch(e) {
ok(true, "PostMessage should throw! - no transfered port");
}
try {
evt.data.port.postMessage({port: a.port2}, [a.port2, a.port2]);
ok(false, "PostMessage should throw - no duplicate!");
} catch(e) {
ok(true, "PostMessage should throw - no duplicate!");
}
evt.data.port.postMessage({port: a.port2}, [a.port2]);
}
a.port1.onmessage = function(evt) {
ok(evt.data.port, "Port has been received!");
window.removeEventListener('message', receiveMessage);
runTest();
}
try {
postMessage({ port: a.port2}, 42, '*');
ok(false, "PostMessage should throw! - no transfered port");
} catch(e) {
ok(true, "PostMessage should throw! - no transfered port");
}
try {
postMessage({ port: a.port2}, 42, '*', [a.port2, a.port2]);
ok(false, "PostMessage should throw - no duplicate!");
} catch(e) {
ok(true, "PostMessage should throw - no duplicate!");
}
postMessage({port: a.port2}, '*', [a.port2]);
}
function testPorts() {
var a = new MessageChannel();
ok(a, "MessageChannel created");
window.addEventListener('message', receiveMessage, false);
function receiveMessage(evt) {
ok(evt.data, "Data is 42");
ok(evt.ports, "Port is received");
is(evt.ports.length, 1, "Ports.length is 1");
var a = new MessageChannel();
ok(a, "MessageChannel created");
evt.ports[0].postMessage(42, [a.port2]);
}
a.port1.onmessage = function(evt) {
ok(evt.data, "Data is 42");
ok(evt.ports, "Port is received");
is(evt.ports.length, 1, "Ports.length is 1");
window.removeEventListener('message', receiveMessage);
runTest();
}
postMessage(42, '*', [a.port2]);
}
var tests = [
testTransfer,
testPorts
];
function runTest() {
if (!tests.length) {
SimpleTest.finish();
return;
}
var test = tests.shift();
test();
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["dom.messageChannel.enabled", true]]}, runTest);
</script>
</body>
</html>

View File

@ -9,6 +9,7 @@
#include "mozilla/dom/Event.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMMessageEvent.h"
#include "mozilla/dom/MessagePortList.h"
namespace mozilla {
namespace dom {
@ -57,6 +58,17 @@ public:
void SetPorts(MessagePortList* aPorts);
// Non WebIDL methods
void SetSource(mozilla::dom::MessagePort* aPort)
{
mPortSource = aPort;
}
void SetSource(nsPIDOMWindow* aWindow)
{
mWindowSource = aWindow;
}
static already_AddRefed<MessageEvent>
Constructor(const GlobalObject& aGlobal, JSContext* aCx,
const nsAString& aType,

Some files were not shown because too many files have changed in this diff Show More