Merge m-c to b2g-inbound
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -37,7 +37,7 @@ BRANDING_FILES := \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
|
||||
ifdef MOZ_WIDGET_GTK
|
||||
BRANDING_FILES := \
|
||||
default16.png \
|
||||
default32.png \
|
||||
|
@ -37,7 +37,7 @@ BRANDING_FILES := \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
|
||||
ifdef MOZ_WIDGET_GTK
|
||||
BRANDING_FILES := \
|
||||
default16.png \
|
||||
default32.png \
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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"
|
||||
|
134
browser/components/translation/Translation.jsm
Normal 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();
|
||||
}
|
||||
};
|
@ -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']
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
@ -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"]}
|
||||
});
|
||||
}());
|
||||
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
@ -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"
|
||||
});
|
||||
})();
|
||||
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
@ -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");
|
||||
|
||||
});
|
||||
|
@ -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 });
|
||||
|
||||
});
|
||||
|
@ -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("-");
|
||||
})();
|
||||
});
|
||||
|
505
browser/devtools/sourceeditor/codemirror/keymap/sublime.js
vendored
Normal 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";
|
||||
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
}};
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
@ -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);};
|
||||
})();
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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});
|
||||
|
||||
});
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
348
browser/devtools/sourceeditor/test/cm_doc_test.js
Normal 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");
|
||||
});
|
||||
})();
|
@ -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;
|
||||
|
@ -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"]',
|
||||
'}');
|
||||
})();
|
||||
|
285
browser/devtools/sourceeditor/test/cm_multi_test.js
Normal 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"});
|
||||
})();
|
62
browser/devtools/sourceeditor/test/cm_search_test.js
Normal 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);
|
||||
});
|
||||
})();
|
297
browser/devtools/sourceeditor/test/cm_sublime_test.js
Normal 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));
|
||||
})();
|
@ -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"});
|
||||
|
@ -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' });
|
||||
|
@ -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>
|
||||
-->
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -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]
|
||||
|
@ -0,0 +1,3 @@
|
||||
div {
|
||||
opacity: 1;
|
||||
}
|
@ -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");
|
||||
|
@ -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]
|
||||
|
@ -222,7 +222,7 @@ function test()
|
||||
|
||||
checkToolboxUI();
|
||||
|
||||
testDestroy();
|
||||
toolbox.switchHost(Toolbox.HostType.BOTTOM).then(testDestroy);
|
||||
}
|
||||
|
||||
function checkHostType(hostType)
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.");
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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");
|
||||
|
||||
/*=============================================================================
|
||||
|
@ -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);
|
||||
|
@ -247,7 +247,7 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
|
||||
|
||||
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
|
||||
};
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.9 KiB |
@ -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 {
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 6.0 KiB |
@ -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;
|
||||
|
@ -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 {
|
||||
|
BIN
browser/themes/shared/translation/translation-16.png
Normal file
After Width: | Height: | Size: 889 B |
BIN
browser/themes/shared/translation/translation-16@2x.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
@ -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,
|
||||
|
@ -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)
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.9 KiB |
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -2881,7 +2881,10 @@ nsJSContext::EnsureStatics()
|
||||
static JSStructuredCloneCallbacks cloneCallbacks = {
|
||||
NS_DOMReadStructuredClone,
|
||||
NS_DOMWriteStructuredClone,
|
||||
NS_DOMStructuredCloneError
|
||||
NS_DOMStructuredCloneError,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
};
|
||||
JS_SetStructuredCloneCallbacks(sRuntime, &cloneCallbacks);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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!");
|
||||
|
@ -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();
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -213,7 +213,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638
|
||||
}
|
||||
}
|
||||
|
||||
postMessage(a.port2, '*');
|
||||
postMessage(a.port2, '*', [a.port2]);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
|
@ -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);
|
||||
|
115
dom/base/test/test_messagePort.html
Normal 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>
|
||||
|
@ -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,
|
||||
|