diff -r 1034c05df7c0 packages/jetpack-core/docs/selection.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packages/jetpack-core/docs/selection.md	Wed Apr 14 16:42:06 2010 -0400
@@ -0,0 +1,74 @@
+The `selection` module provides a means to get and set current text/HTML
+selections, as well as observe new selections.
+
+## Properties ##
+
+<tt>selection.**text**</tt>
+
+Gets or sets the current selection as plain text. Setting
+the selection removes all current selections, inserts the specified text at the
+location of the first selection, and selects the new text. Getting the selection
+when there is no current selection returns <tt>undefined</tt>. Setting the selection
+when there is no current selection has no effect. Getting the selection when
+**contiguous** is <tt>true</tt> returns the text of the first selection. 
+
+<tt>selection.**html**</tt>
+
+Gets or sets the current selection as html. Setting
+the selection removes all current selections, inserts the specified text at the
+location of the first selection, and selects the new text. Getting the selection
+when there is no current selection returns <tt>undefined</tt>. Setting the selection
+when there is no current selection has no effect. Getting the selection when
+**contiguous** is <tt>true</tt> returns the text of the first selection. 
+
+<tt>selection.**contiguous**</tt>
+
+Getter which returns <tt>true</tt> if the current selection is a single, contiguous selection.
+Returns <tt>false</tt> if there are one or more discrete selections, each of which may or may
+not be spatially adjacent. If there is no current selection, <tt>undefined</tt> is returned.
+Discontiguous selections can be created interactively with <tt>Ctrl+double mouse click</tt>.
+
+<tt>selection.**onSelect**</tt>
+
+Gets a singleton object with two functions for managing selection event notifications.
+
+### onSelect Functions ###
+
+<tt>selection.onSelect.**add**(*callback*)</tt>
+
+Adds *callback* to a list of callbacks which are called when text/html is selected.
+
+<tt>selection.onSelect.**remove**(*callback*)</tt>
+
+Removes *callback* from a list of callbacks which are called when text/html is selected.
+
+<tt>selection.**iterator**</tt>
+
+Discontiguous selections can be accessed by iterating over <tt>selection</tt>. Each iteration
+instance returns a <tt>Selection</tt> object on which <tt>text</tt>, <tt>html</tt>, or
+<tt>contiguous<tt> may be called.
+
+## Examples ##
+
+Log the current contiguous selection as text:
+
+    let selection = require("selection").selection;
+    if (selection.text)
+      console.log(selection.text);
+
+Log the current discontiguous selections as hmtl:
+
+    let selection = require("selection").selection;
+    if (!selection.contiguous) {
+      for each (let subselection in selection) {
+         console.log(subselection.html);
+      }
+    }
+
+Surround HTML selections with delimiters: 
+
+    let selection = require("selection").selection;
+    selection.onSelect.add(function() {
+        selection.html = "\\\" + selection.html + "///";
+    };
+
diff -r 1034c05df7c0 packages/jetpack-core/lib/selection.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packages/jetpack-core/lib/selection.js	Wed Apr 14 16:42:06 2010 -0400
@@ -0,0 +1,233 @@
+/* ***** 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 Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Eric H. Jung <eric.jung@yahoo.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 ***** */
+
+const HTML = 0x01;
+const TEXT = 0x02;
+const DOM  = 0x03;
+
+function Selection(rangeNumber) {
+    this._rangeNumber = rangeNumber;
+}
+
+Selection.prototype = {
+    
+    _rangeNumber : 0,
+
+    get text() {
+        return getSelection(TEXT, this._rangeNumber);
+    },
+
+    set text(str) {
+        setSelection(TEXT, str, this._rangeNumber);      
+    },
+
+    get html() {
+        return getSelection(HTML, this._rangeNumber);
+    },
+
+    set html(str) {
+        setSelection(HTML, str, this._rangeNumber);
+    },
+    
+    get contiguous() {
+        let sel = getSelection(DOM, this._rangeNumber);      
+        return sel.rangeCount == 0 ? undefined : sel.rangeCount == 1;
+    },
+
+    set contiguous() { /* no-op */}
+};
+
+function context() {
+    let tmp = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+    return (tmp.getMostRecentWindow("navigator:browser") ||
+        tmp.getMostRecentWindow("mail:3pane")).document.commandDispatcher.focusedWindow;
+}
+
+function getSelection(type, rangeNumber) {
+    let window, selection;
+    try {
+      window = context();
+      selection = window.getSelection();
+    }
+    catch(e) {
+      return undefined;
+    }
+
+    // Get the selected content as the specified type
+    if (type == DOM) {
+        return selection;
+    }
+    else if (type == TEXT) {
+        let range = safeGetRange();
+        return range ? range.toString() : undefined;
+    }
+    else if (type == HTML) {
+        let range = safeGetRange();
+        if (range) {
+            let html = range.cloneContents();
+            let node = window.document.createElement("span");
+            node.appendChild(html);
+            return node.innerHTML;
+        }
+        return undefined;
+    }
+    throw "Error: type is " + type;
+
+    function safeGetRange() {
+        try {
+            return selection.getRangeAt(rangeNumber);
+        }
+        catch(e) {
+          return null;
+        }
+    }
+}
+
+function setSelection(type, val, rangeNumber) {
+    // Make sure we have a window context and that there is a current selection.
+    // Selection cannot be set unless there is an existing selection.
+    let window, range;
+    try {
+      window = context();
+      range = window.getSelection().getRangeAt(rangeNumber);
+    }
+    catch(e) {
+      return undefined;
+    }
+    // Get rid of the current selection and insert our own
+    range.deleteContents();
+    let node = window.document.createElement("span");
+    range.surroundContents(node);
+
+    /* setting the text property replaces the selection with the value to which the property is set and sets
+       the html property to the same value to which the text property is being set.
+       
+       setting the html property replaces the selection with the value to which the property is set and sets
+       the text property to the text version of the HTML value. */
+
+    // This sets both the HTML and text properties
+    node["innerHTML"] = val;
+}
+
+exports.selection = new Selection(0);
+exports.selection.onSelect = {
+    add: function addSelectionListener(callback) {
+        return SelectionListenerManager.addSelectionListener(callback);
+    },
+
+    remove: function removeSelectionListener(callback) {
+        return SelectionListenerManager.removeSelectionListener(callback);
+    }
+};
+
+exports.selection.__iterator__ = function __iterator__() {
+    let sel = getSelection(DOM);
+    for (let i = 0; i < sel.rangeCount; i++) {
+        yield new Selection(i);
+    }
+};
+
+/************************* selection listener handling ***************************/
+
+var SelectionListenerManager = {
+
+    listeners: [],
+
+    /** nsISelectionListener implementation */
+    notifySelectionChanged: function notifySelectionChanged(document, selection, reason) {
+        // We only look for certain types of selection reasons
+        if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(function(type) reason &
+          Ci.nsISelectionListener[type + "_REASON"]))
+          return;
+        if (selection.toString() == "")
+          return;
+        // Notify each listener immediately but don't block on them
+        this.listeners.forEach(function(listener) {
+            let safeCallback = function() {
+                try {
+                    callback();
+                }
+                catch(e) {
+                    console.exception(e);
+                }
+            }
+            setTimeout(safeCallback, 0);
+        });
+    },
+
+    addSelectionListener: function addSelectionListener(callback) {
+        if (callback) {
+            // Prevent duplicates
+            if (this.listeners.indexOf(callback) == -1) {      
+                this.listeners.push(callback);
+                return this.listeners.length;
+            }
+        }
+        return false;
+    },
+
+    removeSelectionListener: function removeSelectionListener(callback) {
+        if (callback) {
+            // Remove |callback| from our listeners
+            let index = this.listeners.indexOf(callback);
+            if (index != -1) {
+                this.listeners.splice(index, 1);
+                return this.listeners.length;
+            }
+        }
+        return false;
+    },
+
+    tabAdded: function(window) {
+        let selection = window.getSelection();
+        if ((selection instanceof Ci.nsISelectionPrivate)) {
+            selection.addSelectionListener(SelectionListenerManager);
+        }
+    },
+
+    tabClosed: function(window) {
+        let selection = window.getSelection();
+        if ((selection instanceof Ci.nsISelectionPrivate)) {
+            selection.removeSelectionListener(SelectionListenerManager);
+        }
+    }
+};
+
+var tabBrowser = require("tab-browser");
+tabBrowser.whenContentLoaded(SelectionListenerManager.tabAdded);
+tabBrowser.whenTabClosed(SelectionListenerManager.tabClosed);
+
+
diff -r 1034c05df7c0 packages/jetpack-core/lib/tab-browser.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packages/jetpack-core/lib/tab-browser.js	Wed Apr 14 16:42:06 2010 -0400
@@ -0,0 +1,157 @@
+/* ***** 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 Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Atul Varma <atul@mozilla.com>
+ *   Eric H. Jung <eric.jung@yahoo.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 ***** */
+
+var errors = require("errors");
+var windowUtils = require("window-utils");
+
+// TODO: The hard-coding of app-specific info here isn't very nice;
+// ideally such app-specific info should be more decoupled, and the
+// module should be extensible, allowing for support of new apps at
+// runtime, perhaps by inspecting supported packages (e.g. via
+// dynamically-named modules or package-defined extension points).
+
+exports.isAppSupported = function isAppSupported() {
+  return require("xul-app").isOneOf(["Firefox"]);
+};
+
+var addTab = exports.addTab = function addTab(url, options) {
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+           .getService(Ci.nsIWindowMediator);
+  var win = wm.getMostRecentWindow("navigator:browser");
+  if (!options)
+    options = {};
+  if (!win || options.inNewWindow) {
+    var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+             .getService(Ci.nsIWindowWatcher);
+    var urlStr = Cc["@mozilla.org/supports-string;1"]
+                 .createInstance(Ci.nsISupportsString);
+    urlStr.data = url;
+    ww.openWindow(null, "chrome://browser/content/browser.xul",
+                  null, "chrome", urlStr);
+  } else {
+    var browser = win.document.querySelector("tabbrowser");
+    browser.selectedTab = browser.addTab(url);
+  }
+};
+
+function tabBrowserIterator(window) {
+  var browsers = window.document.querySelectorAll("tabbrowser");
+  for (var i = 0; i < browsers.length; i++)
+    yield browsers[i];
+}
+
+var Tracker = exports.Tracker = function Tracker(delegate) {
+  this._delegate = delegate;
+  this._browsers = [];
+  this._windowTracker = new windowUtils.WindowTracker(this);
+
+  require("unload-2").ensure(this);
+};
+
+Tracker.prototype = {
+  __iterator__: function __iterator__() {
+    for (var i = 0; i < this._browsers.length; i++)
+      yield this._browsers[i];
+  },
+  get: function get(index) {
+    return this._browsers[index];
+  },
+  onTrack: function onTrack(window) {
+    for (browser in tabBrowserIterator(window))
+      this._browsers.push(browser);
+    if (this._delegate)
+      for (browser in tabBrowserIterator(window))
+        this._delegate.onTrack(browser);
+  },
+  onUntrack: function onUntrack(window) {
+    for (browser in tabBrowserIterator(window))
+      this._browsers.splice(this._browsers.indexOf(browser), 1);
+    if (this._delegate)
+      for (browser in tabBrowserIterator(window))
+        this._delegate.onUntrack(browser);
+  },
+  get length() {
+    return this._browsers.length;
+  },
+  unload: function unload() {
+    this._windowTracker.unload();
+  }
+};
+
+exports.whenTabClosed = function whenTabClosed(callback) { 
+  var cb = require("errors").catchAndLog(function eventHandler(event) {
+    // Not sure all if all of the following checks are necessary
+    if (event.target && event.target.linkedBrowser && event.target.linkedBrowser.contentWindow) {
+      callback(event.target.linkedBrowser.contentWindow);
+    }
+  });
+
+  var tracker = new Tracker({
+    onTrack: function(tabBrowser) {
+      // tabBrowser is the equivalent of gBrowser
+      tabBrowser.tabContainer.addEventListener("TabClose", cb, false);
+    },
+    onUntrack: function(tabBrowser) {
+      tabBrowser.tabContainer.removeEventListener("TabClose", cb, false);
+      /* Window is closing. Callback for each tab about to close */
+      for (let i=0; i<tabBrowser.tabContainer.children.length; i++) {
+        let tab = tabBrowser.tabContainer.children.item(i);
+        callback(tab.linkedBrowser.contentWindow);
+      }
+    }
+  });
+
+  return tracker;
+};
+
+exports.whenContentLoaded = function whenContentLoaded(callback) {
+  var cb = require("errors").catchAndLog(function eventHandler(event) {
+    if (event.target && event.target.defaultView)
+      callback(event.target.defaultView);
+  });
+
+  var tracker = new Tracker({
+    onTrack: function(tabBrowser) {
+      tabBrowser.addEventListener("DOMContentLoaded", cb, false);
+    },
+    onUntrack: function(tabBrowser) {
+      tabBrowser.removeEventListener("DOMContentLoaded", cb, false);
+    }
+  });
+
+  return tracker;
+};
diff -r 1034c05df7c0 packages/jetpack-core/package.json
--- a/packages/jetpack-core/package.json	Tue Apr 13 23:20:55 2010 -0700
+++ b/packages/jetpack-core/package.json	Wed Apr 14 16:42:06 2010 -0400
@@ -5,7 +5,8 @@
     "author": "Atul Varma (http://toolness.com/) <atul@mozilla.com>",
     "contributors": [
         "Myk Melez (http://melez.com/) <myk@mozilla.org>",
-        "Daniel Aquino <mr.danielaquino@gmail.com>"
+        "Daniel Aquino <mr.danielaquino@gmail.com>",
+        "Eric H. Jung <eric.jung@yahoo.com>"
     ],
     "version": "0.3pre",
     "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
