michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "onSpellCheck", michael@0: ]; michael@0: michael@0: const SPELL_CHECK_ENDED_TOPIC = "inlineSpellChecker-spellCheck-ended"; michael@0: const SPELL_CHECK_STARTED_TOPIC = "inlineSpellChecker-spellCheck-started"; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: /** michael@0: * Waits until spell checking has stopped on the given element. michael@0: * michael@0: * When a spell check is pending, this waits indefinitely until the spell check michael@0: * ends. When a spell check is not pending, it waits a small number of turns of michael@0: * the event loop: if a spell check begins, it resumes waiting indefinitely for michael@0: * the end, and otherwise it stops waiting and calls the callback. michael@0: * michael@0: * This this can therefore trap spell checks that have not started at the time michael@0: * of calling, spell checks that have already started, multiple consecutive michael@0: * spell checks, and the absence of spell checks altogether. michael@0: * michael@0: * @param editableElement The element being spell checked. michael@0: * @param callback Called when spell check has completed or enough turns michael@0: * of the event loop have passed to determine it has not michael@0: * started. michael@0: */ michael@0: function onSpellCheck(editableElement, callback) { michael@0: let editor = editableElement.editor; michael@0: if (!editor) { michael@0: let win = editableElement.ownerDocument.defaultView; michael@0: editor = win.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIWebNavigation). michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIEditingSession). michael@0: getEditorForWindow(win); michael@0: } michael@0: if (!editor) michael@0: throw new Error("Unable to find editor for element " + editableElement); michael@0: michael@0: try { michael@0: // False is important here. Pass false so that the inline spell checker michael@0: // isn't created if it doesn't already exist. michael@0: var isc = editor.getInlineSpellChecker(false); michael@0: } michael@0: catch (err) { michael@0: // getInlineSpellChecker throws if spell checking is not enabled instead of michael@0: // just returning null, which seems kind of lame. (Spell checking is not michael@0: // enabled on Android.) The point here is only to determine whether spell michael@0: // check is pending, and if getInlineSpellChecker throws, then it's not michael@0: // pending. michael@0: } michael@0: let waitingForEnded = isc && isc.spellCheckPending; michael@0: let count = 0; michael@0: michael@0: function observe(subj, topic, data) { michael@0: if (subj != editor) michael@0: return; michael@0: count = 0; michael@0: let expectedTopic = waitingForEnded ? SPELL_CHECK_ENDED_TOPIC : michael@0: SPELL_CHECK_STARTED_TOPIC; michael@0: if (topic != expectedTopic) michael@0: Cu.reportError("Expected " + expectedTopic + " but got " + topic + "!"); michael@0: waitingForEnded = !waitingForEnded; michael@0: } michael@0: michael@0: let os = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: os.addObserver(observe, SPELL_CHECK_STARTED_TOPIC, false); michael@0: os.addObserver(observe, SPELL_CHECK_ENDED_TOPIC, false); michael@0: michael@0: let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: timer.init(function tick() { michael@0: // Wait an arbitrarily large number -- 50 -- turns of the event loop before michael@0: // declaring that no spell checks will start. michael@0: if (waitingForEnded || ++count < 50) michael@0: return; michael@0: timer.cancel(); michael@0: os.removeObserver(observe, SPELL_CHECK_STARTED_TOPIC); michael@0: os.removeObserver(observe, SPELL_CHECK_ENDED_TOPIC); michael@0: callback(); michael@0: }, 0, Ci.nsITimer.TYPE_REPEATING_SLACK); michael@0: };