зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central into mozilla-inbound.
This commit is contained in:
Коммит
be36ebc4ba
|
@ -1100,12 +1100,9 @@ pref("devtools.editor.expandtab", true);
|
||||||
// Tells which component you want to use for source editing in developer tools.
|
// Tells which component you want to use for source editing in developer tools.
|
||||||
//
|
//
|
||||||
// Available components:
|
// Available components:
|
||||||
// "textarea" - this is a basic text editor, like an HTML <textarea>.
|
|
||||||
//
|
|
||||||
// "orion" - this is the Orion source code editor from the Eclipse project. It
|
// "orion" - this is the Orion source code editor from the Eclipse project. It
|
||||||
// provides programmer-specific editor features such as syntax highlighting,
|
// provides programmer-specific editor features such as syntax highlighting,
|
||||||
// indenting and bracket recognition. It may not be appropriate for all
|
// indenting and bracket recognition.
|
||||||
// locales (esp. RTL) or a11y situations.
|
|
||||||
pref("devtools.editor.component", "orion");
|
pref("devtools.editor.component", "orion");
|
||||||
|
|
||||||
// Whether the character encoding menu is under the main Firefox button. This
|
// Whether the character encoding menu is under the main Firefox button. This
|
||||||
|
|
|
@ -86,7 +86,8 @@ var gLastValidURLStr = "";
|
||||||
var gInPrintPreviewMode = false;
|
var gInPrintPreviewMode = false;
|
||||||
var gDownloadMgr = null;
|
var gDownloadMgr = null;
|
||||||
var gContextMenu = null; // nsContextMenu instance
|
var gContextMenu = null; // nsContextMenu instance
|
||||||
var gDelayedStartupTimeoutId;
|
var gDelayedStartupTimeoutId; // used for non-browser-windows
|
||||||
|
var gFirstPaintListener = null;
|
||||||
var gStartupRan = false;
|
var gStartupRan = false;
|
||||||
|
|
||||||
#ifndef XP_MACOSX
|
#ifndef XP_MACOSX
|
||||||
|
@ -1383,7 +1384,18 @@ function BrowserStartup() {
|
||||||
|
|
||||||
retrieveToolbarIconsizesFromTheme();
|
retrieveToolbarIconsizesFromTheme();
|
||||||
|
|
||||||
gDelayedStartupTimeoutId = setTimeout(delayedStartup, 0, isLoadingBlank, mustLoadSidebar);
|
// Listen for the first paint event for this window, and only do non-critical
|
||||||
|
// work then, so that things like session restore don't block the window from
|
||||||
|
// being visible.
|
||||||
|
gFirstPaintListener = function(e) {
|
||||||
|
if (e.target == window) {
|
||||||
|
window.removeEventListener("MozAfterPaint", gFirstPaintListener, false);
|
||||||
|
gFirstPaintListener = null;
|
||||||
|
delayedStartup(isLoadingBlank, mustLoadSidebar);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("MozAfterPaint", gFirstPaintListener, false);
|
||||||
|
|
||||||
gStartupRan = true;
|
gStartupRan = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1514,7 +1526,6 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) {
|
||||||
Cu.import("resource:///modules/TelemetryTimestamps.jsm", tmp);
|
Cu.import("resource:///modules/TelemetryTimestamps.jsm", tmp);
|
||||||
let TelemetryTimestamps = tmp.TelemetryTimestamps;
|
let TelemetryTimestamps = tmp.TelemetryTimestamps;
|
||||||
TelemetryTimestamps.add("delayedStartupStarted");
|
TelemetryTimestamps.add("delayedStartupStarted");
|
||||||
gDelayedStartupTimeoutId = null;
|
|
||||||
|
|
||||||
Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
|
Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
|
||||||
Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
|
Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
|
||||||
|
@ -1867,8 +1878,9 @@ function BrowserShutdown() {
|
||||||
|
|
||||||
// Now either cancel delayedStartup, or clean up the services initialized from
|
// Now either cancel delayedStartup, or clean up the services initialized from
|
||||||
// it.
|
// it.
|
||||||
if (gDelayedStartupTimeoutId) {
|
if (gFirstPaintListener) {
|
||||||
clearTimeout(gDelayedStartupTimeoutId);
|
window.removeEventListener("MozAfterPaint", gFirstPaintListener, false);
|
||||||
|
gFirstPaintListener = null;
|
||||||
} else {
|
} else {
|
||||||
if (Win7Features)
|
if (Win7Features)
|
||||||
Win7Features.onCloseWindow();
|
Win7Features.onCloseWindow();
|
||||||
|
|
|
@ -87,10 +87,11 @@ let gPage = {
|
||||||
// Initialize the drop target shim.
|
// Initialize the drop target shim.
|
||||||
gDropTargetShim.init();
|
gDropTargetShim.init();
|
||||||
|
|
||||||
|
#ifdef XP_MACOSX
|
||||||
// Workaround to prevent a delay on MacOSX due to a slow drop animation.
|
// Workaround to prevent a delay on MacOSX due to a slow drop animation.
|
||||||
let doc = document.documentElement;
|
document.addEventListener("dragover", this.onDragOver, false);
|
||||||
doc.addEventListener("dragover", this.onDragOver, false);
|
document.addEventListener("drop", this.onDrop, false);
|
||||||
doc.addEventListener("drop", this.onDrop, false);
|
#endif
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ let gPage = {
|
||||||
* @param aEvent The 'dragover' event.
|
* @param aEvent The 'dragover' event.
|
||||||
*/
|
*/
|
||||||
onDragOver: function Page_onDragOver(aEvent) {
|
onDragOver: function Page_onDragOver(aEvent) {
|
||||||
if (gDrag.isValid(aEvent))
|
if (gDrag.isValid(aEvent) && gDrag.draggedSite)
|
||||||
aEvent.preventDefault();
|
aEvent.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -165,7 +166,7 @@ let gPage = {
|
||||||
* @param aEvent The 'drop' event.
|
* @param aEvent The 'drop' event.
|
||||||
*/
|
*/
|
||||||
onDrop: function Page_onDrop(aEvent) {
|
onDrop: function Page_onDrop(aEvent) {
|
||||||
if (gDrag.isValid(aEvent)) {
|
if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
|
||||||
aEvent.preventDefault();
|
aEvent.preventDefault();
|
||||||
aEvent.stopPropagation();
|
aEvent.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,15 +322,19 @@ function newWindowWithState(state, callback) {
|
||||||
callback(win);
|
callback(win);
|
||||||
};
|
};
|
||||||
|
|
||||||
whenWindowLoaded(win, function () {
|
whenDelayedStartupFinished(win, function () {
|
||||||
whenWindowStateReady(win, function () {
|
ss.setWindowState(win, JSON.stringify(state), true);
|
||||||
afterAllTabsLoaded(check, win);
|
win.close();
|
||||||
|
win = ss.undoCloseWindow(0);
|
||||||
|
|
||||||
|
whenWindowLoaded(win, function () {
|
||||||
|
whenWindowStateReady(win, function () {
|
||||||
|
afterAllTabsLoaded(check, win);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ss.setWindowState(win, JSON.stringify(state), true);
|
whenDelayedStartupFinished(win, check);
|
||||||
});
|
});
|
||||||
|
|
||||||
whenDelayedStartupFinished(win, check);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
|
|
|
@ -49,7 +49,6 @@ TEST_DIRS += test
|
||||||
EXTRA_JS_MODULES = \
|
EXTRA_JS_MODULES = \
|
||||||
source-editor.jsm \
|
source-editor.jsm \
|
||||||
source-editor-orion.jsm \
|
source-editor-orion.jsm \
|
||||||
source-editor-textarea.jsm \
|
|
||||||
source-editor-ui.jsm \
|
source-editor-ui.jsm \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
|
|
@ -1,879 +0,0 @@
|
||||||
/* vim:set ft=javascript ts=2 sw=2 sts=2 et tw=80:
|
|
||||||
* ***** BEGIN LICENSE BLOCK *****
|
|
||||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License Version
|
|
||||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
||||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing rights and limitations under the
|
|
||||||
* License.
|
|
||||||
*
|
|
||||||
* The Original Code is the Source Editor component (textarea fallback).
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is
|
|
||||||
* The Mozilla Foundation.
|
|
||||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
|
||||||
* the Initial Developer. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
* Mihai Sucan <mihai.sucan@gmail.com> (original author)
|
|
||||||
* Kenny Heaton <kennyheaton@gmail.com>
|
|
||||||
*
|
|
||||||
* Alternatively, the contents of this file may be used under the terms of
|
|
||||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
||||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
||||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
||||||
* of those above. If you wish to allow use of your version of this file only
|
|
||||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
||||||
* use your version of this file under the terms of the MPL, indicate your
|
|
||||||
* decision by deleting the provisions above and replace them with the notice
|
|
||||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
||||||
* the provisions above, a recipient may use your version of this file under
|
|
||||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
||||||
*
|
|
||||||
* ***** END LICENSE BLOCK *****/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const Cu = Components.utils;
|
|
||||||
const Ci = Components.interfaces;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
||||||
Cu.import("resource:///modules/source-editor-ui.jsm");
|
|
||||||
|
|
||||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default key bindings in the textarea editor.
|
|
||||||
*/
|
|
||||||
const DEFAULT_KEYBINDINGS = [
|
|
||||||
{
|
|
||||||
_action: "_doTab",
|
|
||||||
keyCode: Ci.nsIDOMKeyEvent.DOM_VK_TAB,
|
|
||||||
shiftKey: false,
|
|
||||||
accelKey: false,
|
|
||||||
altKey: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["SourceEditor"];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The SourceEditor object constructor. The SourceEditor component allows you to
|
|
||||||
* provide users with an editor tailored to the specific needs of editing source
|
|
||||||
* code, aimed primarily at web developers.
|
|
||||||
*
|
|
||||||
* The editor used here is a simple textarea. This is used as a fallback
|
|
||||||
* mechanism for when the user disables the code editor feature.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function SourceEditor() {
|
|
||||||
// Update the SourceEditor defaults from user preferences.
|
|
||||||
|
|
||||||
SourceEditor.DEFAULTS.TAB_SIZE =
|
|
||||||
Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
|
|
||||||
SourceEditor.DEFAULTS.EXPAND_TAB =
|
|
||||||
Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
|
|
||||||
|
|
||||||
this._listeners = {};
|
|
||||||
this._lastSelection = {};
|
|
||||||
|
|
||||||
this.ui = new SourceEditorUI(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
SourceEditor.prototype = {
|
|
||||||
_textbox: null,
|
|
||||||
_editor: null,
|
|
||||||
_listeners: null,
|
|
||||||
_lineDelimiter: null,
|
|
||||||
_editActionListener: null,
|
|
||||||
_expandTab: null,
|
|
||||||
_tabSize: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Source Editor user interface manager.
|
|
||||||
* @type object
|
|
||||||
* An instance of the SourceEditorUI.
|
|
||||||
*/
|
|
||||||
ui: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The editor container element.
|
|
||||||
* @type nsIDOMElement
|
|
||||||
*/
|
|
||||||
parentElement: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the editor.
|
|
||||||
*
|
|
||||||
* @param nsIDOMElement aElement
|
|
||||||
* The DOM element where you want the editor to show.
|
|
||||||
* @param object aConfig
|
|
||||||
* Editor configuration object. Properties:
|
|
||||||
* - placeholderText - the text you want to be shown by default.
|
|
||||||
* - mode - the editor mode, based on the file type you want to edit.
|
|
||||||
* You can use one of the predefined modes.
|
|
||||||
* - tabSize - define how many spaces to use for a tab character.
|
|
||||||
* - expandTab - tells if you want tab characters to be expanded to
|
|
||||||
* spaces.
|
|
||||||
* - readOnly - make the editor read only.
|
|
||||||
* - undoLimit - how many steps should the undo stack hold.
|
|
||||||
* @param function [aCallback]
|
|
||||||
* Function you want to execute once the editor is loaded and
|
|
||||||
* initialized.
|
|
||||||
*/
|
|
||||||
init: function SE_init(aElement, aConfig, aCallback)
|
|
||||||
{
|
|
||||||
if (this._textbox) {
|
|
||||||
throw new Error("SourceEditor is already initialized!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc = aElement.ownerDocument;
|
|
||||||
let win = doc.defaultView;
|
|
||||||
|
|
||||||
this._textbox = doc.createElementNS(XUL_NS, "textbox");
|
|
||||||
this._textbox.flex = 1;
|
|
||||||
this._textbox.setAttribute("multiline", true);
|
|
||||||
this._textbox.setAttribute("dir", "ltr");
|
|
||||||
|
|
||||||
aElement.appendChild(this._textbox);
|
|
||||||
|
|
||||||
this.parentElement = aElement;
|
|
||||||
this._editor = this._textbox.editor;
|
|
||||||
|
|
||||||
this._expandTab = aConfig.expandTab !== undefined ?
|
|
||||||
aConfig.expandTab : SourceEditor.DEFAULTS.EXPAND_TAB;
|
|
||||||
this._tabSize = aConfig.tabSize || SourceEditor.DEFAULTS.TAB_SIZE;
|
|
||||||
|
|
||||||
this._textbox.style.MozTabSize = this._tabSize;
|
|
||||||
|
|
||||||
this._textbox.setAttribute("value", aConfig.placeholderText || "");
|
|
||||||
this._textbox.setAttribute("class", "monospace");
|
|
||||||
this._textbox.style.direction = "ltr";
|
|
||||||
this._textbox.readOnly = aConfig.readOnly;
|
|
||||||
|
|
||||||
// Make sure that the SourceEditor Selection events are fired properly.
|
|
||||||
// Also make sure that the configured keyboard bindings work.
|
|
||||||
this._textbox.addEventListener("select", this._onSelect.bind(this), false);
|
|
||||||
this._textbox.addEventListener("keypress", this._onKeyPress.bind(this), false);
|
|
||||||
this._textbox.addEventListener("keyup", this._onSelect.bind(this), false);
|
|
||||||
this._textbox.addEventListener("click", this._onSelect.bind(this), false);
|
|
||||||
|
|
||||||
// Mimic the mode change.
|
|
||||||
this.setMode(aConfig.mode || SourceEditor.DEFAULTS.MODE);
|
|
||||||
|
|
||||||
this._editor.transactionManager.maxTransactionCount =
|
|
||||||
aConfig.undoLimit || SourceEditor.DEFAULTS.UNDO_LIMIT;
|
|
||||||
|
|
||||||
// Make sure that the transactions stack is clean.
|
|
||||||
this.resetUndo();
|
|
||||||
|
|
||||||
// Add the edit action listener so we can fire the SourceEditor TextChanged
|
|
||||||
// events.
|
|
||||||
this._editActionListener = new EditActionListener(this);
|
|
||||||
this._editor.addEditActionListener(this._editActionListener);
|
|
||||||
|
|
||||||
this._lineDelimiter = win.navigator.platform.indexOf("Win") > -1 ?
|
|
||||||
"\r\n" : "\n";
|
|
||||||
|
|
||||||
this._config = aConfig;
|
|
||||||
|
|
||||||
for each (let key in DEFAULT_KEYBINDINGS) {
|
|
||||||
for (let prop in key) {
|
|
||||||
if (prop == "accelKey") {
|
|
||||||
let newProp = Services.appinfo.OS == "Darwin" ? "metaKey" : "ctrlKey";
|
|
||||||
key[newProp] = key[prop];
|
|
||||||
delete key[prop];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ui.init();
|
|
||||||
this.ui.onReady();
|
|
||||||
|
|
||||||
if (aCallback) {
|
|
||||||
aCallback(this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The textbox keypress event handler calls the configured action for keyboard
|
|
||||||
* event.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param nsIDOMEvent aEvent
|
|
||||||
* The DOM object for the event.
|
|
||||||
* @see DEFAULT_KEYBINDINGS
|
|
||||||
*/
|
|
||||||
_onKeyPress: function SE__onKeyPress(aEvent)
|
|
||||||
{
|
|
||||||
for each (let key in DEFAULT_KEYBINDINGS) {
|
|
||||||
let matched = true;
|
|
||||||
for (let prop in key) {
|
|
||||||
if (prop.charAt(0) != "_" && aEvent[prop] !== key[prop]) {
|
|
||||||
matched = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (matched) {
|
|
||||||
let context = key._context ? this[key._context] : this;
|
|
||||||
context[key._action].call(context);
|
|
||||||
aEvent.preventDefault();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Tab keypress event handler. This allows the user to indent the code
|
|
||||||
* with spaces, when expandTab is true.
|
|
||||||
*/
|
|
||||||
_doTab: function SE__doTab()
|
|
||||||
{
|
|
||||||
let selection = this.getSelection();
|
|
||||||
let caret = selection.start;
|
|
||||||
let indent = "\t";
|
|
||||||
|
|
||||||
if (this._expandTab) {
|
|
||||||
let text = this._textbox.value;
|
|
||||||
let lineStart = caret;
|
|
||||||
while (lineStart > 0) {
|
|
||||||
let c = text.charAt(lineStart - 1);
|
|
||||||
if (c == "\r" || c == "\n") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lineStart--;
|
|
||||||
}
|
|
||||||
let offset = caret - lineStart;
|
|
||||||
let spaces = this._tabSize - (offset % this._tabSize);
|
|
||||||
indent = (new Array(spaces + 1)).join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setText(indent, selection.start, selection.end);
|
|
||||||
this.setCaretOffset(selection.start + indent.length);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The textbox keyup, click and select event handler tracks selection
|
|
||||||
* changes. This method invokes the SourceEditor Selection event handlers.
|
|
||||||
*
|
|
||||||
* @see SourceEditor.EVENTS.SELECTION
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onSelect: function SE__onSelect()
|
|
||||||
{
|
|
||||||
let selection = this.getSelection();
|
|
||||||
selection.collapsed = selection.start == selection.end;
|
|
||||||
if (selection.collapsed && this._lastSelection.collapsed) {
|
|
||||||
this._lastSelection = selection;
|
|
||||||
return; // just a cursor move.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._lastSelection.start != selection.start ||
|
|
||||||
this._lastSelection.end != selection.end) {
|
|
||||||
let sendEvent = {
|
|
||||||
oldValue: {start: this._lastSelection.start,
|
|
||||||
end: this._lastSelection.end},
|
|
||||||
newValue: {start: selection.start, end: selection.end},
|
|
||||||
};
|
|
||||||
|
|
||||||
let listeners = this._listeners[SourceEditor.EVENTS.SELECTION] || [];
|
|
||||||
listeners.forEach(function(aListener) {
|
|
||||||
aListener.callback.call(null, sendEvent);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this._lastSelection = selection;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The TextChanged event dispatcher. This method is called when a change in
|
|
||||||
* the text occurs. All of the SourceEditor TextChanged event handlers are
|
|
||||||
* notified about the lower level change.
|
|
||||||
*
|
|
||||||
* @see SourceEditor.EVENTS.TEXT_CHANGED
|
|
||||||
* @see EditActionListener
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param object aEvent
|
|
||||||
* The TextChanged event object that is going to be sent to the
|
|
||||||
* SourceEditor event handlers.
|
|
||||||
*/
|
|
||||||
_onTextChanged: function SE__onTextChanged(aEvent)
|
|
||||||
{
|
|
||||||
let listeners = this._listeners[SourceEditor.EVENTS.TEXT_CHANGED] || [];
|
|
||||||
listeners.forEach(function(aListener) {
|
|
||||||
aListener.callback.call(null, aEvent);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the editor element.
|
|
||||||
*
|
|
||||||
* @return nsIDOMElement
|
|
||||||
* In this implementation a xul:textbox is returned.
|
|
||||||
*/
|
|
||||||
get editorElement() {
|
|
||||||
return this._textbox;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an event listener to the editor. You can use one of the known events.
|
|
||||||
*
|
|
||||||
* @see SourceEditor.EVENTS
|
|
||||||
*
|
|
||||||
* @param string aEventType
|
|
||||||
* The event type you want to listen for.
|
|
||||||
* @param function aCallback
|
|
||||||
* The function you want executed when the event is triggered.
|
|
||||||
*/
|
|
||||||
addEventListener:
|
|
||||||
function SE_addEventListener(aEventType, aCallback)
|
|
||||||
{
|
|
||||||
const EVENTS = SourceEditor.EVENTS;
|
|
||||||
let listener = {
|
|
||||||
type: aEventType,
|
|
||||||
callback: aCallback,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (aEventType == EVENTS.CONTEXT_MENU) {
|
|
||||||
listener.domType = "contextmenu";
|
|
||||||
listener.target = this._textbox;
|
|
||||||
listener.handler = this._onContextMenu.bind(this, listener);
|
|
||||||
listener.target.addEventListener(listener.domType, listener.handler, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(aEventType in this._listeners)) {
|
|
||||||
this._listeners[aEventType] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._listeners[aEventType].push(listener);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an event listener from the editor. You can use one of the known
|
|
||||||
* events.
|
|
||||||
*
|
|
||||||
* @see SourceEditor.EVENTS
|
|
||||||
*
|
|
||||||
* @param string aEventType
|
|
||||||
* The event type you have a listener for.
|
|
||||||
* @param function aCallback
|
|
||||||
* The function you have as the event handler.
|
|
||||||
*/
|
|
||||||
removeEventListener:
|
|
||||||
function SE_removeEventListener(aEventType, aCallback)
|
|
||||||
{
|
|
||||||
let listeners = this._listeners[aEventType];
|
|
||||||
if (!listeners) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EVENTS = SourceEditor.EVENTS;
|
|
||||||
|
|
||||||
this._listeners[aEventType] = listeners.filter(function(aListener) {
|
|
||||||
let isSameListener = aListener.type == aEventType &&
|
|
||||||
aListener.callback === aCallback;
|
|
||||||
if (isSameListener && aListener.domType) {
|
|
||||||
aListener.target.removeEventListener(aListener.domType,
|
|
||||||
aListener.handler, false);
|
|
||||||
}
|
|
||||||
return !isSameListener;
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The xul:textbox contextmenu event handler. This is used a wrapper for each
|
|
||||||
* contextmenu event listener added by the SourceEditor client.
|
|
||||||
*
|
|
||||||
* @param object aListener
|
|
||||||
* The object that holds listener information, see this._listener and
|
|
||||||
* this.addEventListener().
|
|
||||||
* @param nsIDOMEvent aDOMEvent
|
|
||||||
* The nsIDOMEvent object that triggered the context menu.
|
|
||||||
*/
|
|
||||||
_onContextMenu: function SE__onContextMenu(aListener, aDOMEvent)
|
|
||||||
{
|
|
||||||
let input = this._textbox.inputField;
|
|
||||||
let rect = this._textbox.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Prepare the event object we send to the event handler.
|
|
||||||
let sendEvent = {
|
|
||||||
x: aDOMEvent.clientX - rect.left + input.scrollLeft,
|
|
||||||
y: aDOMEvent.clientY - rect.top + input.scrollTop,
|
|
||||||
screenX: aDOMEvent.screenX,
|
|
||||||
screenY: aDOMEvent.screenY,
|
|
||||||
};
|
|
||||||
|
|
||||||
aDOMEvent.preventDefault();
|
|
||||||
aListener.callback.call(null, sendEvent);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undo a change in the editor.
|
|
||||||
*/
|
|
||||||
undo: function SE_undo()
|
|
||||||
{
|
|
||||||
this._editor.undo(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redo a change in the editor.
|
|
||||||
*/
|
|
||||||
redo: function SE_redo()
|
|
||||||
{
|
|
||||||
this._editor.redo(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are changes that can be undone.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
* True if there are changes that can be undone, false otherwise.
|
|
||||||
*/
|
|
||||||
canUndo: function SE_canUndo()
|
|
||||||
{
|
|
||||||
let isEnabled = {};
|
|
||||||
let canUndo = {};
|
|
||||||
this._editor.canUndo(isEnabled, canUndo);
|
|
||||||
return canUndo.value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are changes that can be repeated.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
* True if there are changes that can be repeated, false otherwise.
|
|
||||||
*/
|
|
||||||
canRedo: function SE_canRedo()
|
|
||||||
{
|
|
||||||
let isEnabled = {};
|
|
||||||
let canRedo = {};
|
|
||||||
this._editor.canRedo(isEnabled, canRedo);
|
|
||||||
return canRedo.value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the Undo stack
|
|
||||||
*/
|
|
||||||
resetUndo: function SE_resetUndo()
|
|
||||||
{
|
|
||||||
this._editor.transactionManager.clear();
|
|
||||||
this._editor.resetModificationCount();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a compound change in the editor. Compound changes are grouped into
|
|
||||||
* only one change that you can undo later, after you invoke
|
|
||||||
* endCompoundChange().
|
|
||||||
*/
|
|
||||||
startCompoundChange: function SE_startCompoundChange()
|
|
||||||
{
|
|
||||||
this._editor.beginTransaction();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End a compound change in the editor.
|
|
||||||
*/
|
|
||||||
endCompoundChange: function SE_endCompoundChange()
|
|
||||||
{
|
|
||||||
this._editor.endTransaction();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Focus the editor.
|
|
||||||
*/
|
|
||||||
focus: function SE_focus()
|
|
||||||
{
|
|
||||||
this._textbox.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the editor has focus.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
* True if the editor is focused, false otherwise.
|
|
||||||
*/
|
|
||||||
hasFocus: function SE_hasFocus()
|
|
||||||
{
|
|
||||||
return this._textbox.ownerDocument.activeElement ===
|
|
||||||
this._textbox.inputField;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the editor content, in the given range. If no range is given you get
|
|
||||||
* the entire editor content.
|
|
||||||
*
|
|
||||||
* @param number [aStart=0]
|
|
||||||
* Optional, start from the given offset.
|
|
||||||
* @param number [aEnd=content char count]
|
|
||||||
* Optional, end offset for the text you want. If this parameter is not
|
|
||||||
* given, then the text returned goes until the end of the editor
|
|
||||||
* content.
|
|
||||||
* @return string
|
|
||||||
* The text in the given range.
|
|
||||||
*/
|
|
||||||
getText: function SE_getText(aStart, aEnd)
|
|
||||||
{
|
|
||||||
let value = this._textbox.value || "";
|
|
||||||
if (aStart === undefined || aStart === null) {
|
|
||||||
aStart = 0;
|
|
||||||
}
|
|
||||||
if (aEnd === undefined || aEnd === null) {
|
|
||||||
aEnd = value.length;
|
|
||||||
}
|
|
||||||
return value.substring(aStart, aEnd);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of characters in the editor content.
|
|
||||||
*
|
|
||||||
* @return number
|
|
||||||
* The number of editor content characters.
|
|
||||||
*/
|
|
||||||
getCharCount: function SE_getCharCount()
|
|
||||||
{
|
|
||||||
return this._textbox.textLength;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the selected text.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* The currently selected text.
|
|
||||||
*/
|
|
||||||
getSelectedText: function SE_getSelectedText()
|
|
||||||
{
|
|
||||||
let selection = this.getSelection();
|
|
||||||
return selection.start != selection.end ?
|
|
||||||
this.getText(selection.start, selection.end) : "";
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace text in the source editor with the given text, in the given range.
|
|
||||||
*
|
|
||||||
* @param string aText
|
|
||||||
* The text you want to put into the editor.
|
|
||||||
* @param number [aStart=0]
|
|
||||||
* Optional, the start offset, zero based, from where you want to start
|
|
||||||
* replacing text in the editor.
|
|
||||||
* @param number [aEnd=char count]
|
|
||||||
* Optional, the end offset, zero based, where you want to stop
|
|
||||||
* replacing text in the editor.
|
|
||||||
*/
|
|
||||||
setText: function SE_setText(aText, aStart, aEnd)
|
|
||||||
{
|
|
||||||
if (aStart === undefined) {
|
|
||||||
this._textbox.value = aText;
|
|
||||||
} else {
|
|
||||||
if (aEnd === undefined) {
|
|
||||||
aEnd = this._textbox.textLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = this._textbox.value || "";
|
|
||||||
let removedText = value.substring(aStart, aEnd);
|
|
||||||
let prefix = value.substr(0, aStart);
|
|
||||||
let suffix = value.substr(aEnd);
|
|
||||||
|
|
||||||
if (suffix) {
|
|
||||||
this._editActionListener._setTextRangeEvent = {
|
|
||||||
start: aStart,
|
|
||||||
removedCharCount: removedText.length,
|
|
||||||
addedCharCount: aText.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this._textbox.value = prefix + aText + suffix;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop the current selection / deselect.
|
|
||||||
*/
|
|
||||||
dropSelection: function SE_dropSelection()
|
|
||||||
{
|
|
||||||
let selection = this._editor.selection;
|
|
||||||
selection.collapse(selection.focusNode, selection.focusOffset);
|
|
||||||
this._onSelect();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a specific range in the editor.
|
|
||||||
*
|
|
||||||
* @param number aStart
|
|
||||||
* Selection range start.
|
|
||||||
* @param number aEnd
|
|
||||||
* Selection range end.
|
|
||||||
*/
|
|
||||||
setSelection: function SE_setSelection(aStart, aEnd)
|
|
||||||
{
|
|
||||||
this._textbox.setSelectionRange(aStart, aEnd);
|
|
||||||
this._onSelect();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current selection range.
|
|
||||||
*
|
|
||||||
* @return object
|
|
||||||
* An object with two properties, start and end, that give the
|
|
||||||
* selection range (zero based offsets).
|
|
||||||
*/
|
|
||||||
getSelection: function SE_getSelection()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
start: this._textbox.selectionStart,
|
|
||||||
end: this._textbox.selectionEnd
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current caret offset.
|
|
||||||
*
|
|
||||||
* @return number
|
|
||||||
* The current caret offset.
|
|
||||||
*/
|
|
||||||
getCaretOffset: function SE_getCaretOffset()
|
|
||||||
{
|
|
||||||
let selection = this.getSelection();
|
|
||||||
return selection.start < selection.end ?
|
|
||||||
selection.end : selection.start;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the caret offset.
|
|
||||||
*
|
|
||||||
* @param number aOffset
|
|
||||||
* The new caret offset you want to set.
|
|
||||||
*/
|
|
||||||
setCaretOffset: function SE_setCaretOffset(aOffset)
|
|
||||||
{
|
|
||||||
this.setSelection(aOffset, aOffset);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the caret position: line and column.
|
|
||||||
*
|
|
||||||
* @param number aLine
|
|
||||||
* The new caret line location. Line numbers start from 0.
|
|
||||||
* @param number [aColumn=0]
|
|
||||||
* Optional. The new caret column location. Columns start from 0.
|
|
||||||
*/
|
|
||||||
setCaretPosition: function SE_setCaretPosition(aLine, aColumn)
|
|
||||||
{
|
|
||||||
aColumn = aColumn || 0;
|
|
||||||
|
|
||||||
let text = this._textbox.value;
|
|
||||||
let i = -1, n = text.length, c0, c1;
|
|
||||||
let line = 0, col = -1;
|
|
||||||
while (i < n) {
|
|
||||||
c1 = text.charAt(i++);
|
|
||||||
if (line < aLine && (c1 == "\r" || (c0 != "\r" && c1 == "\n"))) {
|
|
||||||
// Count lines and reset the column only until we reach the desired line
|
|
||||||
// such that if the desired column is out of boundaries we will stop
|
|
||||||
// after the given number of characters from the line start.
|
|
||||||
line++;
|
|
||||||
col = 0;
|
|
||||||
} else {
|
|
||||||
col++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == aLine && col == aColumn) {
|
|
||||||
this.setCaretOffset(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
c0 = c1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the line delimiter used in the document being edited.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* The line delimiter.
|
|
||||||
*/
|
|
||||||
getLineDelimiter: function SE_getLineDelimiter()
|
|
||||||
{
|
|
||||||
return this._lineDelimiter;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the source editor mode to the file type you are editing.
|
|
||||||
*
|
|
||||||
* Note: this implementation makes no difference between any of the available
|
|
||||||
* modes.
|
|
||||||
*
|
|
||||||
* @param string aMode
|
|
||||||
* One of the predefined SourceEditor.MODES.
|
|
||||||
*/
|
|
||||||
setMode: function SE_setMode(aMode)
|
|
||||||
{
|
|
||||||
// nothing to do here
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current source editor mode.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* Returns one of the predefined SourceEditor.MODES. In this
|
|
||||||
* implementation SourceEditor.MODES.TEXT is always returned.
|
|
||||||
*/
|
|
||||||
getMode: function SE_getMode()
|
|
||||||
{
|
|
||||||
return SourceEditor.MODES.TEXT;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setter for the read-only state of the editor.
|
|
||||||
* @param boolean aValue
|
|
||||||
* Tells if you want the editor to read-only or not.
|
|
||||||
*/
|
|
||||||
set readOnly(aValue)
|
|
||||||
{
|
|
||||||
this._textbox.readOnly = aValue;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for the read-only state of the editor.
|
|
||||||
* @type boolean
|
|
||||||
*/
|
|
||||||
get readOnly()
|
|
||||||
{
|
|
||||||
return this._textbox.readOnly;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy/uninitialize the editor.
|
|
||||||
*/
|
|
||||||
destroy: function SE_destroy()
|
|
||||||
{
|
|
||||||
for (let eventType in this._listeners) {
|
|
||||||
this._listeners[eventType].forEach(function(aListener) {
|
|
||||||
if (aListener.domType) {
|
|
||||||
aListener.target.removeEventListener(aListener.domType,
|
|
||||||
aListener.handler, false);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._editor.removeEditActionListener(this._editActionListener);
|
|
||||||
|
|
||||||
this.ui.destroy();
|
|
||||||
this.ui = null;
|
|
||||||
|
|
||||||
this.parentElement.removeChild(this._textbox);
|
|
||||||
this.parentElement = null;
|
|
||||||
this._editor = null;
|
|
||||||
this._textbox = null;
|
|
||||||
this._config = null;
|
|
||||||
this._listeners = null;
|
|
||||||
this._lastSelection = null;
|
|
||||||
this._editActionListener = null;
|
|
||||||
this._lastFind = null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The nsIEditActionListener for the nsIEditor of the xul:textbox used by the
|
|
||||||
* SourceEditor. This listener traces text changes such that SourceEditor
|
|
||||||
* TextChanged event handlers get their events.
|
|
||||||
*
|
|
||||||
* @see
|
|
||||||
* http://mxr.mozilla.org/mozilla-central/source/editor/idl/nsIEditActionListener.idl
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param object aSourceEditor
|
|
||||||
* An instance of the SourceEditor to notify when text changes happen.
|
|
||||||
*/
|
|
||||||
function EditActionListener(aSourceEditor) {
|
|
||||||
this._sourceEditor = aSourceEditor;
|
|
||||||
}
|
|
||||||
|
|
||||||
EditActionListener.prototype = {
|
|
||||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIEditActionListener]),
|
|
||||||
|
|
||||||
WillCreateNode: function() { },
|
|
||||||
DidCreateNode: function() { },
|
|
||||||
WillInsertNode: function() { },
|
|
||||||
|
|
||||||
DidInsertNode: function EAL_DidInsertNode(aNode)
|
|
||||||
{
|
|
||||||
if (aNode.nodeType != aNode.TEXT_NODE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let event;
|
|
||||||
|
|
||||||
if (this._setTextRangeEvent) {
|
|
||||||
event = this._setTextRangeEvent;
|
|
||||||
delete this._setTextRangeEvent;
|
|
||||||
} else {
|
|
||||||
event = {
|
|
||||||
start: 0,
|
|
||||||
removedCharCount: 0,
|
|
||||||
addedCharCount: aNode.textContent.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sourceEditor._onTextChanged(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
WillDeleteNode: function() { },
|
|
||||||
DidDeleteNode: function() { },
|
|
||||||
WillSplitNode: function() { },
|
|
||||||
DidSplitNode: function() { },
|
|
||||||
WillJoinNodes: function() { },
|
|
||||||
DidJoinNodes: function() { },
|
|
||||||
WillInsertText: function() { },
|
|
||||||
|
|
||||||
DidInsertText: function EAL_DidInsertText(aTextNode, aOffset, aString)
|
|
||||||
{
|
|
||||||
let event = {
|
|
||||||
start: aOffset,
|
|
||||||
removedCharCount: 0,
|
|
||||||
addedCharCount: aString.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._sourceEditor._onTextChanged(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
WillDeleteText: function() { },
|
|
||||||
|
|
||||||
DidDeleteText: function EAL_DidDeleteText(aTextNode, aOffset, aLength)
|
|
||||||
{
|
|
||||||
let event = {
|
|
||||||
start: aOffset,
|
|
||||||
removedCharCount: aLength,
|
|
||||||
addedCharCount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._sourceEditor._onTextChanged(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
WillDeleteSelection: function EAL_WillDeleteSelection()
|
|
||||||
{
|
|
||||||
if (this._setTextRangeEvent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let selection = this._sourceEditor.getSelection();
|
|
||||||
let str = this._sourceEditor.getSelectedText();
|
|
||||||
|
|
||||||
let event = {
|
|
||||||
start: selection.start,
|
|
||||||
removedCharCount: str.length,
|
|
||||||
addedCharCount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._sourceEditor._onTextChanged(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
DidDeleteSelection: function() { },
|
|
||||||
};
|
|
|
@ -51,7 +51,7 @@ var component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT);
|
||||||
var obj = {};
|
var obj = {};
|
||||||
try {
|
try {
|
||||||
if (component == "ui") {
|
if (component == "ui") {
|
||||||
throw new Error("The UI editor component is not available.");
|
throw new Error("The ui editor component is not available.");
|
||||||
}
|
}
|
||||||
Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
|
Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
|
|
@ -111,6 +111,7 @@ function StyleEditor(aDocument, aStyleSheet)
|
||||||
|
|
||||||
this._styleSheet = aStyleSheet;
|
this._styleSheet = aStyleSheet;
|
||||||
this._styleSheetIndex = -1; // unknown for now, will be set after load
|
this._styleSheetIndex = -1; // unknown for now, will be set after load
|
||||||
|
this._styleSheetFilePath = null; // original file path for the style sheet
|
||||||
|
|
||||||
this._loaded = false;
|
this._loaded = false;
|
||||||
|
|
||||||
|
@ -571,6 +572,7 @@ StyleEditor.prototype = {
|
||||||
* @param mixed aFile
|
* @param mixed aFile
|
||||||
* Optional nsIFile or string representing the filename to save in the
|
* Optional nsIFile or string representing the filename to save in the
|
||||||
* background, no UI will be displayed.
|
* background, no UI will be displayed.
|
||||||
|
* If not specified, the original style sheet URI is used.
|
||||||
* To implement 'Save' instead of 'Save as', you can pass savedFile here.
|
* To implement 'Save' instead of 'Save as', you can pass savedFile here.
|
||||||
* @param function(nsIFile aFile) aCallback
|
* @param function(nsIFile aFile) aCallback
|
||||||
* Optional callback called when the operation has finished.
|
* Optional callback called when the operation has finished.
|
||||||
|
@ -580,7 +582,8 @@ StyleEditor.prototype = {
|
||||||
*/
|
*/
|
||||||
saveToFile: function SE_saveToFile(aFile, aCallback)
|
saveToFile: function SE_saveToFile(aFile, aCallback)
|
||||||
{
|
{
|
||||||
aFile = this._showFilePicker(aFile, true);
|
aFile = this._showFilePicker(aFile || this._styleSheetFilePath, true);
|
||||||
|
|
||||||
if (!aFile) {
|
if (!aFile) {
|
||||||
if (aCallback) {
|
if (aCallback) {
|
||||||
aCallback(null);
|
aCallback(null);
|
||||||
|
@ -729,6 +732,14 @@ StyleEditor.prototype = {
|
||||||
_showFilePicker: function SE__showFilePicker(aFile, aSave, aParentWindow)
|
_showFilePicker: function SE__showFilePicker(aFile, aSave, aParentWindow)
|
||||||
{
|
{
|
||||||
if (typeof(aFile) == "string") {
|
if (typeof(aFile) == "string") {
|
||||||
|
try {
|
||||||
|
if (Services.io.extractScheme(aFile) == "file") {
|
||||||
|
let uri = Services.io.newURI(aFile, null, null);
|
||||||
|
let file = uri.QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||||
file.initWithPath(aFile);
|
file.initWithPath(aFile);
|
||||||
|
@ -772,6 +783,7 @@ StyleEditor.prototype = {
|
||||||
let scheme = Services.io.extractScheme(this.styleSheet.href);
|
let scheme = Services.io.extractScheme(this.styleSheet.href);
|
||||||
switch (scheme) {
|
switch (scheme) {
|
||||||
case "file":
|
case "file":
|
||||||
|
this._styleSheetFilePath = this.styleSheet.href;
|
||||||
case "chrome":
|
case "chrome":
|
||||||
case "resource":
|
case "resource":
|
||||||
this._loadSourceFromFile(this.styleSheet.href);
|
this._loadSourceFromFile(this.styleSheet.href);
|
||||||
|
|
|
@ -46,6 +46,7 @@ include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
_BROWSER_TEST_FILES = \
|
_BROWSER_TEST_FILES = \
|
||||||
browser_styleeditor_enabled.js \
|
browser_styleeditor_enabled.js \
|
||||||
|
browser_styleeditor_filesave.js \
|
||||||
browser_styleeditor_import.js \
|
browser_styleeditor_import.js \
|
||||||
browser_styleeditor_init.js \
|
browser_styleeditor_init.js \
|
||||||
browser_styleeditor_loading.js \
|
browser_styleeditor_loading.js \
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
const TESTCASE_URI_HTML = TEST_BASE + "simple.html";
|
||||||
|
const TESTCASE_URI_CSS = TEST_BASE + "simple.css";
|
||||||
|
|
||||||
|
const Cc = Components.classes;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
|
||||||
|
let tempScope = {};
|
||||||
|
Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
|
||||||
|
Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
|
||||||
|
let FileUtils = tempScope.FileUtils;
|
||||||
|
let NetUtil = tempScope.NetUtil;
|
||||||
|
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
waitForExplicitFinish();
|
||||||
|
|
||||||
|
copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
|
||||||
|
copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
|
||||||
|
|
||||||
|
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||||
|
aChrome.addChromeListener({
|
||||||
|
onEditorAdded: function (aChrome, aEditor) {
|
||||||
|
if (aEditor.styleSheetIndex != 0) {
|
||||||
|
return; // we want to test against the first stylesheet
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aEditor.sourceEditor) {
|
||||||
|
run(aEditor); // already attached to input element
|
||||||
|
} else {
|
||||||
|
aEditor.addActionListener({
|
||||||
|
onAttach: run
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let uri = Services.io.newFileURI(htmlFile);
|
||||||
|
let filePath = uri.resolve("");
|
||||||
|
|
||||||
|
content.location = filePath;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(aEditor)
|
||||||
|
{
|
||||||
|
aEditor.saveToFile(null, function (aFile) {
|
||||||
|
ok(aFile, "file should get saved directly when using a file:// URI");
|
||||||
|
|
||||||
|
gChromeWindow.close();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy(aSrcChromeURL, aDestFileName, aCallback)
|
||||||
|
{
|
||||||
|
let destFile = FileUtils.getFile("ProfD", [aDestFileName]);
|
||||||
|
write(read(aSrcChromeURL), destFile, aCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(aSrcChromeURL)
|
||||||
|
{
|
||||||
|
let scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
|
||||||
|
.getService(Ci.nsIScriptableInputStream);
|
||||||
|
|
||||||
|
let channel = Services.io.newChannel(aSrcChromeURL, null, null);
|
||||||
|
let input = channel.open();
|
||||||
|
scriptableStream.init(input);
|
||||||
|
|
||||||
|
let data = scriptableStream.read(input.available());
|
||||||
|
scriptableStream.close();
|
||||||
|
input.close();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(aData, aFile, aCallback)
|
||||||
|
{
|
||||||
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||||
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||||
|
|
||||||
|
converter.charset = "UTF-8";
|
||||||
|
|
||||||
|
let istream = converter.convertToInputStream(aData);
|
||||||
|
let ostream = FileUtils.openSafeFileOutputStream(aFile);
|
||||||
|
|
||||||
|
NetUtil.asyncCopy(istream, ostream, function(status) {
|
||||||
|
if (!Components.isSuccessCode(status)) {
|
||||||
|
info("Coudln't write to " + aFile.path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aCallback(aFile);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1959,6 +1959,7 @@ nsObjectLoadingContent::SyncStartPluginInstance()
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIURI> kungFuURIGrip(mURI);
|
||||||
return InstantiatePluginInstance(mContentType.get(), mURI.get());
|
return InstantiatePluginInstance(mContentType.get(), mURI.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,8 @@ function init()
|
||||||
}
|
}
|
||||||
|
|
||||||
description(
|
description(
|
||||||
"Tests that glActiveTexture and glBindTexture work as expected" +
|
"Tests that glActiveTexture and glBindTexture work as expected." +
|
||||||
"Specifically texture targets are per active texture unit.");
|
" Specifically texture targets are per active texture unit.");
|
||||||
|
|
||||||
var canvas2d = document.getElementById("canvas2d");
|
var canvas2d = document.getElementById("canvas2d");
|
||||||
var ctx2d = canvas2d.getContext("2d");
|
var ctx2d = canvas2d.getContext("2d");
|
||||||
|
|
|
@ -78,7 +78,7 @@ function cmpMatrix(a, b, msg)
|
||||||
a.e == b.e &&
|
a.e == b.e &&
|
||||||
a.f == b.f,
|
a.f == b.f,
|
||||||
msg + " - got " + formatMatrix(a)
|
msg + " - got " + formatMatrix(a)
|
||||||
+ ", expected" + formatMatrix(b));
|
+ ", expected " + formatMatrix(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
function roughCmpMatrix(a, b, msg)
|
function roughCmpMatrix(a, b, msg)
|
||||||
|
@ -96,7 +96,7 @@ function roughCmpMatrix(a, b, msg)
|
||||||
Math.abs(b.e - a.e) < tolerance &&
|
Math.abs(b.e - a.e) < tolerance &&
|
||||||
Math.abs(b.f - a.f) < tolerance,
|
Math.abs(b.f - a.f) < tolerance,
|
||||||
msg + " - got " + formatMatrix(a)
|
msg + " - got " + formatMatrix(a)
|
||||||
+ ", expected" + formatMatrix(b));
|
+ ", expected " + formatMatrix(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMatrix(m)
|
function formatMatrix(m)
|
||||||
|
|
|
@ -464,7 +464,12 @@ PluginInstanceParent::AnswerPStreamNotifyConstructor(PStreamNotifyParent* actor,
|
||||||
file, actor);
|
file, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!streamDestroyed) {
|
if (streamDestroyed) {
|
||||||
|
// If the stream was destroyed, we must return an error code in the
|
||||||
|
// constructor.
|
||||||
|
*result = NPERR_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
else {
|
||||||
static_cast<StreamNotifyParent*>(actor)->ClearDestructionFlag();
|
static_cast<StreamNotifyParent*>(actor)->ClearDestructionFlag();
|
||||||
if (*result != NPERR_NO_ERROR)
|
if (*result != NPERR_NO_ERROR)
|
||||||
return PStreamNotifyParent::Send__delete__(actor,
|
return PStreamNotifyParent::Send__delete__(actor,
|
||||||
|
|
|
@ -48,10 +48,10 @@ function runTests() {
|
||||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||||
var p = $("plugin1").getLastMouseX();
|
var p = $("plugin1").getLastMouseX();
|
||||||
const delta = 2;
|
const delta = 2;
|
||||||
ok(p-delta <= x && x <= p+delta, "test"+test+" LastMouseX got " + p + " expected" + x +
|
ok(p-delta <= x && x <= p+delta, "test"+test+" LastMouseX got " + p + " expected " + x +
|
||||||
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
||||||
p = $("plugin1").getLastMouseY();
|
p = $("plugin1").getLastMouseY();
|
||||||
ok(p-delta <= y && y <= p+delta, "test"+test+" LastMouseY got " + p + " expected" + y +
|
ok(p-delta <= y && y <= p+delta, "test"+test+" LastMouseY got " + p + " expected " + y +
|
||||||
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
||||||
if (next) next();
|
if (next) next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,10 @@ function runTests() {
|
||||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||||
var p = $("plugin1").getLastMouseX();
|
var p = $("plugin1").getLastMouseX();
|
||||||
const delta = 2;
|
const delta = 2;
|
||||||
ok(p-delta <= x && x <= p+delta, "test"+test+" LastMouseX got " + p + " expected" + x +
|
ok(p-delta <= x && x <= p+delta, "test"+test+" LastMouseX got " + p + " expected " + x +
|
||||||
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
||||||
p = $("plugin1").getLastMouseY();
|
p = $("plugin1").getLastMouseY();
|
||||||
ok(p-delta <= y && y <= p+delta, "test"+test+" LastMouseY got " + p + " expected" + y +
|
ok(p-delta <= y && y <= p+delta, "test"+test+" LastMouseY got " + p + " expected " + y +
|
||||||
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
" with fullZoom="+viewer.fullZoom+" MozTransform='"+$("container").style.MozTransform+"'");
|
||||||
if (next) next();
|
if (next) next();
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче