diff --git a/common/content/buffer.js b/common/content/buffer.js index 884e2afe..6744eb70 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -444,7 +444,7 @@ const Buffer = Module("buffer", { */ get focusedFrame() { let frame = (dactyl.has("tabs") ? tabs.localStore : this.localStore).focusedFrame; - return frame && frame.get(); + return frame && frame.get() || content; }, set focusedFrame(frame) { (dactyl.has("tabs") ? tabs.localStore : this.localStore).focusedFrame = Cu.getWeakReference(frame); @@ -508,13 +508,19 @@ const Buffer = Module("buffer", { win.dactylFocusAllowed = true; if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement])) - elem.contentWindow.focus(); - else if (elem instanceof HTMLInputElement && elem.type == "file") { + elem = elem.contentWindow; + elem.dactylFocusAllowed = true; + + if (elem instanceof HTMLInputElement && elem.type == "file") { Buffer.openUploadPrompt(elem); buffer.lastInputField = elem; } else { elem.focus(); + if (elem instanceof Window && elem.getSelection() && !elem.getSelection().rangeCount) + elem.getSelection().addRange(RangeFind.endpoint( + RangeFind.nodeRange(elem.document.body || elem.document.documentElement), + true)); // for imagemap if (elem instanceof HTMLAreaElement) { @@ -1618,12 +1624,14 @@ const Buffer = Module("buffer", { let elem = buffer.lastInputField; if (count >= 1 || !elem || !Events.isContentNode(elem)) { - let xpath = ["input", "textarea[not(@disabled) and not(@readonly)]"]; + let xpath = ["frame", "iframe", "input", "textarea[not(@disabled) and not(@readonly)]"]; let frames = buffer.allFrames(null, true); let elements = array.flatten(frames.map(function (win) [m for (m in util.evaluateXPath(xpath, win.document))])) .filter(function (elem) { + if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement])) + return Editor.getEditor(elem.contentWindow); if (elem.readOnly || elem instanceof HTMLInputElement && !set.has(util.editableInputs, elem.type)) return false; diff --git a/common/content/commands.js b/common/content/commands.js index 538fce71..6d2e3098 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -257,15 +257,15 @@ const Command = Class("Command", { options: [], optionMap: Class.memoize(function () array(this.options) .map(function (opt) opt.names.map(function (name) [name, opt])) - .flatten.toObject()), + .flatten().toObject()), newArgs: function () { let res = []; res.__proto__ = this.argsPrototype; return res; }, argsPrototype: Class.memoize(function () update([], - array([opt, opt.default] for (opt in values(this.options)) if (set.has(opt, "default"))) - .toObject(), + array(this.options).filter(function (opt) opt.default !== undefined) + .map(function (opt) [opt.names[0], opt.default]).toObject(), { __iterator__: function () array.iterItems(this), command: this, diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js index 676a403d..f28b5400 100644 --- a/common/content/dactyl-overlay.js +++ b/common/content/dactyl-overlay.js @@ -6,7 +6,7 @@ (function () { function newContext(proto) { - let sandbox = Components.utils.Sandbox(window); + let sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules, wantXrays: false }); // Hack: sandbox.Object = jsmodules.Object; sandbox.Math = jsmodules.Math; diff --git a/common/content/dactyl.js b/common/content/dactyl.js index cc15de02..86473575 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -348,6 +348,7 @@ const Dactyl = Module("dactyl", { if (window != services.get("windowWatcher").activeWindow) return; + let win = document.commandDispatcher.focusedWindow; let elem = config.mainWidget || window.content; // TODO: make more generic try { @@ -357,16 +358,26 @@ const Dactyl = Module("dactyl", { i = 0; gDBView.selection.select(i); } - else if (this.has("tabs")) { + else { let frame = buffer.focusedFrame; - if (frame && frame.top == window.content) + if (frame && frame.top == window.content && !Editor.getEditor(frame)) elem = frame; } } catch (e) {} - if (clearFocusedElement && dactyl.focus) - dactyl.focus.blur(); + if (clearFocusedElement) + if (dactyl.focus) + dactyl.focus.blur(); + else if (win) { + win.blur(); + if (win.frameElement) + win.frameElement.blur(); + } + + if (elem instanceof Window && Editor.getEditor(elem)) + elem = window; + if (elem && elem != dactyl.focus) elem.focus(); }, diff --git a/common/content/editor.js b/common/content/editor.js index fd08de87..3fd62605 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -20,43 +20,14 @@ const Editor = Module("editor", { get isCaret() modes.getStack(1).main === modes.CARET, get isTextEdit() modes.getStack(1).main === modes.TEXT_EDIT, - line: function () { - let line = 1; - let text = Editor.getEditor().value; - for (let i = 0; i < Editor.getEditor().selectionStart; i++) - if (text[i] == "\n") - line++; - return line; - }, - - col: function () { - let col = 1; - let text = Editor.getEditor().value; - for (let i = 0; i < Editor.getEditor().selectionStart; i++) { - col++; - if (text[i] == "\n") - col = 1; - } - return col; - }, - unselectText: function (toEnd) { - let elem = dactyl.focus; - // A error occurs if the element has been removed when "elem.selectionStart" is executed. try { - if (elem && elem.selectionEnd) - if (toEnd) - elem.selectionStart = elem.selectionEnd; - else - elem.selectionEnd = elem.selectionStart; + Editor.getEditor(null).selection[toEnd ? "collapseToEnd" : "collapseToStart"](); } - catch (e) {} + catch (e) {}; }, - selectedText: function () { - let text = Editor.getEditor().value; - return text.substring(Editor.getEditor().selectionStart, Editor.getEditor().selectionEnd); - }, + selectedText: function () String(Editor.getEditor(null).selection), pasteClipboard: function (clipboard, toStart) { if (util.isOS("WINNT")) { @@ -98,10 +69,9 @@ const Editor = Module("editor", { // count is optional, defaults to 1 executeCommand: function (cmd, count) { let controller = Editor.getController(); - if (!controller || !controller.supportsCommand(cmd) || !controller.isCommandEnabled(cmd)) { - dactyl.beep(); - return false; - } + dactyl.assert(controller && + controller.supportsCommand(cmd) && + controller.isCommandEnabled(cmd)); if (typeof count != "number" || count < 1) count = 1; @@ -116,13 +86,10 @@ const Editor = Module("editor", { didCommand = true; } catch (e) { - if (!didCommand) - dactyl.beep(); - return false; + dactyl.assert(didCommand); + break; } } - - return true; }, // cmd = y, d, c @@ -316,10 +283,12 @@ const Editor = Module("editor", { let text = ""; // XXX if (textBox) text = textBox.value; - else if (typeof GetCurrentEditor == "function") // Thunderbird composer - text = GetCurrentEditor().outputToString("text/plain", 2); - else - return; + else { + var editor = window.GetCurrentEditor ? GetCurrentEditor() + : Editor.getEditor(document.commandDispatcher.focusedWindow); + dactyl.assert(editor); + text = Array.map(editor.rootElement.childNodes, function (e) util.domToString(e, true)).join(""); + } let oldBg, tmpBg; try { @@ -330,8 +299,6 @@ const Editor = Module("editor", { tmpBg = "yellow"; textBox.style.backgroundColor = "#bbbbbb"; } - else - var editor = GetCurrentEditor(); if (!tmpfile.write(text)) throw Error("Input contains characters not valid in the current " + @@ -347,9 +314,9 @@ const Editor = Module("editor", { if (textBox) textBox.value = val; else { - editor.selection.addRange(RangeFind.nodeRange(editor.rootNode)); - editor.selection.deleteFromDocument(); - editor.insertText(val); + while (editor.rootElement.firstChild) + editor.rootElement.removeChild(editor.rootElement.firstChild); + editor.rootElement.innerHTML = val; } } @@ -417,10 +384,25 @@ const Editor = Module("editor", { return true; }, }, { - getEditor: function () dactyl.focus, + getEditor: function (elem) { + if (arguments.length === 0) { + dactyl.assert(dactyl.focus); + return dactyl.focus; + } + + if (!elem) + elem = dactyl.focus || document.commandDispatcher.focusedWindow; + dactyl.assert(elem); + + if (elem instanceof Element) + return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor; + return elem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession) + .getEditorForWindow(elem); + }, getController: function () { - let ed = Editor.getEditor(); + let ed = dactyl.focus; if (!ed || !ed.controllers) return null; diff --git a/common/content/events.js b/common/content/events.js index 54f7c4ae..4d389e98 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -659,8 +659,11 @@ const Events = Module("events", { let elem = event.originalTarget; if (Events.isContentNode(elem) && !buffer.focusAllowed(elem) - && isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement])) - elem.blur(); + && isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, Window])) + if (elem.frameElement) + dactyl.focusContent(true); + else + elem.blur(); }, // argument "event" is deliberately not used, as i don't seem to have @@ -696,9 +699,12 @@ const Events = Module("events", { return; } - if (elem instanceof HTMLTextAreaElement || (elem && util.computedStyle(elem).MozUserModify == "read-write")) { + if (elem instanceof HTMLTextAreaElement || (elem && util.computedStyle(elem).MozUserModify == "read-write") + || elem == null && win && Editor.getEditor(win)) { + if (modes.main === modes.VISUAL && elem.selectionEnd == elem.selectionStart) modes.pop(); + if (options["insertmode"]) modes.set(modes.INSERT); else { @@ -706,6 +712,7 @@ const Events = Module("events", { if (elem.selectionEnd - elem.selectionStart > 0) modes.push(modes.VISUAL); } + if (hasHTMLDocument(win)) buffer.lastInputField = elem; return; @@ -720,8 +727,8 @@ const Events = Module("events", { if (elem == null && urlbar && urlbar.inputField == this._lastFocus) util.threadYield(true); - if (modes.getMode(modes.main).ownsFocus) - modes.reset(); + while (modes.getMode(modes.main).ownsFocus) + modes.pop(); } finally { this._lastFocus = elem; @@ -782,9 +789,6 @@ const Events = Module("events", { let stop = false; let mode = modes.getStack(0); - let win = document.commandDispatcher.focusedWindow; - if (win && win.document && "designMode" in win.document && win.document.designMode == "on" && !config.isComposeWindow) - stop = true; // menus have their own command handlers if (modes.extended & modes.MENU) stop = true; @@ -985,7 +989,7 @@ const Events = Module("events", { } }, { isContentNode: function isContentNode(node) { - let win = (node.ownerDocument || node).defaultView; + let win = (node.ownerDocument || node).defaultView || node; for (; win; win = win.parent != win && win.parent) if (win == window.content) return true; diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 711b30e3..f27b2fac 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -485,8 +485,11 @@ function isObject(obj) typeof obj === "object" && obj != null; * any window, frame, namespace, or execution context, which * is not the case when using (obj instanceof Array). */ -const isArray = Array.isArray || - function isArray(val) objproto.toString.call(val) == "[object Array]"; +const isArray = + Array.isArray + // This is bloody stupid. + ? function isArray(val) Array.isArray(val) || val && val.constructor && val.constructor.name === "Array" + : function isArray(val) objproto.toString.call(val) == "[object Array]"; /** * Returns true if and only if its sole argument is an diff --git a/common/modules/util.jsm b/common/modules/util.jsm index a2893e6d..73e71e53 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -262,7 +262,7 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) dequote: function dequote(pattern, chars) pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0), - domToString: function (node) { + domToString: function (node, html) { if (node instanceof Ci.nsISelection && node.isCollapsed) return ""; @@ -282,6 +282,8 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) let str = services.create("string"); str.data = encoder.encodeToString(); + if (html) + return str.data; let [result, length] = [{}, {}]; services.create("htmlConverter").convert("text/html", str, str.data.length*2, "text/unicode", result, length);