diff -r 1034c05df7c0 packages/jetpack-core/tests/test-selection.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/packages/jetpack-core/tests/test-selection.js	Wed Apr 14 16:42:06 2010 -0400
@@ -0,0 +1,276 @@
+var tabBrowser = require("tab-browser"),
+    timer = require("timer"),
+    selection = require("selection").selection;
+
+// Arbitrary delay needed to avoid weird behavior.
+// TODO: We need to find all uses of this and replace them
+// with more deterministic solutions.
+const ARB_DELAY = 100;
+
+function openBrowserWindow(callback) {
+    let window = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+        .getService(Ci.nsIWindowWatcher).openWindow(null, "chrome://browser/content/browser.xul",
+        null, "chrome", null);
+
+    function onLoad(event) {
+      if (event.target && event.target.defaultView == window) {
+        window.removeEventListener("load", onLoad, true);
+        let browsers = window.document.getElementsByTagName("tabbrowser");
+        try {
+          callback(window, browsers[0]);
+        } catch (e) { dump(e); }
+      }
+    }
+
+    window.addEventListener("load", onLoad, true);
+    return window;
+}
+
+/* Select all divs elements in an HTML document */
+function selectAllDivs(window) {
+    let divs = window.document.getElementsByTagName("div");
+    let s = window.getSelection();
+    if (s.rangeCount > 0) {
+        s.removeAllRanges();
+    }
+    for (let i = 0; i < divs.length; i++) {
+        let range = window.document.createRange();
+        range.selectNode(divs[i]);
+        s.addRange(range);
+    }
+}
+
+function primeTestCase(html, test, callback) {
+  let tracker = tabBrowser.whenContentLoaded(
+    function(window) {
+      callback(window, test);
+      timer.setTimeout(function() {
+          tracker.unload();
+          test.done(); },
+        ARB_DELAY);
+    }
+  );
+  tabBrowser.addTab("data:text/html," + html);
+}
+
+const DIV1 = '<div id="foo">bar</div>';
+const DIV2 = '<div>noodles</div>';
+const HTML_MULTIPLE = '<html><body>' + DIV1 + DIV2 + '</body></html>';
+const HTML_SINGLE = '<html><body>' + DIV1 + '</body></html>';
+
+/* Tests of contiguous */
+
+exports.testContiguousMultiple = function testContiguousMultiple(test) {
+    primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);        
+        test.assertEqual(selection.contiguous, false, "selection.contiguous multiple works.");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testContiguousSingle = function testContiguousSingle(test) {
+    primeTestCase(HTML_SINGLE, test, function(window, test) {
+        selectAllDivs(window);
+        test.assertEqual(selection.contiguous, true, "selection.contiguous single works.");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testContiguousUndefined = function testContiguousUndefined(test) {
+    primeTestCase(HTML_SINGLE, test, function(window, test) {
+        test.assertEqual(selection.contiguous, undefined, "selection.contiguous undefined works.");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testSetContiguous = function testSetContiguous(test) {
+    // Test that setting the contiguous property has no effect
+    primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);
+        selection.contiguous = false;
+        test.assertEqual(selection.contiguous, true, "setting selection.contiguous doesn't work (as expected).");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+
+/* HTML tests */
+
+exports.testGetHTMLSingleSelection = function testGetHTMLSingleSelection(test) {
+    primeTestCase(HTML_SINGLE, test, function(window, test) {
+        selectAllDivs(window);
+        test.assertEqual(selection.html, DIV1, "get html selection works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLMultipleSelection = function testGetHTMLMultipleSelection(test) {
+    primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);
+        let assertions = false;
+        for (let i in selection) {
+            test.assertEqual(true, [DIV1, DIV2].some(function(t) t == i.html), "get multiple selection html works");
+            assertions = true;
+        }
+        test.assert(assertions, "No assertions were called"); // Ensure we ran at least one assertEqual()
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLUndefined = function testGetHTMLUndefined(test) {
+    primeTestCase(HTML_SINGLE, test, function(window, test) {
+        test.assertEqual(selection.html, undefined, "get html undefined works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testGetHTMLWeird = function testGetHTMLWeird(test) {
+    // If the getter is used when there are contiguous selections, the first selection should be returned
+    primeTestCase(HTML_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);
+        test.assertEqual(selection.html, DIV1, "get html weird works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+const REPLACEMENT_HTML = "<b>Lorem ipsum dolor sit amet</b>";
+
+exports.testSetHTMLSelection = function testSetHTMLSelection(test) {
+    primeTestCase(HTML_SINGLE, test, function(window, test) {
+        selectAllDivs(window);
+        selection.html = REPLACEMENT_HTML;
+        test.assertEqual(selection.html, "<span>" + REPLACEMENT_HTML + "</span>", "selection html works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testSetHTMLUndefined = function testSetHTMLUndefined(test) {
+    primeTestCase(HTML_SINGLE, test, function(window, test) {
+        selection.html = REPLACEMENT_HTML;
+        test.assertEqual(selection.html, undefined, "selection html undefined works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+
+const TEXT1 = "foo";
+const TEXT2 = "noodles";
+const TEXT_MULTIPLE = "<html><body><div>" + TEXT1 + "</div><div>" + TEXT2 + "</div></body></html>";
+const TEXT_SINGLE = "<html><body><div>" + TEXT1 + "</div></body></html>";
+
+/* Text tests */
+
+exports.testGetTextSingleSelection = function testGetTextSingleSelection(test) {
+    primeTestCase(TEXT_SINGLE, test, function(window, test) {
+        selectAllDivs(window);
+        test.assertEqual(selection.text, TEXT1, "get text selection works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testGetTextMultipleSelection = function testGetTextMultipleSelection(test) {
+    primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);
+        let assertions = false;
+        for (let i in selection) {
+            test.assertEqual(true, [TEXT1, TEXT2].some(function(t) t == i.text), "get multiple selection text works");
+            assertions = true;
+        }
+        test.assert(assertions, "No assertions were called"); // Ensure we ran at least one assertEqual()
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testGetTextUndefined = function testGetTextUndefined(test) {
+    primeTestCase(TEXT_SINGLE, test, function(window, test) {
+        test.assertEqual(selection.text, undefined, "get text undefined works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testGetTextWeird = function testGetTextWeird(test) {
+    // If the getter is used when there are contiguous selections, the first selection should be returned
+    primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);
+        test.assertEqual(selection.text, TEXT1, "get text weird works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+
+const REPLACEMENT_TEXT = "Lorem ipsum dolor sit amet";
+
+exports.testSetTextSelection = function testSetTextSelection(test) {
+    primeTestCase(TEXT_SINGLE, test, function(window, test) {
+        selectAllDivs(window);
+        selection.text = REPLACEMENT_TEXT;
+        test.assertEqual(selection.text, REPLACEMENT_TEXT, "selection text works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+exports.testSetTextUndefined = function testSetTextUndefined(test) {
+    primeTestCase(TEXT_SINGLE, test, function(window, test) {
+        selection.text = REPLACEMENT_TEXT;
+        test.assertEqual(selection.text, undefined, "selection text undefined works");
+        });
+
+    test.waitUntilDone(5000);
+};
+
+/* onSelect tests. We need to figure out how to programmatically select something
+   such that selection's notifier will be notified. */
+
+/*exports.testOnSelectTextSeltestOnSelectTextSelectionection = function testOnSelect(test) {
+    let callbackCount = 0;
+    primeTestCase(TEXT_SINGLE, test, function(window, test) {
+        selection.onSelect.add(function() {callbackCount++});
+        selectAllDivs(window);
+        selection.text = REPLACEMENT_TEXT;
+        test.assertEqual(1, calledbackCount, "onSelect text listener works.");
+        });
+
+    test.waitUntilDone(5000);
+};
+exports.testOnSelectExceptionNoBubble = function testOnSelectTextSelection(test) {
+    primeTestCase(TEXT_SINGLE, test, function(window, test) {
+        selection.onSelect.add(function() {throw "Hi Karolina"});
+        selectAllDivs(window);
+        test.pass("onSelect catches exceptions.");
+        test.done();
+        });
+
+    test.waitUntilDone(5000);
+};
+*/
+
+
+/* iterator tests */
+
+exports.testIterator = function testIterator(test) {
+    let selectionCount = 0;
+    primeTestCase(TEXT_MULTIPLE, test, function(window, test) {
+        selectAllDivs(window);
+        for (let i in selection) {
+            selectionCount++;
+        }
+        test.assertEqual(2, selectionCount, "iterator works.");
+        });
+
+    test.waitUntilDone(5000);
+};
