summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/tests
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/tests')
-rw-r--r--editor/libeditor/tests/browser.ini6
-rw-r--r--editor/libeditor/tests/browser_bug527935.js63
-rw-r--r--editor/libeditor/tests/browser_bug629172.js106
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/LICENSE202
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/README58
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/README.Mozilla17
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/currentStatus.js46
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/current_revision1
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/richtext/editable.html11
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/richtext/js/range.js1069
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/richtext/richtext.html1081
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext/update_from_upstream16
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/LICENSE202
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/README58
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/README.Mozilla23
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/currentStatus.js1850
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/current_revision1
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/__init__.py0
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/common.py25
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/handlers.py107
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/common.css116
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-body.html11
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-dM.html17
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-div.html11
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable.css66
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/canonicalize.js436
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js489
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/output.js456
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/pad.js269
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range-bootstrap.js5
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range.js6184
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/run.js383
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/units.js416
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/variables.js227
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/output.html138
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/richtext2.html107
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/__init__.py17
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/apply.py364
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/applyCSS.py244
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/change.py273
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/changeCSS.py210
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/delete.py330
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/forwarddelete.py315
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/insert.py285
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryEnabled.py215
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryIndeterm.py214
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryState.py575
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/querySupported.py226
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryValue.py429
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/selection.py772
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapply.py462
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapplyCSS.py226
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/richtext2/unittestexample.html103
-rw-r--r--editor/libeditor/tests/browserscope/lib/richtext2/update_from_upstream19
-rw-r--r--editor/libeditor/tests/browserscope/mochitest.ini59
-rw-r--r--editor/libeditor/tests/browserscope/test_richtext.html48
-rw-r--r--editor/libeditor/tests/browserscope/test_richtext2.html233
-rw-r--r--editor/libeditor/tests/bug527935.html11
-rw-r--r--editor/libeditor/tests/bug629172.html15
-rw-r--r--editor/libeditor/tests/chrome.ini14
-rw-r--r--editor/libeditor/tests/data/cfhtml-chromium.txtbin0 -> 856 bytes
-rw-r--r--editor/libeditor/tests/data/cfhtml-firefox.txtbin0 -> 266 bytes
-rw-r--r--editor/libeditor/tests/data/cfhtml-ie.txtbin0 -> 1080 bytes
-rw-r--r--editor/libeditor/tests/data/cfhtml-nocontext.txt18
-rw-r--r--editor/libeditor/tests/data/cfhtml-ooo.txtbin0 -> 649 bytes
-rw-r--r--editor/libeditor/tests/file_bug549262.html8
-rw-r--r--editor/libeditor/tests/file_bug586662.html7
-rw-r--r--editor/libeditor/tests/file_bug674770-1.html5
-rw-r--r--editor/libeditor/tests/file_bug915962.html13
-rw-r--r--editor/libeditor/tests/file_select_all_without_body.html41
-rw-r--r--editor/libeditor/tests/green.pngbin0 -> 334 bytes
-rw-r--r--editor/libeditor/tests/mochitest.ini245
-rw-r--r--editor/libeditor/tests/spellcheck.js20
-rw-r--r--editor/libeditor/tests/test_CF_HTML_clipboard.html159
-rw-r--r--editor/libeditor/tests/test_backspace_vs.html130
-rw-r--r--editor/libeditor/tests/test_bug1026397.html103
-rw-r--r--editor/libeditor/tests/test_bug1053048.html73
-rw-r--r--editor/libeditor/tests/test_bug1067255.html57
-rw-r--r--editor/libeditor/tests/test_bug1068979.html72
-rw-r--r--editor/libeditor/tests/test_bug1094000.html104
-rw-r--r--editor/libeditor/tests/test_bug1100966.html65
-rw-r--r--editor/libeditor/tests/test_bug1101392.html78
-rw-r--r--editor/libeditor/tests/test_bug1102906.html51
-rw-r--r--editor/libeditor/tests/test_bug1109465.html69
-rw-r--r--editor/libeditor/tests/test_bug1140105.html71
-rw-r--r--editor/libeditor/tests/test_bug1140617.html37
-rw-r--r--editor/libeditor/tests/test_bug1153237.html49
-rw-r--r--editor/libeditor/tests/test_bug1154791.html67
-rw-r--r--editor/libeditor/tests/test_bug1162952.html43
-rw-r--r--editor/libeditor/tests/test_bug1181130-1.html50
-rw-r--r--editor/libeditor/tests/test_bug1181130-2.html44
-rw-r--r--editor/libeditor/tests/test_bug1186799.html81
-rw-r--r--editor/libeditor/tests/test_bug1230473.html124
-rw-r--r--editor/libeditor/tests/test_bug1247483.html61
-rw-r--r--editor/libeditor/tests/test_bug1248128.html52
-rw-r--r--editor/libeditor/tests/test_bug1248185.html57
-rw-r--r--editor/libeditor/tests/test_bug1250010.html93
-rw-r--r--editor/libeditor/tests/test_bug1257363.html182
-rw-r--r--editor/libeditor/tests/test_bug1258085.html66
-rw-r--r--editor/libeditor/tests/test_bug1268736.html64
-rw-r--r--editor/libeditor/tests/test_bug1270235.html46
-rw-r--r--editor/libeditor/tests/test_bug1310912.html93
-rw-r--r--editor/libeditor/tests/test_bug1314790.html65
-rw-r--r--editor/libeditor/tests/test_bug1315065.html145
-rw-r--r--editor/libeditor/tests/test_bug1328023.html64
-rw-r--r--editor/libeditor/tests/test_bug1330796.html101
-rw-r--r--editor/libeditor/tests/test_bug1332876.html49
-rw-r--r--editor/libeditor/tests/test_bug200416.html15
-rw-r--r--editor/libeditor/tests/test_bug289384.html49
-rw-r--r--editor/libeditor/tests/test_bug290026.html52
-rw-r--r--editor/libeditor/tests/test_bug291780.html50
-rw-r--r--editor/libeditor/tests/test_bug309731.html58
-rw-r--r--editor/libeditor/tests/test_bug316447.html16
-rw-r--r--editor/libeditor/tests/test_bug318065.html82
-rw-r--r--editor/libeditor/tests/test_bug332636.html75
-rw-r--r--editor/libeditor/tests/test_bug332636.html^headers^1
-rw-r--r--editor/libeditor/tests/test_bug366682.html67
-rw-r--r--editor/libeditor/tests/test_bug372345.html59
-rw-r--r--editor/libeditor/tests/test_bug404320.html91
-rw-r--r--editor/libeditor/tests/test_bug408231.html250
-rw-r--r--editor/libeditor/tests/test_bug410986.html80
-rw-r--r--editor/libeditor/tests/test_bug414526.html247
-rw-r--r--editor/libeditor/tests/test_bug417418.html78
-rw-r--r--editor/libeditor/tests/test_bug432225.html71
-rw-r--r--editor/libeditor/tests/test_bug439808.html37
-rw-r--r--editor/libeditor/tests/test_bug442186.html103
-rw-r--r--editor/libeditor/tests/test_bug449243.html136
-rw-r--r--editor/libeditor/tests/test_bug455992.html97
-rw-r--r--editor/libeditor/tests/test_bug456244.html70
-rw-r--r--editor/libeditor/tests/test_bug460740.html124
-rw-r--r--editor/libeditor/tests/test_bug46555.html47
-rw-r--r--editor/libeditor/tests/test_bug468353.html117
-rw-r--r--editor/libeditor/tests/test_bug471319.html80
-rw-r--r--editor/libeditor/tests/test_bug471722.html81
-rw-r--r--editor/libeditor/tests/test_bug478725.html131
-rw-r--r--editor/libeditor/tests/test_bug480647.html110
-rw-r--r--editor/libeditor/tests/test_bug480972.html98
-rw-r--r--editor/libeditor/tests/test_bug483651.html53
-rw-r--r--editor/libeditor/tests/test_bug484181.html78
-rw-r--r--editor/libeditor/tests/test_bug487524.html65
-rw-r--r--editor/libeditor/tests/test_bug489202.xul81
-rw-r--r--editor/libeditor/tests/test_bug490879.html45
-rw-r--r--editor/libeditor/tests/test_bug502673.html108
-rw-r--r--editor/libeditor/tests/test_bug514156.html50
-rw-r--r--editor/libeditor/tests/test_bug520189.html621
-rw-r--r--editor/libeditor/tests/test_bug525389.html198
-rw-r--r--editor/libeditor/tests/test_bug537046.html51
-rw-r--r--editor/libeditor/tests/test_bug549262.html132
-rw-r--r--editor/libeditor/tests/test_bug550434.html42
-rw-r--r--editor/libeditor/tests/test_bug551704.html125
-rw-r--r--editor/libeditor/tests/test_bug552782.html47
-rw-r--r--editor/libeditor/tests/test_bug567213.html58
-rw-r--r--editor/libeditor/tests/test_bug569988.html99
-rw-r--r--editor/libeditor/tests/test_bug570144.html123
-rw-r--r--editor/libeditor/tests/test_bug578771.html63
-rw-r--r--editor/libeditor/tests/test_bug586662.html60
-rw-r--r--editor/libeditor/tests/test_bug587461.html16
-rw-r--r--editor/libeditor/tests/test_bug590554.html36
-rw-r--r--editor/libeditor/tests/test_bug592592.html72
-rw-r--r--editor/libeditor/tests/test_bug596001.html55
-rw-r--r--editor/libeditor/tests/test_bug596333.html124
-rw-r--r--editor/libeditor/tests/test_bug596506.html60
-rw-r--r--editor/libeditor/tests/test_bug597331.html72
-rw-r--r--editor/libeditor/tests/test_bug597784.html37
-rw-r--r--editor/libeditor/tests/test_bug599322.html58
-rw-r--r--editor/libeditor/tests/test_bug599983.html16
-rw-r--r--editor/libeditor/tests/test_bug599983.xul70
-rw-r--r--editor/libeditor/tests/test_bug600570.html80
-rw-r--r--editor/libeditor/tests/test_bug602130.html45
-rw-r--r--editor/libeditor/tests/test_bug603556.html48
-rw-r--r--editor/libeditor/tests/test_bug604532.html42
-rw-r--r--editor/libeditor/tests/test_bug607584.html41
-rw-r--r--editor/libeditor/tests/test_bug607584.xul115
-rw-r--r--editor/libeditor/tests/test_bug611182.html239
-rw-r--r--editor/libeditor/tests/test_bug612128.html42
-rw-r--r--editor/libeditor/tests/test_bug612447.html74
-rw-r--r--editor/libeditor/tests/test_bug616590.xul105
-rw-r--r--editor/libeditor/tests/test_bug620906.html50
-rw-r--r--editor/libeditor/tests/test_bug622371.html40
-rw-r--r--editor/libeditor/tests/test_bug625452.html66
-rw-r--r--editor/libeditor/tests/test_bug629845.html60
-rw-r--r--editor/libeditor/tests/test_bug635636.html65
-rw-r--r--editor/libeditor/tests/test_bug636465.html54
-rw-r--r--editor/libeditor/tests/test_bug638596.html37
-rw-r--r--editor/libeditor/tests/test_bug640321.html190
-rw-r--r--editor/libeditor/tests/test_bug641466.html46
-rw-r--r--editor/libeditor/tests/test_bug645914.html63
-rw-r--r--editor/libeditor/tests/test_bug646194.html38
-rw-r--r--editor/libeditor/tests/test_bug668599.html73
-rw-r--r--editor/libeditor/tests/test_bug674770-1.html86
-rw-r--r--editor/libeditor/tests/test_bug674770-2.html395
-rw-r--r--editor/libeditor/tests/test_bug674861.html185
-rw-r--r--editor/libeditor/tests/test_bug676401.html119
-rw-r--r--editor/libeditor/tests/test_bug677752.html107
-rw-r--r--editor/libeditor/tests/test_bug681229.html51
-rw-r--r--editor/libeditor/tests/test_bug686203.html50
-rw-r--r--editor/libeditor/tests/test_bug692520.html42
-rw-r--r--editor/libeditor/tests/test_bug697842.html117
-rw-r--r--editor/libeditor/tests/test_bug725069.html35
-rw-r--r--editor/libeditor/tests/test_bug735059.html22
-rw-r--r--editor/libeditor/tests/test_bug738366.html24
-rw-r--r--editor/libeditor/tests/test_bug740784.html48
-rw-r--r--editor/libeditor/tests/test_bug742261.html14
-rw-r--r--editor/libeditor/tests/test_bug757371.html26
-rw-r--r--editor/libeditor/tests/test_bug757771.html31
-rw-r--r--editor/libeditor/tests/test_bug767684.html15
-rw-r--r--editor/libeditor/tests/test_bug772796.html223
-rw-r--r--editor/libeditor/tests/test_bug773262.html63
-rw-r--r--editor/libeditor/tests/test_bug780035.html22
-rw-r--r--editor/libeditor/tests/test_bug780908.xul113
-rw-r--r--editor/libeditor/tests/test_bug787432.html17
-rw-r--r--editor/libeditor/tests/test_bug790475.html95
-rw-r--r--editor/libeditor/tests/test_bug795418-2.html88
-rw-r--r--editor/libeditor/tests/test_bug795418-3.html88
-rw-r--r--editor/libeditor/tests/test_bug795418-4.html67
-rw-r--r--editor/libeditor/tests/test_bug795418-5.html67
-rw-r--r--editor/libeditor/tests/test_bug795418-6.html67
-rw-r--r--editor/libeditor/tests/test_bug795418.html67
-rw-r--r--editor/libeditor/tests/test_bug795785.html168
-rw-r--r--editor/libeditor/tests/test_bug796839.html17
-rw-r--r--editor/libeditor/tests/test_bug830600.html100
-rw-r--r--editor/libeditor/tests/test_bug832025.html42
-rw-r--r--editor/libeditor/tests/test_bug850043.html65
-rw-r--r--editor/libeditor/tests/test_bug857487.html72
-rw-r--r--editor/libeditor/tests/test_bug858918.html16
-rw-r--r--editor/libeditor/tests/test_bug915962.html100
-rw-r--r--editor/libeditor/tests/test_bug966155.html57
-rw-r--r--editor/libeditor/tests/test_bug966552.html45
-rw-r--r--editor/libeditor/tests/test_bug974309.html78
-rw-r--r--editor/libeditor/tests/test_bug998188.html52
-rw-r--r--editor/libeditor/tests/test_composition_event_created_in_chrome.html82
-rw-r--r--editor/libeditor/tests/test_contenteditable_focus.html209
-rw-r--r--editor/libeditor/tests/test_contenteditable_text_input_handling.html329
-rw-r--r--editor/libeditor/tests/test_css_chrome_load_access.html67
-rw-r--r--editor/libeditor/tests/test_dom_input_event_on_htmleditor.html182
-rw-r--r--editor/libeditor/tests/test_dom_input_event_on_texteditor.html140
-rw-r--r--editor/libeditor/tests/test_dragdrop.html178
-rw-r--r--editor/libeditor/tests/test_htmleditor_keyevent_handling.html664
-rw-r--r--editor/libeditor/tests/test_keypress_untrusted_event.html99
-rw-r--r--editor/libeditor/tests/test_root_element_replacement.html148
-rw-r--r--editor/libeditor/tests/test_select_all_without_body.html27
-rw-r--r--editor/libeditor/tests/test_selection_move_commands.html219
-rw-r--r--editor/libeditor/tests/test_set_document_title_transaction.html79
-rw-r--r--editor/libeditor/tests/test_spellcheck_pref.html23
-rw-r--r--editor/libeditor/tests/test_texteditor_keyevent_handling.html386
245 files changed, 35842 insertions, 0 deletions
diff --git a/editor/libeditor/tests/browser.ini b/editor/libeditor/tests/browser.ini
new file mode 100644
index 000000000..249f59aa8
--- /dev/null
+++ b/editor/libeditor/tests/browser.ini
@@ -0,0 +1,6 @@
+[browser_bug527935.js]
+skip-if = toolkit == 'android'
+support-files = bug527935.html
+[browser_bug629172.js]
+skip-if = toolkit == 'android'
+support-files = bug629172.html
diff --git a/editor/libeditor/tests/browser_bug527935.js b/editor/libeditor/tests/browser_bug527935.js
new file mode 100644
index 000000000..dc6e74d3e
--- /dev/null
+++ b/editor/libeditor/tests/browser_bug527935.js
@@ -0,0 +1,63 @@
+add_task(function*() {
+ yield new Promise(resolve => waitForFocus(resolve, window));
+
+ const kPageURL = "http://example.org/browser/editor/libeditor/tests/bug527935.html";
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: kPageURL
+ }, function*(aBrowser) {
+ var popupShown = false;
+ function listener() {
+ popupShown = true;
+ }
+ SpecialPowers.addAutoCompletePopupEventListener(window, "popupshowing", listener);
+
+ yield ContentTask.spawn(aBrowser, {}, function*() {
+ var window = content.window.wrappedJSObject;
+ var document = window.document;
+ var formTarget = document.getElementById("formTarget");
+ var initValue = document.getElementById("initValue");
+
+ window.loadPromise = new Promise(resolve => {
+ formTarget.onload = resolve;
+ });
+
+ initValue.focus();
+ initValue.value = "foo";
+ });
+
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ yield ContentTask.spawn(aBrowser, {}, function*() {
+ var window = content.window.wrappedJSObject;
+ var document = window.document;
+
+ yield window.loadPromise;
+
+ var newInput = document.createElement("input");
+ newInput.setAttribute("name", "test");
+ document.body.appendChild(newInput);
+
+ var event = document.createEvent("KeyboardEvent");
+
+ event.initKeyEvent("keypress", true, true, null, false, false,
+ false, false, 0, "f".charCodeAt(0));
+ newInput.value = "";
+ newInput.focus();
+ newInput.dispatchEvent(event);
+ });
+
+ yield new Promise(resolve => hitEventLoop(resolve, 100));
+
+ ok(!popupShown, "Popup must not be opened");
+ SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshowing", listener);
+ });
+});
+
+function hitEventLoop(func, times) {
+ if (times > 0) {
+ setTimeout(hitEventLoop, 0, func, times - 1);
+ } else {
+ setTimeout(func, 0);
+ }
+}
diff --git a/editor/libeditor/tests/browser_bug629172.js b/editor/libeditor/tests/browser_bug629172.js
new file mode 100644
index 000000000..0c4f34069
--- /dev/null
+++ b/editor/libeditor/tests/browser_bug629172.js
@@ -0,0 +1,106 @@
+add_task(function*() {
+ yield new Promise(resolve => waitForFocus(resolve, window));
+
+ const kPageURL = "http://example.org/browser/editor/libeditor/tests/bug629172.html";
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: kPageURL
+ }, function*(aBrowser) {
+ yield ContentTask.spawn(aBrowser, {}, function*() {
+ var window = content.window.wrappedJSObject;
+ var document = window.document;
+
+ // Note: Using the with keyword, we would have been able to write this as:
+ //
+ // with (window) {
+ // Screenshots = {};
+ // // so on
+ // }
+ //
+ // However, browser-chrome tests are somehow forced to run in strict mode,
+ // which doesn't permit the usage of the with keyword, turning the following
+ // into the ugliest test you've ever seen. :(
+ var LTRRef = document.getElementById("ltr-ref");
+ var RTLRef = document.getElementById("rtl-ref");
+ window.Screenshots = {};
+
+ // generate the reference screenshots
+ LTRRef.style.display = "";
+ document.body.clientWidth;
+ window.Screenshots.ltr = window.snapshotWindow(window);
+ LTRRef.parentNode.removeChild(LTRRef);
+ RTLRef.style.display = "";
+ document.body.clientWidth;
+ window.Screenshots.rtl = window.snapshotWindow(window);
+ RTLRef.parentNode.removeChild(RTLRef);
+ window.Screenshots.get = function(dir, flip) {
+ if (flip) {
+ return this[dir == "rtl" ? "ltr" : "rtl"];
+ } else {
+ return this[dir];
+ }
+ };
+ });
+
+ function simulateCtrlShiftX(aBrowser) {
+ // In e10s, the keypress event will be dispatched to the content process,
+ // but in non-e10s it is handled by the browser UI code and hence won't
+ // reach the web page. As a result, we need to observe the event in
+ // the content process only in e10s mode.
+ var waitForKeypressContent = BrowserTestUtils.waitForContentEvent(aBrowser, "keypress");
+ EventUtils.synthesizeKey("x", {accelKey: true, shiftKey: true});
+ if (gMultiProcessBrowser) {
+ return waitForKeypressContent;
+ }
+ return Promise.resolve();
+ }
+
+ function* testDirection(initialDir, aBrowser) {
+ yield ContentTask.spawn(aBrowser, {initialDir}, function({initialDir}) {
+ var window = content.window.wrappedJSObject;
+ var document = window.document;
+
+ var t = window.t = document.createElement("textarea");
+ t.setAttribute("dir", initialDir);
+ t.value = "test.";
+ window.inputEventCount = 0;
+ t.oninput = function() { window.inputEventCount++; };
+ document.getElementById("content").appendChild(t);
+ document.body.clientWidth;
+ var s1 = window.snapshotWindow(window);
+ ok(window.compareSnapshots(s1, window.Screenshots.get(initialDir, false), true)[0],
+ "Textarea should appear correctly before switching the direction (" + initialDir + ")");
+ t.focus();
+ is(window.inputEventCount, 0, "input event count must be 0 before");
+ });
+ yield simulateCtrlShiftX(aBrowser);
+ yield ContentTask.spawn(aBrowser, {initialDir}, function({initialDir}) {
+ var window = content.window.wrappedJSObject;
+
+ is(window.t.getAttribute("dir"), initialDir == "ltr" ? "rtl" : "ltr", "The dir attribute must be correctly updated");
+ is(window.inputEventCount, 1, "input event count must be 1 after");
+ window.t.blur();
+ var s2 = window.snapshotWindow(window);
+ ok(window.compareSnapshots(s2, window.Screenshots.get(initialDir, true), true)[0],
+ "Textarea should appear correctly after switching the direction (" + initialDir + ")");
+ window.t.focus();
+ is(window.inputEventCount, 1, "input event count must be 1 before");
+ });
+ yield simulateCtrlShiftX(aBrowser);
+ yield ContentTask.spawn(aBrowser, {initialDir}, function({initialDir}) {
+ var window = content.window.wrappedJSObject;
+
+ is(window.inputEventCount, 2, "input event count must be 2 after");
+ is(window.t.getAttribute("dir"), initialDir == "ltr" ? "ltr" : "rtl", "The dir attribute must be correctly updated");
+ window.t.blur();
+ var s3 = window.snapshotWindow(window);
+ ok(window.compareSnapshots(s3, window.Screenshots.get(initialDir, false), true)[0],
+ "Textarea should appear correctly after switching back the direction (" + initialDir + ")");
+ window.t.parentNode.removeChild(window.t);
+ });
+ }
+
+ yield testDirection("ltr", aBrowser);
+ yield testDirection("rtl", aBrowser);
+ });
+});
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/LICENSE b/editor/libeditor/tests/browserscope/lib/richtext/LICENSE
new file mode 100644
index 000000000..57bc88a15
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/README b/editor/libeditor/tests/browserscope/lib/richtext/README
new file mode 100644
index 000000000..a3bc3110f
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/README
@@ -0,0 +1,58 @@
+README FOR BROWSERSCOPE
+-----------------------
+
+Hey there - thanks for downloading the code. This file has instructions
+for getting setup so that you can run the codebase locally.
+
+This project is built on Google App Engine using the
+Django web application framework and written in Python.
+
+To get started, you'll need to first download the App Engine SDK at:
+http://code.google.com/appengine/downloads.html
+
+For local development, just startup the server:
+./pathto/google_appengine/dev_appserver.py --port=8080 browserscope
+
+You should then be able to access the local application at:
+http://localhost:8080/
+
+Note: the first time you hit the homepage it may take a little
+while - that's because it's trying to read out median times for all
+of the tests from a nonexistent datastore and write to memcache.
+Just be a lil patient.
+
+You can run the unit tests at:
+ http://localhost:8080/test
+
+
+CONTRIBUTING
+------------------
+
+Most likely you are interested in adding new tests or creating
+a new test category. If you are interested in adding tests to an existing
+"category" you may want to get in touch with the maintainer for that
+branch of the tree. We are really looking forward to receiving your
+code in patch format. Currently the category maintainers are:
+Network: Steve Souders <souders@gmail.com>
+Reflow: Lindsey Simon <elsigh@gmail.com>
+Security: Adam Barth <adam@adambarth.com> and Collin Jackson <collin@collinjackson.com>
+
+
+To create a completely new test category:
+ * Copy one of the existing directories in categories/
+ * Edit your test_set.py, handlers.py
+ * Add your files in templates/ and static/
+ * Update urls.py and settings.CATEGORIES
+ * Follow the examples of other tests re:
+ * beaconing using/testdriver_base
+ * your GetScoreAndDisplayValue method
+ * your GetRowScoreAndDisplayValue method
+
+References:
+ * App Engine Docs - http://code.google.com/appengine/docs/python/overview.html
+ * App Engine Group - http://groups.google.com/group/google-appengine
+ * Python Docs - http://www.python.org/doc/
+ * Django - http://www.djangoproject.com/
+
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/README.Mozilla b/editor/libeditor/tests/browserscope/lib/richtext/README.Mozilla
new file mode 100644
index 000000000..5d304943f
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/README.Mozilla
@@ -0,0 +1,17 @@
+The BrowserScope project provides a set of cross-browser HTML editor tests,
+which we import in our test suite in order to run them as part of our
+continuous integration system.
+
+We pull tests occasionally from their Subversion repository using the pull
+script which can be found in this directory. We also record the revision ID
+which we've used in the current_revision file inside this directory.
+
+Using the pull script is quite easy, just switch to this directory, and say:
+
+sh update_from_upstream
+
+There are tests which we're currently failing on, and there will probably be
+more of those in the future. We should maintain a list of the failing tests
+manually in currentStatus.js (which can also be found in this directory), to
+make sure that the suite passes entirely, with failing tests marked as todo
+items.
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/currentStatus.js b/editor/libeditor/tests/browserscope/lib/richtext/currentStatus.js
new file mode 100644
index 000000000..b30775d04
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/currentStatus.js
@@ -0,0 +1,46 @@
+/**
+ * This file lists the tests in the BrowserScope suite which we are currently
+ * failing. We mark them as todo items to keep track of them.
+ */
+
+var knownFailures = {
+ // Dummy result items. There is one for each category.
+ 'apply' : {
+ '0-undefined' : true
+ },
+ 'unapply' : {
+ '0-undefined' : true
+ },
+ 'change' : {
+ '0-undefined' : true
+ },
+ 'query' : {
+ '0-undefined' : true
+ },
+ 'a' : {
+ 'createbookmark-0' : true,
+ 'fontsize-1' : true,
+ 'subscript-1' : true,
+ 'superscript-1' : true,
+ },
+ 'u': {
+ 'removeformat-1' : true,
+ 'removeformat-2' : true,
+ 'strikethrough-2' : true,
+ 'subscript-1' : true,
+ 'superscript-1' : true,
+ 'unbookmark-0' : true,
+ },
+ 'q': {
+ 'fontsize-1' : true,
+ 'fontsize-2' : true,
+ },
+ 'c': {
+ 'fontsize-1' : true,
+ 'fontsize-2' : true,
+ },
+};
+
+function isKnownFailure(type, test, param) {
+ return (type in knownFailures) && ((test + "-" + param) in knownFailures[type]);
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/current_revision b/editor/libeditor/tests/browserscope/lib/richtext/current_revision
new file mode 100644
index 000000000..1e2569914
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/current_revision
@@ -0,0 +1 @@
+775
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/richtext/editable.html b/editor/libeditor/tests/browserscope/lib/richtext/richtext/editable.html
new file mode 100644
index 000000000..a294f0b56
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/richtext/editable.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <script>
+ function load(){
+ window.document.designMode = "On";
+ }
+ </script>
+</head>
+<body contentEditable="true" onload="load()">
+</body>
+</html> \ No newline at end of file
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/richtext/js/range.js b/editor/libeditor/tests/browserscope/lib/richtext/richtext/js/range.js
new file mode 100644
index 000000000..3e4463e11
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/richtext/js/range.js
@@ -0,0 +1,1069 @@
+var goog$global = this, goog$isString = function(val) {
+ return typeof val == "string"
+};
+Math.floor(Math.random() * 2147483648).toString(36);
+var goog$now = Date.now || function() {
+ return(new Date).getTime()
+}, goog$inherits = function(childCtor, parentCtor) {
+ function tempCtor() {
+ }
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor
+};var goog$array$peek = function(array) {
+ return array[array.length - 1]
+}, goog$array$indexOf = function(arr, obj, opt_fromIndex) {
+ if(arr.indexOf)return arr.indexOf(obj, opt_fromIndex);
+ if(Array.indexOf)return Array.indexOf(arr, obj, opt_fromIndex);
+ for(var fromIndex = opt_fromIndex == null ? 0 : opt_fromIndex < 0 ? Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex, i = fromIndex;i < arr.length;i++)if(i in arr && arr[i] === obj)return i;
+ return-1
+}, goog$array$map = function(arr, f, opt_obj) {
+ if(arr.map)return arr.map(f, opt_obj);
+ if(Array.map)return Array.map(arr, f, opt_obj);
+ for(var l = arr.length, res = [], resLength = 0, arr2 = goog$isString(arr) ? arr.split("") : arr, i = 0;i < l;i++)if(i in arr2)res[resLength++] = f.call(opt_obj, arr2[i], i, arr);
+ return res
+}, goog$array$some = function(arr, f, opt_obj) {
+ if(arr.some)return arr.some(f, opt_obj);
+ if(Array.some)return Array.some(arr, f, opt_obj);
+ for(var l = arr.length, arr2 = goog$isString(arr) ? arr.split("") : arr, i = 0;i < l;i++)if(i in arr2 && f.call(opt_obj, arr2[i], i, arr))return true;
+ return false
+}, goog$array$every = function(arr, f, opt_obj) {
+ if(arr.every)return arr.every(f, opt_obj);
+ if(Array.every)return Array.every(arr, f, opt_obj);
+ for(var l = arr.length, arr2 = goog$isString(arr) ? arr.split("") : arr, i = 0;i < l;i++)if(i in arr2 && !f.call(opt_obj, arr2[i], i, arr))return false;
+ return true
+}, goog$array$find = function(arr, f, opt_obj) {
+ var i;
+ JSCompiler_inline_label_goog$array$findIndex_12: {
+ for(var JSCompiler_inline_l = arr.length, JSCompiler_inline_arr2 = goog$isString(arr) ? arr.split("") : arr, JSCompiler_inline_i = 0;JSCompiler_inline_i < JSCompiler_inline_l;JSCompiler_inline_i++)if(JSCompiler_inline_i in JSCompiler_inline_arr2 && f.call(opt_obj, JSCompiler_inline_arr2[JSCompiler_inline_i], JSCompiler_inline_i, arr)) {
+ i = JSCompiler_inline_i;
+ break JSCompiler_inline_label_goog$array$findIndex_12
+ }i = -1
+ }return i < 0 ? null : goog$isString(arr) ? arr.charAt(i) : arr[i]
+};var goog$string$trim = function(str) {
+ return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, "")
+}, goog$string$htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
+ if(opt_isLikelyToContainHtmlChars)return str.replace(goog$string$amperRe_, "&amp;").replace(goog$string$ltRe_, "&lt;").replace(goog$string$gtRe_, "&gt;").replace(goog$string$quotRe_, "&quot;");
+ else {
+ if(!goog$string$allRe_.test(str))return str;
+ if(str.indexOf("&") != -1)str = str.replace(goog$string$amperRe_, "&amp;");
+ if(str.indexOf("<") != -1)str = str.replace(goog$string$ltRe_, "&lt;");
+ if(str.indexOf(">") != -1)str = str.replace(goog$string$gtRe_, "&gt;");
+ if(str.indexOf('"') != -1)str = str.replace(goog$string$quotRe_, "&quot;");
+ return str
+ }
+}, goog$string$amperRe_ = /&/g, goog$string$ltRe_ = /</g, goog$string$gtRe_ = />/g, goog$string$quotRe_ = /\"/g, goog$string$allRe_ = /[&<>\"]/, goog$string$contains = function(s, ss) {
+ return s.indexOf(ss) != -1
+}, goog$string$compareVersions = function(version1, version2) {
+ for(var order = 0, v1Subs = goog$string$trim(String(version1)).split("."), v2Subs = goog$string$trim(String(version2)).split("."), subCount = Math.max(v1Subs.length, v2Subs.length), subIdx = 0;order == 0 && subIdx < subCount;subIdx++) {
+ var v1Sub = v1Subs[subIdx] || "", v2Sub = v2Subs[subIdx] || "", v1CompParser = new RegExp("(\\d*)(\\D*)", "g"), v2CompParser = new RegExp("(\\d*)(\\D*)", "g");
+ do {
+ var v1Comp = v1CompParser.exec(v1Sub) || ["", "", ""], v2Comp = v2CompParser.exec(v2Sub) || ["", "", ""];
+ if(v1Comp[0].length == 0 && v2Comp[0].length == 0)break;
+ var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10), v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
+ order = goog$string$compareElements_(v1CompNum, v2CompNum) || goog$string$compareElements_(v1Comp[2].length == 0, v2Comp[2].length == 0) || goog$string$compareElements_(v1Comp[2], v2Comp[2])
+ }while(order == 0)
+ }return order
+}, goog$string$compareElements_ = function(left, right) {
+ if(left < right)return-1;
+ else if(left > right)return 1;
+ return 0
+};
+goog$now();var goog$userAgent$detectedOpera_, goog$userAgent$detectedIe_, goog$userAgent$detectedWebkit_, goog$userAgent$detectedMobile_, goog$userAgent$detectedGecko_, goog$userAgent$detectedCamino_, goog$userAgent$detectedMac_, goog$userAgent$detectedWindows_, goog$userAgent$detectedLinux_, goog$userAgent$detectedX11_, goog$userAgent$getUserAgentString = function() {
+ return goog$global.navigator ? goog$global.navigator.userAgent : null
+}, goog$userAgent$getNavigator = function() {
+ return goog$global.navigator
+};
+goog$userAgent$detectedCamino_ = goog$userAgent$detectedGecko_ = goog$userAgent$detectedMobile_ = goog$userAgent$detectedWebkit_ = goog$userAgent$detectedIe_ = goog$userAgent$detectedOpera_ = false;
+var JSCompiler_inline_ua_15;
+if(JSCompiler_inline_ua_15 = goog$userAgent$getUserAgentString()) {
+ var JSCompiler_inline_navigator$$1_16 = goog$userAgent$getNavigator();
+ goog$userAgent$detectedOpera_ = JSCompiler_inline_ua_15.indexOf("Opera") == 0;
+ goog$userAgent$detectedIe_ = !goog$userAgent$detectedOpera_ && JSCompiler_inline_ua_15.indexOf("MSIE") != -1;
+ goog$userAgent$detectedMobile_ = (goog$userAgent$detectedWebkit_ = !goog$userAgent$detectedOpera_ && JSCompiler_inline_ua_15.indexOf("WebKit") != -1) && JSCompiler_inline_ua_15.indexOf("Mobile") != -1;
+ goog$userAgent$detectedCamino_ = (goog$userAgent$detectedGecko_ = !goog$userAgent$detectedOpera_ && !goog$userAgent$detectedWebkit_ && JSCompiler_inline_navigator$$1_16.product == "Gecko") && JSCompiler_inline_navigator$$1_16.vendor == "Camino"
+}var goog$userAgent$OPERA = goog$userAgent$detectedOpera_, goog$userAgent$IE = goog$userAgent$detectedIe_, goog$userAgent$GECKO = goog$userAgent$detectedGecko_, goog$userAgent$WEBKIT = goog$userAgent$detectedWebkit_, goog$userAgent$MOBILE = goog$userAgent$detectedMobile_, goog$userAgent$PLATFORM, JSCompiler_inline_navigator$$2_19 = goog$userAgent$getNavigator();
+goog$userAgent$PLATFORM = JSCompiler_inline_navigator$$2_19 && JSCompiler_inline_navigator$$2_19.platform || "";
+goog$userAgent$detectedMac_ = goog$string$contains(goog$userAgent$PLATFORM, "Mac");
+goog$userAgent$detectedWindows_ = goog$string$contains(goog$userAgent$PLATFORM, "Win");
+goog$userAgent$detectedLinux_ = goog$string$contains(goog$userAgent$PLATFORM, "Linux");
+goog$userAgent$detectedX11_ = !!goog$userAgent$getNavigator() && goog$string$contains(goog$userAgent$getNavigator().appVersion || "", "X11");
+var goog$userAgent$VERSION, JSCompiler_inline_version$$6_26 = "", JSCompiler_inline_re$$2_27;
+if(goog$userAgent$OPERA && goog$global.opera) {
+ var JSCompiler_inline_operaVersion_28 = goog$global.opera.version;
+ JSCompiler_inline_version$$6_26 = typeof JSCompiler_inline_operaVersion_28 == "function" ? JSCompiler_inline_operaVersion_28() : JSCompiler_inline_operaVersion_28
+}else {
+ if(goog$userAgent$GECKO)JSCompiler_inline_re$$2_27 = /rv\:([^\);]+)(\)|;)/;
+ else if(goog$userAgent$IE)JSCompiler_inline_re$$2_27 = /MSIE\s+([^\);]+)(\)|;)/;
+ else if(goog$userAgent$WEBKIT)JSCompiler_inline_re$$2_27 = /WebKit\/(\S+)/;
+ if(JSCompiler_inline_re$$2_27) {
+ var JSCompiler_inline_arr$$41_29 = JSCompiler_inline_re$$2_27.exec(goog$userAgent$getUserAgentString());
+ JSCompiler_inline_version$$6_26 = JSCompiler_inline_arr$$41_29 ? JSCompiler_inline_arr$$41_29[1] : ""
+ }
+}goog$userAgent$VERSION = JSCompiler_inline_version$$6_26;
+var goog$userAgent$isVersionCache_ = {}, goog$userAgent$isVersion = function(version) {
+ return goog$userAgent$isVersionCache_[version] || (goog$userAgent$isVersionCache_[version] = goog$string$compareVersions(goog$userAgent$VERSION, version) >= 0)
+};var goog$dom$getWindow = function(opt_doc) {
+ return opt_doc ? goog$dom$getWindow_(opt_doc) : window
+}, goog$dom$getWindow_ = function(doc) {
+ if(doc.parentWindow)return doc.parentWindow;
+ if(goog$userAgent$WEBKIT && !goog$userAgent$isVersion("500") && !goog$userAgent$MOBILE) {
+ var scriptElement = doc.createElement("script");
+ scriptElement.innerHTML = "document.parentWindow=window";
+ var parentElement = doc.documentElement;
+ parentElement.appendChild(scriptElement);
+ parentElement.removeChild(scriptElement);
+ return doc.parentWindow
+ }return doc.defaultView
+}, goog$dom$appendChild = function(parent, child) {
+ parent.appendChild(child)
+}, goog$dom$BAD_CONTAINS_WEBKIT_ = goog$userAgent$WEBKIT && goog$userAgent$isVersion("522"), goog$dom$contains = function(parent, descendant) {
+ if(typeof parent.contains != "undefined" && !goog$dom$BAD_CONTAINS_WEBKIT_ && descendant.nodeType == 1)return parent == descendant || parent.contains(descendant);
+ if(typeof parent.compareDocumentPosition != "undefined")return parent == descendant || Boolean(parent.compareDocumentPosition(descendant) & 16);
+ for(;descendant && parent != descendant;)descendant = descendant.parentNode;
+ return descendant == parent
+}, goog$dom$compareNodeOrder = function(node1, node2) {
+ if(node1 == node2)return 0;
+ if(node1.compareDocumentPosition)return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
+ if("sourceIndex" in node1 || node1.parentNode && "sourceIndex" in node1.parentNode) {
+ var isElement1 = node1.nodeType == 1, isElement2 = node2.nodeType == 1;
+ if(isElement1 && isElement2)return node1.sourceIndex - node2.sourceIndex;
+ else {
+ var parent1 = node1.parentNode, parent2 = node2.parentNode;
+ if(parent1 == parent2)return goog$dom$compareSiblingOrder_(node1, node2);
+ if(!isElement1 && goog$dom$contains(parent1, node2))return-1 * goog$dom$compareParentsDescendantNodeIe_(node1, node2);
+ if(!isElement2 && goog$dom$contains(parent2, node1))return goog$dom$compareParentsDescendantNodeIe_(node2, node1);
+ return(isElement1 ? node1.sourceIndex : parent1.sourceIndex) - (isElement2 ? node2.sourceIndex : parent2.sourceIndex)
+ }
+ }var doc = goog$dom$getOwnerDocument(node1), range1, range2;
+ range1 = doc.createRange();
+ range1.selectNode(node1);
+ range1.collapse(true);
+ range2 = doc.createRange();
+ range2.selectNode(node2);
+ range2.collapse(true);
+ return range1.compareBoundaryPoints(goog$global.Range.START_TO_END, range2)
+}, goog$dom$compareParentsDescendantNodeIe_ = function(textNode, node) {
+ var parent = textNode.parentNode;
+ if(parent == node)return-1;
+ for(var sibling = node;sibling.parentNode != parent;)sibling = sibling.parentNode;
+ return goog$dom$compareSiblingOrder_(sibling, textNode)
+}, goog$dom$compareSiblingOrder_ = function(node1, node2) {
+ for(var s = node2;s = s.previousSibling;)if(s == node1)return-1;
+ return 1
+}, goog$dom$findCommonAncestor = function() {
+ var i, count = arguments.length;
+ if(count) {
+ if(count == 1)return arguments[0]
+ }else return null;
+ var paths = [], minLength = Infinity;
+ for(i = 0;i < count;i++) {
+ for(var ancestors = [], node = arguments[i];node;) {
+ ancestors.unshift(node);
+ node = node.parentNode
+ }paths.push(ancestors);
+ minLength = Math.min(minLength, ancestors.length)
+ }var output = null;
+ for(i = 0;i < minLength;i++) {
+ for(var first = paths[0][i], j = 1;j < count;j++)if(first != paths[j][i])return output;
+ output = first
+ }return output
+}, goog$dom$getOwnerDocument = function(node) {
+ // Added 'editorDoc' as hack for browsers that don't support node.ownerDocument
+ return node.nodeType == 9 ? node : node.ownerDocument || node.document || editorDoc
+}, goog$dom$DomHelper = function(opt_document) {
+ this.document_ = opt_document || goog$global.document || document
+};
+goog$dom$DomHelper.prototype.getDocument = function() {
+ return this.document_
+};
+goog$dom$DomHelper.prototype.createElement = function(name) {
+ return this.document_.createElement(name)
+};
+goog$dom$DomHelper.prototype.getWindow = function() {
+ return goog$dom$getWindow_(this.document_)
+};
+goog$dom$DomHelper.prototype.appendChild = goog$dom$appendChild;
+goog$dom$DomHelper.prototype.contains = goog$dom$contains;var goog$Disposable = function() {
+};if("StopIteration" in goog$global)var goog$iter$StopIteration = goog$global.StopIteration;
+else goog$iter$StopIteration = Error("StopIteration");
+var goog$iter$Iterator = function() {
+};
+goog$iter$Iterator.prototype.next = function() {
+ throw goog$iter$StopIteration;
+};
+goog$iter$Iterator.prototype.__iterator__ = function() {
+ return this
+};var goog$debug$exposeException = function(err, opt_fn) {
+ try {
+ var e, JSCompiler_inline_href_34;
+ JSCompiler_inline_label_goog$getObjectByName_61: {
+ for(var JSCompiler_inline_parts = "window.location.href".split("."), JSCompiler_inline_cur = goog$global, JSCompiler_inline_part;JSCompiler_inline_part = JSCompiler_inline_parts.shift();)if(JSCompiler_inline_cur[JSCompiler_inline_part])JSCompiler_inline_cur = JSCompiler_inline_cur[JSCompiler_inline_part];
+ else {
+ JSCompiler_inline_href_34 = null;
+ break JSCompiler_inline_label_goog$getObjectByName_61
+ }JSCompiler_inline_href_34 = JSCompiler_inline_cur
+ }e = typeof err == "string" ? {message:err, name:"Unknown error", lineNumber:"Not available", fileName:JSCompiler_inline_href_34, stack:"Not available"} : !err.lineNumber || !err.fileName || !err.stack ? {message:err.message, name:err.name, lineNumber:err.lineNumber || err.line || "Not available", fileName:err.fileName || err.filename || err.sourceURL || JSCompiler_inline_href_34, stack:err.stack || "Not available"} : err;
+ var error = "Message: " + goog$string$htmlEscape(e.message) + '\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' + e.fileName + "</a>\nLine: " + e.lineNumber + "\n\nBrowser stack:\n" + goog$string$htmlEscape(e.stack + "-> ") + "[end]\n\nJS stack traversal:\n" + goog$string$htmlEscape(goog$debug$getStacktrace(opt_fn) + "-> ");
+ return error
+ }catch(e2) {
+ return"Exception trying to expose exception! You win, we lose. " + e2
+ }
+}, goog$debug$getStacktrace = function(opt_fn) {
+ return goog$debug$getStacktraceHelper_(opt_fn || arguments.callee.caller, [])
+}, goog$debug$getStacktraceHelper_ = function(fn, visited) {
+ var sb = [], JSCompiler_inline_result_36;
+ JSCompiler_inline_label_goog$array$contains_41:JSCompiler_inline_result_36 = visited.contains ? visited.contains(fn) : goog$array$indexOf(visited, fn) > -1;
+ if(JSCompiler_inline_result_36)sb.push("[...circular reference...]");
+ else if(fn && visited.length < 50) {
+ sb.push(goog$debug$getFunctionName(fn) + "(");
+ for(var args = fn.arguments, i = 0;i < args.length;i++) {
+ i > 0 && sb.push(", ");
+ var argDesc, arg = args[i];
+ switch(typeof arg) {
+ case "object":
+ argDesc = arg ? "object" : "null";
+ break;
+ case "string":
+ argDesc = arg;
+ break;
+ case "number":
+ argDesc = String(arg);
+ break;
+ case "boolean":
+ argDesc = arg ? "true" : "false";
+ break;
+ case "function":
+ argDesc = (argDesc = goog$debug$getFunctionName(arg)) ? argDesc : "[fn]";
+ break;
+ case "undefined":
+ ;
+ default:
+ argDesc = typeof arg;
+ break
+ }
+ if(argDesc.length > 40)argDesc = argDesc.substr(0, 40) + "...";
+ sb.push(argDesc)
+ }visited.push(fn);
+ sb.push(")\n");
+ try {
+ sb.push(goog$debug$getStacktraceHelper_(fn.caller, visited))
+ }catch(e) {
+ sb.push("[exception trying to get caller]\n")
+ }
+ }else fn ? sb.push("[...long stack...]") : sb.push("[end]");
+ return sb.join("")
+}, goog$debug$getFunctionName = function(fn) {
+ var functionSource = String(fn);
+ if(!goog$debug$fnNameCache_[functionSource]) {
+ var matches = /function ([^\(]+)/.exec(functionSource);
+ if(matches) {
+ var method = matches[1];
+ goog$debug$fnNameCache_[functionSource] = method
+ }else goog$debug$fnNameCache_[functionSource] = "[Anonymous]"
+ }return goog$debug$fnNameCache_[functionSource]
+}, goog$debug$fnNameCache_ = {};var goog$debug$LogRecord = function(level, msg, loggerName, opt_time, opt_sequenceNumber) {
+ this.sequenceNumber_ = typeof opt_sequenceNumber == "number" ? opt_sequenceNumber : goog$debug$LogRecord$nextSequenceNumber_++;
+ this.time_ = opt_time || goog$now();
+ this.level_ = level;
+ this.msg_ = msg;
+ this.loggerName_ = loggerName
+};
+goog$debug$LogRecord.prototype.exception_ = null;
+goog$debug$LogRecord.prototype.exceptionText_ = null;
+var goog$debug$LogRecord$nextSequenceNumber_ = 0;
+goog$debug$LogRecord.prototype.setException = function(exception) {
+ this.exception_ = exception
+};
+goog$debug$LogRecord.prototype.setExceptionText = function(text) {
+ this.exceptionText_ = text
+};
+goog$debug$LogRecord.prototype.setLevel = function(level) {
+ this.level_ = level
+};var goog$debug$Logger = function(name) {
+ this.name_ = name;
+ this.parent_ = null;
+ this.children_ = {};
+ this.handlers_ = []
+};
+goog$debug$Logger.prototype.level_ = null;
+var goog$debug$Logger$Level = function(name, value) {
+ this.name = name;
+ this.value = value
+};
+goog$debug$Logger$Level.prototype.toString = function() {
+ return this.name
+};
+new goog$debug$Logger$Level("OFF", Infinity);
+new goog$debug$Logger$Level("SHOUT", 1200);
+var goog$debug$Logger$Level$SEVERE = new goog$debug$Logger$Level("SEVERE", 1000), goog$debug$Logger$Level$WARNING = new goog$debug$Logger$Level("WARNING", 900);
+new goog$debug$Logger$Level("INFO", 800);
+var goog$debug$Logger$Level$CONFIG = new goog$debug$Logger$Level("CONFIG", 700);
+new goog$debug$Logger$Level("FINE", 500);
+new goog$debug$Logger$Level("FINER", 400);
+new goog$debug$Logger$Level("FINEST", 300);
+new goog$debug$Logger$Level("ALL", 0);
+goog$debug$Logger.prototype.setLevel = function(level) {
+ this.level_ = level
+};
+goog$debug$Logger.prototype.isLoggable = function(level) {
+ if(this.level_)return level.value >= this.level_.value;
+ if(this.parent_)return this.parent_.isLoggable(level);
+ return false
+};
+goog$debug$Logger.prototype.log = function(level, msg, opt_exception) {
+ this.isLoggable(level) && this.logRecord(this.getLogRecord(level, msg, opt_exception))
+};
+goog$debug$Logger.prototype.getLogRecord = function(level, msg, opt_exception) {
+ var logRecord = new goog$debug$LogRecord(level, String(msg), this.name_);
+ if(opt_exception) {
+ logRecord.setException(opt_exception);
+ logRecord.setExceptionText(goog$debug$exposeException(opt_exception, arguments.callee.caller))
+ }return logRecord
+};
+goog$debug$Logger.prototype.severe = function(msg, opt_exception) {
+ this.log(goog$debug$Logger$Level$SEVERE, msg, opt_exception)
+};
+goog$debug$Logger.prototype.warning = function(msg, opt_exception) {
+ this.log(goog$debug$Logger$Level$WARNING, msg, opt_exception)
+};
+goog$debug$Logger.prototype.logRecord = function(logRecord) {
+ if(this.isLoggable(logRecord.level_))for(var target = this;target;) {
+ target.callPublish_(logRecord);
+ target = target.parent_
+ }
+};
+goog$debug$Logger.prototype.callPublish_ = function(logRecord) {
+ for(var i = 0;i < this.handlers_.length;i++)this.handlers_[i](logRecord)
+};
+goog$debug$Logger.prototype.setParent_ = function(parent) {
+ this.parent_ = parent
+};
+goog$debug$Logger.prototype.addChild_ = function(name, logger) {
+ this.children_[name] = logger
+};
+var goog$debug$LogManager$loggers_ = {}, goog$debug$LogManager$rootLogger_ = null, goog$debug$LogManager$getLogger = function(name) {
+ if(!goog$debug$LogManager$rootLogger_) {
+ goog$debug$LogManager$rootLogger_ = new goog$debug$Logger("");
+ goog$debug$LogManager$loggers_[""] = goog$debug$LogManager$rootLogger_;
+ goog$debug$LogManager$rootLogger_.setLevel(goog$debug$Logger$Level$CONFIG)
+ }return name in goog$debug$LogManager$loggers_ ? goog$debug$LogManager$loggers_[name] : goog$debug$LogManager$createLogger_(name)
+}, goog$debug$LogManager$createLogger_ = function(name) {
+ var logger = new goog$debug$Logger(name), parts = name.split("."), leafName = parts[parts.length - 1];
+ parts.length = parts.length - 1;
+ var parentName = parts.join("."), parentLogger = goog$debug$LogManager$getLogger(parentName);
+ parentLogger.addChild_(leafName, logger);
+ logger.setParent_(parentLogger);
+ return goog$debug$LogManager$loggers_[name] = logger
+};var goog$dom$SavedRange = function() {
+ goog$Disposable.call(this)
+};
+goog$inherits(goog$dom$SavedRange, goog$Disposable);
+goog$debug$LogManager$getLogger("goog.dom.SavedRange");var goog$dom$TagIterator = function(opt_node, opt_reversed, opt_unconstrained, opt_tagType, opt_depth) {
+ this.reversed = !!opt_reversed;
+ opt_node && this.setPosition(opt_node, opt_tagType);
+ this.depth = opt_depth != undefined ? opt_depth : this.tagType || 0;
+ if(this.reversed)this.depth *= -1;
+ this.constrained = !opt_unconstrained
+};
+goog$inherits(goog$dom$TagIterator, goog$iter$Iterator);
+goog$dom$TagIterator.prototype.node = null;
+goog$dom$TagIterator.prototype.tagType = null;
+goog$dom$TagIterator.prototype.started_ = false;
+goog$dom$TagIterator.prototype.setPosition = function(node, opt_tagType, opt_depth) {
+ if(this.node = node)this.tagType = typeof opt_tagType == "number" ? opt_tagType : this.node.nodeType != 1 ? 0 : this.reversed ? -1 : 1;
+ if(typeof opt_depth == "number")this.depth = opt_depth
+};
+goog$dom$TagIterator.prototype.next = function() {
+ var node;
+ if(this.started_) {
+ if(!this.node || this.constrained && this.depth == 0)throw goog$iter$StopIteration;node = this.node;
+ var startType = this.reversed ? -1 : 1;
+ if(this.tagType == startType) {
+ var child = this.reversed ? node.lastChild : node.firstChild;
+ child ? this.setPosition(child) : this.setPosition(node, startType * -1)
+ }else {
+ var sibling = this.reversed ? node.previousSibling : node.nextSibling;
+ sibling ? this.setPosition(sibling) : this.setPosition(node.parentNode, startType * -1)
+ }this.depth += this.tagType * (this.reversed ? -1 : 1)
+ }else this.started_ = true;
+ node = this.node;
+ if(!this.node)throw goog$iter$StopIteration;return node
+};
+goog$dom$TagIterator.prototype.isStartTag = function() {
+ return this.tagType == 1
+};var goog$dom$AbstractRange = function() {
+};
+goog$dom$AbstractRange.prototype.getTextRanges = function() {
+ for(var output = [], i = 0, len = this.getTextRangeCount();i < len;i++)output.push(this.getTextRange(i));
+ return output
+};
+goog$dom$AbstractRange.prototype.getAnchorNode = function() {
+ return this.isReversed() ? this.getEndNode() : this.getStartNode()
+};
+goog$dom$AbstractRange.prototype.getAnchorOffset = function() {
+ return this.isReversed() ? this.getEndOffset() : this.getStartOffset()
+};
+goog$dom$AbstractRange.prototype.getFocusNode = function() {
+ return this.isReversed() ? this.getStartNode() : this.getEndNode()
+};
+goog$dom$AbstractRange.prototype.getFocusOffset = function() {
+ return this.isReversed() ? this.getStartOffset() : this.getEndOffset()
+};
+goog$dom$AbstractRange.prototype.isReversed = function() {
+ return false
+};
+goog$dom$AbstractRange.prototype.getDocument = function() {
+ return goog$dom$getOwnerDocument(goog$userAgent$IE ? this.getContainer() : this.getStartNode())
+};
+goog$dom$AbstractRange.prototype.getWindow = function() {
+ return goog$dom$getWindow(this.getDocument())
+};
+goog$dom$AbstractRange.prototype.containsNode = function(node, opt_allowPartial) {
+ return this.containsRange(goog$dom$TextRange$createFromNodeContents(node, undefined), opt_allowPartial)
+};
+var goog$dom$RangeIterator = function(node, opt_reverse) {
+ goog$dom$TagIterator.call(this, node, opt_reverse, true)
+};
+goog$inherits(goog$dom$RangeIterator, goog$dom$TagIterator);var goog$dom$AbstractMultiRange = function() {
+};
+goog$inherits(goog$dom$AbstractMultiRange, goog$dom$AbstractRange);
+goog$dom$AbstractMultiRange.prototype.containsRange = function(otherRange, opt_allowPartial) {
+ var ranges = this.getTextRanges(), otherRanges = otherRange.getTextRanges(), fn = opt_allowPartial ? goog$array$some : goog$array$every;
+ return fn(otherRanges, function(otherRange) {
+ return goog$array$some(ranges, function(range) {
+ return range.containsRange(otherRange, opt_allowPartial)
+ })
+ })
+};var goog$dom$TextRangeIterator = function(startNode, startOffset, endNode, endOffset, opt_reverse) {
+ var goNext;
+ if(startNode) {
+ this.startNode_ = startNode;
+ this.startOffset_ = startOffset;
+ this.endNode_ = endNode;
+ this.endOffset_ = endOffset;
+ if(startNode.nodeType == 1 && startNode.tagName != "BR") {
+ var startChildren = startNode.childNodes, candidate = startChildren[startOffset];
+ if(candidate) {
+ this.startNode_ = candidate;
+ this.startOffset_ = 0
+ }else {
+ if(startChildren.length)this.startNode_ = goog$array$peek(startChildren);
+ goNext = true
+ }
+ }if(endNode.nodeType == 1)if(this.endNode_ = endNode.childNodes[endOffset])this.endOffset_ = 0;
+ else this.endNode_ = endNode
+ }goog$dom$RangeIterator.call(this, opt_reverse ? this.endNode_ : this.startNode_, opt_reverse);
+ if(goNext)try {
+ this.next()
+ }catch(e) {
+ if(e != goog$iter$StopIteration)throw e;
+ }
+};
+goog$inherits(goog$dom$TextRangeIterator, goog$dom$RangeIterator);
+goog$dom$TextRangeIterator.prototype.startNode_ = null;
+goog$dom$TextRangeIterator.prototype.endNode_ = null;
+goog$dom$TextRangeIterator.prototype.startOffset_ = 0;
+goog$dom$TextRangeIterator.prototype.endOffset_ = 0;
+goog$dom$TextRangeIterator.prototype.getStartNode = function() {
+ return this.startNode_
+};
+goog$dom$TextRangeIterator.prototype.getEndNode = function() {
+ return this.endNode_
+};
+goog$dom$TextRangeIterator.prototype.isLast = function() {
+ return this.started_ && this.node == this.endNode_ && (!this.endOffset_ || !this.isStartTag())
+};
+goog$dom$TextRangeIterator.prototype.next = function() {
+ if(this.isLast())throw goog$iter$StopIteration;return goog$dom$TextRangeIterator.superClass_.next.call(this)
+};var goog$userAgent$jscript$DETECTED_HAS_JSCRIPT_, goog$userAgent$jscript$DETECTED_VERSION_, JSCompiler_inline_hasScriptEngine_44 = "ScriptEngine" in goog$global;
+goog$userAgent$jscript$DETECTED_VERSION_ = (goog$userAgent$jscript$DETECTED_HAS_JSCRIPT_ = JSCompiler_inline_hasScriptEngine_44 && goog$global.ScriptEngine() == "JScript") ? goog$global.ScriptEngineMajorVersion() + "." + goog$global.ScriptEngineMinorVersion() + "." + goog$global.ScriptEngineBuildVersion() : "0";var goog$dom$browserrange$AbstractRange = function() {
+};
+goog$dom$browserrange$AbstractRange.prototype.containsRange = function(range, opt_allowPartial) {
+ return this.containsBrowserRange(range.range_, opt_allowPartial)
+};
+goog$dom$browserrange$AbstractRange.prototype.containsBrowserRange = function(range, opt_allowPartial) {
+ try {
+ return opt_allowPartial ? this.compareBrowserRangeEndpoints(range, 0, 1) >= 0 && this.compareBrowserRangeEndpoints(range, 1, 0) <= 0 : this.compareBrowserRangeEndpoints(range, 0, 0) >= 0 && this.compareBrowserRangeEndpoints(range, 1, 1) <= 0
+ }catch(e) {
+ if(!goog$userAgent$IE)throw e;return false
+ }
+};
+goog$dom$browserrange$AbstractRange.prototype.containsNode = function(node, opt_allowPartial) {
+ return this.containsRange(goog$userAgent$IE ? goog$dom$browserrange$IeRange$createFromNodeContents(node) : goog$userAgent$WEBKIT ? new goog$dom$browserrange$WebKitRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNode(node)) : goog$userAgent$GECKO ? new goog$dom$browserrange$GeckoRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNode(node)) : new goog$dom$browserrange$W3cRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNode(node)), opt_allowPartial)
+};
+goog$dom$browserrange$AbstractRange.prototype.__iterator__ = function() {
+ return new goog$dom$TextRangeIterator(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset())
+};var goog$dom$browserrange$W3cRange = function(range) {
+ this.range_ = range
+};
+goog$inherits(goog$dom$browserrange$W3cRange, goog$dom$browserrange$AbstractRange);
+var goog$dom$browserrange$W3cRange$getBrowserRangeForNode = function(node) {
+ var nodeRange = goog$dom$getOwnerDocument(node).createRange();
+ if(node.nodeType == 3) {
+ nodeRange.setStart(node, 0);
+ nodeRange.setEnd(node, node.length)
+ }else {
+ for(var tempNode, leaf = node;tempNode = leaf.firstChild;)leaf = tempNode;
+ nodeRange.setStart(leaf, 0);
+ for(leaf = node;tempNode = leaf.lastChild;)leaf = tempNode;
+ nodeRange.setEnd(leaf, leaf.nodeType == 1 ? leaf.childNodes.length : leaf.length)
+ }return nodeRange
+}, goog$dom$browserrange$W3cRange$getBrowserRangeForNodes_ = function(startNode, startOffset, endNode, endOffset) {
+ var nodeRange = goog$dom$getOwnerDocument(startNode).createRange();
+ nodeRange.setStart(startNode, startOffset);
+ nodeRange.setEnd(endNode, endOffset);
+ return nodeRange
+};
+goog$dom$browserrange$W3cRange.prototype.getContainer = function() {
+ return this.range_.commonAncestorContainer
+};
+goog$dom$browserrange$W3cRange.prototype.getStartNode = function() {
+ return this.range_.startContainer
+};
+goog$dom$browserrange$W3cRange.prototype.getStartOffset = function() {
+ return this.range_.startOffset
+};
+goog$dom$browserrange$W3cRange.prototype.getEndNode = function() {
+ return this.range_.endContainer
+};
+goog$dom$browserrange$W3cRange.prototype.getEndOffset = function() {
+ return this.range_.endOffset
+};
+goog$dom$browserrange$W3cRange.prototype.compareBrowserRangeEndpoints = function(range, thisEndpoint, otherEndpoint) {
+ return this.range_.compareBoundaryPoints(otherEndpoint == 1 ? thisEndpoint == 1 ? goog$global.Range.START_TO_START : goog$global.Range.START_TO_END : thisEndpoint == 1 ? goog$global.Range.END_TO_START : goog$global.Range.END_TO_END, range)
+};
+goog$dom$browserrange$W3cRange.prototype.isCollapsed = function() {
+ return this.range_.collapsed
+};
+goog$dom$browserrange$W3cRange.prototype.select = function(reverse) {
+ var win = goog$dom$getWindow(goog$dom$getOwnerDocument(this.getStartNode()));
+ this.selectInternal(win.getSelection(), reverse)
+};
+goog$dom$browserrange$W3cRange.prototype.selectInternal = function(selection) {
+ selection.addRange(this.range_)
+};
+goog$dom$browserrange$W3cRange.prototype.collapse = function(toStart) {
+ this.range_.collapse(toStart)
+};var goog$dom$browserrange$GeckoRange = function(range) {
+ goog$dom$browserrange$W3cRange.call(this, range)
+};
+goog$inherits(goog$dom$browserrange$GeckoRange, goog$dom$browserrange$W3cRange);
+goog$dom$browserrange$GeckoRange.prototype.selectInternal = function(selection, reversed) {
+ var anchorNode = reversed ? this.getEndNode() : this.getStartNode(), anchorOffset = reversed ? this.getEndOffset() : this.getStartOffset(), focusNode = reversed ? this.getStartNode() : this.getEndNode(), focusOffset = reversed ? this.getStartOffset() : this.getEndOffset();
+ selection.collapse(anchorNode, anchorOffset);
+ if(anchorNode != focusNode || anchorOffset != focusOffset)selection.extend(focusNode, focusOffset)
+};var goog$dom$browserrange$IeRange = function(range, doc) {
+ this.range_ = range;
+ this.doc_ = doc
+};
+goog$inherits(goog$dom$browserrange$IeRange, goog$dom$browserrange$AbstractRange);
+var goog$dom$browserrange$IeRange$logger_ = goog$debug$LogManager$getLogger("goog.dom.browserrange.IeRange"), goog$dom$browserrange$IeRange$getBrowserRangeForNode_ = function(node) {
+ var nodeRange = goog$dom$getOwnerDocument(node).body.createTextRange();
+ if(node.nodeType == 1)nodeRange.moveToElementText(node);
+ else {
+ for(var offset = 0, sibling = node;sibling = sibling.previousSibling;) {
+ var nodeType = sibling.nodeType;
+ if(nodeType == 3)offset += sibling.length;
+ else if(nodeType == 1) {
+ nodeRange.moveToElementText(sibling);
+ break
+ }
+ }sibling || nodeRange.moveToElementText(node.parentNode);
+ nodeRange.collapse(!sibling);
+ offset && nodeRange.move("character", offset);
+ nodeRange.moveEnd("character", node.length)
+ }return nodeRange
+}, goog$dom$browserrange$IeRange$getBrowserRangeForNodes_ = function(startNode, startOffset, endNode, endOffset) {
+ var child, collapse = false;
+ if(startNode.nodeType == 1) {
+ startOffset > startNode.childNodes.length && goog$dom$browserrange$IeRange$logger_.severe("Cannot have startOffset > startNode child count");
+ child = startNode.childNodes[startOffset];
+ collapse = !child;
+ startNode = child || startNode;
+ startOffset = 0
+ }var leftRange = goog$dom$browserrange$IeRange$getBrowserRangeForNode_(startNode);
+ startOffset && leftRange.move("character", startOffset);
+ collapse && leftRange.collapse(false);
+ collapse = false;
+ if(endNode.nodeType == 1) {
+ startOffset > startNode.childNodes.length && goog$dom$browserrange$IeRange$logger_.severe("Cannot have endOffset > endNode child count");
+ endNode = (child = endNode.childNodes[endOffset]) || endNode;
+ if(endNode.tagName == "BR")endOffset = 1;
+ else {
+ endOffset = 0;
+ collapse = !child
+ }
+ }var rightRange = goog$dom$browserrange$IeRange$getBrowserRangeForNode_(endNode);
+ rightRange.collapse(!collapse);
+ endOffset && rightRange.moveEnd("character", endOffset);
+ leftRange.setEndPoint("EndToEnd", rightRange);
+ return leftRange
+}, goog$dom$browserrange$IeRange$createFromNodeContents = function(node) {
+ var range = new goog$dom$browserrange$IeRange(goog$dom$browserrange$IeRange$getBrowserRangeForNode_(node), goog$dom$getOwnerDocument(node));
+ range.parentNode_ = node;
+ return range
+};
+goog$dom$browserrange$IeRange.prototype.parentNode_ = null;
+goog$dom$browserrange$IeRange.prototype.startNode_ = null;
+goog$dom$browserrange$IeRange.prototype.endNode_ = null;
+goog$dom$browserrange$IeRange.prototype.clearCachedValues_ = function() {
+ this.parentNode_ = this.startNode_ = this.endNode_ = null
+};
+goog$dom$browserrange$IeRange.prototype.getContainer = function() {
+ if(!this.parentNode_) {
+ for(var selectText = this.range_.text, i = 1;selectText.charAt(selectText.length - i) == " ";i++)this.range_.moveEnd("character", -1);
+ for(var parent = this.range_.parentElement(), htmlText = this.range_.htmlText.replace(/(\r\n|\r|\n)+/g, " ");htmlText.length > parent.outerHTML.replace(/(\r\n|\r|\n)+/g, " ").length;)parent = parent.parentNode;
+ for(;parent.childNodes.length == 1 && parent.innerText == (parent.firstChild.nodeType == 3 ? parent.firstChild.nodeValue : parent.firstChild.innerText);) {
+ if(parent.firstChild.tagName == "IMG")break;
+ parent = parent.firstChild
+ }if(selectText.length == 0)parent = this.findDeepestContainer_(parent);
+ this.parentNode_ = parent
+ }return this.parentNode_
+};
+goog$dom$browserrange$IeRange.prototype.findDeepestContainer_ = function(node) {
+ for(var childNodes = node.childNodes, i = 0, len = childNodes.length;i < len;i++) {
+ var child = childNodes[i];
+ if(child.nodeType == 1)if(this.range_.inRange(goog$dom$browserrange$IeRange$getBrowserRangeForNode_(child)))return this.findDeepestContainer_(child)
+ }return node
+};
+goog$dom$browserrange$IeRange.prototype.getStartNode = function() {
+ return this.startNode_ || (this.startNode_ = this.getEndpointNode_(1))
+};
+goog$dom$browserrange$IeRange.prototype.getStartOffset = function() {
+ return this.getOffset_(1)
+};
+goog$dom$browserrange$IeRange.prototype.getEndNode = function() {
+ return this.endNode_ || (this.endNode_ = this.getEndpointNode_(0))
+};
+goog$dom$browserrange$IeRange.prototype.getEndOffset = function() {
+ return this.getOffset_(0)
+};
+goog$dom$browserrange$IeRange.prototype.containsRange = function(range, opt_allowPartial) {
+ return this.containsBrowserRange(range.range_, opt_allowPartial)
+};
+goog$dom$browserrange$IeRange.prototype.compareBrowserRangeEndpoints = function(range, thisEndpoint, otherEndpoint) {
+ return this.range_.compareEndPoints((thisEndpoint == 1 ? "Start" : "End") + "To" + (otherEndpoint == 1 ? "Start" : "End"), range)
+};
+goog$dom$browserrange$IeRange.prototype.getEndpointNode_ = function(endpoint, opt_node) {
+ var node = opt_node || this.getContainer();
+ if(!node || !node.firstChild) {
+ if(endpoint == 0 && node.previousSibling && node.previousSibling.tagName == "BR" && this.getOffset_(endpoint, node) == 0)node = node.previousSibling;
+ return node.tagName == "BR" ? node.parentNode : node
+ }for(var child = endpoint == 1 ? node.firstChild : node.lastChild;child;) {
+ if(this.containsNode(child, true))return this.getEndpointNode_(endpoint, child);
+ child = endpoint == 1 ? child.nextSibling : child.previousSibling
+ }return node
+};
+goog$dom$browserrange$IeRange.prototype.getOffset_ = function(endpoint, opt_container) {
+ var container = opt_container || (endpoint == 1 ? this.getStartNode() : this.getEndNode());
+ if(container.nodeType == 1) {
+ for(var children = container.childNodes, len = children.length, i = endpoint == 1 ? 0 : len - 1;i >= 0 && i < len;) {
+ var child = children[i];
+ if(this.containsNode(child, true)) {
+ endpoint == 0 && child.previousSibling && child.previousSibling.tagName == "BR" && this.getOffset_(endpoint, child) == 0 && i--;
+ break
+ }i += endpoint == 1 ? 1 : -1
+ }return i == -1 ? 0 : i
+ }else {
+ var range = this.range_.duplicate(), nodeRange = goog$dom$browserrange$IeRange$getBrowserRangeForNode_(container);
+ range.setEndPoint(endpoint == 1 ? "EndToEnd" : "StartToStart", nodeRange);
+ var rangeLength = range.text.length;
+ return endpoint == 0 ? rangeLength : container.length - rangeLength
+ }
+};
+goog$dom$browserrange$IeRange.prototype.isCollapsed = function() {
+ return this.range_.text == ""
+};
+goog$dom$browserrange$IeRange.prototype.select = function() {
+ this.range_.select()
+};
+goog$dom$browserrange$IeRange.prototype.collapse = function(toStart) {
+ this.range_.collapse(toStart);
+ if(toStart)this.endNode_ = this.startNode_;
+ else this.startNode_ = this.endNode_
+};var goog$dom$browserrange$WebKitRange = function(range) {
+ goog$dom$browserrange$W3cRange.call(this, range)
+};
+goog$inherits(goog$dom$browserrange$WebKitRange, goog$dom$browserrange$W3cRange);
+goog$dom$browserrange$WebKitRange.prototype.compareBrowserRangeEndpoints = function(range, thisEndpoint, otherEndpoint) {
+ if(goog$userAgent$isVersion("528"))return goog$dom$browserrange$WebKitRange.superClass_.compareBrowserRangeEndpoints.call(this, range, thisEndpoint, otherEndpoint);
+ return this.range_.compareBoundaryPoints(otherEndpoint == 1 ? thisEndpoint == 1 ? goog$global.Range.START_TO_START : goog$global.Range.END_TO_START : thisEndpoint == 1 ? goog$global.Range.START_TO_END : goog$global.Range.END_TO_END, range)
+};
+goog$dom$browserrange$WebKitRange.prototype.selectInternal = function(selection, reversed) {
+ selection.removeAllRanges();
+ reversed ? selection.setBaseAndExtent(this.getEndNode(), this.getEndOffset(), this.getStartNode(), this.getStartOffset()) : selection.setBaseAndExtent(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset())
+};var goog$dom$browserrange$createRangeFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return goog$userAgent$IE ? new goog$dom$browserrange$IeRange(goog$dom$browserrange$IeRange$getBrowserRangeForNodes_(startNode, startOffset, endNode, endOffset), goog$dom$getOwnerDocument(startNode)) : goog$userAgent$WEBKIT ? new goog$dom$browserrange$WebKitRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNodes_(startNode, startOffset, endNode, endOffset)) : goog$userAgent$GECKO ? new goog$dom$browserrange$GeckoRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNodes_(startNode, startOffset,
+ endNode, endOffset)) : new goog$dom$browserrange$W3cRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNodes_(startNode, startOffset, endNode, endOffset))
+};var goog$dom$TextRange = function() {
+};
+goog$inherits(goog$dom$TextRange, goog$dom$AbstractRange);
+var goog$dom$TextRange$createFromBrowserRangeWrapper_ = function(browserRange, opt_isReversed) {
+ var range = new goog$dom$TextRange;
+ range.browserRangeWrapper_ = browserRange;
+ range.isReversed_ = !!opt_isReversed;
+ return range
+}, goog$dom$TextRange$createFromNodeContents = function(node, opt_isReversed) {
+ return goog$dom$TextRange$createFromBrowserRangeWrapper_(goog$userAgent$IE ? goog$dom$browserrange$IeRange$createFromNodeContents(node) : goog$userAgent$WEBKIT ? new goog$dom$browserrange$WebKitRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNode(node)) : goog$userAgent$GECKO ? new goog$dom$browserrange$GeckoRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNode(node)) : new goog$dom$browserrange$W3cRange(goog$dom$browserrange$W3cRange$getBrowserRangeForNode(node)), opt_isReversed)
+}, goog$dom$TextRange$createFromNodes = function(anchorNode, anchorOffset, focusNode, focusOffset) {
+ var range = new goog$dom$TextRange;
+ range.isReversed_ = goog$dom$Range$isReversed(anchorNode, anchorOffset, focusNode, focusOffset);
+ if(anchorNode.tagName == "BR") {
+ var parent = anchorNode.parentNode;
+ anchorOffset = goog$array$indexOf(parent.childNodes, anchorNode);
+ anchorNode = parent
+ }if(focusNode.tagName == "BR") {
+ parent = focusNode.parentNode;
+ focusOffset = goog$array$indexOf(parent.childNodes, focusNode);
+ focusNode = parent
+ }if(range.isReversed_) {
+ range.startNode_ = focusNode;
+ range.startOffset_ = focusOffset;
+ range.endNode_ = anchorNode;
+ range.endOffset_ = anchorOffset
+ }else {
+ range.startNode_ = anchorNode;
+ range.startOffset_ = anchorOffset;
+ range.endNode_ = focusNode;
+ range.endOffset_ = focusOffset
+ }return range
+};
+goog$dom$TextRange.prototype.browserRangeWrapper_ = null;
+goog$dom$TextRange.prototype.startNode_ = null;
+goog$dom$TextRange.prototype.startOffset_ = null;
+goog$dom$TextRange.prototype.endNode_ = null;
+goog$dom$TextRange.prototype.endOffset_ = null;
+goog$dom$TextRange.prototype.isReversed_ = false;
+goog$dom$TextRange.prototype.getType = function() {
+ return"text"
+};
+goog$dom$TextRange.prototype.getBrowserRangeObject = function() {
+ return this.getBrowserRangeWrapper_().range_
+};
+goog$dom$TextRange.prototype.clearCachedValues_ = function() {
+ this.startNode_ = this.startOffset_ = this.endNode_ = this.endOffset_ = null
+};
+goog$dom$TextRange.prototype.getTextRangeCount = function() {
+ return 1
+};
+goog$dom$TextRange.prototype.getTextRange = function() {
+ return this
+};
+goog$dom$TextRange.prototype.getBrowserRangeWrapper_ = function() {
+ return this.browserRangeWrapper_ || (this.browserRangeWrapper_ = goog$dom$browserrange$createRangeFromNodes(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset()))
+};
+goog$dom$TextRange.prototype.getContainer = function() {
+ return this.getBrowserRangeWrapper_().getContainer()
+};
+goog$dom$TextRange.prototype.getStartNode = function() {
+ return this.startNode_ || (this.startNode_ = this.getBrowserRangeWrapper_().getStartNode())
+};
+goog$dom$TextRange.prototype.getStartOffset = function() {
+ return this.startOffset_ != null ? this.startOffset_ : (this.startOffset_ = this.getBrowserRangeWrapper_().getStartOffset())
+};
+goog$dom$TextRange.prototype.getEndNode = function() {
+ return this.endNode_ || (this.endNode_ = this.getBrowserRangeWrapper_().getEndNode())
+};
+goog$dom$TextRange.prototype.getEndOffset = function() {
+ return this.endOffset_ != null ? this.endOffset_ : (this.endOffset_ = this.getBrowserRangeWrapper_().getEndOffset())
+};
+goog$dom$TextRange.prototype.isReversed = function() {
+ return this.isReversed_
+};
+goog$dom$TextRange.prototype.containsRange = function(otherRange, opt_allowPartial) {
+ var otherRangeType = otherRange.getType();
+ if(otherRangeType == "text")return this.getBrowserRangeWrapper_().containsRange(otherRange.getBrowserRangeWrapper_(), opt_allowPartial);
+ else if(otherRangeType == "control") {
+ var elements = otherRange.getElements(), fn = opt_allowPartial ? goog$array$some : goog$array$every;
+ return fn(elements, function(el) {
+ return this.containsNode(el, opt_allowPartial)
+ }, this)
+ }
+};
+goog$dom$TextRange.prototype.isCollapsed = function() {
+ return this.getBrowserRangeWrapper_().isCollapsed()
+};
+goog$dom$TextRange.prototype.__iterator__ = function() {
+ return new goog$dom$TextRangeIterator(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset())
+};
+goog$dom$TextRange.prototype.select = function() {
+ this.getBrowserRangeWrapper_().select(this.isReversed_)
+};
+goog$dom$TextRange.prototype.saveUsingDom = function() {
+ return new goog$dom$DomSavedTextRange_(this)
+};
+goog$dom$TextRange.prototype.collapse = function(toAnchor) {
+ var toStart = this.isReversed() ? !toAnchor : toAnchor;
+ this.browserRangeWrapper_ && this.browserRangeWrapper_.collapse(toStart);
+ if(toStart) {
+ this.endNode_ = this.startNode_;
+ this.endOffset_ = this.startOffset_
+ }else {
+ this.startNode_ = this.endNode_;
+ this.startOffset_ = this.endOffset_
+ }this.isReversed_ = false
+};
+var goog$dom$DomSavedTextRange_ = function(range) {
+ this.anchorNode_ = range.getAnchorNode();
+ this.anchorOffset_ = range.getAnchorOffset();
+ this.focusNode_ = range.getFocusNode();
+ this.focusOffset_ = range.getFocusOffset()
+};
+goog$inherits(goog$dom$DomSavedTextRange_, goog$dom$SavedRange);var goog$dom$ControlRange = function() {
+};
+goog$inherits(goog$dom$ControlRange, goog$dom$AbstractMultiRange);
+goog$dom$ControlRange.prototype.range_ = null;
+goog$dom$ControlRange.prototype.elements_ = null;
+goog$dom$ControlRange.prototype.sortedElements_ = null;
+goog$dom$ControlRange.prototype.clearCachedValues_ = function() {
+ this.sortedElements_ = this.elements_ = null
+};
+goog$dom$ControlRange.prototype.getType = function() {
+ return"control"
+};
+goog$dom$ControlRange.prototype.getBrowserRangeObject = function() {
+ return this.range_ || document.body.createControlRange()
+};
+goog$dom$ControlRange.prototype.getTextRangeCount = function() {
+ return this.range_ ? this.range_.length : 0
+};
+goog$dom$ControlRange.prototype.getTextRange = function(i) {
+ return goog$dom$TextRange$createFromNodeContents(this.range_.item(i))
+};
+goog$dom$ControlRange.prototype.getContainer = function() {
+ return goog$dom$findCommonAncestor.apply(null, this.getElements())
+};
+goog$dom$ControlRange.prototype.getStartNode = function() {
+ return this.getSortedElements()[0]
+};
+goog$dom$ControlRange.prototype.getStartOffset = function() {
+ return 0
+};
+goog$dom$ControlRange.prototype.getEndNode = function() {
+ var sorted = this.getSortedElements(), startsLast = goog$array$peek(sorted);
+ return goog$array$find(sorted, function(el) {
+ return goog$dom$contains(el, startsLast)
+ })
+};
+goog$dom$ControlRange.prototype.getEndOffset = function() {
+ return this.getEndNode().childNodes.length
+};
+goog$dom$ControlRange.prototype.getElements = function() {
+ if(!this.elements_) {
+ this.elements_ = [];
+ if(this.range_)for(var i = 0;i < this.range_.length;i++)this.elements_.push(this.range_.item(i))
+ }return this.elements_
+};
+goog$dom$ControlRange.prototype.getSortedElements = function() {
+ if(!this.sortedElements_) {
+ this.sortedElements_ = this.getElements().concat();
+ this.sortedElements_.sort(function(a, b) {
+ return a.sourceIndex - b.sourceIndex
+ })
+ }return this.sortedElements_
+};
+goog$dom$ControlRange.prototype.isCollapsed = function() {
+ return!this.range_ || !this.range_.length
+};
+goog$dom$ControlRange.prototype.__iterator__ = function() {
+ return new goog$dom$ControlRangeIterator(this)
+};
+goog$dom$ControlRange.prototype.select = function() {
+ this.range_ && this.range_.select()
+};
+goog$dom$ControlRange.prototype.saveUsingDom = function() {
+ return new goog$dom$DomSavedControlRange_(this)
+};
+goog$dom$ControlRange.prototype.collapse = function() {
+ this.range_ = null;
+ this.clearCachedValues_()
+};
+var goog$dom$DomSavedControlRange_ = function(range) {
+ this.elements_ = range.getElements()
+};
+goog$inherits(goog$dom$DomSavedControlRange_, goog$dom$SavedRange);
+var goog$dom$ControlRangeIterator = function(range) {
+ if(range) {
+ this.elements_ = range.getSortedElements();
+ this.startNode_ = this.elements_.shift();
+ this.endNode_ = goog$array$peek(this.elements_) || this.startNode_
+ }goog$dom$RangeIterator.call(this, this.startNode_, false)
+};
+goog$inherits(goog$dom$ControlRangeIterator, goog$dom$RangeIterator);
+goog$dom$ControlRangeIterator.prototype.startNode_ = null;
+goog$dom$ControlRangeIterator.prototype.endNode_ = null;
+goog$dom$ControlRangeIterator.prototype.elements_ = null;
+goog$dom$ControlRangeIterator.prototype.getStartNode = function() {
+ return this.startNode_
+};
+goog$dom$ControlRangeIterator.prototype.getEndNode = function() {
+ return this.endNode_
+};
+goog$dom$ControlRangeIterator.prototype.isLast = function() {
+ return!this.depth && !this.elements_.length
+};
+goog$dom$ControlRangeIterator.prototype.next = function() {
+ if(this.isLast())throw goog$iter$StopIteration;else if(!this.depth) {
+ var el = this.elements_.shift();
+ this.setPosition(el, 1, 1);
+ return el
+ }return goog$dom$ControlRangeIterator.superClass_.next.call(this)
+};var goog$dom$MultiRange = function() {
+ this.browserRanges_ = [];
+ this.ranges_ = [];
+ this.container_ = this.sortedRanges_ = null
+};
+goog$inherits(goog$dom$MultiRange, goog$dom$AbstractMultiRange);
+goog$dom$MultiRange.prototype.logger_ = goog$debug$LogManager$getLogger("goog.dom.MultiRange");
+goog$dom$MultiRange.prototype.clearCachedValues_ = function() {
+ this.ranges_ = [];
+ this.container_ = this.sortedRanges_ = null
+};
+goog$dom$MultiRange.prototype.getType = function() {
+ return"mutli"
+};
+goog$dom$MultiRange.prototype.getBrowserRangeObject = function() {
+ this.browserRanges_.length > 1 && this.logger_.warning("getBrowserRangeObject called on MultiRange with more than 1 range");
+ return this.browserRanges_[0]
+};
+goog$dom$MultiRange.prototype.getTextRangeCount = function() {
+ return this.browserRanges_.length
+};
+goog$dom$MultiRange.prototype.getTextRange = function(i) {
+ this.ranges_[i] || (this.ranges_[i] = goog$dom$TextRange$createFromBrowserRangeWrapper_(goog$userAgent$IE ? new goog$dom$browserrange$IeRange(this.browserRanges_[i], goog$dom$getOwnerDocument(this.browserRanges_[i].parentElement())) : goog$userAgent$WEBKIT ? new goog$dom$browserrange$WebKitRange(this.browserRanges_[i]) : goog$userAgent$GECKO ? new goog$dom$browserrange$GeckoRange(this.browserRanges_[i]) : new goog$dom$browserrange$W3cRange(this.browserRanges_[i]), undefined));
+ return this.ranges_[i]
+};
+goog$dom$MultiRange.prototype.getContainer = function() {
+ if(!this.container_) {
+ for(var nodes = [], i = 0, len = this.getTextRangeCount();i < len;i++)nodes.push(this.getTextRange(i).getContainer());
+ this.container_ = goog$dom$findCommonAncestor.apply(null, nodes)
+ }return this.container_
+};
+goog$dom$MultiRange.prototype.getSortedRanges = function() {
+ if(!this.sortedRanges_) {
+ this.sortedRanges_ = this.getTextRanges();
+ this.sortedRanges_.sort(function(a, b) {
+ var aStartNode = a.getStartNode(), aStartOffset = a.getStartOffset(), bStartNode = b.getStartNode(), bStartOffset = b.getStartOffset();
+ if(aStartNode == bStartNode && aStartOffset == bStartOffset)return 0;
+ return goog$dom$Range$isReversed(aStartNode, aStartOffset, bStartNode, bStartOffset) ? 1 : -1
+ })
+ }return this.sortedRanges_
+};
+goog$dom$MultiRange.prototype.getStartNode = function() {
+ return this.getSortedRanges()[0].getStartNode()
+};
+goog$dom$MultiRange.prototype.getStartOffset = function() {
+ return this.getSortedRanges()[0].getStartOffset()
+};
+goog$dom$MultiRange.prototype.getEndNode = function() {
+ return goog$array$peek(this.getSortedRanges()).getEndNode()
+};
+goog$dom$MultiRange.prototype.getEndOffset = function() {
+ return goog$array$peek(this.getSortedRanges()).getEndOffset()
+};
+goog$dom$MultiRange.prototype.isCollapsed = function() {
+ return this.browserRanges_.length == 0 || this.browserRanges_.length == 1 && this.getTextRange(0).isCollapsed()
+};
+goog$dom$MultiRange.prototype.__iterator__ = function() {
+ return new goog$dom$MultiRangeIterator(this)
+};
+goog$dom$MultiRange.prototype.select = function() {
+ var selection;
+ JSCompiler_inline_label_goog$dom$AbstractRange$getBrowserSelectionForWindow_50: {
+ var JSCompiler_inline_win = this.getWindow();
+ if(JSCompiler_inline_win.getSelection)selection = JSCompiler_inline_win.getSelection();
+ else {
+ var JSCompiler_inline_doc = JSCompiler_inline_win.document;
+ selection = JSCompiler_inline_doc.selection || JSCompiler_inline_doc.getSelection && JSCompiler_inline_doc.getSelection()
+ }
+ }selection.removeAllRanges();
+ for(var i = 0, len = this.getTextRangeCount();i < len;i++)selection.addRange(this.getTextRange(i).getBrowserRangeObject())
+};
+goog$dom$MultiRange.prototype.saveUsingDom = function() {
+ return new goog$dom$DomSavedMultiRange_(this)
+};
+goog$dom$MultiRange.prototype.collapse = function(toAnchor) {
+ if(!this.isCollapsed()) {
+ var range = toAnchor ? this.getTextRange(0) : this.getTextRange(this.getTextRangeCount() - 1);
+ this.clearCachedValues_();
+ range.collapse(toAnchor);
+ this.ranges_ = [range];
+ this.sortedRanges_ = [range];
+ this.browserRanges_ = [range.getBrowserRangeObject()]
+ }
+};
+var goog$dom$DomSavedMultiRange_ = function(range) {
+ this.savedRanges_ = goog$array$map(range.getTextRanges(), function(range) {
+ return range.saveUsingDom()
+ })
+};
+goog$inherits(goog$dom$DomSavedMultiRange_, goog$dom$SavedRange);
+var goog$dom$MultiRangeIterator = function(range) {
+ if(range) {
+ this.ranges_ = range.getSortedRanges();
+ if(this.ranges_.length) {
+ this.startNode_ = this.ranges_[0].getStartNode();
+ this.endNode_ = goog$array$peek(this.ranges_).getEndNode()
+ }
+ }goog$dom$RangeIterator.call(this, this.startNode_, false)
+};
+goog$inherits(goog$dom$MultiRangeIterator, goog$dom$RangeIterator);
+goog$dom$MultiRangeIterator.prototype.startNode_ = null;
+goog$dom$MultiRangeIterator.prototype.endNode_ = null;
+goog$dom$MultiRangeIterator.prototype.ranges_ = null;
+goog$dom$MultiRangeIterator.prototype.getStartNode = function() {
+ return this.startNode_
+};
+goog$dom$MultiRangeIterator.prototype.getEndNode = function() {
+ return this.endNode_
+};
+goog$dom$MultiRangeIterator.prototype.isLast = function() {
+ return this.ranges_.length == 1 && this.ranges_[0].isLast()
+};
+goog$dom$MultiRangeIterator.prototype.next = function() {
+ do try {
+ this.ranges_[0].next();
+ break
+ }catch(ex) {
+ if(ex != goog$iter$StopIteration)throw ex;this.ranges_.shift()
+ }while(this.ranges_.length);
+ if(this.ranges_.length) {
+ var range = this.ranges_[0];
+ this.setPosition(range.node, range.tagType, range.depth);
+ return range.node
+ }else throw goog$iter$StopIteration;
+};var goog$dom$Range$createCaret = function(node, offset) {
+ return goog$dom$TextRange$createFromNodes(node, offset, node, offset)
+}, goog$dom$Range$createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return goog$dom$TextRange$createFromNodes(startNode, startOffset, endNode, endOffset)
+}, goog$dom$Range$isReversed = function(anchorNode, anchorOffset, focusNode, focusOffset) {
+ if(anchorNode == focusNode)return focusOffset < anchorOffset;
+ var child;
+ if(anchorNode.nodeType == 1 && anchorOffset)if(child = anchorNode.childNodes[anchorOffset]) {
+ anchorNode = child;
+ anchorOffset = 0
+ }else if(goog$dom$contains(anchorNode, focusNode))return true;
+ if(focusNode.nodeType == 1 && focusOffset)if(child = focusNode.childNodes[focusOffset]) {
+ focusNode = child;
+ focusOffset = 0
+ }else if(goog$dom$contains(focusNode, anchorNode))return false;
+ return(goog$dom$compareNodeOrder(anchorNode, focusNode) || anchorOffset - focusOffset) > 0
+};window.createCaret = goog$dom$Range$createCaret;
+window.createFromNodes = goog$dom$Range$createFromNodes;
+try {
+ goog$dom$Range$createCaret(document.body, 0).select()
+}catch(e$$13) {
+};
+
+/**************************************************
+ Trace:
+ 56.427 Start Handling request
+ 0 56.427 Start Building cUnit
+ 1 56.428 Done 1 ms Building cUnit
+ 0 56.428 Start Checking memcacheg
+ 0 56.428 Start Connecting to memcacheg
+ 8 56.436 Done 8 ms Connecting to memcacheg
+ 1 56.437 Done 9 ms Checking memcacheg
+ 0 56.437 Done 10 ms Handling request
+**************************************************/
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/richtext/richtext.html b/editor/libeditor/tests/browserscope/lib/richtext/richtext/richtext.html
new file mode 100644
index 000000000..ef0e22f2a
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/richtext/richtext.html
@@ -0,0 +1,1081 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>Rich Text Tests</title>
+ <script src="js/range.js"></script>
+ <script>
+ /**
+ * Color class allows cross-browser comparison of values, which can
+ * be returned from queryCommandValue in several formats:
+ * 0xff00ff
+ * rgb(255, 0, 0)
+ * Number containing the hex value
+ */
+ function Color(value) {
+ this.compare = function(other) {
+ if (!this.valid || !other.valid) {
+ return false;
+ }
+ return this.red == other.red && this.green == other.green && this.blue == other.blue;
+ }
+ this.parse = function(value) {
+ var hexMatch = String(value).match(/#([0-9a-f]{6})/i);
+ if (hexMatch) {
+ this.red = parseInt(hexMatch[1].substring(0, 2), 16);
+ this.green = parseInt(hexMatch[1].substring(2, 4), 16);
+ this.blue = parseInt(hexMatch[1].substring(4, 6), 16);
+ return true;
+ }
+ var rgbMatch = String(value).match(/rgb\(([0-9]{1,3}),\s*([0-9]{1,3}),\s*([0-9]{1,3})\)/i);
+ if (rgbMatch) {
+ this.red = Number(rgbMatch[1]);
+ this.green = Number(rgbMatch[2]);
+ this.blue = Number(rgbMatch[3]);
+ return true;
+ }
+ if (Number(value)) {
+ this.red = value & 0xFF;
+ this.green = (value & 0xFF00) >> 8;
+ this.blue = (value & 0xFF0000) >> 16;
+ return true;
+ }
+ return false;
+ }
+ this.toString = function() {
+ return this.red + ',' + this.green + ',' + this.blue;
+ }
+ this.valid = this.parse(value);
+ }
+
+ /**
+ * Utility class for converting font sizes to the size
+ * attribute in a font tag. Currently only converts px because
+ * only the sizes and px ever come from queryCommandValue.
+ */
+ function Size(value) {
+ var pxMatch = String(value).match(/([0-9]+)px/);
+ if (pxMatch) {
+ var px = Number(pxMatch[1]);
+ if (px <= 10) {
+ this.size = 1;
+ } else if (px <= 13) {
+ this.size = 2;
+ } else if (px <= 16) {
+ this.size = 3;
+ } else if (px <= 18) {
+ this.size = 4;
+ } else if (px <= 24) {
+ this.size = 5;
+ } else if (px <= 32) {
+ this.size = 6;
+ } else if (px <= 47) {
+ this.size = 7;
+ } else {
+ this.size = NaN;
+ }
+ } else if (Number(value)) {
+ this.size = Number(value);
+ } else {
+ switch (value) {
+ case 'x-small':
+ this.size = 1;
+ break;
+ case 'small':
+ this.size = 2;
+ break;
+ case 'medium':
+ this.size = 3;
+ break;
+ case 'large':
+ this.size = 4;
+ break;
+ case 'x-large':
+ this.size = 5;
+ break;
+ case 'xx-large':
+ this.size = 6;
+ break;
+ case 'xxx-large':
+ case '-webkit-xxx-large':
+ this.size = 7;
+ break;
+ default:
+ this.size = null;
+ }
+ }
+ this.compare = function(other) {
+ return this.size == other.size;
+ }
+ this.toString = function() {
+ return String(this.size);
+ }
+ }
+
+ var IMAGE_URI = '/tests/editor/libeditor/tests/green.png';
+
+ var APPLY_TESTS = {
+ 'backcolor' : {
+ opt_arg: '#FF0000',
+ styleWithCSS: 'background-color'},
+ 'bold' : {
+ opt_arg: null,
+ styleWithCSS: 'font-weight'},
+ 'createbookmark' : {
+ opt_arg: 'bookmark_name'},
+ 'createlink' : {
+ opt_arg: 'http://www.openweb.org'},
+ 'decreasefontsize' : {
+ opt_arg: null},
+ 'fontname' : {
+ opt_arg: 'Arial',
+ styleWithCSS: 'font-family'},
+ 'fontsize' : {
+ opt_arg: 4,
+ styleWithCSS: 'font-size'},
+ 'forecolor' : {
+ opt_arg: '#FF0000',
+ styleWithCSS: 'color'},
+ 'formatblock' : {
+ opt_arg: 'h1',
+ wholeline: true},
+ 'hilitecolor' : {
+ opt_arg: '#FF0000',
+ styleWithCSS: 'background-color'},
+ 'indent' : {
+ opt_arg: null,
+ wholeline: true,
+ styleWithCSS: 'margin'},
+ 'inserthorizontalrule' : {
+ opt_arg: null,
+ collapse: true},
+ 'inserthtml': {
+ opt_arg: '<br>',
+ collapse: true},
+ 'insertimage': {
+ opt_arg: IMAGE_URI,
+ collapse: true},
+ 'insertorderedlist' : {
+ opt_arg: null,
+ wholeline: true},
+ 'insertunorderedlist' : {
+ opt_arg: null,
+ wholeline: true},
+ 'italic' : {
+ opt_arg: null,
+ styleWithCSS: 'font-style'},
+ 'justifycenter' : {
+ opt_arg: null,
+ wholeline: true,
+ styleWithCSS: 'text-align'},
+ 'justifyfull' : {
+ opt_arg: null,
+ wholeline: true,
+ styleWithCSS: 'text-align'},
+ 'justifyleft' : {
+ opt_arg: null,
+ wholeline: true,
+ styleWithCSS: 'text-align'},
+ 'justifyright' : {
+ opt_arg: null,
+ wholeline: true,
+ styleWithCSS: 'text-align'},
+ 'strikethrough' : {
+ opt_arg: null,
+ styleWithCSS: 'text-decoration'},
+ 'subscript' : {
+ opt_arg: null,
+ styleWithCSS: 'vertical-align'},
+ 'superscript' : {
+ opt_arg: null,
+ styleWithCSS: 'vertical-align'},
+ 'underline' : {
+ opt_arg: null,
+ styleWithCSS: 'text-decoration'}};
+
+ var UNAPPLY_TESTS = {
+ 'bold' : {
+ tags: [
+ ['<b>', '</b>'],
+ ['<STRONG>', '</STRONG>'],
+ ['<span style="font-weight: bold;">', '</span>']]},
+ 'italic' : {
+ tags: [
+ ['<i>', '</i>'],
+ ['<EM>', '</EM>'],
+ ['<span style="font-style: italic;">', '</span>']]},
+ 'outdent' : {
+ unapply: 'indent',
+ block: true,
+ tags: [
+ ['<blockquote>', '</blockquote>'],
+ ['<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px;">', '</blockquote>'],
+ ['<ul><li>', '</li></ul>'],
+ ['<ol><li>', '</li></ol>'],
+ ['<div style="margin-left: 40px;">', '</div>']]},
+ 'removeformat' : {
+ unapply: '*',
+ block: true,
+ tags: [
+ ['<b>', '</b>'],
+ ['<a href="http://www.foo.com">', '</a>'],
+ ['<table><tr><td>', '</td></tr></table>']]},
+ 'strikethrough' : {
+ tags: [
+ ['<strike>', '</strike>'],
+ ['<s>', '</s>'],
+ ['<del>', '</del>'],
+ ['<span style="text-decoration: line-through;">', '</span>']]},
+ 'subscript' : {
+ tags: [
+ ['<sub>', '</sub>'],
+ ['<span style="vertical-align: sub;">', '</span>']]},
+ 'superscript' : {
+ tags: [
+ ['<sup>', '</sup>'],
+ ['<span style="vertical-align: super;">', '</span>']]},
+ 'unbookmark' : {
+ unapply: 'createbookmark',
+ tags: [
+ ['<a name="bookmark">', '</a>']]},
+ 'underline' : {
+ tags: [
+ ['<u>', '</u>'],
+ ['<span style="text-decoration: underline;">', '</span>']]},
+ 'unlink' : {
+ unapply: 'createbookmark',
+ tags: [
+ ['<a href="http://www.foo.com">', '</a>']]}};
+
+ var QUERY_TESTS = {
+ 'backcolor' : {
+ type: 'value',
+ tests: [
+ {html: '<FONT style="BACKGROUND-COLOR: #ffccaa">foo bar baz</FONT>', expected: new Color('#ffccaa')},
+ {html: '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">foo bar baz</span>', expected: new Color('#ff0000')},
+ {html: '<span style="background-color: #ff0000">foo bar baz</span>', expected: new Color('#ff0000')}
+ ]
+ },
+ 'bold' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<b>foo bar baz</b>', expected: true},
+ {html: '<STRONG>foo bar baz</STRONG>', expected: true},
+ {html: '<span style="font-weight:bold">foo bar baz</span>', expected: true},
+ {html: '<b style="font-weight:normal">foo bar baz</b>', expected: false},
+ {html: '<b><span style="font-weight:normal;">foo bar baz</span>', expected: false}
+ ]
+ },
+ 'fontname' : {
+ type: 'value',
+ tests: [
+ {html: '<font face="Arial">foo bar baz</font>', expected: 'Arial'},
+ {html: '<span style="font-family:Arial">foo bar baz</span>', expected: 'Arial'},
+ {html: '<font face="Arial" style="font-family:Courier">foo bar baz</font>', expected: 'Courier'},
+ {html: '<font face="Courier"><font face="Arial">foo bar baz</font></font>', expected: 'Arial'},
+ {html: '<span style="font-family:Courier"><font face="Arial">foo bar baz</font></span>', expected: 'Arial'}
+ ]
+ },
+ 'fontsize' : {
+ type: 'value',
+ tests: [
+ {html: '<font size=4>foo bar baz</font>', expected: new Size(4)},
+ // IE adds +1 to font size from font-size style attributes.
+ // This is hard to correct for since it does NOT add +1 to size attribute from font tag.
+ {html: '<span class="Apple-style-span" style="font-size: large;">foo bar baz</span>', expected: new Size(4)},
+ {html: '<font size=1 style="font-size:x-large;">foo bar baz</font>', expected: new Size(5)}
+ ]
+ },
+ 'forecolor' : {
+ type: 'value',
+ tests: [
+ {html: '<font color="#ff0000">foo bar baz</font>', expected: new Color('#ff0000')},
+ {html: '<span style="color:#ff0000">foo bar baz</span>', expected: new Color('#ff0000')},
+ {html: '<font color="#0000ff" style="color:#ff0000">foo bar baz</span>', expected: new Color('#ff0000')}
+ ]
+ },
+ 'hilitecolor' : {
+ type: 'value',
+ tests: [
+ {html: '<FONT style="BACKGROUND-COLOR: #ffccaa">foo bar baz</FONT>', expected: new Color('#ffccaa')},
+ {html: '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">foo bar baz</span>', expected: new Color('#ff0000')},
+ {html: '<span style="background-color: #ff0000">foo bar baz</span>', expected: new Color('#ff0000')}
+ ]
+ },
+ 'insertorderedlist' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<ol><li>foo bar baz</li></ol>', expected: true},
+ {html: '<ul><li>foo bar baz</li></ul>', expected: false}
+ ]
+ },
+ 'insertunorderedlist' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<ol><li>foo bar baz</li></ol>', expected: false},
+ {html: '<ul><li>foo bar baz</li></ul>', expected: true}
+ ]
+ },
+ 'italic' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<i>foo bar baz</i>', expected: true},
+ {html: '<EM>foo bar baz</EM>', expected: true},
+ {html: '<span style="font-style:italic">foo bar baz</span>', expected: true},
+ {html: '<i><span style="font-style:normal">foo bar baz</span></i>', expected: false}
+ ]
+ },
+ 'justifycenter' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<div align="center">foo bar baz</div>', expected: true},
+ {html: '<p align="center">foo bar baz</p>', expected: true},
+ {html: '<div style="text-align: center;">foo bar baz</div>', expected: true}
+ ]
+ },
+ 'justifyfull' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<div align="justify">foo bar baz</div>', expected: true},
+ {html: '<p align="justify">foo bar baz</p>', expected: true},
+ {html: '<div style="text-align: justify;">foo bar baz</div>', expected: true}
+ ]
+ },
+ 'justifyleft' : {
+ type: 'state',
+ tests: [
+ {html: '<div align="left">foo bar baz</div>', expected: true},
+ {html: '<p align="left">foo bar baz</p>', expected: true},
+ {html: '<div style="text-align: left;">foo bar baz</div>', expected: true}
+ ]
+ },
+ 'justifyright' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<div align="right">foo bar baz</div>', expected: true},
+ {html: '<p align="right">foo bar baz</p>', expected: true},
+ {html: '<div style="text-align: right;">foo bar baz</div>', expected: true}
+ ]
+ },
+ 'strikethrough' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<strike>foo bar baz</strike>', expected: true},
+ {html: '<strike style="text-decoration: none">foo bar baz</strike>', expected: false},
+ {html: '<s>foo bar baz</s>', expected: true},
+ {html: '<del>foo bar baz</del>', expected: true},
+ {html: '<span style="text-decoration:line-through">foo bar baz</span>', expected: true}
+ ]
+ },
+ 'subscript' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<sub>foo bar baz</sub>', expected: true}
+ ]
+ },
+ 'superscript' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<sup>foo bar baz</sup>', expected: true}
+ ]
+ },
+ 'underline' : {
+ type: 'state',
+ tests: [
+ {html: 'foo bar baz', expected: false},
+ {html: '<u>foo bar baz</u>', expected: true},
+ {html: '<a href="http://www.foo.com">foo bar baz</a>', expected: true},
+ {html: '<span style="text-decoration:underline">foo bar baz</span>', expected: true},
+ {html: '<u style="text-decoration:none">foo bar baz</u>', expected: false},
+ {html: '<a style="text-decoration:none" href="http://www.foo.com">foo bar baz</a>', expected: false}
+ ]
+ }
+ };
+
+ var CHANGE_TESTS = {
+ 'backcolor' : {
+ type: 'value',
+ tests: [
+ {html: '<FONT style="BACKGROUND-COLOR: #ffccaa">foo bar baz</FONT>', opt_arg: '#884422'},
+ {html: '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">foo bar baz</span>', opt_arg: '#0000ff'},
+ {html: '<span style="background-color: #ff0000">foo bar baz</span>', opt_arg: '#0000ff'}
+ ]
+ },
+ 'fontname' : {
+ type: 'value',
+ tests: [
+ {html: '<font face="Arial">foo bar baz</font>', opt_arg: 'Courier'},
+ {html: '<span style="font-family:Arial">foo bar baz</span>', opt_arg: 'Courier'},
+ {html: '<font face="Arial" style="font-family:Verdana">foo bar baz</font>', opt_arg: 'Courier'},
+ {html: '<font face="Verdana"><font face="Arial">foo bar baz</font></font>', opt_arg: 'Courier'},
+ {html: '<span style="font-family:Verdana"><font face="Arial">foo bar baz</font></span>', opt_arg: 'Courier'}
+ ]
+ },
+ 'fontsize' : {
+ type: 'value',
+ tests: [
+ {html: '<font size=4>foo bar baz</font>', opt_arg: 1},
+ {html: '<span class="Apple-style-span" style="font-size: large;">foo bar baz</span>', opt_arg: 1},
+ {html: '<font size=1 style="font-size:x-small;">foo bar baz</font>', opt_arg: 5}
+ ]
+ },
+ 'forecolor' : {
+ type: 'value',
+ tests: [
+ {html: '<font color="#ff0000">foo bar baz</font>', opt_arg: '#00ff00'},
+ {html: '<span style="color:#ff0000">foo bar baz</span>', opt_arg: '#00ff00'},
+ {html: '<font color="#0000ff" style="color:#ff0000">foo bar baz</span>', opt_arg: '#00ff00'}
+ ]
+ },
+ 'hilitecolor' : {
+ type: 'value',
+ tests: [
+ {html: '<FONT style="BACKGROUND-COLOR: #ffccaa">foo bar baz</FONT>', opt_arg: '#884422'},
+ {html: '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">foo bar baz</span>', opt_arg: '#00ff00'},
+ {html: '<span style="background-color: #ff0000">foo bar baz</span>', opt_arg: '#00ff00'}
+ ]
+ }
+ };
+
+ /** The document of the editable iframe */
+ var editorDoc = null;
+ /** Dummy text to apply and unapply formatting to */
+ var TEST_CONTENT = 'foo bar baz';
+ /**
+ * Word in dummy text that should change. Formatting is sometimes applied
+ * to a single word instead of the entire text node because sometimes a
+ * style might get applied to the body node instead of wrapped around
+ * the text, and that's not what's being tested.
+ */
+ var TEST_WORD = 'bar';
+ /** Constant for indicating an action is unsupported (threw exception) */
+ var UNSUPPORTED = 'UNSUPPORTED';
+ /** <br> and <p> are acceptable HTML to be left over from block elements */
+ var BLOCK_REMOVE_TAGS = [/\s*<br>\s*/i, /\s*<p>\s*/i];
+ /** Array used to accumulate test results */
+ // Tack on the actual display tests with bogus data
+ // otherwise the beacon will fail.
+ var results = ['apply=0', 'unapply=0', 'change=0', 'query=0'];
+
+ /**
+ *
+ */
+ function resetIframe(newHtml) {
+ // These attributes can get set on the iframe by some errant execCommands
+ editorDoc.body.setAttribute('style', '');
+ editorDoc.body.setAttribute('bgcolor', '');
+ editorDoc.body.innerHTML = newHtml;
+ }
+
+ /**
+ * Finds the text node in the given node containing the given word.
+ * Returns null if not found.
+ */
+ function findTextNode(word, node) {
+ if (node.nodeType == 3) {
+ // Text node, check value.
+ if (node.data.indexOf(word) != -1) {
+ return node;
+ }
+ } else if (node.nodeType == 1) {
+ // Element node, check children.
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var result = findTextNode(word, node.childNodes[i]);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the selection to be collapsed at the start of the word,
+ * or the start of the editor if no word is passed in.
+ */
+ function selectStart(word) {
+ var textNode = findTextNode(word || '', editorDoc.body);
+ var startOffset = 0;
+ if (word) {
+ startOffset = textNode.data.indexOf(word);
+ }
+ var range = createCaret(textNode, startOffset);
+ range.select();
+ }
+
+ /**
+ * Selects the given word in the editor iframe.
+ */
+ function selectWord(word) {
+ var textNode = findTextNode(word, editorDoc.body);
+ if (!textNode) {
+ return;
+ }
+ var start = textNode.data.indexOf(word);
+ var range = createFromNodes(textNode, start, textNode, start + word.length);
+ range.select();
+ }
+
+ /**
+ * Gets the HTML before the text, so that we know how the browser
+ * applied a style
+ */
+ function getSurroundingTags(text) {
+ var html = editorDoc.body.innerHTML;
+ var tagStart = html.indexOf('<');
+ var index = editorDoc.body.innerHTML.indexOf(text);
+ if (tagStart == -1 || index == -1) {
+ return '';
+ }
+ return editorDoc.body.innerHTML.substring(tagStart, index);
+ }
+
+ /**
+ * Does the test for an apply execCommand.
+ */
+ function doApplyTest(command, styleWithCSS) {
+ try {
+ // Set styleWithCSS
+ try {
+ editorDoc.execCommand('styleWithCSS', false, styleWithCSS);
+ } catch (ex) {
+ // Ignore errors
+ }
+ resetIframe(TEST_CONTENT);
+ if (APPLY_TESTS[command].collapse) {
+ selectStart(TEST_WORD);
+ } else {
+ selectWord(TEST_WORD);
+ }
+ try {
+ editorDoc.execCommand(command, false, APPLY_TESTS[command].opt_arg);
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ return getSurroundingTags(APPLY_TESTS[command].wholeline? TEST_CONTENT : TEST_WORD);
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Outputs the result of the apply command to a table.
+ * @return {boolean} success
+ */
+ function outputApplyResult(command, result, styleWithCSS) {
+ // The apply command "succeeded" if HTML was generated.
+ var success = (result != UNSUPPORTED) && result;
+ // Except for styleWithCSS commands, which only succeed if the
+ // expected style was applied.
+ if (styleWithCSS) {
+ success = result && result.toLowerCase().indexOf(APPLY_TESTS[command].styleWithCSS) != -1;
+ }
+ results.push('a-' + command + '-' + (styleWithCSS ? 1 : 0) + '=' + (success ? '1' : '0'));
+
+ // Each command is displayed as a table row with 3 columns
+ var tr = document.createElement('TR');
+ tr.className = success ? 'success' : 'fail';
+
+ // Column 1: command name
+ var td = document.createElement('TD');
+ td.innerHTML = command;
+ tr.appendChild(td);
+
+ // Column 2: styleWithCSS
+ var td = document.createElement('TD');
+ td.innerHTML = styleWithCSS ? 'true' : 'false';
+ tr.appendChild(td);
+
+ // Column 3: pass/fail
+ td = document.createElement('TD');
+ td.innerHTML = success ? 'PASS' : 'FAIL';
+ tr.appendChild(td);
+
+ // Column 4: generated HTML (for passing commands)
+ td = document.createElement('TD');
+ // Escape the HTML in the result for printing.
+ result = result.replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
+ td.innerHTML = result;
+ tr.appendChild(td);
+ var table = document.getElementById('apply_output');
+ table.appendChild(tr);
+ return success;
+ }
+
+ /**
+ * Does the test for an unapply execCommand.
+ */
+ function doUnapplyTest(command, index) {
+ try {
+ var wordStart = TEST_CONTENT.indexOf(TEST_WORD);
+ resetIframe(
+ TEST_CONTENT.substring(0, wordStart) +
+ UNAPPLY_TESTS[command].tags[index][0] +
+ TEST_WORD +
+ UNAPPLY_TESTS[command].tags[index][1] +
+ TEST_CONTENT.substring(wordStart + TEST_WORD.length));
+ selectWord(TEST_WORD);
+ try {
+ editorDoc.execCommand(command, false, UNAPPLY_TESTS[command].opt_arg || null);
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ return getSurroundingTags(TEST_WORD);
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Check if the given unapply execCommand succeeded. It succeeded if
+ * the following conditions are true:
+ * - The execCommand did not throw an exception
+ * - One of the following:
+ * - The html was removed after the execCommand
+ * - The html was block and the html was replaced with <p> or <br>
+ */
+ function unapplyCommandSucceeded(command, result) {
+ if (result != UNSUPPORTED) {
+ if (!result) {
+ return true;
+ } else if (UNAPPLY_TESTS[command].block) {
+ for (var i = 0; i < BLOCK_REMOVE_TAGS.length; i++) {
+ if (result.match(BLOCK_REMOVE_TAGS[i])) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Outputs the result of the unapply command to a table.
+ * @return {boolean} success
+ */
+ function outputUnapplyResult(command, result, index) {
+ // The apply command "succeeded" if HTML was removed.
+ var success = unapplyCommandSucceeded(command, result);
+ results.push('u-' + command + '-' + index + '=' + (success ? '1' : '0'));
+
+ // Each command is displayed as a table row with 5 columns
+ var tr = document.createElement('TR');
+ tr.className = success ? 'success' : 'fail';
+
+ // Column 1: command name
+ var td = document.createElement('TD');
+ td.innerHTML = command;
+ tr.appendChild(td);
+
+ // Column 2: command name being unapplied
+ var td = document.createElement('TD');
+ td.innerHTML = UNAPPLY_TESTS[command].unapply || command;
+ tr.appendChild(td);
+
+ // Column 3: pass/fail
+ td = document.createElement('TD');
+ td.innerHTML = success ? 'PASS' : 'FAIL';
+ tr.appendChild(td);
+
+ // Column 4: html being removed
+ td = document.createElement('TD');
+ // Escape the html for printing.
+ var htmlToRemove = UNAPPLY_TESTS[command].tags[index][0].replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
+ td.innerHTML = htmlToRemove;
+ tr.appendChild(td);
+
+ // Column 5: resulting html
+ td = document.createElement('TD');
+ // Escape the HTML in the result for printing.
+ result = result.replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
+ td.innerHTML = success ? '&nbsp;' : result;
+ tr.appendChild(td);
+ var table = document.getElementById('unapply_output');
+ table.appendChild(tr);
+ return success;
+ }
+
+ /**
+ * Does a queryCommandState or queryCommandValue test for an execCommand.
+ */
+ function doQueryTest(command, index) {
+ try {
+ resetIframe(QUERY_TESTS[command].tests[index].html);
+ selectWord(TEST_WORD);
+ // Dummy val that won't match any expected vals, including false.
+ var result = UNSUPPORTED;
+ if (QUERY_TESTS[command].type == 'state') {
+ try {
+ result = editorDoc.queryCommandState(command);
+ } catch (ex) {
+ result = UNSUPPORTED;
+ }
+ } else {
+ try {
+ // A return value of false indicates the command is not supported.
+ result = editorDoc.queryCommandValue(command) || UNSUPPORTED;
+ } catch (ex) {
+ result = UNSUPPORTED;
+ }
+ }
+ return result;
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Check if the given queryCommandState or queryCommandValue succeeded.
+ */
+ function queryCommandSucceeded(command, index, result) {
+ var expected = QUERY_TESTS[command].tests[index].expected;
+ if (expected instanceof Color) {
+ return expected.compare(new Color(result));
+ } else if (expected instanceof Size) {
+ return expected.compare(new Size(result));
+ } else {
+ return (result == expected);
+ }
+ }
+
+ /**
+ * @return {boolean} success
+ */
+ function outputQueryResult(command, index, result) {
+ // Create table row for results.
+ var tr = document.createElement('TR');
+ var success = queryCommandSucceeded(command, index, result);
+ tr.className = success ? 'success' : 'fail';
+ results.push('q-' + command + '-' + index + '=' + (success ? '1' : '0'));
+
+ // Column 1: command name
+ var td = document.createElement('TD');
+ td.innerHTML = command;
+ tr.appendChild(td);
+
+ // Column 2: pass/fail
+ td = document.createElement('TD');
+ td.innerHTML = success ? 'PASS' : 'FAIL';
+ tr.appendChild(td);
+
+ // Column 3: test HTML
+ td = document.createElement('TD');
+ var testHtml = QUERY_TESTS[command].tests[index].html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ td.innerHTML = testHtml.substring(0, testHtml.indexOf(TEST_CONTENT));
+ tr.appendChild(td);
+
+ // Column 4: Expected result
+ td = document.createElement('TD');
+ td.innerHTML = QUERY_TESTS[command].tests[index].expected;
+ tr.appendChild(td);
+
+ // Column 5: Actual result
+ td = document.createElement('TD');
+ td.innerHTML = result;
+ tr.appendChild(td);
+
+ // Append result to the state or value table, depending on what
+ // type of command this is.
+ var table = document.getElementById(
+ QUERY_TESTS[command].type == 'state' ? 'querystate_output' : 'queryvalue_output');
+ table.appendChild(tr);
+ return success;
+ }
+
+ function doChangeTest(command, index) {
+ try {
+ resetIframe(CHANGE_TESTS[command].tests[index].html);
+ selectWord(TEST_CONTENT);
+ try {
+ editorDoc.execCommand(command, false, CHANGE_TESTS[command].tests[index].opt_arg);
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ } catch (ex) {
+ return UNSUPPORTED;
+ }
+ }
+
+ function checkChangeSuccess(command, index) {
+ var textNode = findTextNode(TEST_CONTENT, editorDoc.body);
+ if (!textNode) {
+ // The text has been removed from the document, or split up for no reason.
+ return false;
+ }
+ var expected = null, attributeName = null, styleName = null;
+ switch (command) {
+ case 'backcolor':
+ case 'hilitecolor':
+ expected = new Color(CHANGE_TESTS[command].tests[index].opt_arg);
+ styleName = 'backgroundColor';
+ break;
+ case 'fontname':
+ expected = CHANGE_TESTS[command].tests[index].opt_arg;
+ attributeName = 'face';
+ styleName = 'fontFamily';
+ break;
+ case 'fontsize':
+ expected = new Size(CHANGE_TESTS[command].tests[index].opt_arg);
+ attributeName = 'size';
+ styleName = 'fontSize';
+ break;
+ case 'forecolor':
+ expected = new Color(CHANGE_TESTS[command].tests[index].opt_arg);
+ attributeName = 'color';
+ styleName = 'color';
+ }
+ var foundExpected = false;
+
+ // Loop through all the parent nodes that format the text node,
+ // checking that there is exactly one font attribute or
+ // style, and that it's set correctly.
+ var currentNode = textNode.parentNode;
+ while(currentNode && currentNode.nodeName != 'BODY') {
+ // Check font attribute.
+ if (attributeName && currentNode.nodeName == 'FONT' && currentNode.getAttribute(attributeName)) {
+ var foundAttribute = false;
+ switch(command) {
+ case 'backcolor':
+ case 'forecolor':
+ case 'hilitecolor':
+ foundAttribute = new Color(currentNode.getAttribute(attributeName)).compare(expected);
+ break;
+ case 'fontsize':
+ foundAttribute = new Size(currentNode.getAttribute(attributeName)).compare(expected);
+ break;
+ case 'fontname':
+ foundAttribute = (currentNode.getAttribute(attributeName).toLowerCase() == expected.toLowerCase());
+ }
+ if (foundAttribute && foundExpected) {
+ // This is the correct attribute, but the style has been applied
+ // twice. This makes it hard for other browsers to remove the
+ // style.
+ return false;
+ } else if (!foundAttribute) {
+ // This node has an incorrect font attribute.
+ return false;
+ }
+ // The expected font attribute was found.
+ foundExpected = true;
+ }
+ // Check node style.
+ if (currentNode.style[styleName]) {
+ var foundStyle = false;
+ switch(command) {
+ case 'backcolor':
+ case 'forecolor':
+ case 'hilitecolor':
+ foundStyle = new Color(currentNode.style[styleName]).compare(expected);
+ break;
+ case 'fontsize':
+ foundStyle = new Size(currentNode.style[styleName]).compare(expected);
+ break;
+ case 'fontname':
+ foundStyle = (currentNode.style[styleName].toLowerCase() == expected.toLowerCase());
+ }
+ if (foundStyle && foundExpected) {
+ // This is the correct style, but the style has been
+ // applied twice. This makes it hard for other browsers to
+ // remove the style.
+ return false;
+ } else if (!foundStyle) {
+ // This node has an incorrect font style.
+ return false;
+ }
+ foundExpected = true;
+ }
+ currentNode = currentNode.parentNode;
+ }
+ return foundExpected;
+ }
+
+ /**
+ * @return {boolean} success
+ */
+ function outputChangeResult(command, index) {
+ // Each command is displayed as a table row with 4 columns
+ var tr = document.createElement('TR');
+ var success = checkChangeSuccess(command, index);
+ tr.className = success ? 'success' : 'fail';
+ results.push('c-' + command + '-' + index + '=' + (success ? '1' : '0'));
+
+ // Column 1: command name
+ var td = document.createElement('TD');
+ td.innerHTML = command;
+ tr.appendChild(td);
+
+ // Column 2: status
+ td = document.createElement('TD');
+ td.innerHTML = (success == null) ? '?' : (success == true ? 'PASS' : 'FAIL');
+ tr.appendChild(td);
+
+ // Column 3: opt_arg
+ td = document.createElement('TD');
+ td.innerHTML = CHANGE_TESTS[command].tests[index].opt_arg;
+ tr.appendChild(td);
+
+ // Column 4: original html
+ td = document.createElement('TD');
+ td.innerHTML = CHANGE_TESTS[command].tests[index].html.replace(/\</g, '&lt;').replace(/\>/g, '&gt;');;
+ tr.appendChild(td);
+
+ // Column 5: resulting html
+ td = document.createElement('TD');
+ td.innerHTML = editorDoc.body.innerHTML.replace(/\</g, '&lt;').replace(/\>/g, '&gt;');;
+ tr.appendChild(td);
+
+ var table = document.getElementById('change_output');
+ table.appendChild(tr);
+ return success;
+ }
+
+ function runTests() {
+ // Wrap initialization code in a try/catch so we can fail gracefully
+ // on older browsers.
+ try {
+ editorDoc = document.getElementById('editor').contentWindow.document;
+ // Default styleWithCSS to false, since it's not supported by IE.
+ try {
+ editorDoc.execCommand('styleWithCSS', false, false);
+ } catch (ex) {
+ // Not supported by IE.
+ }
+ } catch (ex) {}
+
+ // Apply tests
+ var apply_score = 0;
+ var apply_count = 0;
+ var unapply_score= 0;
+ var unapply_count = 0;
+ var change_score = 0;
+ var change_count = 0;
+ var query_score = 0;
+ var query_count = 0;
+ for (var command in APPLY_TESTS) {
+ try {
+ var result = doApplyTest(command, false);
+ var success = outputApplyResult(command, result, false);
+ apply_score += success ? 1 : 0;
+ } catch (ex) {
+ // An exception is counted as a failed test, don't increment success.
+ }
+ apply_count++;
+ if (APPLY_TESTS[command].styleWithCSS) {
+ try {
+ var result = doApplyTest(command, true);
+ var success = outputApplyResult(command, result, true);
+ apply_score += success ? 1 : 0;
+ } catch (ex) {
+ // An exception is counted as a failed test, don't increment success.
+ }
+ apply_count++;
+ }
+ }
+
+ // Unapply tests
+ for (var command in UNAPPLY_TESTS) {
+ for (var i = 0; i < UNAPPLY_TESTS[command].tags.length; i++) {
+ try {
+ var result = doUnapplyTest(command, i);
+ var success = outputUnapplyResult(command, result, i);
+ unapply_score += success ? 1 : 0;
+ } catch (ex) {
+ // An exception is counted as a failed test, don't increment success.
+ }
+ unapply_count++;
+ }
+ }
+
+ // Query tests
+ for (var command in QUERY_TESTS) {
+ for (var i = 0; i < QUERY_TESTS[command].tests.length; i++) {
+ try {
+ var result = doQueryTest(command, i);
+ var success = outputQueryResult(command, i, result);
+ query_score += success ? 1 : 0;
+ } catch (ex) {
+ // An exception is counted as a failed test, don't increment success.
+ }
+ query_count++;
+ }
+ }
+
+ // Change tests
+ for (var command in CHANGE_TESTS) {
+ for (var i = 0; i < CHANGE_TESTS[command].tests.length; i++) {
+ try {
+ doChangeTest(command, i);
+ var success = outputChangeResult(command, i);
+ change_score += success ? 1 : 0;
+ } catch (ex) {
+ // An exception is counted as a failed test, don't increment success.
+ }
+ change_count++;
+ }
+ }
+
+ // Beacon all test results.
+ // and construct a shorter version for the results page.
+ try {
+ document.getElementById('apply-score').innerHTML =
+ apply_score + '/' + apply_count;
+ document.getElementById('unapply-score').innerHTML =
+ unapply_score + '/' + unapply_count;
+ document.getElementById('query-score').innerHTML =
+ query_score + '/' + query_count;
+ document.getElementById('change-score').innerHTML =
+ change_score + '/' + change_count;
+ } catch (ex) {}
+ var continueParams = [
+ 'apply=' + apply_score,
+ 'unapply=' + unapply_score,
+ 'query=' + query_score,
+ 'change=' + change_score
+ ];
+ parent.sendScore(results, continueParams);
+ }
+ </script>
+ <style>
+ .success {
+ background-color: #93c47d;
+ }
+ .fail {
+ background-color: #ea9999;
+ }
+ .score {
+ color: #666;
+ }
+ </style>
+</head>
+<body onload="runTests()">
+ <h1>Apply Formatting <span id="apply-score" class="score"></span></h1>
+ <table id="apply"><tbody id="apply_output"><tr><th>Command</th><th>styleWithCSS</th><th>Status</th><th>Output</th></tr></tbody></table>
+ <h1>Unapply Formatting <span id="unapply-score" class="score"></span></h1>
+ <table id="unapply">
+ <thead><tr><th>Command</th><th>Command unapplied</th><th>Status</th><th>HTML Attempted to Unapply</th><th>Resulting HTML</th></tr></thead>
+ <tbody id="unapply_output"></tbody></table>
+ <h1>Query Formatting State <span id="query-score" class="score"></span></h1>
+ <table id="querystate">
+ <thead><tr><th>Command</th><th>Status</th><th>HTML</th><th>Expected</th><th>Actual</th></tr></thead>
+ <tbody id="querystate_output"></tbody></table>
+ <h1>Query Formatting Value </h1>
+ <table id="queryvalue">
+ <thead><tr><th>Command</th><th>Status</th><th>HTML</th><th>Expected</th><th>Actual</th></tr></thead>
+ <tbody id="queryvalue_output"></tbody></table>
+ <h1>Change Formatting <span id="change-score" class="score"></span></h1>
+ <table id="change">
+ <thead><tr><th>Command</th><th>Status</th><th>Argument</th><th>Original HTML</th><th>Resulting HTML</th></tr></thead>
+ <tbody id="change_output"></tbody></table>
+ <iframe name="editor" id="editor" src="editable.html"></iframe>
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/lib/richtext/update_from_upstream b/editor/libeditor/tests/browserscope/lib/richtext/update_from_upstream
new file mode 100644
index 000000000..2071454a8
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext/update_from_upstream
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -x
+
+if test -d richtext; then
+ rm -drf richtext;
+fi
+
+svn checkout http://browserscope.googlecode.com/svn/trunk/categories/richtext/static richtext | tail -1 | sed 's/[^0-9]//g' > current_revision
+
+find richtext -type d -name .svn -exec rm -drf \{\} \; 2> /dev/null
+
+hg add current_revision richtext
+
+hg stat .
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/LICENSE b/editor/libeditor/tests/browserscope/lib/richtext2/LICENSE
new file mode 100644
index 000000000..57bc88a15
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/README b/editor/libeditor/tests/browserscope/lib/richtext2/README
new file mode 100644
index 000000000..a3bc3110f
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/README
@@ -0,0 +1,58 @@
+README FOR BROWSERSCOPE
+-----------------------
+
+Hey there - thanks for downloading the code. This file has instructions
+for getting setup so that you can run the codebase locally.
+
+This project is built on Google App Engine using the
+Django web application framework and written in Python.
+
+To get started, you'll need to first download the App Engine SDK at:
+http://code.google.com/appengine/downloads.html
+
+For local development, just startup the server:
+./pathto/google_appengine/dev_appserver.py --port=8080 browserscope
+
+You should then be able to access the local application at:
+http://localhost:8080/
+
+Note: the first time you hit the homepage it may take a little
+while - that's because it's trying to read out median times for all
+of the tests from a nonexistent datastore and write to memcache.
+Just be a lil patient.
+
+You can run the unit tests at:
+ http://localhost:8080/test
+
+
+CONTRIBUTING
+------------------
+
+Most likely you are interested in adding new tests or creating
+a new test category. If you are interested in adding tests to an existing
+"category" you may want to get in touch with the maintainer for that
+branch of the tree. We are really looking forward to receiving your
+code in patch format. Currently the category maintainers are:
+Network: Steve Souders <souders@gmail.com>
+Reflow: Lindsey Simon <elsigh@gmail.com>
+Security: Adam Barth <adam@adambarth.com> and Collin Jackson <collin@collinjackson.com>
+
+
+To create a completely new test category:
+ * Copy one of the existing directories in categories/
+ * Edit your test_set.py, handlers.py
+ * Add your files in templates/ and static/
+ * Update urls.py and settings.CATEGORIES
+ * Follow the examples of other tests re:
+ * beaconing using/testdriver_base
+ * your GetScoreAndDisplayValue method
+ * your GetRowScoreAndDisplayValue method
+
+References:
+ * App Engine Docs - http://code.google.com/appengine/docs/python/overview.html
+ * App Engine Group - http://groups.google.com/group/google-appengine
+ * Python Docs - http://www.python.org/doc/
+ * Django - http://www.djangoproject.com/
+
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/README.Mozilla b/editor/libeditor/tests/browserscope/lib/richtext2/README.Mozilla
new file mode 100644
index 000000000..3e667a0b7
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/README.Mozilla
@@ -0,0 +1,23 @@
+The BrowserScope project provides a set of cross-browser HTML editor tests,
+which we import in our test suite in order to run them as part of our
+continuous integration system.
+
+We pull tests occasionally from their Subversion repository using the pull
+script which can be found in this directory. We also record the revision ID
+which we've used in the current_revision file inside this directory.
+
+Using the pull script is quite easy, just switch to this directory, and say:
+
+sh update_from_upstream
+
+There are tests which we're currently failing on, and there will probably be
+more of those in the future. We should maintain a list of the failing tests
+manually in currentStatus.js (which can also be found in this directory), to
+make sure that the suite passes entirely, with failing tests marked as todo
+items.
+
+The current status of the test suite needs to be updated whenever an editor
+bug gets fixed, which makes us pass one of the tests. When that happens,
+you should set the UPDATE_TEST_RESULTS constant to true in test_richtext2.html,
+run the test suite, paste the result JSON string in a JSON beautifier (such
+as http://jsbeautifier.org/), and use the result to update currentStatus.js.
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/currentStatus.js b/editor/libeditor/tests/browserscope/lib/richtext2/currentStatus.js
new file mode 100644
index 000000000..570853afa
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/currentStatus.js
@@ -0,0 +1,1850 @@
+/**
+ * The current status of the test suite.
+ *
+ * See README.Mozilla for details on how to generate this.
+ */
+const knownFailures = {
+ "value": {
+ "A-Proposed-FS:18px_TEXT-1_SI-dM": true,
+ "A-Proposed-FS:18px_TEXT-1_SI-body": true,
+ "A-Proposed-FS:18px_TEXT-1_SI-div": true,
+ "A-Proposed-FS:large_TEXT-1_SI-dM": true,
+ "A-Proposed-FS:large_TEXT-1_SI-body": true,
+ "A-Proposed-FS:large_TEXT-1_SI-div": true,
+ "A-Proposed-CB:name_TEXT-1_SI-dM": true,
+ "A-Proposed-CB:name_TEXT-1_SI-body": true,
+ "A-Proposed-CB:name_TEXT-1_SI-div": true,
+ "AC-Proposed-SUB_TEXT-1_SI-dM": true,
+ "AC-Proposed-SUB_TEXT-1_SI-body": true,
+ "AC-Proposed-SUB_TEXT-1_SI-div": true,
+ "AC-Proposed-SUP_TEXT-1_SI-dM": true,
+ "AC-Proposed-SUP_TEXT-1_SI-body": true,
+ "AC-Proposed-SUP_TEXT-1_SI-div": true,
+ "AC-Proposed-FS:2_TEXT-1_SI-dM": true,
+ "AC-Proposed-FS:2_TEXT-1_SI-body": true,
+ "AC-Proposed-FS:2_TEXT-1_SI-div": true,
+ "AC-Proposed-FS:18px_TEXT-1_SI-dM": true,
+ "AC-Proposed-FS:18px_TEXT-1_SI-body": true,
+ "AC-Proposed-FS:18px_TEXT-1_SI-div": true,
+ "AC-Proposed-FS:large_TEXT-1_SI-dM": true,
+ "AC-Proposed-FS:large_TEXT-1_SI-body": true,
+ "AC-Proposed-FS:large_TEXT-1_SI-div": true,
+ "C-Proposed-BC:ace_FONT.ass.s:bc:rgb-1_SW-dM": true,
+ "C-Proposed-BC:ace_FONT.ass.s:bc:rgb-1_SW-body": true,
+ "C-Proposed-BC:ace_FONT.ass.s:bc:rgb-1_SW-div": true,
+ "C-Proposed-HC:g_SPAN.ass.s:c:rgb-1_SW-dM": true,
+ "C-Proposed-HC:g_SPAN.ass.s:c:rgb-1_SW-body": true,
+ "C-Proposed-HC:g_SPAN.ass.s:c:rgb-1_SW-div": true,
+ "C-Proposed-FN:c_FONTf:a-1_SI-dM": true,
+ "C-Proposed-FN:c_FONTf:a-1_SI-body": true,
+ "C-Proposed-FN:c_FONTf:a-1_SI-div": true,
+ "C-Proposed-FN:c_FONTf:a-2_SL-dM": true,
+ "C-Proposed-FN:c_FONTf:a-2_SL-body": true,
+ "C-Proposed-FN:c_FONTf:a-2_SL-div": true,
+ "C-Proposed-FS:1_SPAN.ass.s:fs:large-1_SW-dM": true,
+ "C-Proposed-FS:1_SPAN.ass.s:fs:large-1_SW-body": true,
+ "C-Proposed-FS:1_SPAN.ass.s:fs:large-1_SW-div": true,
+ "C-Proposed-FS:5_FONTsz:1.s:fs:xs-1_SW-dM": true,
+ "C-Proposed-FS:5_FONTsz:1.s:fs:xs-1_SW-body": true,
+ "C-Proposed-FS:5_FONTsz:1.s:fs:xs-1_SW-div": true,
+ "C-Proposed-FS:larger_FONTsz:4-dM": true,
+ "C-Proposed-FS:larger_FONTsz:4-body": true,
+ "C-Proposed-FS:larger_FONTsz:4-div": true,
+ "C-Proposed-FS:smaller_FONTsz:4-dM": true,
+ "C-Proposed-FS:smaller_FONTsz:4-body": true,
+ "C-Proposed-FS:smaller_FONTsz:4-div": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SO-dM": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SO-body": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SO-div": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SW-dM": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SW-body": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SW-div": true,
+ "C-Proposed-FB:h1_ADDRESS-FONT.ass.sz:4-1_SW-dM": true,
+ "C-Proposed-FB:h1_ADDRESS-FONT.ass.sz:4-1_SW-body": true,
+ "C-Proposed-FB:h1_ADDRESS-FONT.ass.sz:4-1_SW-div": true,
+ "CC-Proposed-I_B-1_SW-dM": true,
+ "CC-Proposed-I_B-1_SW-body": true,
+ "CC-Proposed-I_B-1_SW-div": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-1_SI-dM": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-1_SI-body": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-1_SI-div": true,
+ "CC-Proposed-BC:gray_P-SPANs:bc:b-3_SL-dM": true,
+ "CC-Proposed-BC:gray_P-SPANs:bc:b-3_SL-body": true,
+ "CC-Proposed-BC:gray_P-SPANs:bc:b-3_SL-div": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SL-dM": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SL-body": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SL-div": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SR-dM": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SR-body": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SR-div": true,
+ "CC-Proposed-FN:c_FONTf:a-1_SI-dM": true,
+ "CC-Proposed-FN:c_FONTf:a-1_SI-body": true,
+ "CC-Proposed-FN:c_FONTf:a-1_SI-div": true,
+ "CC-Proposed-FN:c_FONTf:a-2_SL-dM": true,
+ "CC-Proposed-FN:c_FONTf:a-2_SL-body": true,
+ "CC-Proposed-FN:c_FONTf:a-2_SL-div": true,
+ "CC-Proposed-FS:1_SPANs:fs:l-1_SW-dM": true,
+ "CC-Proposed-FS:1_SPANs:fs:l-1_SW-body": true,
+ "CC-Proposed-FS:1_SPANs:fs:l-1_SW-div": true,
+ "CC-Proposed-FS:18px_SPANs:fs:l-1_SW-dM": true,
+ "CC-Proposed-FS:18px_SPANs:fs:l-1_SW-body": true,
+ "CC-Proposed-FS:18px_SPANs:fs:l-1_SW-div": true,
+ "CC-Proposed-FS:4_SPANs:fs:l-1_SW-dM": true,
+ "CC-Proposed-FS:4_SPANs:fs:l-1_SW-body": true,
+ "CC-Proposed-FS:4_SPANs:fs:l-1_SW-div": true,
+ "CC-Proposed-FS:4_SPANs:fs:18px-1_SW-dM": true,
+ "CC-Proposed-FS:4_SPANs:fs:18px-1_SW-body": true,
+ "CC-Proposed-FS:4_SPANs:fs:18px-1_SW-div": true,
+ "CC-Proposed-FS:larger_SPANs:fs:l-1_SI-dM": true,
+ "CC-Proposed-FS:larger_SPANs:fs:l-1_SI-body": true,
+ "CC-Proposed-FS:larger_SPANs:fs:l-1_SI-div": true,
+ "CC-Proposed-FS:smaller_SPANs:fs:l-1_SI-dM": true,
+ "CC-Proposed-FS:smaller_SPANs:fs:l-1_SI-body": true,
+ "CC-Proposed-FS:smaller_SPANs:fs:l-1_SI-div": true,
+ "U-RFC-UNLINK_A-1_SO-dM": true,
+ "U-RFC-UNLINK_A-1_SO-body": true,
+ "U-RFC-UNLINK_A-1_SO-div": true,
+ "U-RFC-UNLINK_A-1_SW-dM": true,
+ "U-RFC-UNLINK_A-1_SW-body": true,
+ "U-RFC-UNLINK_A-1_SW-div": true,
+ "U-RFC-UNLINK_A-2_SO-dM": true,
+ "U-RFC-UNLINK_A-2_SO-body": true,
+ "U-RFC-UNLINK_A-2_SO-div": true,
+ "U-RFC-UNLINK_A2-1_SO-dM": true,
+ "U-RFC-UNLINK_A2-1_SO-body": true,
+ "U-RFC-UNLINK_A2-1_SO-div": true,
+ "U-Proposed-B_B-P-I..P-1_SO-I-dM": true,
+ "U-Proposed-B_B-P-I..P-1_SO-I-body": true,
+ "U-Proposed-B_B-P-I..P-1_SO-I-div": true,
+ "U-Proposed-B_B-2_SL-dM": true,
+ "U-Proposed-B_B-2_SL-body": true,
+ "U-Proposed-B_B-2_SL-div": true,
+ "U-Proposed-B_B-2_SR-dM": true,
+ "U-Proposed-B_B-2_SR-body": true,
+ "U-Proposed-B_B-2_SR-div": true,
+ "U-Proposed-U_U-S-2_SI-dM": true,
+ "U-Proposed-U_U-S-2_SI-body": true,
+ "U-Proposed-U_U-S-2_SI-div": true,
+ "U-Proposed-S_DEL-1_SW-dM": true,
+ "U-Proposed-S_DEL-1_SW-body": true,
+ "U-Proposed-S_DEL-1_SW-div": true,
+ "U-Proposed-SUB_SPANs:va:sub-1_SW-dM": true,
+ "U-Proposed-SUB_SPANs:va:sub-1_SW-body": true,
+ "U-Proposed-SUB_SPANs:va:sub-1_SW-div": true,
+ "U-Proposed-SUP_SPANs:va:super-1_SW-dM": true,
+ "U-Proposed-SUP_SPANs:va:super-1_SW-body": true,
+ "U-Proposed-SUP_SPANs:va:super-1_SW-div": true,
+ "U-Proposed-UNLINK_A-1_SC-dM": true,
+ "U-Proposed-UNLINK_A-1_SC-body": true,
+ "U-Proposed-UNLINK_A-1_SC-div": true,
+ "U-Proposed-UNLINK_A-1_SI-dM": true,
+ "U-Proposed-UNLINK_A-1_SI-body": true,
+ "U-Proposed-UNLINK_A-1_SI-div": true,
+ "U-Proposed-UNLINK_A-2_SL-dM": true,
+ "U-Proposed-UNLINK_A-2_SL-body": true,
+ "U-Proposed-UNLINK_A-2_SL-div": true,
+ "U-Proposed-UNLINK_A-3_SR-dM": true,
+ "U-Proposed-UNLINK_A-3_SR-body": true,
+ "U-Proposed-UNLINK_A-3_SR-div": true,
+ "U-Proposed-OUTDENT_BQ-1_SW-dM": true,
+ "U-Proposed-OUTDENT_BQ-1_SW-body": true,
+ "U-Proposed-OUTDENT_BQ-1_SW-div": true,
+ "U-Proposed-OUTDENT_BQ.wibq.s:m:00040.b:n.p:0-1_SW-dM": true,
+ "U-Proposed-OUTDENT_BQ.wibq.s:m:00040.b:n.p:0-1_SW-body": true,
+ "U-Proposed-OUTDENT_BQ.wibq.s:m:00040.b:n.p:0-1_SW-div": true,
+ "U-Proposed-OUTDENT_OL-LI-1_SW-dM": true,
+ "U-Proposed-OUTDENT_OL-LI-1_SW-body": true,
+ "U-Proposed-OUTDENT_OL-LI-1_SW-div": true,
+ "U-Proposed-OUTDENT_UL-LI-1_SW-dM": true,
+ "U-Proposed-OUTDENT_UL-LI-1_SW-body": true,
+ "U-Proposed-OUTDENT_UL-LI-1_SW-div": true,
+ "U-Proposed-OUTDENT_DIV-1_SW-dM": true,
+ "U-Proposed-OUTDENT_DIV-1_SW-body": true,
+ "U-Proposed-OUTDENT_DIV-1_SW-div": true,
+ "U-Proposed-REMOVEFORMAT_Ahref:url-1_SW-dM": true,
+ "U-Proposed-REMOVEFORMAT_Ahref:url-1_SW-body": true,
+ "U-Proposed-REMOVEFORMAT_Ahref:url-1_SW-div": true,
+ "U-Proposed-REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW-dM": true,
+ "U-Proposed-REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW-body": true,
+ "U-Proposed-REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW-div": true,
+ "U-Proposed-UNBOOKMARK_An:name-1_SW-dM": true,
+ "U-Proposed-UNBOOKMARK_An:name-1_SW-body": true,
+ "U-Proposed-UNBOOKMARK_An:name-1_SW-div": true,
+ "UC-Proposed-S_SPANc:s-1_SW-dM": true,
+ "UC-Proposed-S_SPANc:s-1_SW-body": true,
+ "UC-Proposed-S_SPANc:s-1_SW-div": true,
+ "UC-Proposed-S_SPANc:s-2_SI-dM": true,
+ "UC-Proposed-S_SPANc:s-2_SI-body": true,
+ "UC-Proposed-S_SPANc:s-2_SI-div": true,
+ "D-Proposed-CHAR-3_SC-dM": true,
+ "D-Proposed-CHAR-3_SC-body": true,
+ "D-Proposed-CHAR-3_SC-div": true,
+ "D-Proposed-CHAR-4_SC-dM": true,
+ "D-Proposed-CHAR-4_SC-body": true,
+ "D-Proposed-CHAR-4_SC-div": true,
+ "D-Proposed-CHAR-5_SC-dM": true,
+ "D-Proposed-CHAR-5_SC-body": true,
+ "D-Proposed-CHAR-5_SC-div": true,
+ "D-Proposed-CHAR-5_SI-1-dM": true,
+ "D-Proposed-CHAR-5_SI-1-body": true,
+ "D-Proposed-CHAR-5_SI-1-div": true,
+ "D-Proposed-CHAR-5_SI-2-dM": true,
+ "D-Proposed-CHAR-5_SI-2-body": true,
+ "D-Proposed-CHAR-5_SI-2-div": true,
+ "D-Proposed-CHAR-5_SR-dM": true,
+ "D-Proposed-CHAR-5_SR-body": true,
+ "D-Proposed-CHAR-5_SR-div": true,
+ "D-Proposed-CHAR-6_SC-dM": true,
+ "D-Proposed-CHAR-6_SC-body": true,
+ "D-Proposed-CHAR-6_SC-div": true,
+ "D-Proposed-CHAR-7_SC-dM": true,
+ "D-Proposed-CHAR-7_SC-body": true,
+ "D-Proposed-CHAR-7_SC-div": true,
+ "D-Proposed-OL-LI-1_SW-dM": true,
+ "D-Proposed-OL-LI-1_SW-body": true,
+ "D-Proposed-OL-LI-1_SW-div": true,
+ "D-Proposed-TR2rs:2-1_SO1-dM": true,
+ "D-Proposed-TR2rs:2-1_SO1-body": true,
+ "D-Proposed-TR2rs:2-1_SO1-div": true,
+ "D-Proposed-TR2rs:2-1_SO2-dM": true,
+ "D-Proposed-TR2rs:2-1_SO2-body": true,
+ "D-Proposed-TR2rs:2-1_SO2-div": true,
+ "D-Proposed-TR3rs:3-1_SO1-dM": true,
+ "D-Proposed-TR3rs:3-1_SO1-body": true,
+ "D-Proposed-TR3rs:3-1_SO1-div": true,
+ "D-Proposed-TR3rs:3-1_SO2-dM": true,
+ "D-Proposed-TR3rs:3-1_SO2-body": true,
+ "D-Proposed-TR3rs:3-1_SO2-div": true,
+ "D-Proposed-TR3rs:3-1_SO3-dM": true,
+ "D-Proposed-TR3rs:3-1_SO3-body": true,
+ "D-Proposed-TR3rs:3-1_SO3-div": true,
+ "D-Proposed-DIV:ce:false-1_SB-dM": true,
+ "D-Proposed-DIV:ce:false-1_SB-body": true,
+ "D-Proposed-DIV:ce:false-1_SB-div": true,
+ "D-Proposed-DIV:ce:false-1_SL-dM": true,
+ "D-Proposed-DIV:ce:false-1_SL-body": true,
+ "D-Proposed-DIV:ce:false-1_SL-div": true,
+ "D-Proposed-DIV:ce:false-1_SR-dM": true,
+ "D-Proposed-DIV:ce:false-1_SR-body": true,
+ "D-Proposed-DIV:ce:false-1_SR-div": true,
+ "D-Proposed-DIV:ce:false-1_SI-dM": true,
+ "FD-Proposed-OL-LI-1_SW-dM": true,
+ "FD-Proposed-OL-LI-1_SW-body": true,
+ "FD-Proposed-OL-LI-1_SW-div": true,
+ "FD-Proposed-TR2rs:2-1_SO1-dM": true,
+ "FD-Proposed-TR2rs:2-1_SO1-body": true,
+ "FD-Proposed-TR2rs:2-1_SO1-div": true,
+ "FD-Proposed-TR2rs:2-1_SO2-dM": true,
+ "FD-Proposed-TR2rs:2-1_SO2-body": true,
+ "FD-Proposed-TR2rs:2-1_SO2-div": true,
+ "FD-Proposed-TR3rs:3-1_SO1-dM": true,
+ "FD-Proposed-TR3rs:3-1_SO1-body": true,
+ "FD-Proposed-TR3rs:3-1_SO1-div": true,
+ "FD-Proposed-TR3rs:3-1_SO2-dM": true,
+ "FD-Proposed-TR3rs:3-1_SO2-body": true,
+ "FD-Proposed-TR3rs:3-1_SO2-div": true,
+ "FD-Proposed-TR3rs:3-1_SO3-dM": true,
+ "FD-Proposed-TR3rs:3-1_SO3-body": true,
+ "FD-Proposed-TR3rs:3-1_SO3-div": true,
+ "FD-Proposed-DIV:ce:false-1_SB-dM": true,
+ "FD-Proposed-DIV:ce:false-1_SB-body": true,
+ "FD-Proposed-DIV:ce:false-1_SB-div": true,
+ "FD-Proposed-DIV:ce:false-1_SL-dM": true,
+ "FD-Proposed-DIV:ce:false-1_SL-body": true,
+ "FD-Proposed-DIV:ce:false-1_SL-div": true,
+ "FD-Proposed-DIV:ce:false-1_SR-dM": true,
+ "FD-Proposed-DIV:ce:false-1_SR-body": true,
+ "FD-Proposed-DIV:ce:false-1_SR-div": true,
+ "FD-Proposed-DIV:ce:false-1_SI-dM": true,
+ "I-Proposed-IIMG:._SPAN-IMG-1_SO-dM": true,
+ "I-Proposed-IIMG:._SPAN-IMG-1_SO-body": true,
+ "I-Proposed-IIMG:._SPAN-IMG-1_SO-div": true,
+ "I-Proposed-IIMG:._IMG-1_SO-dM": true,
+ "I-Proposed-IIMG:._IMG-1_SO-body": true,
+ "I-Proposed-IIMG:._IMG-1_SO-div": true,
+ "Q-Proposed-UNSELECT_TEXT-1-dM": true,
+ "Q-Proposed-UNSELECT_TEXT-1-body": true,
+ "Q-Proposed-UNSELECT_TEXT-1-div": true,
+ "Q-Proposed-CREATEBOOKMARK_TEXT-1-dM": true,
+ "Q-Proposed-CREATEBOOKMARK_TEXT-1-body": true,
+ "Q-Proposed-CREATEBOOKMARK_TEXT-1-div": true,
+ "Q-Proposed-UNBOOKMARK_TEXT-1-dM": true,
+ "Q-Proposed-UNBOOKMARK_TEXT-1-body": true,
+ "Q-Proposed-UNBOOKMARK_TEXT-1-div": true,
+ "Q-Proposed-PASTE_TEXT-1-dM": true,
+ "Q-Proposed-PASTE_TEXT-1-body": true,
+ "Q-Proposed-PASTE_TEXT-1-div": true,
+ "QE-Proposed-UNSELECT_TEXT-1-dM": true,
+ "QE-Proposed-UNSELECT_TEXT-1-body": true,
+ "QE-Proposed-UNSELECT_TEXT-1-div": true,
+ "QE-Proposed-REDO_TEXT-1-dM": true,
+ "QE-Proposed-REDO_TEXT-1-body": true,
+ "QE-Proposed-REDO_TEXT-1-div": true,
+ "QE-Proposed-CREATEBOOKMARK_TEXT-1-dM": true,
+ "QE-Proposed-CREATEBOOKMARK_TEXT-1-body": true,
+ "QE-Proposed-CREATEBOOKMARK_TEXT-1-div": true,
+ "QE-Proposed-UNBOOKMARK_TEXT-1-dM": true,
+ "QE-Proposed-UNBOOKMARK_TEXT-1-body": true,
+ "QE-Proposed-UNBOOKMARK_TEXT-1-div": true,
+ "QE-Proposed-COPY_TEXT-1-dM": true,
+ "QE-Proposed-COPY_TEXT-1-body": true,
+ "QE-Proposed-COPY_TEXT-1-div": true,
+ "QE-Proposed-CUT_TEXT-1-dM": true,
+ "QE-Proposed-CUT_TEXT-1-body": true,
+ "QE-Proposed-CUT_TEXT-1-div": true,
+ "QE-Proposed-PASTE_TEXT-1-dM": true,
+ "QE-Proposed-PASTE_TEXT-1-body": true,
+ "QE-Proposed-PASTE_TEXT-1-div": true,
+ "QS-Proposed-SUB_SPAN.sub-1-SI-dM": true,
+ "QS-Proposed-SUB_SPAN.sub-1-SI-body": true,
+ "QS-Proposed-SUB_SPAN.sub-1-SI-div": true,
+ "QS-Proposed-SUB_MYSUB-1-SI-dM": true,
+ "QS-Proposed-SUB_MYSUB-1-SI-body": true,
+ "QS-Proposed-SUB_MYSUB-1-SI-div": true,
+ "QS-Proposed-SUP_SPAN.sup-1-SI-dM": true,
+ "QS-Proposed-SUP_SPAN.sup-1-SI-body": true,
+ "QS-Proposed-SUP_SPAN.sup-1-SI-div": true,
+ "QS-Proposed-SUP_MYSUP-1-SI-dM": true,
+ "QS-Proposed-SUP_MYSUP-1-SI-body": true,
+ "QS-Proposed-SUP_MYSUP-1-SI-div": true,
+ "QS-Proposed-JC_SPANs:ta:c-1_SI-dM": true,
+ "QS-Proposed-JC_SPANs:ta:c-1_SI-body": true,
+ "QS-Proposed-JC_SPANs:ta:c-1_SI-div": true,
+ "QS-Proposed-JC_SPAN.jc-1-SI-dM": true,
+ "QS-Proposed-JC_SPAN.jc-1-SI-body": true,
+ "QS-Proposed-JC_SPAN.jc-1-SI-div": true,
+ "QS-Proposed-JC_MYJC-1-SI-dM": true,
+ "QS-Proposed-JC_MYJC-1-SI-body": true,
+ "QS-Proposed-JC_MYJC-1-SI-div": true,
+ "QS-Proposed-JF_SPANs:ta:j-1_SI-dM": true,
+ "QS-Proposed-JF_SPANs:ta:j-1_SI-body": true,
+ "QS-Proposed-JF_SPANs:ta:j-1_SI-div": true,
+ "QS-Proposed-JF_SPAN.jf-1-SI-dM": true,
+ "QS-Proposed-JF_SPAN.jf-1-SI-body": true,
+ "QS-Proposed-JF_SPAN.jf-1-SI-div": true,
+ "QS-Proposed-JF_MYJF-1-SI-dM": true,
+ "QS-Proposed-JF_MYJF-1-SI-body": true,
+ "QS-Proposed-JF_MYJF-1-SI-div": true,
+ "QS-Proposed-JL_TEXT_SI-dM": true,
+ "QS-Proposed-JL_TEXT_SI-body": true,
+ "QS-Proposed-JL_TEXT_SI-div": true,
+ "QS-Proposed-JR_SPANs:ta:r-1_SI-dM": true,
+ "QS-Proposed-JR_SPANs:ta:r-1_SI-body": true,
+ "QS-Proposed-JR_SPANs:ta:r-1_SI-div": true,
+ "QS-Proposed-JR_SPAN.jr-1-SI-dM": true,
+ "QS-Proposed-JR_SPAN.jr-1-SI-body": true,
+ "QS-Proposed-JR_SPAN.jr-1-SI-div": true,
+ "QS-Proposed-JR_MYJR-1-SI-dM": true,
+ "QS-Proposed-JR_MYJR-1-SI-body": true,
+ "QS-Proposed-JR_MYJR-1-SI-div": true,
+ "QV-Proposed-B_TEXT_SI-dM": true,
+ "QV-Proposed-B_TEXT_SI-body": true,
+ "QV-Proposed-B_TEXT_SI-div": true,
+ "QV-Proposed-B_B-1_SI-dM": true,
+ "QV-Proposed-B_B-1_SI-body": true,
+ "QV-Proposed-B_B-1_SI-div": true,
+ "QV-Proposed-B_STRONG-1_SI-dM": true,
+ "QV-Proposed-B_STRONG-1_SI-body": true,
+ "QV-Proposed-B_STRONG-1_SI-div": true,
+ "QV-Proposed-B_SPANs:fw:b-1_SI-dM": true,
+ "QV-Proposed-B_SPANs:fw:b-1_SI-body": true,
+ "QV-Proposed-B_SPANs:fw:b-1_SI-div": true,
+ "QV-Proposed-B_SPANs:fw:n-1_SI-dM": true,
+ "QV-Proposed-B_SPANs:fw:n-1_SI-body": true,
+ "QV-Proposed-B_SPANs:fw:n-1_SI-div": true,
+ "QV-Proposed-B_Bs:fw:n-1_SI-dM": true,
+ "QV-Proposed-B_Bs:fw:n-1_SI-body": true,
+ "QV-Proposed-B_Bs:fw:n-1_SI-div": true,
+ "QV-Proposed-B_SPAN.b-1_SI-dM": true,
+ "QV-Proposed-B_SPAN.b-1_SI-body": true,
+ "QV-Proposed-B_SPAN.b-1_SI-div": true,
+ "QV-Proposed-B_MYB-1-SI-dM": true,
+ "QV-Proposed-B_MYB-1-SI-body": true,
+ "QV-Proposed-B_MYB-1-SI-div": true,
+ "QV-Proposed-I_TEXT_SI-dM": true,
+ "QV-Proposed-I_TEXT_SI-body": true,
+ "QV-Proposed-I_TEXT_SI-div": true,
+ "QV-Proposed-I_I-1_SI-dM": true,
+ "QV-Proposed-I_I-1_SI-body": true,
+ "QV-Proposed-I_I-1_SI-div": true,
+ "QV-Proposed-I_EM-1_SI-dM": true,
+ "QV-Proposed-I_EM-1_SI-body": true,
+ "QV-Proposed-I_EM-1_SI-div": true,
+ "QV-Proposed-I_SPANs:fs:i-1_SI-dM": true,
+ "QV-Proposed-I_SPANs:fs:i-1_SI-body": true,
+ "QV-Proposed-I_SPANs:fs:i-1_SI-div": true,
+ "QV-Proposed-I_SPANs:fs:n-1_SI-dM": true,
+ "QV-Proposed-I_SPANs:fs:n-1_SI-body": true,
+ "QV-Proposed-I_SPANs:fs:n-1_SI-div": true,
+ "QV-Proposed-I_I-SPANs:fs:n-1_SI-dM": true,
+ "QV-Proposed-I_I-SPANs:fs:n-1_SI-body": true,
+ "QV-Proposed-I_I-SPANs:fs:n-1_SI-div": true,
+ "QV-Proposed-I_SPAN.i-1_SI-dM": true,
+ "QV-Proposed-I_SPAN.i-1_SI-body": true,
+ "QV-Proposed-I_SPAN.i-1_SI-div": true,
+ "QV-Proposed-I_MYI-1-SI-dM": true,
+ "QV-Proposed-I_MYI-1-SI-body": true,
+ "QV-Proposed-I_MYI-1-SI-div": true,
+ "QV-Proposed-FB_BQ-1_SC-dM": true,
+ "QV-Proposed-FB_BQ-1_SC-body": true,
+ "QV-Proposed-FB_BQ-1_SC-div": true,
+ "QV-Proposed-FB_H1-H2-1_SL-dM": true,
+ "QV-Proposed-FB_H1-H2-1_SL-body": true,
+ "QV-Proposed-FB_H1-H2-1_SL-div": true,
+ "QV-Proposed-FB_H1-H2-1_SR-dM": true,
+ "QV-Proposed-FB_H1-H2-1_SR-body": true,
+ "QV-Proposed-FB_H1-H2-1_SR-div": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SL-dM": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SL-body": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SL-div": true,
+ "QV-Proposed-FB_H1-H2.TEXT.H2-1_SM-dM": true,
+ "QV-Proposed-FB_H1-H2.TEXT.H2-1_SM-body": true,
+ "QV-Proposed-FB_H1-H2.TEXT.H2-1_SM-div": true,
+ "QV-Proposed-H_P-1_SC-dM": true,
+ "QV-Proposed-H_P-1_SC-body": true,
+ "QV-Proposed-H_P-1_SC-div": true,
+ "QV-Proposed-FS_FONTs:fs:l-1_SI-dM": true,
+ "QV-Proposed-FS_FONTs:fs:l-1_SI-body": true,
+ "QV-Proposed-FS_FONTs:fs:l-1_SI-div": true,
+ "QV-Proposed-FS_FONT.ass.s:fs:l-1_SI-dM": true,
+ "QV-Proposed-FS_FONT.ass.s:fs:l-1_SI-body": true,
+ "QV-Proposed-FS_FONT.ass.s:fs:l-1_SI-div": true,
+ "QV-Proposed-FS_FONTsz:1.s:fs:xl-1_SI-dM": true,
+ "QV-Proposed-FS_FONTsz:1.s:fs:xl-1_SI-body": true,
+ "QV-Proposed-FS_FONTsz:1.s:fs:xl-1_SI-div": true,
+ "QV-Proposed-FS_SPAN.large-1_SI-dM": true,
+ "QV-Proposed-FS_SPAN.large-1_SI-body": true,
+ "QV-Proposed-FS_SPAN.large-1_SI-div": true,
+ "QV-Proposed-FS_SPAN.fs18px-1_SI-dM": true,
+ "QV-Proposed-FS_SPAN.fs18px-1_SI-body": true,
+ "QV-Proposed-FS_SPAN.fs18px-1_SI-div": true,
+ "QV-Proposed-FA_MYLARGE-1-SI-dM": true,
+ "QV-Proposed-FA_MYLARGE-1-SI-body": true,
+ "QV-Proposed-FA_MYLARGE-1-SI-div": true,
+ "QV-Proposed-FA_MYFS18PX-1-SI-dM": true,
+ "QV-Proposed-FA_MYFS18PX-1-SI-body": true,
+ "QV-Proposed-FA_MYFS18PX-1-SI-div": true,
+ "QV-Proposed-BC_FONTs:bc:fca-1_SI-dM": true,
+ "QV-Proposed-BC_FONTs:bc:fca-1_SI-body": true,
+ "QV-Proposed-BC_FONTs:bc:fca-1_SI-div": true,
+ "QV-Proposed-BC_SPANs:bc:abc-1_SI-dM": true,
+ "QV-Proposed-BC_SPANs:bc:abc-1_SI-body": true,
+ "QV-Proposed-BC_SPANs:bc:abc-1_SI-div": true,
+ "QV-Proposed-BC_FONTs:bc:084-SPAN-1_SI-dM": true,
+ "QV-Proposed-BC_FONTs:bc:084-SPAN-1_SI-body": true,
+ "QV-Proposed-BC_FONTs:bc:084-SPAN-1_SI-div": true,
+ "QV-Proposed-BC_SPANs:bc:cde-SPAN-1_SI-dM": true,
+ "QV-Proposed-BC_SPANs:bc:cde-SPAN-1_SI-body": true,
+ "QV-Proposed-BC_SPANs:bc:cde-SPAN-1_SI-div": true,
+ "QV-Proposed-BC_SPAN.ass.s:bc:rgb-1_SI-dM": true,
+ "QV-Proposed-BC_SPAN.ass.s:bc:rgb-1_SI-body": true,
+ "QV-Proposed-BC_SPAN.ass.s:bc:rgb-1_SI-div": true,
+ "QV-Proposed-BC_SPAN.bcred-1_SI-dM": true,
+ "QV-Proposed-BC_SPAN.bcred-1_SI-body": true,
+ "QV-Proposed-BC_SPAN.bcred-1_SI-div": true,
+ "QV-Proposed-BC_MYBCRED-1-SI-dM": true,
+ "QV-Proposed-BC_MYBCRED-1-SI-body": true,
+ "QV-Proposed-BC_MYBCRED-1-SI-div": true,
+ "QV-Proposed-HC_FONTs:bc:fc0-1_SI-dM": true,
+ "QV-Proposed-HC_FONTs:bc:fc0-1_SI-body": true,
+ "QV-Proposed-HC_FONTs:bc:fc0-1_SI-div": true,
+ "QV-Proposed-HC_SPANs:bc:a0c-1_SI-dM": true,
+ "QV-Proposed-HC_SPANs:bc:a0c-1_SI-body": true,
+ "QV-Proposed-HC_SPANs:bc:a0c-1_SI-div": true,
+ "QV-Proposed-HC_SPAN.ass.s:bc:rgb-1_SI-dM": true,
+ "QV-Proposed-HC_SPAN.ass.s:bc:rgb-1_SI-body": true,
+ "QV-Proposed-HC_SPAN.ass.s:bc:rgb-1_SI-div": true,
+ "QV-Proposed-HC_FONTs:bc:83e-SPAN-1_SI-dM": true,
+ "QV-Proposed-HC_FONTs:bc:83e-SPAN-1_SI-body": true,
+ "QV-Proposed-HC_FONTs:bc:83e-SPAN-1_SI-div": true,
+ "QV-Proposed-HC_SPANs:bc:b12-SPAN-1_SI-dM": true,
+ "QV-Proposed-HC_SPANs:bc:b12-SPAN-1_SI-body": true,
+ "QV-Proposed-HC_SPANs:bc:b12-SPAN-1_SI-div": true,
+ "QV-Proposed-HC_SPAN.bcred-1_SI-dM": true,
+ "QV-Proposed-HC_SPAN.bcred-1_SI-body": true,
+ "QV-Proposed-HC_SPAN.bcred-1_SI-div": true,
+ "QV-Proposed-HC_MYBCRED-1-SI-dM": true,
+ "QV-Proposed-HC_MYBCRED-1-SI-body": true,
+ "QV-Proposed-HC_MYBCRED-1-SI-div": true
+ },
+ "select": {
+ "S-Proposed-UNSEL_TEXT-1_SI-dM": true,
+ "S-Proposed-UNSEL_TEXT-1_SI-body": true,
+ "S-Proposed-UNSEL_TEXT-1_SI-div": true,
+ "S-Proposed-SM:m.f.c_TEXT-1_SI-1-dM": true,
+ "S-Proposed-SM:m.f.c_TEXT-1_SI-1-body": true,
+ "S-Proposed-SM:m.f.c_TEXT-1_SI-1-div": true,
+ "S-Proposed-SM:m.b.c_TEXT-1_SI-1-dM": true,
+ "S-Proposed-SM:m.b.c_TEXT-1_SI-1-body": true,
+ "S-Proposed-SM:m.b.c_TEXT-1_SI-1-div": true,
+ "S-Proposed-SM:m.b.w_TEXT-1_SI-1-dM": true,
+ "S-Proposed-SM:m.b.w_TEXT-1_SI-1-body": true,
+ "S-Proposed-SM:m.b.w_TEXT-1_SI-1-div": true,
+ "S-Proposed-SM:m.f.c_CHAR-5_SI-2-dM": true,
+ "S-Proposed-SM:m.f.c_CHAR-5_SI-2-body": true,
+ "S-Proposed-SM:m.f.c_CHAR-5_SI-2-div": true,
+ "S-Proposed-SM:m.f.c_CHAR-5_SR-dM": true,
+ "S-Proposed-SM:m.f.c_CHAR-5_SR-body": true,
+ "S-Proposed-SM:m.f.c_CHAR-5_SR-div": true,
+ "S-Proposed-SM:m.b.c_CHAR-5_SR-dM": true,
+ "S-Proposed-SM:m.b.c_CHAR-5_SR-body": true,
+ "S-Proposed-SM:m.b.c_CHAR-5_SR-div": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-1-dM": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-1-body": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-1-div": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-2-dM": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-2-body": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-2-div": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-5-dM": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-5-body": true,
+ "S-Proposed-SM:m.f.w_TEXT-jp_SC-5-div": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-3-dM": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-3-body": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-3-div": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-4-dM": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-4-body": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-4-div": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-5-dM": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-5-body": true,
+ "S-Proposed-SM:e.b.w_TEXT-1_SI-5-div": true,
+ "S-Proposed-SM:e.f.w_TEXT-1_SIR-1-dM": true,
+ "S-Proposed-SM:e.f.w_TEXT-1_SIR-1-body": true,
+ "S-Proposed-SM:e.f.w_TEXT-1_SIR-1-div": true,
+ "S-Proposed-SM:e.f.w_TEXT-1_SIR-3-dM": true,
+ "S-Proposed-SM:e.f.w_TEXT-1_SIR-3-body": true,
+ "S-Proposed-SM:e.f.w_TEXT-1_SIR-3-div": true,
+ "S-Proposed-SM:e.f.lb_BR.BR-1_SI-1-dM": true,
+ "S-Proposed-SM:e.f.lb_BR.BR-1_SI-1-body": true,
+ "S-Proposed-SM:e.f.lb_BR.BR-1_SI-1-div": true,
+ "S-Proposed-SM:e.f.lb_P.P.P-1_SI-1-dM": true,
+ "S-Proposed-SM:e.f.lb_P.P.P-1_SI-1-body": true,
+ "S-Proposed-SM:e.f.lb_P.P.P-1_SI-1-div": true,
+ "S-Proposed-SM:e.b.lb_BR.BR-1_SIR-2-dM": true,
+ "S-Proposed-SM:e.b.lb_BR.BR-1_SIR-2-body": true,
+ "S-Proposed-SM:e.b.lb_BR.BR-1_SIR-2-div": true,
+ "S-Proposed-SM:e.b.lb_P.P.P-1_SIR-2-dM": true,
+ "S-Proposed-SM:e.b.lb_P.P.P-1_SIR-2-body": true,
+ "S-Proposed-SM:e.b.lb_P.P.P-1_SIR-2-div": true,
+ "S-Proposed-SM:e.f.l_BR.BR-2_SI-1-dM": true,
+ "S-Proposed-SM:e.f.l_BR.BR-2_SI-1-body": true,
+ "S-Proposed-SM:e.f.l_BR.BR-2_SI-1-div": true,
+ "A-Proposed-B_TEXT-1_SI-dM": true,
+ "A-Proposed-B_TEXT-1_SI-body": true,
+ "A-Proposed-B_TEXT-1_SI-div": true,
+ "A-Proposed-B_TEXT-1_SIR-dM": true,
+ "A-Proposed-B_TEXT-1_SIR-body": true,
+ "A-Proposed-B_TEXT-1_SIR-div": true,
+ "A-Proposed-B_I-1_SL-dM": true,
+ "A-Proposed-B_I-1_SL-body": true,
+ "A-Proposed-B_I-1_SL-div": true,
+ "A-Proposed-I_TEXT-1_SI-dM": true,
+ "A-Proposed-I_TEXT-1_SI-body": true,
+ "A-Proposed-I_TEXT-1_SI-div": true,
+ "A-Proposed-U_TEXT-1_SI-dM": true,
+ "A-Proposed-U_TEXT-1_SI-body": true,
+ "A-Proposed-U_TEXT-1_SI-div": true,
+ "A-Proposed-S_TEXT-1_SI-dM": true,
+ "A-Proposed-S_TEXT-1_SI-body": true,
+ "A-Proposed-S_TEXT-1_SI-div": true,
+ "A-Proposed-SUB_TEXT-1_SI-dM": true,
+ "A-Proposed-SUB_TEXT-1_SI-body": true,
+ "A-Proposed-SUB_TEXT-1_SI-div": true,
+ "A-Proposed-SUP_TEXT-1_SI-dM": true,
+ "A-Proposed-SUP_TEXT-1_SI-body": true,
+ "A-Proposed-SUP_TEXT-1_SI-div": true,
+ "A-Proposed-CL:url_TEXT-1_SI-dM": true,
+ "A-Proposed-CL:url_TEXT-1_SI-body": true,
+ "A-Proposed-CL:url_TEXT-1_SI-div": true,
+ "A-Proposed-BC:blue_TEXT-1_SI-dM": true,
+ "A-Proposed-BC:blue_TEXT-1_SI-body": true,
+ "A-Proposed-BC:blue_TEXT-1_SI-div": true,
+ "A-Proposed-FC:blue_TEXT-1_SI-dM": true,
+ "A-Proposed-FC:blue_TEXT-1_SI-body": true,
+ "A-Proposed-FC:blue_TEXT-1_SI-div": true,
+ "A-Proposed-HC:blue_TEXT-1_SI-dM": true,
+ "A-Proposed-HC:blue_TEXT-1_SI-body": true,
+ "A-Proposed-HC:blue_TEXT-1_SI-div": true,
+ "A-Proposed-FN:a_TEXT-1_SI-dM": true,
+ "A-Proposed-FN:a_TEXT-1_SI-body": true,
+ "A-Proposed-FN:a_TEXT-1_SI-div": true,
+ "A-Proposed-FS:2_TEXT-1_SI-dM": true,
+ "A-Proposed-FS:2_TEXT-1_SI-body": true,
+ "A-Proposed-FS:2_TEXT-1_SI-div": true,
+ "A-Proposed-FS:18px_TEXT-1_SI-dM": true,
+ "A-Proposed-FS:18px_TEXT-1_SI-body": true,
+ "A-Proposed-FS:18px_TEXT-1_SI-div": true,
+ "A-Proposed-FS:large_TEXT-1_SI-dM": true,
+ "A-Proposed-FS:large_TEXT-1_SI-body": true,
+ "A-Proposed-FS:large_TEXT-1_SI-div": true,
+ "A-Proposed-INCFS:2_TEXT-1_SI-dM": true,
+ "A-Proposed-INCFS:2_TEXT-1_SI-body": true,
+ "A-Proposed-INCFS:2_TEXT-1_SI-div": true,
+ "A-Proposed-DECFS:2_TEXT-1_SI-dM": true,
+ "A-Proposed-DECFS:2_TEXT-1_SI-body": true,
+ "A-Proposed-DECFS:2_TEXT-1_SI-div": true,
+ "A-Proposed-CB:name_TEXT-1_SI-dM": true,
+ "A-Proposed-CB:name_TEXT-1_SI-body": true,
+ "A-Proposed-CB:name_TEXT-1_SI-div": true,
+ "AC-Proposed-B_TEXT-1_SI-dM": true,
+ "AC-Proposed-B_TEXT-1_SI-body": true,
+ "AC-Proposed-B_TEXT-1_SI-div": true,
+ "AC-Proposed-I_TEXT-1_SI-dM": true,
+ "AC-Proposed-I_TEXT-1_SI-body": true,
+ "AC-Proposed-I_TEXT-1_SI-div": true,
+ "AC-Proposed-U_TEXT-1_SI-dM": true,
+ "AC-Proposed-U_TEXT-1_SI-body": true,
+ "AC-Proposed-U_TEXT-1_SI-div": true,
+ "AC-Proposed-S_TEXT-1_SI-dM": true,
+ "AC-Proposed-S_TEXT-1_SI-body": true,
+ "AC-Proposed-S_TEXT-1_SI-div": true,
+ "AC-Proposed-SUB_TEXT-1_SI-dM": true,
+ "AC-Proposed-SUB_TEXT-1_SI-body": true,
+ "AC-Proposed-SUB_TEXT-1_SI-div": true,
+ "AC-Proposed-SUP_TEXT-1_SI-dM": true,
+ "AC-Proposed-SUP_TEXT-1_SI-body": true,
+ "AC-Proposed-SUP_TEXT-1_SI-div": true,
+ "AC-Proposed-BC:blue_TEXT-1_SI-dM": true,
+ "AC-Proposed-BC:blue_TEXT-1_SI-body": true,
+ "AC-Proposed-BC:blue_TEXT-1_SI-div": true,
+ "AC-Proposed-FC:blue_TEXT-1_SI-dM": true,
+ "AC-Proposed-FC:blue_TEXT-1_SI-body": true,
+ "AC-Proposed-FC:blue_TEXT-1_SI-div": true,
+ "AC-Proposed-HC:blue_TEXT-1_SI-dM": true,
+ "AC-Proposed-HC:blue_TEXT-1_SI-body": true,
+ "AC-Proposed-HC:blue_TEXT-1_SI-div": true,
+ "AC-Proposed-FN:a_TEXT-1_SI-dM": true,
+ "AC-Proposed-FN:a_TEXT-1_SI-body": true,
+ "AC-Proposed-FN:a_TEXT-1_SI-div": true,
+ "AC-Proposed-FS:2_TEXT-1_SI-dM": true,
+ "AC-Proposed-FS:2_TEXT-1_SI-body": true,
+ "AC-Proposed-FS:2_TEXT-1_SI-div": true,
+ "AC-Proposed-FS:18px_TEXT-1_SI-dM": true,
+ "AC-Proposed-FS:18px_TEXT-1_SI-body": true,
+ "AC-Proposed-FS:18px_TEXT-1_SI-div": true,
+ "AC-Proposed-FS:large_TEXT-1_SI-dM": true,
+ "AC-Proposed-FS:large_TEXT-1_SI-body": true,
+ "AC-Proposed-FS:large_TEXT-1_SI-div": true,
+ "C-Proposed-I_I-1_SL-dM": true,
+ "C-Proposed-I_I-1_SL-body": true,
+ "C-Proposed-I_I-1_SL-div": true,
+ "C-Proposed-I_B-I-1_SO-dM": true,
+ "C-Proposed-I_B-I-1_SO-body": true,
+ "C-Proposed-I_B-I-1_SO-div": true,
+ "C-Proposed-U_U-1_SO-dM": true,
+ "C-Proposed-U_U-1_SO-body": true,
+ "C-Proposed-U_U-1_SO-div": true,
+ "C-Proposed-U_U-1_SL-dM": true,
+ "C-Proposed-U_U-1_SL-body": true,
+ "C-Proposed-U_U-1_SL-div": true,
+ "C-Proposed-U_S-U-1_SO-dM": true,
+ "C-Proposed-U_S-U-1_SO-body": true,
+ "C-Proposed-U_S-U-1_SO-div": true,
+ "C-Proposed-BC:ace_FONT.ass.s:bc:rgb-1_SW-dM": true,
+ "C-Proposed-BC:ace_FONT.ass.s:bc:rgb-1_SW-body": true,
+ "C-Proposed-BC:ace_FONT.ass.s:bc:rgb-1_SW-div": true,
+ "C-Proposed-FC:g_FONTc:b.sz:6-1_SI-dM": true,
+ "C-Proposed-FC:g_FONTc:b.sz:6-1_SI-body": true,
+ "C-Proposed-FC:g_FONTc:b.sz:6-1_SI-div": true,
+ "C-Proposed-HC:g_SPAN.ass.s:c:rgb-1_SW-dM": true,
+ "C-Proposed-HC:g_SPAN.ass.s:c:rgb-1_SW-body": true,
+ "C-Proposed-HC:g_SPAN.ass.s:c:rgb-1_SW-div": true,
+ "C-Proposed-FN:c_FONTf:a-1_SI-dM": true,
+ "C-Proposed-FN:c_FONTf:a-1_SI-body": true,
+ "C-Proposed-FN:c_FONTf:a-1_SI-div": true,
+ "C-Proposed-FN:c_FONTf:a-2_SL-dM": true,
+ "C-Proposed-FN:c_FONTf:a-2_SL-body": true,
+ "C-Proposed-FN:c_FONTf:a-2_SL-div": true,
+ "C-Proposed-FS:1_SPAN.ass.s:fs:large-1_SW-dM": true,
+ "C-Proposed-FS:1_SPAN.ass.s:fs:large-1_SW-body": true,
+ "C-Proposed-FS:1_SPAN.ass.s:fs:large-1_SW-div": true,
+ "C-Proposed-FS:5_FONTsz:1.s:fs:xs-1_SW-dM": true,
+ "C-Proposed-FS:5_FONTsz:1.s:fs:xs-1_SW-body": true,
+ "C-Proposed-FS:5_FONTsz:1.s:fs:xs-1_SW-div": true,
+ "C-Proposed-FS:2_FONTc:b.sz:6-1_SI-dM": true,
+ "C-Proposed-FS:2_FONTc:b.sz:6-1_SI-body": true,
+ "C-Proposed-FS:2_FONTc:b.sz:6-1_SI-div": true,
+ "C-Proposed-FS:larger_FONTsz:4-dM": true,
+ "C-Proposed-FS:larger_FONTsz:4-body": true,
+ "C-Proposed-FS:larger_FONTsz:4-div": true,
+ "C-Proposed-FS:smaller_FONTsz:4-dM": true,
+ "C-Proposed-FS:smaller_FONTsz:4-body": true,
+ "C-Proposed-FS:smaller_FONTsz:4-div": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SO-dM": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SO-body": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SO-div": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SW-dM": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SW-body": true,
+ "C-Proposed-FB:h1_ADDRESS-FONTsz:4-1_SW-div": true,
+ "C-Proposed-FB:h1_ADDRESS-FONT.ass.sz:4-1_SW-dM": true,
+ "C-Proposed-FB:h1_ADDRESS-FONT.ass.sz:4-1_SW-body": true,
+ "C-Proposed-FB:h1_ADDRESS-FONT.ass.sz:4-1_SW-div": true,
+ "CC-Proposed-I_I-1_SL-dM": true,
+ "CC-Proposed-I_I-1_SL-body": true,
+ "CC-Proposed-I_I-1_SL-div": true,
+ "CC-Proposed-I_B-1_SL-dM": true,
+ "CC-Proposed-I_B-1_SL-body": true,
+ "CC-Proposed-I_B-1_SL-div": true,
+ "CC-Proposed-I_B-1_SW-dM": true,
+ "CC-Proposed-I_B-1_SW-body": true,
+ "CC-Proposed-I_B-1_SW-div": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-1_SI-dM": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-1_SI-body": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-1_SI-div": true,
+ "CC-Proposed-BC:gray_P-SPANs:bc:b-3_SL-dM": true,
+ "CC-Proposed-BC:gray_P-SPANs:bc:b-3_SL-body": true,
+ "CC-Proposed-BC:gray_P-SPANs:bc:b-3_SL-div": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SL-dM": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SL-body": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SL-div": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SR-dM": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SR-body": true,
+ "CC-Proposed-BC:gray_SPANs:bc:b-2_SR-div": true,
+ "CC-Proposed-FN:c_FONTf:a-1_SI-dM": true,
+ "CC-Proposed-FN:c_FONTf:a-1_SI-body": true,
+ "CC-Proposed-FN:c_FONTf:a-1_SI-div": true,
+ "CC-Proposed-FN:c_FONTf:a-2_SL-dM": true,
+ "CC-Proposed-FN:c_FONTf:a-2_SL-body": true,
+ "CC-Proposed-FN:c_FONTf:a-2_SL-div": true,
+ "CC-Proposed-FS:1_SPANs:fs:l-1_SW-dM": true,
+ "CC-Proposed-FS:1_SPANs:fs:l-1_SW-body": true,
+ "CC-Proposed-FS:1_SPANs:fs:l-1_SW-div": true,
+ "CC-Proposed-FS:18px_SPANs:fs:l-1_SW-dM": true,
+ "CC-Proposed-FS:18px_SPANs:fs:l-1_SW-body": true,
+ "CC-Proposed-FS:18px_SPANs:fs:l-1_SW-div": true,
+ "CC-Proposed-FS:4_SPANs:fs:l-1_SW-dM": true,
+ "CC-Proposed-FS:4_SPANs:fs:l-1_SW-body": true,
+ "CC-Proposed-FS:4_SPANs:fs:l-1_SW-div": true,
+ "CC-Proposed-FS:4_SPANs:fs:18px-1_SW-dM": true,
+ "CC-Proposed-FS:4_SPANs:fs:18px-1_SW-body": true,
+ "CC-Proposed-FS:4_SPANs:fs:18px-1_SW-div": true,
+ "CC-Proposed-FS:larger_SPANs:fs:l-1_SI-dM": true,
+ "CC-Proposed-FS:larger_SPANs:fs:l-1_SI-body": true,
+ "CC-Proposed-FS:larger_SPANs:fs:l-1_SI-div": true,
+ "CC-Proposed-FS:smaller_SPANs:fs:l-1_SI-dM": true,
+ "CC-Proposed-FS:smaller_SPANs:fs:l-1_SI-body": true,
+ "CC-Proposed-FS:smaller_SPANs:fs:l-1_SI-div": true,
+ "U-RFC-UNLINK_A-1_SO-dM": true,
+ "U-RFC-UNLINK_A-1_SO-body": true,
+ "U-RFC-UNLINK_A-1_SO-div": true,
+ "U-RFC-UNLINK_A-1_SW-dM": true,
+ "U-RFC-UNLINK_A-1_SW-body": true,
+ "U-RFC-UNLINK_A-1_SW-div": true,
+ "U-RFC-UNLINK_A-2_SO-dM": true,
+ "U-RFC-UNLINK_A-2_SO-body": true,
+ "U-RFC-UNLINK_A-2_SO-div": true,
+ "U-RFC-UNLINK_A2-1_SO-dM": true,
+ "U-RFC-UNLINK_A2-1_SO-body": true,
+ "U-RFC-UNLINK_A2-1_SO-div": true,
+ "U-Proposed-B_B-P3-1_SO12-dM": true,
+ "U-Proposed-B_B-P3-1_SO12-body": true,
+ "U-Proposed-B_B-P3-1_SO12-div": true,
+ "U-Proposed-B_B-P-I..P-1_SO-I-dM": true,
+ "U-Proposed-B_B-P-I..P-1_SO-I-body": true,
+ "U-Proposed-B_B-P-I..P-1_SO-I-div": true,
+ "U-Proposed-B_B-2_SL-dM": true,
+ "U-Proposed-B_B-2_SL-body": true,
+ "U-Proposed-B_B-2_SL-div": true,
+ "U-Proposed-B_B-2_SR-dM": true,
+ "U-Proposed-B_B-2_SR-body": true,
+ "U-Proposed-B_B-2_SR-div": true,
+ "U-Proposed-I_I-P3-1_SO2-dM": true,
+ "U-Proposed-I_I-P3-1_SO2-body": true,
+ "U-Proposed-I_I-P3-1_SO2-div": true,
+ "U-Proposed-U_U-S-1_SO-dM": true,
+ "U-Proposed-U_U-S-1_SO-body": true,
+ "U-Proposed-U_U-S-1_SO-div": true,
+ "U-Proposed-U_U-S-2_SI-dM": true,
+ "U-Proposed-U_U-S-2_SI-body": true,
+ "U-Proposed-U_U-S-2_SI-div": true,
+ "U-Proposed-U_U-P3-1_SO-dM": true,
+ "U-Proposed-U_U-P3-1_SO-body": true,
+ "U-Proposed-U_U-P3-1_SO-div": true,
+ "U-Proposed-S_DEL-1_SW-dM": true,
+ "U-Proposed-S_DEL-1_SW-body": true,
+ "U-Proposed-S_DEL-1_SW-div": true,
+ "U-Proposed-S_S-U-1_SI-dM": true,
+ "U-Proposed-S_S-U-1_SI-body": true,
+ "U-Proposed-S_S-U-1_SI-div": true,
+ "U-Proposed-S_U-S-1_SI-dM": true,
+ "U-Proposed-S_U-S-1_SI-body": true,
+ "U-Proposed-S_U-S-1_SI-div": true,
+ "U-Proposed-SUB_SPANs:va:sub-1_SW-dM": true,
+ "U-Proposed-SUB_SPANs:va:sub-1_SW-body": true,
+ "U-Proposed-SUB_SPANs:va:sub-1_SW-div": true,
+ "U-Proposed-SUP_SPANs:va:super-1_SW-dM": true,
+ "U-Proposed-SUP_SPANs:va:super-1_SW-body": true,
+ "U-Proposed-SUP_SPANs:va:super-1_SW-div": true,
+ "U-Proposed-UNLINK_A-1_SC-dM": true,
+ "U-Proposed-UNLINK_A-1_SC-body": true,
+ "U-Proposed-UNLINK_A-1_SC-div": true,
+ "U-Proposed-UNLINK_A-1_SI-dM": true,
+ "U-Proposed-UNLINK_A-1_SI-body": true,
+ "U-Proposed-UNLINK_A-1_SI-div": true,
+ "U-Proposed-UNLINK_A-2_SL-dM": true,
+ "U-Proposed-UNLINK_A-2_SL-body": true,
+ "U-Proposed-UNLINK_A-2_SL-div": true,
+ "U-Proposed-UNLINK_A-3_SR-dM": true,
+ "U-Proposed-UNLINK_A-3_SR-body": true,
+ "U-Proposed-UNLINK_A-3_SR-div": true,
+ "U-Proposed-OUTDENT_DIV-1_SW-dM": true,
+ "U-Proposed-OUTDENT_DIV-1_SW-body": true,
+ "U-Proposed-OUTDENT_DIV-1_SW-div": true,
+ "U-Proposed-REMOVEFORMAT_Ahref:url-1_SW-dM": true,
+ "U-Proposed-REMOVEFORMAT_Ahref:url-1_SW-body": true,
+ "U-Proposed-REMOVEFORMAT_Ahref:url-1_SW-div": true,
+ "U-Proposed-REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW-dM": true,
+ "U-Proposed-REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW-body": true,
+ "U-Proposed-REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW-div": true,
+ "U-Proposed-UNBOOKMARK_An:name-1_SW-dM": true,
+ "U-Proposed-UNBOOKMARK_An:name-1_SW-body": true,
+ "U-Proposed-UNBOOKMARK_An:name-1_SW-div": true,
+ "UC-Proposed-S_SPANc:s-1_SW-dM": true,
+ "UC-Proposed-S_SPANc:s-1_SW-body": true,
+ "UC-Proposed-S_SPANc:s-1_SW-div": true,
+ "UC-Proposed-S_SPANc:s-2_SI-dM": true,
+ "UC-Proposed-S_SPANc:s-2_SI-body": true,
+ "UC-Proposed-S_SPANc:s-2_SI-div": true,
+ "D-Proposed-CHAR-3_SC-dM": true,
+ "D-Proposed-CHAR-3_SC-body": true,
+ "D-Proposed-CHAR-3_SC-div": true,
+ "D-Proposed-CHAR-4_SC-dM": true,
+ "D-Proposed-CHAR-4_SC-body": true,
+ "D-Proposed-CHAR-4_SC-div": true,
+ "D-Proposed-CHAR-5_SC-dM": true,
+ "D-Proposed-CHAR-5_SC-body": true,
+ "D-Proposed-CHAR-5_SC-div": true,
+ "D-Proposed-CHAR-5_SI-1-dM": true,
+ "D-Proposed-CHAR-5_SI-1-body": true,
+ "D-Proposed-CHAR-5_SI-1-div": true,
+ "D-Proposed-CHAR-5_SI-2-dM": true,
+ "D-Proposed-CHAR-5_SI-2-body": true,
+ "D-Proposed-CHAR-5_SI-2-div": true,
+ "D-Proposed-CHAR-5_SR-dM": true,
+ "D-Proposed-CHAR-5_SR-body": true,
+ "D-Proposed-CHAR-5_SR-div": true,
+ "D-Proposed-CHAR-6_SC-dM": true,
+ "D-Proposed-CHAR-6_SC-body": true,
+ "D-Proposed-CHAR-6_SC-div": true,
+ "D-Proposed-CHAR-7_SC-dM": true,
+ "D-Proposed-CHAR-7_SC-body": true,
+ "D-Proposed-CHAR-7_SC-div": true,
+ "D-Proposed-B-1_SW-div": true,
+ "D-Proposed-B-1_SL-dM": true,
+ "D-Proposed-B-1_SL-body": true,
+ "D-Proposed-B-1_SL-div": true,
+ "D-Proposed-B-1_SR-dM": true,
+ "D-Proposed-B-1_SR-body": true,
+ "D-Proposed-B-1_SR-div": true,
+ "D-Proposed-B.I-1_SM-dM": true,
+ "D-Proposed-B.I-1_SM-body": true,
+ "D-Proposed-B.I-1_SM-div": true,
+ "D-Proposed-OL-LI2-1_SO1-dM": true,
+ "D-Proposed-OL-LI2-1_SO1-body": true,
+ "D-Proposed-OL-LI2-1_SO1-div": true,
+ "D-Proposed-OL-LI-1_SW-dM": true,
+ "D-Proposed-OL-LI-1_SW-body": true,
+ "D-Proposed-OL-LI-1_SW-div": true,
+ "D-Proposed-OL-LI-1_SO-dM": true,
+ "D-Proposed-OL-LI-1_SO-body": true,
+ "D-Proposed-OL-LI-1_SO-div": true,
+ "D-Proposed-HR.BR-1_SM-dM": true,
+ "D-Proposed-HR.BR-1_SM-body": true,
+ "D-Proposed-HR.BR-1_SM-div": true,
+ "D-Proposed-TR2rs:2-1_SO1-dM": true,
+ "D-Proposed-TR2rs:2-1_SO1-body": true,
+ "D-Proposed-TR2rs:2-1_SO1-div": true,
+ "D-Proposed-TR2rs:2-1_SO2-dM": true,
+ "D-Proposed-TR2rs:2-1_SO2-body": true,
+ "D-Proposed-TR2rs:2-1_SO2-div": true,
+ "D-Proposed-TR3rs:3-1_SO1-dM": true,
+ "D-Proposed-TR3rs:3-1_SO1-body": true,
+ "D-Proposed-TR3rs:3-1_SO1-div": true,
+ "D-Proposed-TR3rs:3-1_SO2-dM": true,
+ "D-Proposed-TR3rs:3-1_SO2-body": true,
+ "D-Proposed-TR3rs:3-1_SO2-div": true,
+ "D-Proposed-TR3rs:3-1_SO3-dM": true,
+ "D-Proposed-TR3rs:3-1_SO3-body": true,
+ "D-Proposed-TR3rs:3-1_SO3-div": true,
+ "D-Proposed-DIV:ce:false-1_SB-dM": true,
+ "D-Proposed-DIV:ce:false-1_SB-body": true,
+ "D-Proposed-DIV:ce:false-1_SB-div": true,
+ "D-Proposed-DIV:ce:false-1_SL-dM": true,
+ "D-Proposed-DIV:ce:false-1_SL-body": true,
+ "D-Proposed-DIV:ce:false-1_SL-div": true,
+ "D-Proposed-DIV:ce:false-1_SR-dM": true,
+ "D-Proposed-DIV:ce:false-1_SR-body": true,
+ "D-Proposed-DIV:ce:false-1_SR-div": true,
+ "D-Proposed-DIV:ce:false-1_SI-dM": true,
+ "D-Proposed-SPAN:d:ib-2_SL-dM": true,
+ "D-Proposed-SPAN:d:ib-2_SL-body": true,
+ "D-Proposed-SPAN:d:ib-2_SL-div": true,
+ "D-Proposed-SPAN:d:ib-3_SR-dM": true,
+ "D-Proposed-SPAN:d:ib-3_SR-body": true,
+ "D-Proposed-SPAN:d:ib-3_SR-div": true,
+ "FD-Proposed-B-1_SW-div": true,
+ "FD-Proposed-OL-LI-1_SW-dM": true,
+ "FD-Proposed-OL-LI-1_SW-body": true,
+ "FD-Proposed-OL-LI-1_SW-div": true,
+ "FD-Proposed-OL-LI-1_SO-dM": true,
+ "FD-Proposed-OL-LI-1_SO-body": true,
+ "FD-Proposed-OL-LI-1_SO-div": true,
+ "FD-Proposed-TABLE-1_SB-dM": true,
+ "FD-Proposed-TABLE-1_SB-body": true,
+ "FD-Proposed-TABLE-1_SB-div": true,
+ "FD-Proposed-TD-1_SE-dM": true,
+ "FD-Proposed-TD-1_SE-body": true,
+ "FD-Proposed-TD-1_SE-div": true,
+ "FD-Proposed-TD2-1_SE1-dM": true,
+ "FD-Proposed-TD2-1_SE1-body": true,
+ "FD-Proposed-TD2-1_SE1-div": true,
+ "FD-Proposed-TD2-1_SM-dM": true,
+ "FD-Proposed-TD2-1_SM-body": true,
+ "FD-Proposed-TD2-1_SM-div": true,
+ "FD-Proposed-TR2rs:2-1_SO1-dM": true,
+ "FD-Proposed-TR2rs:2-1_SO1-body": true,
+ "FD-Proposed-TR2rs:2-1_SO1-div": true,
+ "FD-Proposed-TR2rs:2-1_SO2-dM": true,
+ "FD-Proposed-TR2rs:2-1_SO2-body": true,
+ "FD-Proposed-TR2rs:2-1_SO2-div": true,
+ "FD-Proposed-TR3rs:3-1_SO1-dM": true,
+ "FD-Proposed-TR3rs:3-1_SO1-body": true,
+ "FD-Proposed-TR3rs:3-1_SO1-div": true,
+ "FD-Proposed-TR3rs:3-1_SO2-dM": true,
+ "FD-Proposed-TR3rs:3-1_SO2-body": true,
+ "FD-Proposed-TR3rs:3-1_SO2-div": true,
+ "FD-Proposed-TR3rs:3-1_SO3-dM": true,
+ "FD-Proposed-TR3rs:3-1_SO3-body": true,
+ "FD-Proposed-TR3rs:3-1_SO3-div": true,
+ "FD-Proposed-DIV:ce:false-1_SB-dM": true,
+ "FD-Proposed-DIV:ce:false-1_SB-body": true,
+ "FD-Proposed-DIV:ce:false-1_SB-div": true,
+ "FD-Proposed-DIV:ce:false-1_SL-dM": true,
+ "FD-Proposed-DIV:ce:false-1_SL-body": true,
+ "FD-Proposed-DIV:ce:false-1_SL-div": true,
+ "FD-Proposed-DIV:ce:false-1_SR-dM": true,
+ "FD-Proposed-DIV:ce:false-1_SR-body": true,
+ "FD-Proposed-DIV:ce:false-1_SR-div": true,
+ "FD-Proposed-DIV:ce:false-1_SI-dM": true,
+ "I-Proposed-IHR_TEXT-1_SC-dM": true,
+ "I-Proposed-IHR_TEXT-1_SC-body": true,
+ "I-Proposed-IHR_TEXT-1_SC-div": true,
+ "I-Proposed-IHR_TEXT-1_SI-dM": true,
+ "I-Proposed-IHR_TEXT-1_SI-body": true,
+ "I-Proposed-IHR_TEXT-1_SI-div": true,
+ "I-Proposed-IHR_B-1_SC-dM": true,
+ "I-Proposed-IHR_B-1_SC-body": true,
+ "I-Proposed-IHR_B-1_SC-div": true,
+ "I-Proposed-IHR_B-1_SS-dM": true,
+ "I-Proposed-IHR_B-1_SS-body": true,
+ "I-Proposed-IHR_B-1_SS-div": true,
+ "I-Proposed-IHR_B-I-1_SMR-dM": true,
+ "I-Proposed-IHR_B-I-1_SMR-body": true,
+ "I-Proposed-IHR_B-I-1_SMR-div": true,
+ "I-Proposed-IBR_LI-1_SC-dM": true,
+ "I-Proposed-IBR_LI-1_SC-body": true,
+ "I-Proposed-IBR_LI-1_SC-div": true,
+ "I-Proposed-IIMG:._SPAN-IMG-1_SO-dM": true,
+ "I-Proposed-IIMG:._SPAN-IMG-1_SO-body": true,
+ "I-Proposed-IIMG:._SPAN-IMG-1_SO-div": true,
+ "I-Proposed-IIMG:._IMG-1_SO-dM": true,
+ "I-Proposed-IIMG:._IMG-1_SO-body": true,
+ "I-Proposed-IIMG:._IMG-1_SO-div": true,
+ "I-Proposed-IHTML:BR_TEXT-1_SC-dM": true,
+ "I-Proposed-IHTML:BR_TEXT-1_SC-body": true,
+ "I-Proposed-IHTML:BR_TEXT-1_SC-div": true,
+ "I-Proposed-IHTML:S_TEXT-1_SI-dM": true,
+ "I-Proposed-IHTML:S_TEXT-1_SI-body": true,
+ "I-Proposed-IHTML:S_TEXT-1_SI-div": true,
+ "I-Proposed-IHTML:H1.H2_TEXT-1_SI-dM": true,
+ "I-Proposed-IHTML:H1.H2_TEXT-1_SI-body": true,
+ "I-Proposed-IHTML:H1.H2_TEXT-1_SI-div": true,
+ "I-Proposed-IHTML:P-B_TEXT-1_SI-dM": true,
+ "I-Proposed-IHTML:P-B_TEXT-1_SI-body": true,
+ "I-Proposed-IHTML:P-B_TEXT-1_SI-div": true,
+ "Q-Proposed-SELECTALL_TEXT-1-dM": true,
+ "Q-Proposed-SELECTALL_TEXT-1-body": true,
+ "Q-Proposed-SELECTALL_TEXT-1-div": true,
+ "Q-Proposed-UNSELECT_TEXT-1-dM": true,
+ "Q-Proposed-UNSELECT_TEXT-1-body": true,
+ "Q-Proposed-UNSELECT_TEXT-1-div": true,
+ "Q-Proposed-UNDO_TEXT-1-dM": true,
+ "Q-Proposed-UNDO_TEXT-1-body": true,
+ "Q-Proposed-UNDO_TEXT-1-div": true,
+ "Q-Proposed-REDO_TEXT-1-dM": true,
+ "Q-Proposed-REDO_TEXT-1-body": true,
+ "Q-Proposed-REDO_TEXT-1-div": true,
+ "Q-Proposed-BOLD_TEXT-1-dM": true,
+ "Q-Proposed-BOLD_TEXT-1-body": true,
+ "Q-Proposed-BOLD_TEXT-1-div": true,
+ "Q-Proposed-BOLD_B-dM": true,
+ "Q-Proposed-BOLD_B-body": true,
+ "Q-Proposed-BOLD_B-div": true,
+ "Q-Proposed-ITALIC_TEXT-1-dM": true,
+ "Q-Proposed-ITALIC_TEXT-1-body": true,
+ "Q-Proposed-ITALIC_TEXT-1-div": true,
+ "Q-Proposed-ITALIC_I-dM": true,
+ "Q-Proposed-ITALIC_I-body": true,
+ "Q-Proposed-ITALIC_I-div": true,
+ "Q-Proposed-UNDERLINE_TEXT-1-dM": true,
+ "Q-Proposed-UNDERLINE_TEXT-1-body": true,
+ "Q-Proposed-UNDERLINE_TEXT-1-div": true,
+ "Q-Proposed-STRIKETHROUGH_TEXT-1-dM": true,
+ "Q-Proposed-STRIKETHROUGH_TEXT-1-body": true,
+ "Q-Proposed-STRIKETHROUGH_TEXT-1-div": true,
+ "Q-Proposed-SUBSCRIPT_TEXT-1-dM": true,
+ "Q-Proposed-SUBSCRIPT_TEXT-1-body": true,
+ "Q-Proposed-SUBSCRIPT_TEXT-1-div": true,
+ "Q-Proposed-SUPERSCRIPT_TEXT-1-dM": true,
+ "Q-Proposed-SUPERSCRIPT_TEXT-1-body": true,
+ "Q-Proposed-SUPERSCRIPT_TEXT-1-div": true,
+ "Q-Proposed-FORMATBLOCK_TEXT-1-dM": true,
+ "Q-Proposed-FORMATBLOCK_TEXT-1-body": true,
+ "Q-Proposed-FORMATBLOCK_TEXT-1-div": true,
+ "Q-Proposed-CREATELINK_TEXT-1-dM": true,
+ "Q-Proposed-CREATELINK_TEXT-1-body": true,
+ "Q-Proposed-CREATELINK_TEXT-1-div": true,
+ "Q-Proposed-UNLINK_TEXT-1-dM": true,
+ "Q-Proposed-UNLINK_TEXT-1-body": true,
+ "Q-Proposed-UNLINK_TEXT-1-div": true,
+ "Q-Proposed-INSERTHTML_TEXT-1-dM": true,
+ "Q-Proposed-INSERTHTML_TEXT-1-body": true,
+ "Q-Proposed-INSERTHTML_TEXT-1-div": true,
+ "Q-Proposed-INSERTHORIZONTALRULE_TEXT-1-dM": true,
+ "Q-Proposed-INSERTHORIZONTALRULE_TEXT-1-body": true,
+ "Q-Proposed-INSERTHORIZONTALRULE_TEXT-1-div": true,
+ "Q-Proposed-INSERTIMAGE_TEXT-1-dM": true,
+ "Q-Proposed-INSERTIMAGE_TEXT-1-body": true,
+ "Q-Proposed-INSERTIMAGE_TEXT-1-div": true,
+ "Q-Proposed-INSERTLINEBREAK_TEXT-1-dM": true,
+ "Q-Proposed-INSERTLINEBREAK_TEXT-1-body": true,
+ "Q-Proposed-INSERTLINEBREAK_TEXT-1-div": true,
+ "Q-Proposed-INSERTPARAGRAPH_TEXT-1-dM": true,
+ "Q-Proposed-INSERTPARAGRAPH_TEXT-1-body": true,
+ "Q-Proposed-INSERTPARAGRAPH_TEXT-1-div": true,
+ "Q-Proposed-INSERTORDEREDLIST_TEXT-1-dM": true,
+ "Q-Proposed-INSERTORDEREDLIST_TEXT-1-body": true,
+ "Q-Proposed-INSERTORDEREDLIST_TEXT-1-div": true,
+ "Q-Proposed-INSERTUNORDEREDLIST_TEXT-1-dM": true,
+ "Q-Proposed-INSERTUNORDEREDLIST_TEXT-1-body": true,
+ "Q-Proposed-INSERTUNORDEREDLIST_TEXT-1-div": true,
+ "Q-Proposed-INSERTTEXT_TEXT-1-dM": true,
+ "Q-Proposed-INSERTTEXT_TEXT-1-body": true,
+ "Q-Proposed-INSERTTEXT_TEXT-1-div": true,
+ "Q-Proposed-DELETE_TEXT-1-dM": true,
+ "Q-Proposed-DELETE_TEXT-1-body": true,
+ "Q-Proposed-DELETE_TEXT-1-div": true,
+ "Q-Proposed-FORWARDDELETE_TEXT-1-dM": true,
+ "Q-Proposed-FORWARDDELETE_TEXT-1-body": true,
+ "Q-Proposed-FORWARDDELETE_TEXT-1-div": true,
+ "Q-Proposed-STYLEWITHCSS_TEXT-1-dM": true,
+ "Q-Proposed-STYLEWITHCSS_TEXT-1-body": true,
+ "Q-Proposed-STYLEWITHCSS_TEXT-1-div": true,
+ "Q-Proposed-CONTENTREADONLY_TEXT-1-dM": true,
+ "Q-Proposed-CONTENTREADONLY_TEXT-1-body": true,
+ "Q-Proposed-CONTENTREADONLY_TEXT-1-div": true,
+ "Q-Proposed-BACKCOLOR_TEXT-1-dM": true,
+ "Q-Proposed-BACKCOLOR_TEXT-1-body": true,
+ "Q-Proposed-BACKCOLOR_TEXT-1-div": true,
+ "Q-Proposed-FORECOLOR_TEXT-1-dM": true,
+ "Q-Proposed-FORECOLOR_TEXT-1-body": true,
+ "Q-Proposed-FORECOLOR_TEXT-1-div": true,
+ "Q-Proposed-HILITECOLOR_TEXT-1-dM": true,
+ "Q-Proposed-HILITECOLOR_TEXT-1-body": true,
+ "Q-Proposed-HILITECOLOR_TEXT-1-div": true,
+ "Q-Proposed-FONTNAME_TEXT-1-dM": true,
+ "Q-Proposed-FONTNAME_TEXT-1-body": true,
+ "Q-Proposed-FONTNAME_TEXT-1-div": true,
+ "Q-Proposed-FONTSIZE_TEXT-1-dM": true,
+ "Q-Proposed-FONTSIZE_TEXT-1-body": true,
+ "Q-Proposed-FONTSIZE_TEXT-1-div": true,
+ "Q-Proposed-INCREASEFONTSIZE_TEXT-1-dM": true,
+ "Q-Proposed-INCREASEFONTSIZE_TEXT-1-body": true,
+ "Q-Proposed-INCREASEFONTSIZE_TEXT-1-div": true,
+ "Q-Proposed-DECREASEFONTSIZE_TEXT-1-dM": true,
+ "Q-Proposed-DECREASEFONTSIZE_TEXT-1-body": true,
+ "Q-Proposed-DECREASEFONTSIZE_TEXT-1-div": true,
+ "Q-Proposed-HEADING_TEXT-1-dM": true,
+ "Q-Proposed-HEADING_TEXT-1-body": true,
+ "Q-Proposed-HEADING_TEXT-1-div": true,
+ "Q-Proposed-INDENT_TEXT-1-dM": true,
+ "Q-Proposed-INDENT_TEXT-1-body": true,
+ "Q-Proposed-INDENT_TEXT-1-div": true,
+ "Q-Proposed-OUTDENT_TEXT-1-dM": true,
+ "Q-Proposed-OUTDENT_TEXT-1-body": true,
+ "Q-Proposed-OUTDENT_TEXT-1-div": true,
+ "Q-Proposed-CREATEBOOKMARK_TEXT-1-dM": true,
+ "Q-Proposed-CREATEBOOKMARK_TEXT-1-body": true,
+ "Q-Proposed-CREATEBOOKMARK_TEXT-1-div": true,
+ "Q-Proposed-UNBOOKMARK_TEXT-1-dM": true,
+ "Q-Proposed-UNBOOKMARK_TEXT-1-body": true,
+ "Q-Proposed-UNBOOKMARK_TEXT-1-div": true,
+ "Q-Proposed-JUSTIFYCENTER_TEXT-1-dM": true,
+ "Q-Proposed-JUSTIFYCENTER_TEXT-1-body": true,
+ "Q-Proposed-JUSTIFYCENTER_TEXT-1-div": true,
+ "Q-Proposed-JUSTIFYFULL_TEXT-1-dM": true,
+ "Q-Proposed-JUSTIFYFULL_TEXT-1-body": true,
+ "Q-Proposed-JUSTIFYFULL_TEXT-1-div": true,
+ "Q-Proposed-JUSTIFYLEFT_TEXT-1-dM": true,
+ "Q-Proposed-JUSTIFYLEFT_TEXT-1-body": true,
+ "Q-Proposed-JUSTIFYLEFT_TEXT-1-div": true,
+ "Q-Proposed-JUSTIFYRIGHT_TEXT-1-dM": true,
+ "Q-Proposed-JUSTIFYRIGHT_TEXT-1-body": true,
+ "Q-Proposed-JUSTIFYRIGHT_TEXT-1-div": true,
+ "Q-Proposed-REMOVEFORMAT_TEXT-1-dM": true,
+ "Q-Proposed-REMOVEFORMAT_TEXT-1-body": true,
+ "Q-Proposed-REMOVEFORMAT_TEXT-1-div": true,
+ "Q-Proposed-COPY_TEXT-1-dM": true,
+ "Q-Proposed-COPY_TEXT-1-body": true,
+ "Q-Proposed-COPY_TEXT-1-div": true,
+ "Q-Proposed-CUT_TEXT-1-dM": true,
+ "Q-Proposed-CUT_TEXT-1-body": true,
+ "Q-Proposed-CUT_TEXT-1-div": true,
+ "Q-Proposed-PASTE_TEXT-1-dM": true,
+ "Q-Proposed-PASTE_TEXT-1-body": true,
+ "Q-Proposed-PASTE_TEXT-1-div": true,
+ "Q-Proposed-garbage-1_TEXT-1-dM": true,
+ "Q-Proposed-garbage-1_TEXT-1-body": true,
+ "Q-Proposed-garbage-1_TEXT-1-div": true,
+ "QE-Proposed-SELECTALL_TEXT-1-dM": true,
+ "QE-Proposed-SELECTALL_TEXT-1-body": true,
+ "QE-Proposed-SELECTALL_TEXT-1-div": true,
+ "QE-Proposed-UNSELECT_TEXT-1-dM": true,
+ "QE-Proposed-UNSELECT_TEXT-1-body": true,
+ "QE-Proposed-UNSELECT_TEXT-1-div": true,
+ "QE-Proposed-UNDO_TEXT-1-dM": true,
+ "QE-Proposed-UNDO_TEXT-1-body": true,
+ "QE-Proposed-UNDO_TEXT-1-div": true,
+ "QE-Proposed-REDO_TEXT-1-dM": true,
+ "QE-Proposed-REDO_TEXT-1-body": true,
+ "QE-Proposed-REDO_TEXT-1-div": true,
+ "QE-Proposed-BOLD_TEXT-1-dM": true,
+ "QE-Proposed-BOLD_TEXT-1-body": true,
+ "QE-Proposed-BOLD_TEXT-1-div": true,
+ "QE-Proposed-ITALIC_TEXT-1-dM": true,
+ "QE-Proposed-ITALIC_TEXT-1-body": true,
+ "QE-Proposed-ITALIC_TEXT-1-div": true,
+ "QE-Proposed-UNDERLINE_TEXT-1-dM": true,
+ "QE-Proposed-UNDERLINE_TEXT-1-body": true,
+ "QE-Proposed-UNDERLINE_TEXT-1-div": true,
+ "QE-Proposed-STRIKETHROUGH_TEXT-1-dM": true,
+ "QE-Proposed-STRIKETHROUGH_TEXT-1-body": true,
+ "QE-Proposed-STRIKETHROUGH_TEXT-1-div": true,
+ "QE-Proposed-SUBSCRIPT_TEXT-1-dM": true,
+ "QE-Proposed-SUBSCRIPT_TEXT-1-body": true,
+ "QE-Proposed-SUBSCRIPT_TEXT-1-div": true,
+ "QE-Proposed-SUPERSCRIPT_TEXT-1-dM": true,
+ "QE-Proposed-SUPERSCRIPT_TEXT-1-body": true,
+ "QE-Proposed-SUPERSCRIPT_TEXT-1-div": true,
+ "QE-Proposed-FORMATBLOCK_TEXT-1-dM": true,
+ "QE-Proposed-FORMATBLOCK_TEXT-1-body": true,
+ "QE-Proposed-FORMATBLOCK_TEXT-1-div": true,
+ "QE-Proposed-CREATELINK_TEXT-1-dM": true,
+ "QE-Proposed-CREATELINK_TEXT-1-body": true,
+ "QE-Proposed-CREATELINK_TEXT-1-div": true,
+ "QE-Proposed-UNLINK_TEXT-1-dM": true,
+ "QE-Proposed-UNLINK_TEXT-1-body": true,
+ "QE-Proposed-UNLINK_TEXT-1-div": true,
+ "QE-Proposed-INSERTHTML_TEXT-1-dM": true,
+ "QE-Proposed-INSERTHTML_TEXT-1-body": true,
+ "QE-Proposed-INSERTHTML_TEXT-1-div": true,
+ "QE-Proposed-INSERTHORIZONTALRULE_TEXT-1-dM": true,
+ "QE-Proposed-INSERTHORIZONTALRULE_TEXT-1-body": true,
+ "QE-Proposed-INSERTHORIZONTALRULE_TEXT-1-div": true,
+ "QE-Proposed-INSERTIMAGE_TEXT-1-dM": true,
+ "QE-Proposed-INSERTIMAGE_TEXT-1-body": true,
+ "QE-Proposed-INSERTIMAGE_TEXT-1-div": true,
+ "QE-Proposed-INSERTLINEBREAK_TEXT-1-dM": true,
+ "QE-Proposed-INSERTLINEBREAK_TEXT-1-body": true,
+ "QE-Proposed-INSERTLINEBREAK_TEXT-1-div": true,
+ "QE-Proposed-INSERTPARAGRAPH_TEXT-1-dM": true,
+ "QE-Proposed-INSERTPARAGRAPH_TEXT-1-body": true,
+ "QE-Proposed-INSERTPARAGRAPH_TEXT-1-div": true,
+ "QE-Proposed-INSERTORDEREDLIST_TEXT-1-dM": true,
+ "QE-Proposed-INSERTORDEREDLIST_TEXT-1-body": true,
+ "QE-Proposed-INSERTORDEREDLIST_TEXT-1-div": true,
+ "QE-Proposed-INSERTUNORDEREDLIST_TEXT-1-dM": true,
+ "QE-Proposed-INSERTUNORDEREDLIST_TEXT-1-body": true,
+ "QE-Proposed-INSERTUNORDEREDLIST_TEXT-1-div": true,
+ "QE-Proposed-INSERTTEXT_TEXT-1-dM": true,
+ "QE-Proposed-INSERTTEXT_TEXT-1-body": true,
+ "QE-Proposed-INSERTTEXT_TEXT-1-div": true,
+ "QE-Proposed-DELETE_TEXT-1-dM": true,
+ "QE-Proposed-DELETE_TEXT-1-body": true,
+ "QE-Proposed-DELETE_TEXT-1-div": true,
+ "QE-Proposed-FORWARDDELETE_TEXT-1-dM": true,
+ "QE-Proposed-FORWARDDELETE_TEXT-1-body": true,
+ "QE-Proposed-FORWARDDELETE_TEXT-1-div": true,
+ "QE-Proposed-STYLEWITHCSS_TEXT-1-dM": true,
+ "QE-Proposed-STYLEWITHCSS_TEXT-1-body": true,
+ "QE-Proposed-STYLEWITHCSS_TEXT-1-div": true,
+ "QE-Proposed-CONTENTREADONLY_TEXT-1-dM": true,
+ "QE-Proposed-CONTENTREADONLY_TEXT-1-body": true,
+ "QE-Proposed-CONTENTREADONLY_TEXT-1-div": true,
+ "QE-Proposed-BACKCOLOR_TEXT-1-dM": true,
+ "QE-Proposed-BACKCOLOR_TEXT-1-body": true,
+ "QE-Proposed-BACKCOLOR_TEXT-1-div": true,
+ "QE-Proposed-FORECOLOR_TEXT-1-dM": true,
+ "QE-Proposed-FORECOLOR_TEXT-1-body": true,
+ "QE-Proposed-FORECOLOR_TEXT-1-div": true,
+ "QE-Proposed-HILITECOLOR_TEXT-1-dM": true,
+ "QE-Proposed-HILITECOLOR_TEXT-1-body": true,
+ "QE-Proposed-HILITECOLOR_TEXT-1-div": true,
+ "QE-Proposed-FONTNAME_TEXT-1-dM": true,
+ "QE-Proposed-FONTNAME_TEXT-1-body": true,
+ "QE-Proposed-FONTNAME_TEXT-1-div": true,
+ "QE-Proposed-FONTSIZE_TEXT-1-dM": true,
+ "QE-Proposed-FONTSIZE_TEXT-1-body": true,
+ "QE-Proposed-FONTSIZE_TEXT-1-div": true,
+ "QE-Proposed-INCREASEFONTSIZE_TEXT-1-dM": true,
+ "QE-Proposed-INCREASEFONTSIZE_TEXT-1-body": true,
+ "QE-Proposed-INCREASEFONTSIZE_TEXT-1-div": true,
+ "QE-Proposed-DECREASEFONTSIZE_TEXT-1-dM": true,
+ "QE-Proposed-DECREASEFONTSIZE_TEXT-1-body": true,
+ "QE-Proposed-DECREASEFONTSIZE_TEXT-1-div": true,
+ "QE-Proposed-HEADING_TEXT-1-dM": true,
+ "QE-Proposed-HEADING_TEXT-1-body": true,
+ "QE-Proposed-HEADING_TEXT-1-div": true,
+ "QE-Proposed-INDENT_TEXT-1-dM": true,
+ "QE-Proposed-INDENT_TEXT-1-body": true,
+ "QE-Proposed-INDENT_TEXT-1-div": true,
+ "QE-Proposed-OUTDENT_TEXT-1-dM": true,
+ "QE-Proposed-OUTDENT_TEXT-1-body": true,
+ "QE-Proposed-OUTDENT_TEXT-1-div": true,
+ "QE-Proposed-CREATEBOOKMARK_TEXT-1-dM": true,
+ "QE-Proposed-CREATEBOOKMARK_TEXT-1-body": true,
+ "QE-Proposed-CREATEBOOKMARK_TEXT-1-div": true,
+ "QE-Proposed-UNBOOKMARK_TEXT-1-dM": true,
+ "QE-Proposed-UNBOOKMARK_TEXT-1-body": true,
+ "QE-Proposed-UNBOOKMARK_TEXT-1-div": true,
+ "QE-Proposed-JUSTIFYCENTER_TEXT-1-dM": true,
+ "QE-Proposed-JUSTIFYCENTER_TEXT-1-body": true,
+ "QE-Proposed-JUSTIFYCENTER_TEXT-1-div": true,
+ "QE-Proposed-JUSTIFYFULL_TEXT-1-dM": true,
+ "QE-Proposed-JUSTIFYFULL_TEXT-1-body": true,
+ "QE-Proposed-JUSTIFYFULL_TEXT-1-div": true,
+ "QE-Proposed-JUSTIFYLEFT_TEXT-1-dM": true,
+ "QE-Proposed-JUSTIFYLEFT_TEXT-1-body": true,
+ "QE-Proposed-JUSTIFYLEFT_TEXT-1-div": true,
+ "QE-Proposed-JUSTIFYRIGHT_TEXT-1-dM": true,
+ "QE-Proposed-JUSTIFYRIGHT_TEXT-1-body": true,
+ "QE-Proposed-JUSTIFYRIGHT_TEXT-1-div": true,
+ "QE-Proposed-REMOVEFORMAT_TEXT-1-dM": true,
+ "QE-Proposed-REMOVEFORMAT_TEXT-1-body": true,
+ "QE-Proposed-REMOVEFORMAT_TEXT-1-div": true,
+ "QE-Proposed-COPY_TEXT-1-dM": true,
+ "QE-Proposed-COPY_TEXT-1-body": true,
+ "QE-Proposed-COPY_TEXT-1-div": true,
+ "QE-Proposed-CUT_TEXT-1-dM": true,
+ "QE-Proposed-CUT_TEXT-1-body": true,
+ "QE-Proposed-CUT_TEXT-1-div": true,
+ "QE-Proposed-PASTE_TEXT-1-dM": true,
+ "QE-Proposed-PASTE_TEXT-1-body": true,
+ "QE-Proposed-PASTE_TEXT-1-div": true,
+ "QE-Proposed-garbage-1_TEXT-1-dM": true,
+ "QE-Proposed-garbage-1_TEXT-1-body": true,
+ "QE-Proposed-garbage-1_TEXT-1-div": true,
+ "QI-Proposed-SELECTALL_TEXT-1-dM": true,
+ "QI-Proposed-SELECTALL_TEXT-1-body": true,
+ "QI-Proposed-SELECTALL_TEXT-1-div": true,
+ "QI-Proposed-UNSELECT_TEXT-1-dM": true,
+ "QI-Proposed-UNSELECT_TEXT-1-body": true,
+ "QI-Proposed-UNSELECT_TEXT-1-div": true,
+ "QI-Proposed-UNDO_TEXT-1-dM": true,
+ "QI-Proposed-UNDO_TEXT-1-body": true,
+ "QI-Proposed-UNDO_TEXT-1-div": true,
+ "QI-Proposed-REDO_TEXT-1-dM": true,
+ "QI-Proposed-REDO_TEXT-1-body": true,
+ "QI-Proposed-REDO_TEXT-1-div": true,
+ "QI-Proposed-BOLD_TEXT-1-dM": true,
+ "QI-Proposed-BOLD_TEXT-1-body": true,
+ "QI-Proposed-BOLD_TEXT-1-div": true,
+ "QI-Proposed-ITALIC_TEXT-1-dM": true,
+ "QI-Proposed-ITALIC_TEXT-1-body": true,
+ "QI-Proposed-ITALIC_TEXT-1-div": true,
+ "QI-Proposed-UNDERLINE_TEXT-1-dM": true,
+ "QI-Proposed-UNDERLINE_TEXT-1-body": true,
+ "QI-Proposed-UNDERLINE_TEXT-1-div": true,
+ "QI-Proposed-STRIKETHROUGH_TEXT-1-dM": true,
+ "QI-Proposed-STRIKETHROUGH_TEXT-1-body": true,
+ "QI-Proposed-STRIKETHROUGH_TEXT-1-div": true,
+ "QI-Proposed-SUBSCRIPT_TEXT-1-dM": true,
+ "QI-Proposed-SUBSCRIPT_TEXT-1-body": true,
+ "QI-Proposed-SUBSCRIPT_TEXT-1-div": true,
+ "QI-Proposed-SUPERSCRIPT_TEXT-1-dM": true,
+ "QI-Proposed-SUPERSCRIPT_TEXT-1-body": true,
+ "QI-Proposed-SUPERSCRIPT_TEXT-1-div": true,
+ "QI-Proposed-FORMATBLOCK_TEXT-1-dM": true,
+ "QI-Proposed-FORMATBLOCK_TEXT-1-body": true,
+ "QI-Proposed-FORMATBLOCK_TEXT-1-div": true,
+ "QI-Proposed-CREATELINK_TEXT-1-dM": true,
+ "QI-Proposed-CREATELINK_TEXT-1-body": true,
+ "QI-Proposed-CREATELINK_TEXT-1-div": true,
+ "QI-Proposed-UNLINK_TEXT-1-dM": true,
+ "QI-Proposed-UNLINK_TEXT-1-body": true,
+ "QI-Proposed-UNLINK_TEXT-1-div": true,
+ "QI-Proposed-INSERTHTML_TEXT-1-dM": true,
+ "QI-Proposed-INSERTHTML_TEXT-1-body": true,
+ "QI-Proposed-INSERTHTML_TEXT-1-div": true,
+ "QI-Proposed-INSERTHORIZONTALRULE_TEXT-1-dM": true,
+ "QI-Proposed-INSERTHORIZONTALRULE_TEXT-1-body": true,
+ "QI-Proposed-INSERTHORIZONTALRULE_TEXT-1-div": true,
+ "QI-Proposed-INSERTIMAGE_TEXT-1-dM": true,
+ "QI-Proposed-INSERTIMAGE_TEXT-1-body": true,
+ "QI-Proposed-INSERTIMAGE_TEXT-1-div": true,
+ "QI-Proposed-INSERTLINEBREAK_TEXT-1-dM": true,
+ "QI-Proposed-INSERTLINEBREAK_TEXT-1-body": true,
+ "QI-Proposed-INSERTLINEBREAK_TEXT-1-div": true,
+ "QI-Proposed-INSERTPARAGRAPH_TEXT-1-dM": true,
+ "QI-Proposed-INSERTPARAGRAPH_TEXT-1-body": true,
+ "QI-Proposed-INSERTPARAGRAPH_TEXT-1-div": true,
+ "QI-Proposed-INSERTORDEREDLIST_TEXT-1-dM": true,
+ "QI-Proposed-INSERTORDEREDLIST_TEXT-1-body": true,
+ "QI-Proposed-INSERTORDEREDLIST_TEXT-1-div": true,
+ "QI-Proposed-INSERTUNORDEREDLIST_TEXT-1-dM": true,
+ "QI-Proposed-INSERTUNORDEREDLIST_TEXT-1-body": true,
+ "QI-Proposed-INSERTUNORDEREDLIST_TEXT-1-div": true,
+ "QI-Proposed-INSERTTEXT_TEXT-1-dM": true,
+ "QI-Proposed-INSERTTEXT_TEXT-1-body": true,
+ "QI-Proposed-INSERTTEXT_TEXT-1-div": true,
+ "QI-Proposed-DELETE_TEXT-1-dM": true,
+ "QI-Proposed-DELETE_TEXT-1-body": true,
+ "QI-Proposed-DELETE_TEXT-1-div": true,
+ "QI-Proposed-FORWARDDELETE_TEXT-1-dM": true,
+ "QI-Proposed-FORWARDDELETE_TEXT-1-body": true,
+ "QI-Proposed-FORWARDDELETE_TEXT-1-div": true,
+ "QI-Proposed-STYLEWITHCSS_TEXT-1-dM": true,
+ "QI-Proposed-STYLEWITHCSS_TEXT-1-body": true,
+ "QI-Proposed-STYLEWITHCSS_TEXT-1-div": true,
+ "QI-Proposed-CONTENTREADONLY_TEXT-1-dM": true,
+ "QI-Proposed-CONTENTREADONLY_TEXT-1-body": true,
+ "QI-Proposed-CONTENTREADONLY_TEXT-1-div": true,
+ "QI-Proposed-BACKCOLOR_TEXT-1-dM": true,
+ "QI-Proposed-BACKCOLOR_TEXT-1-body": true,
+ "QI-Proposed-BACKCOLOR_TEXT-1-div": true,
+ "QI-Proposed-FORECOLOR_TEXT-1-dM": true,
+ "QI-Proposed-FORECOLOR_TEXT-1-body": true,
+ "QI-Proposed-FORECOLOR_TEXT-1-div": true,
+ "QI-Proposed-HILITECOLOR_TEXT-1-dM": true,
+ "QI-Proposed-HILITECOLOR_TEXT-1-body": true,
+ "QI-Proposed-HILITECOLOR_TEXT-1-div": true,
+ "QI-Proposed-FONTNAME_TEXT-1-dM": true,
+ "QI-Proposed-FONTNAME_TEXT-1-body": true,
+ "QI-Proposed-FONTNAME_TEXT-1-div": true,
+ "QI-Proposed-FONTSIZE_TEXT-1-dM": true,
+ "QI-Proposed-FONTSIZE_TEXT-1-body": true,
+ "QI-Proposed-FONTSIZE_TEXT-1-div": true,
+ "QI-Proposed-INCREASEFONTSIZE_TEXT-1-dM": true,
+ "QI-Proposed-INCREASEFONTSIZE_TEXT-1-body": true,
+ "QI-Proposed-INCREASEFONTSIZE_TEXT-1-div": true,
+ "QI-Proposed-DECREASEFONTSIZE_TEXT-1-dM": true,
+ "QI-Proposed-DECREASEFONTSIZE_TEXT-1-body": true,
+ "QI-Proposed-DECREASEFONTSIZE_TEXT-1-div": true,
+ "QI-Proposed-HEADING_TEXT-1-dM": true,
+ "QI-Proposed-HEADING_TEXT-1-body": true,
+ "QI-Proposed-HEADING_TEXT-1-div": true,
+ "QI-Proposed-INDENT_TEXT-1-dM": true,
+ "QI-Proposed-INDENT_TEXT-1-body": true,
+ "QI-Proposed-INDENT_TEXT-1-div": true,
+ "QI-Proposed-OUTDENT_TEXT-1-dM": true,
+ "QI-Proposed-OUTDENT_TEXT-1-body": true,
+ "QI-Proposed-OUTDENT_TEXT-1-div": true,
+ "QI-Proposed-CREATEBOOKMARK_TEXT-1-dM": true,
+ "QI-Proposed-CREATEBOOKMARK_TEXT-1-body": true,
+ "QI-Proposed-CREATEBOOKMARK_TEXT-1-div": true,
+ "QI-Proposed-UNBOOKMARK_TEXT-1-dM": true,
+ "QI-Proposed-UNBOOKMARK_TEXT-1-body": true,
+ "QI-Proposed-UNBOOKMARK_TEXT-1-div": true,
+ "QI-Proposed-JUSTIFYCENTER_TEXT-1-dM": true,
+ "QI-Proposed-JUSTIFYCENTER_TEXT-1-body": true,
+ "QI-Proposed-JUSTIFYCENTER_TEXT-1-div": true,
+ "QI-Proposed-JUSTIFYFULL_TEXT-1-dM": true,
+ "QI-Proposed-JUSTIFYFULL_TEXT-1-body": true,
+ "QI-Proposed-JUSTIFYFULL_TEXT-1-div": true,
+ "QI-Proposed-JUSTIFYLEFT_TEXT-1-dM": true,
+ "QI-Proposed-JUSTIFYLEFT_TEXT-1-body": true,
+ "QI-Proposed-JUSTIFYLEFT_TEXT-1-div": true,
+ "QI-Proposed-JUSTIFYRIGHT_TEXT-1-dM": true,
+ "QI-Proposed-JUSTIFYRIGHT_TEXT-1-body": true,
+ "QI-Proposed-JUSTIFYRIGHT_TEXT-1-div": true,
+ "QI-Proposed-REMOVEFORMAT_TEXT-1-dM": true,
+ "QI-Proposed-REMOVEFORMAT_TEXT-1-body": true,
+ "QI-Proposed-REMOVEFORMAT_TEXT-1-div": true,
+ "QI-Proposed-COPY_TEXT-1-dM": true,
+ "QI-Proposed-COPY_TEXT-1-body": true,
+ "QI-Proposed-COPY_TEXT-1-div": true,
+ "QI-Proposed-CUT_TEXT-1-dM": true,
+ "QI-Proposed-CUT_TEXT-1-body": true,
+ "QI-Proposed-CUT_TEXT-1-div": true,
+ "QI-Proposed-PASTE_TEXT-1-dM": true,
+ "QI-Proposed-PASTE_TEXT-1-body": true,
+ "QI-Proposed-PASTE_TEXT-1-div": true,
+ "QI-Proposed-garbage-1_TEXT-1-dM": true,
+ "QI-Proposed-garbage-1_TEXT-1-body": true,
+ "QI-Proposed-garbage-1_TEXT-1-div": true,
+ "QS-Proposed-B_TEXT_SI-dM": true,
+ "QS-Proposed-B_TEXT_SI-body": true,
+ "QS-Proposed-B_TEXT_SI-div": true,
+ "QS-Proposed-B_B-1_SI-dM": true,
+ "QS-Proposed-B_B-1_SI-body": true,
+ "QS-Proposed-B_B-1_SI-div": true,
+ "QS-Proposed-B_STRONG-1_SI-dM": true,
+ "QS-Proposed-B_STRONG-1_SI-body": true,
+ "QS-Proposed-B_STRONG-1_SI-div": true,
+ "QS-Proposed-B_SPANs:fw:b-1_SI-dM": true,
+ "QS-Proposed-B_SPANs:fw:b-1_SI-body": true,
+ "QS-Proposed-B_SPANs:fw:b-1_SI-div": true,
+ "QS-Proposed-B_SPANs:fw:n-1_SI-dM": true,
+ "QS-Proposed-B_SPANs:fw:n-1_SI-body": true,
+ "QS-Proposed-B_SPANs:fw:n-1_SI-div": true,
+ "QS-Proposed-B_Bs:fw:n-1_SI-dM": true,
+ "QS-Proposed-B_Bs:fw:n-1_SI-body": true,
+ "QS-Proposed-B_Bs:fw:n-1_SI-div": true,
+ "QS-Proposed-B_B-SPANs:fw:n-1_SI-dM": true,
+ "QS-Proposed-B_B-SPANs:fw:n-1_SI-body": true,
+ "QS-Proposed-B_B-SPANs:fw:n-1_SI-div": true,
+ "QS-Proposed-B_SPAN.b-1-SI-dM": true,
+ "QS-Proposed-B_SPAN.b-1-SI-body": true,
+ "QS-Proposed-B_SPAN.b-1-SI-div": true,
+ "QS-Proposed-B_MYB-1-SI-dM": true,
+ "QS-Proposed-B_MYB-1-SI-body": true,
+ "QS-Proposed-B_MYB-1-SI-div": true,
+ "QS-Proposed-B_B-I-1_SC-dM": true,
+ "QS-Proposed-B_B-I-1_SC-body": true,
+ "QS-Proposed-B_B-I-1_SC-div": true,
+ "QS-Proposed-B_B-I-1_SL-dM": true,
+ "QS-Proposed-B_B-I-1_SL-body": true,
+ "QS-Proposed-B_B-I-1_SL-div": true,
+ "QS-Proposed-B_B-I-1_SR-dM": true,
+ "QS-Proposed-B_B-I-1_SR-body": true,
+ "QS-Proposed-B_B-I-1_SR-div": true,
+ "QS-Proposed-B_STRONG-I-1_SC-dM": true,
+ "QS-Proposed-B_STRONG-I-1_SC-body": true,
+ "QS-Proposed-B_STRONG-I-1_SC-div": true,
+ "QS-Proposed-B_B-I-U-1_SC-dM": true,
+ "QS-Proposed-B_B-I-U-1_SC-body": true,
+ "QS-Proposed-B_B-I-U-1_SC-div": true,
+ "QS-Proposed-B_B-I-U-1_SM-dM": true,
+ "QS-Proposed-B_B-I-U-1_SM-body": true,
+ "QS-Proposed-B_B-I-U-1_SM-div": true,
+ "QS-Proposed-B_TEXT-B-1_SO-1-dM": true,
+ "QS-Proposed-B_TEXT-B-1_SO-1-body": true,
+ "QS-Proposed-B_TEXT-B-1_SO-1-div": true,
+ "QS-Proposed-B_TEXT-B-1_SO-2-dM": true,
+ "QS-Proposed-B_TEXT-B-1_SO-2-body": true,
+ "QS-Proposed-B_TEXT-B-1_SO-2-div": true,
+ "QS-Proposed-B_TEXT-B-1_SL-dM": true,
+ "QS-Proposed-B_TEXT-B-1_SL-body": true,
+ "QS-Proposed-B_TEXT-B-1_SL-div": true,
+ "QS-Proposed-B_TEXT-B-1_SR-dM": true,
+ "QS-Proposed-B_TEXT-B-1_SR-body": true,
+ "QS-Proposed-B_TEXT-B-1_SR-div": true,
+ "QS-Proposed-B_TEXT-B-1_SO-3-dM": true,
+ "QS-Proposed-B_TEXT-B-1_SO-3-body": true,
+ "QS-Proposed-B_TEXT-B-1_SO-3-div": true,
+ "QS-Proposed-B_B.TEXT.B-1_SM-dM": true,
+ "QS-Proposed-B_B.TEXT.B-1_SM-body": true,
+ "QS-Proposed-B_B.TEXT.B-1_SM-div": true,
+ "QS-Proposed-B_B.B.B-1_SM-dM": true,
+ "QS-Proposed-B_B.B.B-1_SM-body": true,
+ "QS-Proposed-B_B.B.B-1_SM-div": true,
+ "QS-Proposed-B_B.STRONG.B-1_SM-dM": true,
+ "QS-Proposed-B_B.STRONG.B-1_SM-body": true,
+ "QS-Proposed-B_B.STRONG.B-1_SM-div": true,
+ "QS-Proposed-B_SPAN.b.MYB.SPANs:fw:b-1_SM-dM": true,
+ "QS-Proposed-B_SPAN.b.MYB.SPANs:fw:b-1_SM-body": true,
+ "QS-Proposed-B_SPAN.b.MYB.SPANs:fw:b-1_SM-div": true,
+ "QS-Proposed-I_TEXT_SI-dM": true,
+ "QS-Proposed-I_TEXT_SI-body": true,
+ "QS-Proposed-I_TEXT_SI-div": true,
+ "QS-Proposed-I_I-1_SI-dM": true,
+ "QS-Proposed-I_I-1_SI-body": true,
+ "QS-Proposed-I_I-1_SI-div": true,
+ "QS-Proposed-I_EM-1_SI-dM": true,
+ "QS-Proposed-I_EM-1_SI-body": true,
+ "QS-Proposed-I_EM-1_SI-div": true,
+ "QS-Proposed-I_SPANs:fs:i-1_SI-dM": true,
+ "QS-Proposed-I_SPANs:fs:i-1_SI-body": true,
+ "QS-Proposed-I_SPANs:fs:i-1_SI-div": true,
+ "QS-Proposed-I_SPANs:fs:n-1_SI-dM": true,
+ "QS-Proposed-I_SPANs:fs:n-1_SI-body": true,
+ "QS-Proposed-I_SPANs:fs:n-1_SI-div": true,
+ "QS-Proposed-I_I-SPANs:fs:n-1_SI-dM": true,
+ "QS-Proposed-I_I-SPANs:fs:n-1_SI-body": true,
+ "QS-Proposed-I_I-SPANs:fs:n-1_SI-div": true,
+ "QS-Proposed-I_SPAN.i-1-SI-dM": true,
+ "QS-Proposed-I_SPAN.i-1-SI-body": true,
+ "QS-Proposed-I_SPAN.i-1-SI-div": true,
+ "QS-Proposed-I_MYI-1-SI-dM": true,
+ "QS-Proposed-I_MYI-1-SI-body": true,
+ "QS-Proposed-I_MYI-1-SI-div": true,
+ "QS-Proposed-U_TEXT_SI-dM": true,
+ "QS-Proposed-U_TEXT_SI-body": true,
+ "QS-Proposed-U_TEXT_SI-div": true,
+ "QS-Proposed-U_U-1_SI-dM": true,
+ "QS-Proposed-U_U-1_SI-body": true,
+ "QS-Proposed-U_U-1_SI-div": true,
+ "QS-Proposed-U_Us:td:n-1_SI-dM": true,
+ "QS-Proposed-U_Us:td:n-1_SI-body": true,
+ "QS-Proposed-U_Us:td:n-1_SI-div": true,
+ "QS-Proposed-U_Ah:url-1_SI-dM": true,
+ "QS-Proposed-U_Ah:url-1_SI-body": true,
+ "QS-Proposed-U_Ah:url-1_SI-div": true,
+ "QS-Proposed-U_Ah:url.s:td:n-1_SI-dM": true,
+ "QS-Proposed-U_Ah:url.s:td:n-1_SI-body": true,
+ "QS-Proposed-U_Ah:url.s:td:n-1_SI-div": true,
+ "QS-Proposed-U_SPANs:td:u-1_SI-dM": true,
+ "QS-Proposed-U_SPANs:td:u-1_SI-body": true,
+ "QS-Proposed-U_SPANs:td:u-1_SI-div": true,
+ "QS-Proposed-U_SPAN.u-1-SI-dM": true,
+ "QS-Proposed-U_SPAN.u-1-SI-body": true,
+ "QS-Proposed-U_SPAN.u-1-SI-div": true,
+ "QS-Proposed-U_MYU-1-SI-dM": true,
+ "QS-Proposed-U_MYU-1-SI-body": true,
+ "QS-Proposed-U_MYU-1-SI-div": true,
+ "QS-Proposed-S_TEXT_SI-dM": true,
+ "QS-Proposed-S_TEXT_SI-body": true,
+ "QS-Proposed-S_TEXT_SI-div": true,
+ "QS-Proposed-S_S-1_SI-dM": true,
+ "QS-Proposed-S_S-1_SI-body": true,
+ "QS-Proposed-S_S-1_SI-div": true,
+ "QS-Proposed-S_STRIKE-1_SI-dM": true,
+ "QS-Proposed-S_STRIKE-1_SI-body": true,
+ "QS-Proposed-S_STRIKE-1_SI-div": true,
+ "QS-Proposed-S_STRIKEs:td:n-1_SI-dM": true,
+ "QS-Proposed-S_STRIKEs:td:n-1_SI-body": true,
+ "QS-Proposed-S_STRIKEs:td:n-1_SI-div": true,
+ "QS-Proposed-S_DEL-1_SI-dM": true,
+ "QS-Proposed-S_DEL-1_SI-body": true,
+ "QS-Proposed-S_DEL-1_SI-div": true,
+ "QS-Proposed-S_SPANs:td:lt-1_SI-dM": true,
+ "QS-Proposed-S_SPANs:td:lt-1_SI-body": true,
+ "QS-Proposed-S_SPANs:td:lt-1_SI-div": true,
+ "QS-Proposed-S_SPAN.s-1-SI-dM": true,
+ "QS-Proposed-S_SPAN.s-1-SI-body": true,
+ "QS-Proposed-S_SPAN.s-1-SI-div": true,
+ "QS-Proposed-S_MYS-1-SI-dM": true,
+ "QS-Proposed-S_MYS-1-SI-body": true,
+ "QS-Proposed-S_MYS-1-SI-div": true,
+ "QS-Proposed-S_S.STRIKE.DEL-1_SM-dM": true,
+ "QS-Proposed-S_S.STRIKE.DEL-1_SM-body": true,
+ "QS-Proposed-S_S.STRIKE.DEL-1_SM-div": true,
+ "QS-Proposed-SUB_TEXT_SI-dM": true,
+ "QS-Proposed-SUB_TEXT_SI-body": true,
+ "QS-Proposed-SUB_TEXT_SI-div": true,
+ "QS-Proposed-SUB_SUB-1_SI-dM": true,
+ "QS-Proposed-SUB_SUB-1_SI-body": true,
+ "QS-Proposed-SUB_SUB-1_SI-div": true,
+ "QS-Proposed-SUB_SPAN.sub-1-SI-dM": true,
+ "QS-Proposed-SUB_SPAN.sub-1-SI-body": true,
+ "QS-Proposed-SUB_SPAN.sub-1-SI-div": true,
+ "QS-Proposed-SUB_MYSUB-1-SI-dM": true,
+ "QS-Proposed-SUB_MYSUB-1-SI-body": true,
+ "QS-Proposed-SUB_MYSUB-1-SI-div": true,
+ "QS-Proposed-SUP_TEXT_SI-dM": true,
+ "QS-Proposed-SUP_TEXT_SI-body": true,
+ "QS-Proposed-SUP_TEXT_SI-div": true,
+ "QS-Proposed-SUP_SUP-1_SI-dM": true,
+ "QS-Proposed-SUP_SUP-1_SI-body": true,
+ "QS-Proposed-SUP_SUP-1_SI-div": true,
+ "QS-Proposed-IOL_TEXT_SI-dM": true,
+ "QS-Proposed-IOL_TEXT_SI-body": true,
+ "QS-Proposed-IOL_TEXT_SI-div": true,
+ "QS-Proposed-SUP_SPAN.sup-1-SI-dM": true,
+ "QS-Proposed-SUP_SPAN.sup-1-SI-body": true,
+ "QS-Proposed-SUP_SPAN.sup-1-SI-div": true,
+ "QS-Proposed-SUP_MYSUP-1-SI-dM": true,
+ "QS-Proposed-SUP_MYSUP-1-SI-body": true,
+ "QS-Proposed-SUP_MYSUP-1-SI-div": true,
+ "QS-Proposed-IOL_TEXT-1_SI-dM": true,
+ "QS-Proposed-IOL_TEXT-1_SI-body": true,
+ "QS-Proposed-IOL_TEXT-1_SI-div": true,
+ "QS-Proposed-IOL_OL-LI-1_SI-dM": true,
+ "QS-Proposed-IOL_OL-LI-1_SI-body": true,
+ "QS-Proposed-IOL_OL-LI-1_SI-div": true,
+ "QS-Proposed-IOL_UL_LI-1_SI-dM": true,
+ "QS-Proposed-IOL_UL_LI-1_SI-body": true,
+ "QS-Proposed-IOL_UL_LI-1_SI-div": true,
+ "QS-Proposed-IUL_TEXT_SI-dM": true,
+ "QS-Proposed-IUL_TEXT_SI-body": true,
+ "QS-Proposed-IUL_TEXT_SI-div": true,
+ "QS-Proposed-IUL_OL-LI-1_SI-dM": true,
+ "QS-Proposed-IUL_OL-LI-1_SI-body": true,
+ "QS-Proposed-IUL_OL-LI-1_SI-div": true,
+ "QS-Proposed-IUL_UL-LI-1_SI-dM": true,
+ "QS-Proposed-IUL_UL-LI-1_SI-body": true,
+ "QS-Proposed-IUL_UL-LI-1_SI-div": true,
+ "QS-Proposed-JC_TEXT_SI-dM": true,
+ "QS-Proposed-JC_TEXT_SI-body": true,
+ "QS-Proposed-JC_TEXT_SI-div": true,
+ "QS-Proposed-JC_DIVa:c-1_SI-dM": true,
+ "QS-Proposed-JC_DIVa:c-1_SI-body": true,
+ "QS-Proposed-JC_DIVa:c-1_SI-div": true,
+ "QS-Proposed-JC_Pa:c-1_SI-dM": true,
+ "QS-Proposed-JC_Pa:c-1_SI-body": true,
+ "QS-Proposed-JC_Pa:c-1_SI-div": true,
+ "QS-Proposed-JC_SPANs:ta:c-1_SI-dM": true,
+ "QS-Proposed-JC_SPANs:ta:c-1_SI-body": true,
+ "QS-Proposed-JC_SPANs:ta:c-1_SI-div": true,
+ "QS-Proposed-JC_SPAN.jc-1-SI-dM": true,
+ "QS-Proposed-JC_SPAN.jc-1-SI-body": true,
+ "QS-Proposed-JC_SPAN.jc-1-SI-div": true,
+ "QS-Proposed-JC_MYJC-1-SI-dM": true,
+ "QS-Proposed-JC_MYJC-1-SI-body": true,
+ "QS-Proposed-JC_MYJC-1-SI-div": true,
+ "QS-Proposed-JF_TEXT_SI-dM": true,
+ "QS-Proposed-JF_TEXT_SI-body": true,
+ "QS-Proposed-JF_TEXT_SI-div": true,
+ "QS-Proposed-JF_DIVa:j-1_SI-dM": true,
+ "QS-Proposed-JF_DIVa:j-1_SI-body": true,
+ "QS-Proposed-JF_DIVa:j-1_SI-div": true,
+ "QS-Proposed-JF_Pa:j-1_SI-dM": true,
+ "QS-Proposed-JF_Pa:j-1_SI-body": true,
+ "QS-Proposed-JF_Pa:j-1_SI-div": true,
+ "QS-Proposed-JF_SPANs:ta:j-1_SI-dM": true,
+ "QS-Proposed-JF_SPANs:ta:j-1_SI-body": true,
+ "QS-Proposed-JF_SPANs:ta:j-1_SI-div": true,
+ "QS-Proposed-JF_SPAN.jf-1-SI-dM": true,
+ "QS-Proposed-JF_SPAN.jf-1-SI-body": true,
+ "QS-Proposed-JF_SPAN.jf-1-SI-div": true,
+ "QS-Proposed-JF_MYJF-1-SI-dM": true,
+ "QS-Proposed-JF_MYJF-1-SI-body": true,
+ "QS-Proposed-JF_MYJF-1-SI-div": true,
+ "QS-Proposed-JL_TEXT_SI-dM": true,
+ "QS-Proposed-JL_TEXT_SI-body": true,
+ "QS-Proposed-JL_TEXT_SI-div": true,
+ "QS-Proposed-JL_DIVa:l-1_SI-dM": true,
+ "QS-Proposed-JL_DIVa:l-1_SI-body": true,
+ "QS-Proposed-JL_DIVa:l-1_SI-div": true,
+ "QS-Proposed-JL_Pa:l-1_SI-dM": true,
+ "QS-Proposed-JL_Pa:l-1_SI-body": true,
+ "QS-Proposed-JL_Pa:l-1_SI-div": true,
+ "QS-Proposed-JL_SPANs:ta:l-1_SI-dM": true,
+ "QS-Proposed-JL_SPANs:ta:l-1_SI-body": true,
+ "QS-Proposed-JL_SPANs:ta:l-1_SI-div": true,
+ "QS-Proposed-JL_SPAN.jl-1-SI-dM": true,
+ "QS-Proposed-JL_SPAN.jl-1-SI-body": true,
+ "QS-Proposed-JL_SPAN.jl-1-SI-div": true,
+ "QS-Proposed-JL_MYJL-1-SI-dM": true,
+ "QS-Proposed-JL_MYJL-1-SI-body": true,
+ "QS-Proposed-JL_MYJL-1-SI-div": true,
+ "QS-Proposed-JR_TEXT_SI-dM": true,
+ "QS-Proposed-JR_TEXT_SI-body": true,
+ "QS-Proposed-JR_TEXT_SI-div": true,
+ "QS-Proposed-JR_DIVa:r-1_SI-dM": true,
+ "QS-Proposed-JR_DIVa:r-1_SI-body": true,
+ "QS-Proposed-JR_DIVa:r-1_SI-div": true,
+ "QS-Proposed-JR_Pa:r-1_SI-dM": true,
+ "QS-Proposed-JR_Pa:r-1_SI-body": true,
+ "QS-Proposed-JR_Pa:r-1_SI-div": true,
+ "QS-Proposed-JR_SPANs:ta:r-1_SI-dM": true,
+ "QS-Proposed-JR_SPANs:ta:r-1_SI-body": true,
+ "QS-Proposed-JR_SPANs:ta:r-1_SI-div": true,
+ "QS-Proposed-JR_SPAN.jr-1-SI-dM": true,
+ "QS-Proposed-JR_SPAN.jr-1-SI-body": true,
+ "QS-Proposed-JR_SPAN.jr-1-SI-div": true,
+ "QS-Proposed-JR_MYJR-1-SI-dM": true,
+ "QS-Proposed-JR_MYJR-1-SI-body": true,
+ "QS-Proposed-JR_MYJR-1-SI-div": true,
+ "QV-Proposed-B_TEXT_SI-dM": true,
+ "QV-Proposed-B_TEXT_SI-body": true,
+ "QV-Proposed-B_TEXT_SI-div": true,
+ "QV-Proposed-B_B-1_SI-dM": true,
+ "QV-Proposed-B_B-1_SI-body": true,
+ "QV-Proposed-B_B-1_SI-div": true,
+ "QV-Proposed-B_STRONG-1_SI-dM": true,
+ "QV-Proposed-B_STRONG-1_SI-body": true,
+ "QV-Proposed-B_STRONG-1_SI-div": true,
+ "QV-Proposed-B_SPANs:fw:b-1_SI-dM": true,
+ "QV-Proposed-B_SPANs:fw:b-1_SI-body": true,
+ "QV-Proposed-B_SPANs:fw:b-1_SI-div": true,
+ "QV-Proposed-B_SPANs:fw:n-1_SI-dM": true,
+ "QV-Proposed-B_SPANs:fw:n-1_SI-body": true,
+ "QV-Proposed-B_SPANs:fw:n-1_SI-div": true,
+ "QV-Proposed-B_Bs:fw:n-1_SI-dM": true,
+ "QV-Proposed-B_Bs:fw:n-1_SI-body": true,
+ "QV-Proposed-B_Bs:fw:n-1_SI-div": true,
+ "QV-Proposed-B_SPAN.b-1_SI-dM": true,
+ "QV-Proposed-B_SPAN.b-1_SI-body": true,
+ "QV-Proposed-B_SPAN.b-1_SI-div": true,
+ "QV-Proposed-B_MYB-1-SI-dM": true,
+ "QV-Proposed-B_MYB-1-SI-body": true,
+ "QV-Proposed-B_MYB-1-SI-div": true,
+ "QV-Proposed-I_TEXT_SI-dM": true,
+ "QV-Proposed-I_TEXT_SI-body": true,
+ "QV-Proposed-I_TEXT_SI-div": true,
+ "QV-Proposed-I_I-1_SI-dM": true,
+ "QV-Proposed-I_I-1_SI-body": true,
+ "QV-Proposed-I_I-1_SI-div": true,
+ "QV-Proposed-I_EM-1_SI-dM": true,
+ "QV-Proposed-I_EM-1_SI-body": true,
+ "QV-Proposed-I_EM-1_SI-div": true,
+ "QV-Proposed-I_SPANs:fs:i-1_SI-dM": true,
+ "QV-Proposed-I_SPANs:fs:i-1_SI-body": true,
+ "QV-Proposed-I_SPANs:fs:i-1_SI-div": true,
+ "QV-Proposed-I_SPANs:fs:n-1_SI-dM": true,
+ "QV-Proposed-I_SPANs:fs:n-1_SI-body": true,
+ "QV-Proposed-I_SPANs:fs:n-1_SI-div": true,
+ "QV-Proposed-I_I-SPANs:fs:n-1_SI-dM": true,
+ "QV-Proposed-I_I-SPANs:fs:n-1_SI-body": true,
+ "QV-Proposed-I_I-SPANs:fs:n-1_SI-div": true,
+ "QV-Proposed-I_SPAN.i-1_SI-dM": true,
+ "QV-Proposed-I_SPAN.i-1_SI-body": true,
+ "QV-Proposed-I_SPAN.i-1_SI-div": true,
+ "QV-Proposed-I_MYI-1-SI-dM": true,
+ "QV-Proposed-I_MYI-1-SI-body": true,
+ "QV-Proposed-I_MYI-1-SI-div": true,
+ "QV-Proposed-FB_TEXT-1_SC-dM": true,
+ "QV-Proposed-FB_TEXT-1_SC-body": true,
+ "QV-Proposed-FB_TEXT-1_SC-div": true,
+ "QV-Proposed-FB_H1-1_SC-dM": true,
+ "QV-Proposed-FB_H1-1_SC-body": true,
+ "QV-Proposed-FB_H1-1_SC-div": true,
+ "QV-Proposed-FB_PRE-1_SC-dM": true,
+ "QV-Proposed-FB_PRE-1_SC-body": true,
+ "QV-Proposed-FB_PRE-1_SC-div": true,
+ "QV-Proposed-FB_BQ-1_SC-dM": true,
+ "QV-Proposed-FB_BQ-1_SC-body": true,
+ "QV-Proposed-FB_BQ-1_SC-div": true,
+ "QV-Proposed-FB_ADDRESS-1_SC-dM": true,
+ "QV-Proposed-FB_ADDRESS-1_SC-body": true,
+ "QV-Proposed-FB_ADDRESS-1_SC-div": true,
+ "QV-Proposed-FB_H1-H2-1_SC-dM": true,
+ "QV-Proposed-FB_H1-H2-1_SC-body": true,
+ "QV-Proposed-FB_H1-H2-1_SC-div": true,
+ "QV-Proposed-FB_H1-H2-1_SL-dM": true,
+ "QV-Proposed-FB_H1-H2-1_SL-body": true,
+ "QV-Proposed-FB_H1-H2-1_SL-div": true,
+ "QV-Proposed-FB_H1-H2-1_SR-dM": true,
+ "QV-Proposed-FB_H1-H2-1_SR-body": true,
+ "QV-Proposed-FB_H1-H2-1_SR-div": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SL-dM": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SL-body": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SL-div": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SR-dM": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SR-body": true,
+ "QV-Proposed-FB_TEXT-ADDRESS-1_SR-div": true,
+ "QV-Proposed-FB_H1-H2.TEXT.H2-1_SM-dM": true,
+ "QV-Proposed-FB_H1-H2.TEXT.H2-1_SM-body": true,
+ "QV-Proposed-FB_H1-H2.TEXT.H2-1_SM-div": true,
+ "QV-Proposed-H_H1-1_SC-dM": true,
+ "QV-Proposed-H_H1-1_SC-body": true,
+ "QV-Proposed-H_H1-1_SC-div": true,
+ "QV-Proposed-H_H3-1_SC-dM": true,
+ "QV-Proposed-H_H3-1_SC-body": true,
+ "QV-Proposed-H_H3-1_SC-div": true,
+ "QV-Proposed-H_H1-H2-H3-H4-1_SC-dM": true,
+ "QV-Proposed-H_H1-H2-H3-H4-1_SC-body": true,
+ "QV-Proposed-H_H1-H2-H3-H4-1_SC-div": true,
+ "QV-Proposed-H_P-1_SC-dM": true,
+ "QV-Proposed-H_P-1_SC-body": true,
+ "QV-Proposed-H_P-1_SC-div": true,
+ "QV-Proposed-FN_FONTf:a-1_SI-dM": true,
+ "QV-Proposed-FN_FONTf:a-1_SI-body": true,
+ "QV-Proposed-FN_FONTf:a-1_SI-div": true,
+ "QV-Proposed-FN_SPANs:ff:a-1_SI-dM": true,
+ "QV-Proposed-FN_SPANs:ff:a-1_SI-body": true,
+ "QV-Proposed-FN_SPANs:ff:a-1_SI-div": true,
+ "QV-Proposed-FN_FONTf:a.s:ff:c-1_SI-dM": true,
+ "QV-Proposed-FN_FONTf:a.s:ff:c-1_SI-body": true,
+ "QV-Proposed-FN_FONTf:a.s:ff:c-1_SI-div": true,
+ "QV-Proposed-FN_FONTf:a-FONTf:c-1_SI-dM": true,
+ "QV-Proposed-FN_FONTf:a-FONTf:c-1_SI-body": true,
+ "QV-Proposed-FN_FONTf:a-FONTf:c-1_SI-div": true,
+ "QV-Proposed-FN_SPANs:ff:c-FONTf:a-1_SI-dM": true,
+ "QV-Proposed-FN_SPANs:ff:c-FONTf:a-1_SI-body": true,
+ "QV-Proposed-FN_SPANs:ff:c-FONTf:a-1_SI-div": true,
+ "QV-Proposed-FN_SPAN.fs18px-1_SI-dM": true,
+ "QV-Proposed-FN_SPAN.fs18px-1_SI-body": true,
+ "QV-Proposed-FN_SPAN.fs18px-1_SI-div": true,
+ "QV-Proposed-FN_MYCOURIER-1-SI-dM": true,
+ "QV-Proposed-FN_MYCOURIER-1-SI-body": true,
+ "QV-Proposed-FN_MYCOURIER-1-SI-div": true,
+ "QV-Proposed-FS_FONTsz:4-1_SI-dM": true,
+ "QV-Proposed-FS_FONTsz:4-1_SI-body": true,
+ "QV-Proposed-FS_FONTsz:4-1_SI-div": true,
+ "QV-Proposed-FS_FONTs:fs:l-1_SI-dM": true,
+ "QV-Proposed-FS_FONTs:fs:l-1_SI-body": true,
+ "QV-Proposed-FS_FONTs:fs:l-1_SI-div": true,
+ "QV-Proposed-FS_FONT.ass.s:fs:l-1_SI-dM": true,
+ "QV-Proposed-FS_FONT.ass.s:fs:l-1_SI-body": true,
+ "QV-Proposed-FS_FONT.ass.s:fs:l-1_SI-div": true,
+ "QV-Proposed-FS_FONTsz:1.s:fs:xl-1_SI-dM": true,
+ "QV-Proposed-FS_FONTsz:1.s:fs:xl-1_SI-body": true,
+ "QV-Proposed-FS_FONTsz:1.s:fs:xl-1_SI-div": true,
+ "QV-Proposed-FS_SPAN.large-1_SI-dM": true,
+ "QV-Proposed-FS_SPAN.large-1_SI-body": true,
+ "QV-Proposed-FS_SPAN.large-1_SI-div": true,
+ "QV-Proposed-FS_SPAN.fs18px-1_SI-dM": true,
+ "QV-Proposed-FS_SPAN.fs18px-1_SI-body": true,
+ "QV-Proposed-FS_SPAN.fs18px-1_SI-div": true,
+ "QV-Proposed-FA_MYLARGE-1-SI-dM": true,
+ "QV-Proposed-FA_MYLARGE-1-SI-body": true,
+ "QV-Proposed-FA_MYLARGE-1-SI-div": true,
+ "QV-Proposed-FA_MYFS18PX-1-SI-dM": true,
+ "QV-Proposed-FA_MYFS18PX-1-SI-body": true,
+ "QV-Proposed-FA_MYFS18PX-1-SI-div": true,
+ "QV-Proposed-BC_FONTs:bc:fca-1_SI-dM": true,
+ "QV-Proposed-BC_FONTs:bc:fca-1_SI-body": true,
+ "QV-Proposed-BC_FONTs:bc:fca-1_SI-div": true,
+ "QV-Proposed-BC_SPANs:bc:abc-1_SI-dM": true,
+ "QV-Proposed-BC_SPANs:bc:abc-1_SI-body": true,
+ "QV-Proposed-BC_SPANs:bc:abc-1_SI-div": true,
+ "QV-Proposed-BC_FONTs:bc:084-SPAN-1_SI-dM": true,
+ "QV-Proposed-BC_FONTs:bc:084-SPAN-1_SI-body": true,
+ "QV-Proposed-BC_FONTs:bc:084-SPAN-1_SI-div": true,
+ "QV-Proposed-BC_SPANs:bc:cde-SPAN-1_SI-dM": true,
+ "QV-Proposed-BC_SPANs:bc:cde-SPAN-1_SI-body": true,
+ "QV-Proposed-BC_SPANs:bc:cde-SPAN-1_SI-div": true,
+ "QV-Proposed-BC_SPAN.ass.s:bc:rgb-1_SI-dM": true,
+ "QV-Proposed-BC_SPAN.ass.s:bc:rgb-1_SI-body": true,
+ "QV-Proposed-BC_SPAN.ass.s:bc:rgb-1_SI-div": true,
+ "QV-Proposed-BC_SPAN.bcred-1_SI-dM": true,
+ "QV-Proposed-BC_SPAN.bcred-1_SI-body": true,
+ "QV-Proposed-BC_SPAN.bcred-1_SI-div": true,
+ "QV-Proposed-BC_MYBCRED-1-SI-dM": true,
+ "QV-Proposed-BC_MYBCRED-1-SI-body": true,
+ "QV-Proposed-BC_MYBCRED-1-SI-div": true,
+ "QV-Proposed-FC_FONTc:f00-1_SI-dM": true,
+ "QV-Proposed-FC_FONTc:f00-1_SI-body": true,
+ "QV-Proposed-FC_FONTc:f00-1_SI-div": true,
+ "QV-Proposed-FC_SPANs:c:0f0-1_SI-dM": true,
+ "QV-Proposed-FC_SPANs:c:0f0-1_SI-body": true,
+ "QV-Proposed-FC_SPANs:c:0f0-1_SI-div": true,
+ "QV-Proposed-FC_FONTc:333.s:c:999-1_SI-dM": true,
+ "QV-Proposed-FC_FONTc:333.s:c:999-1_SI-body": true,
+ "QV-Proposed-FC_FONTc:333.s:c:999-1_SI-div": true,
+ "QV-Proposed-FC_FONTc:641-SPAN-1_SI-dM": true,
+ "QV-Proposed-FC_FONTc:641-SPAN-1_SI-body": true,
+ "QV-Proposed-FC_FONTc:641-SPAN-1_SI-div": true,
+ "QV-Proposed-FC_SPANs:c:d95-SPAN-1_SI-dM": true,
+ "QV-Proposed-FC_SPANs:c:d95-SPAN-1_SI-body": true,
+ "QV-Proposed-FC_SPANs:c:d95-SPAN-1_SI-div": true,
+ "QV-Proposed-FC_SPAN.red-1_SI-dM": true,
+ "QV-Proposed-FC_SPAN.red-1_SI-body": true,
+ "QV-Proposed-FC_SPAN.red-1_SI-div": true,
+ "QV-Proposed-FC_MYRED-1-SI-dM": true,
+ "QV-Proposed-FC_MYRED-1-SI-body": true,
+ "QV-Proposed-FC_MYRED-1-SI-div": true,
+ "QV-Proposed-HC_FONTs:bc:fc0-1_SI-dM": true,
+ "QV-Proposed-HC_FONTs:bc:fc0-1_SI-body": true,
+ "QV-Proposed-HC_FONTs:bc:fc0-1_SI-div": true,
+ "QV-Proposed-HC_SPANs:bc:a0c-1_SI-dM": true,
+ "QV-Proposed-HC_SPANs:bc:a0c-1_SI-body": true,
+ "QV-Proposed-HC_SPANs:bc:a0c-1_SI-div": true,
+ "QV-Proposed-HC_SPAN.ass.s:bc:rgb-1_SI-dM": true,
+ "QV-Proposed-HC_SPAN.ass.s:bc:rgb-1_SI-body": true,
+ "QV-Proposed-HC_SPAN.ass.s:bc:rgb-1_SI-div": true,
+ "QV-Proposed-HC_FONTs:bc:83e-SPAN-1_SI-dM": true,
+ "QV-Proposed-HC_FONTs:bc:83e-SPAN-1_SI-body": true,
+ "QV-Proposed-HC_FONTs:bc:83e-SPAN-1_SI-div": true,
+ "QV-Proposed-HC_SPANs:bc:b12-SPAN-1_SI-dM": true,
+ "QV-Proposed-HC_SPANs:bc:b12-SPAN-1_SI-body": true,
+ "QV-Proposed-HC_SPANs:bc:b12-SPAN-1_SI-div": true,
+ "QV-Proposed-HC_SPAN.bcred-1_SI-dM": true,
+ "QV-Proposed-HC_SPAN.bcred-1_SI-body": true,
+ "QV-Proposed-HC_SPAN.bcred-1_SI-div": true,
+ "QV-Proposed-HC_MYBCRED-1-SI-dM": true,
+ "QV-Proposed-HC_MYBCRED-1-SI-body": true,
+ "QV-Proposed-HC_MYBCRED-1-SI-div": true
+ }
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/current_revision b/editor/libeditor/tests/browserscope/lib/richtext2/current_revision
new file mode 100644
index 000000000..cc34bb397
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/current_revision
@@ -0,0 +1 @@
+805
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/__init__.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/__init__.py
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/common.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/common.py
new file mode 100644
index 000000000..345f9bbb0
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/common.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Common constants"""
+
+__author__ = 'rolandsteiner@google.com (Roland Steiner)'
+
+CATEGORY = 'richtext2'
+
+TEST_ID_PREFIX = 'RTE2'
+
+CLASSES = ['Finalized', 'RFC', 'Proposed'] \ No newline at end of file
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/handlers.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/handlers.py
new file mode 100644
index 000000000..2ee1e79ad
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/handlers.py
@@ -0,0 +1,107 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Handlers for New Rich Text Tests"""
+
+__author__ = 'rolandsteiner@google.com (Roland Steiner)'
+
+from google.appengine.api import users
+from google.appengine.ext import db
+from google.appengine.api import memcache
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+
+import django
+from django import http
+from django import shortcuts
+
+from django.template import add_to_builtins
+add_to_builtins('base.custom_filters')
+
+# Shared stuff
+from categories import all_test_sets
+from base import decorators
+from base import util
+
+# common to the RichText2 suite
+from categories.richtext2 import common
+
+# tests
+from categories.richtext2.tests.apply import APPLY_TESTS
+from categories.richtext2.tests.applyCSS import APPLY_TESTS_CSS
+from categories.richtext2.tests.change import CHANGE_TESTS
+from categories.richtext2.tests.changeCSS import CHANGE_TESTS_CSS
+from categories.richtext2.tests.delete import DELETE_TESTS
+from categories.richtext2.tests.forwarddelete import FORWARDDELETE_TESTS
+from categories.richtext2.tests.insert import INSERT_TESTS
+from categories.richtext2.tests.selection import SELECTION_TESTS
+from categories.richtext2.tests.unapply import UNAPPLY_TESTS
+from categories.richtext2.tests.unapplyCSS import UNAPPLY_TESTS_CSS
+
+from categories.richtext2.tests.querySupported import QUERYSUPPORTED_TESTS
+from categories.richtext2.tests.queryEnabled import QUERYENABLED_TESTS
+from categories.richtext2.tests.queryIndeterm import QUERYINDETERM_TESTS
+from categories.richtext2.tests.queryState import QUERYSTATE_TESTS, QUERYSTATE_TESTS_CSS
+from categories.richtext2.tests.queryValue import QUERYVALUE_TESTS, QUERYVALUE_TESTS_CSS
+
+
+def About(request):
+ """About page."""
+ overview = """These tests cover browers' implementations of
+ <a href="http://blog.whatwg.org/the-road-to-html-5-contenteditable">contenteditable</a>
+ for basic rich text formatting commands. Most browser implementations do very
+ well at editing the HTML which is generated by their own execCommands. But a
+ big problem happens when developers try to make cross-browser web
+ applications using contenteditable - most browsers are not able to correctly
+ change formatting generated by other browsers. On top of that, most browsers
+ allow users to to paste arbitrary HTML from other webpages into a
+ contenteditable region, which is even harder for browsers to properly
+ format. These tests check how well the execCommand, queryCommandState,
+ and queryCommandValue functions work with different types of HTML."""
+ return util.About(request, common.CATEGORY, category_title='Rich Text',
+ overview=overview, show_hidden=False)
+
+
+def RunRichText2Tests(request):
+ params = {
+ 'classes': common.CLASSES,
+ 'commonIDPrefix': common.TEST_ID_PREFIX,
+ 'strict': False,
+ 'suites': [
+ SELECTION_TESTS,
+ APPLY_TESTS,
+ APPLY_TESTS_CSS,
+ CHANGE_TESTS,
+ CHANGE_TESTS_CSS,
+ UNAPPLY_TESTS,
+ UNAPPLY_TESTS_CSS,
+ DELETE_TESTS,
+ FORWARDDELETE_TESTS,
+ INSERT_TESTS,
+
+ QUERYSUPPORTED_TESTS,
+ QUERYENABLED_TESTS,
+ QUERYINDETERM_TESTS,
+ QUERYSTATE_TESTS,
+ QUERYSTATE_TESTS_CSS,
+ QUERYVALUE_TESTS,
+ QUERYVALUE_TESTS_CSS
+ ]
+ }
+ return shortcuts.render_to_response('%s/templates/richtext2.html' % common.CATEGORY, params)
+
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/common.css b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/common.css
new file mode 100644
index 000000000..77c6bb872
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/common.css
@@ -0,0 +1,116 @@
+.framed {
+ vertical-align: top;
+ margin: 8px;
+ border: 1px solid black;
+}
+
+.legend {
+ padding: 12px;
+ background-color: #f8f8ff;
+}
+
+.legendHdr {
+ font-size: large;
+ text-decoration: underline;
+}
+
+table.legend {
+ display: inline-table;
+}
+
+.suite-thead {
+ text-align: left;
+}
+
+.lo {
+ background-color: #dddddd;
+}
+.hi {
+ background-color: #eeeeee;
+}
+
+.lo .grey {
+ background-color: #dddddd;
+}
+.lo .na {
+ background-color: #dddddd;
+}
+.lo .pass {
+ background-color: #d4ffc0;
+}
+.lo .canary {
+ background-color: #ffcccc;
+}
+.lo .fail {
+ background-color: #ffcccc;
+}
+.lo .accept {
+ background-color: #ffffc0;
+}
+.lo .exception {
+ background-color: #f0d0f4;
+}
+.lo .unsupported {
+ background-color: #f0d0f4;
+}
+
+.hi .grey {
+ background-color: #eeeeee;
+}
+.hi .na {
+ background-color: #eeeeee;
+}
+.hi .pass {
+ background-color: #e0ffdc;
+}
+.hi .canary {
+ background-color: #ffd8d8;
+}
+.hi .fail {
+ background-color: #ffd8d8;
+}
+.hi .accept {
+ background-color: #ffffd8;
+}
+.hi .exception {
+ background-color: #f4dcf8;
+}
+.hi .unsupported {
+ background-color: #f4dcf8;
+}
+
+
+.sel {
+ color: blue;
+}
+
+.txt {
+ padding: 1px;
+ margin: 1px;
+ border: 1px solid #b0b0b0;
+}
+
+.idLabel {
+ font-size: small;
+}
+
+.fade {
+ color: grey;
+}
+.accexp {
+ color: #606070;
+}
+.comment {
+ color: grey;
+}
+
+.score {
+ color: #666666;
+}
+
+.fatalerror {
+ color: red;
+ font-size: large;
+ font-weight: bold;
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-body.html b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-body.html
new file mode 100644
index 000000000..a254adc03
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-body.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+
+ <link rel="stylesheet" href="editable.css" type="text/css">
+</head>
+<body contentEditable="true">
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-dM.html b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-dM.html
new file mode 100644
index 000000000..e16de3ab9
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-dM.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+
+ <link rel="stylesheet" href="editable.css" type="text/css">
+
+ <script>
+ function setDesignMode() {
+ window.document.designMode = "On";
+ }
+ </script>
+</head>
+<body onload="setDesignMode()">
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-div.html b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-div.html
new file mode 100644
index 000000000..7dd600dbd
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable-div.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+
+ <link rel="stylesheet" href="editable.css" type="text/css">
+</head>
+<body>
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable.css b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable.css
new file mode 100644
index 000000000..99fec4950
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/editable.css
@@ -0,0 +1,66 @@
+.b, myb {
+ font-weight: bold;
+}
+
+.i, myi {
+ font-style: italic;
+}
+
+.s, mys {
+ text-decoration: line-through;
+}
+
+.u, myu {
+ text-decoration: underline;
+}
+
+.sub, mysub {
+ vertical-align: sub;
+}
+
+.sup, mysup {
+ vertical-align: super;
+}
+
+.jc, myjc {
+ text-align: center;
+}
+
+.jf, myjf {
+ text-align: justify;
+}
+
+.jl, myjl {
+ text-align: left;
+}
+
+.jr, myjr {
+ text-align: right;
+}
+
+.red, myred {
+ color: red;
+}
+
+.bcred, mybcred {
+ background-color: red;
+}
+
+.large, mylarge {
+ font-size: large;
+}
+
+.fs18px, myfs18px {
+ font-size: 18px;
+}
+
+.courier, mycourier {
+ font-family: courier;
+}
+
+gen::before {
+ content: "[BEFORE]";
+}
+gen::after {
+ content: "[AFTER]";
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/canonicalize.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/canonicalize.js
new file mode 100644
index 000000000..2236d9dfc
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/canonicalize.js
@@ -0,0 +1,436 @@
+/**
+ * @fileoverview
+ * Canonicalization functions used in the RTE test suite.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+/**
+ * Canonicalize HTML entities to their actual character
+ *
+ * @param str {String} the HTML string to be canonicalized
+ * @return {String} the canonicalized string
+ */
+
+function canonicalizeEntities(str) {
+ // TODO(rolandsteiner): this function is very much not optimized, but that shouldn't
+ // theoretically matter too much - look into it at some point.
+ var match;
+ while (match = str.match(/&#x([0-9A-F]+);/i)) {
+ str = str.replace('&#x' + match[1] + ';', String.fromCharCode(parseInt(match[1], 16)));
+ }
+ while (match = str.match(/&#([0-9]+);/)) {
+ str = str.replace('&#' + match[1] + ';', String.fromCharCode(Number(match[1])));
+ }
+ return str;
+}
+
+/**
+ * Canonicalize the contents of the HTML 'style' attribute.
+ * I.e. sorts the CSS attributes alphabetically and canonicalizes the values
+ * CSS attributes where necessary.
+ *
+ * If this would return an empty string, return null instead to suppress the
+ * whole 'style' attribute.
+ *
+ * Avoid tests that contain {, } or : within CSS values!
+ *
+ * Note that this function relies on the spaces of the input string already
+ * having been normalized by canonicalizeSpaces!
+ *
+ * FIXME: does not canonicalize the contents of compound attributes
+ * (e.g., 'border').
+ *
+ * @param str {String} contents of the 'style' attribute
+ * @param emitFlags {Object} flags used for this output
+ * @return {String/null} canonicalized string, null instead of the empty string
+ */
+function canonicalizeStyle(str, emitFlags) {
+ // Remove any enclosing curly brackets
+ str = str.replace(/ ?[\{\}] ?/g, '');
+
+ var attributes = str.split(';');
+ var count = attributes.length;
+ var resultArr = [];
+
+ for (var a = 0; a < count; ++a) {
+ // Retrieve "name: value" pair
+ // Note: may expectedly fail if the last pair was terminated with ';'
+ var avPair = attributes[a].match(/ ?([^ :]+) ?: ?(.+)/);
+ if (!avPair)
+ continue;
+
+ var name = avPair[1];
+ var value = avPair[2].replace(/ $/, ''); // Remove any trailing space.
+
+ switch (name) {
+ case 'color':
+ case 'background-color':
+ case 'border-color':
+ if (emitFlags.canonicalizeUnits) {
+ resultArr.push(name + ': #' + new Color(value).toHexString());
+ } else {
+ resultArr.push(name + ': ' + value);
+ }
+ break;
+
+ case 'font-family':
+ if (emitFlags.canonicalizeUnits) {
+ resultArr.push(name + ': ' + new FontName(value).toString());
+ } else {
+ resultArr.push(name + ': ' + value);
+ }
+ break;
+
+ case 'font-size':
+ if (emitFlags.canonicalizeUnits) {
+ resultArr.push(name + ': ' + new FontSize(value).toString());
+ } else {
+ resultArr.push(name + ': ' + value);
+ }
+ break;
+
+ default:
+ resultArr.push(name + ': ' + value);
+ }
+ }
+
+ // Sort by name, assuming no duplicate CSS attribute names.
+ resultArr.sort();
+
+ return resultArr.join('; ') || null;
+}
+
+/**
+ * Canonicalize a single attribute value.
+ *
+ * Note that this function relies on the spaces of the input string already
+ * having been normalized by canonicalizeSpaces!
+ *
+ * @param elemName {String} the name of the element
+ * @param attrName {String} the name of the attribute
+ * @param attrValue {String} the value of the attribute
+ * @param emitFlags {Object} flags used for this output
+ * @return {String/null} the canonicalized value, or null if the attribute should be skipped.
+ */
+function canonicalizeSingleAttribute(elemName, attrName, attrValue, emitFlags) {
+ // We emit attributes as name="value", so change any contained apostrophes
+ // to quote marks.
+ attrValue = attrValue.replace(/\x22/, '\x27');
+
+ switch (attrName) {
+ case 'class':
+ return emitFlags.emitClass ? attrValue : null;
+
+ case 'id':
+ if (!emitFlags.emitID) {
+ return null;
+ }
+ if (attrValue && attrValue.substr(0, 7) == 'editor-') {
+ return null;
+ }
+ return attrValue;
+
+ // Remove empty style attributes, canonicalize the contents otherwise,
+ // provided the test cares for styles.
+ case 'style':
+ return (emitFlags.emitStyle && attrValue)
+ ? canonicalizeStyle(attrValue, emitFlags)
+ : null;
+
+ // Never output onload handlers as they are set by the test environment.
+ case 'onload':
+ return null;
+
+ // Canonicalize colors.
+ case 'bgcolor':
+ case 'color':
+ if (!attrValue) {
+ return null;
+ }
+ return emitFlags.canonicalizeUnits ? new Color(attrValue).toString() : attrValue;
+
+ // Canonicalize font names.
+ case 'face':
+ return emitFlags.canonicalizeUnits ? new FontName(attrValue).toString() : attrValue;
+
+ // Canonicalize font sizes (leave other 'size' attributes as-is).
+ case 'size':
+ if (!attrValue) {
+ return null;
+ }
+ switch (elemName) {
+ case 'basefont':
+ case 'font':
+ return emitFlags.canonicalizeUnits ? new FontSize(attrValue).toString() : attrValue;
+ }
+ return attrValue;
+
+ // Remove spans with value 1. Retain spans with other values, even if
+ // empty or with a value 0, since those indicate a flawed implementation.
+ case 'colspan':
+ case 'rowspan':
+ case 'span':
+ return (attrValue == '1' || attrValue === '') ? null : attrValue;
+
+ // Boolean attributes: presence equals true. If present, the value must be
+ // the empty string or the attribute's canonical name.
+ // (http://www.whatwg.org/specs/web-apps/current-work/#boolean-attributes)
+ // Below we only normalize empty string to the canonical name for
+ // comparison purposes. All other values are not touched and will therefore
+ // in all likelihood result in a failed test (even if they may be accepted
+ // by the UA).
+ case 'async':
+ case 'autofocus':
+ case 'checked':
+ case 'compact':
+ case 'declare':
+ case 'defer':
+ case 'disabled':
+ case 'formnovalidate':
+ case 'frameborder':
+ case 'ismap':
+ case 'loop':
+ case 'multiple':
+ case 'nohref':
+ case 'nosize':
+ case 'noshade':
+ case 'novalidate':
+ case 'nowrap':
+ case 'open':
+ case 'readonly':
+ case 'required':
+ case 'reversed':
+ case 'seamless':
+ case 'selected':
+ return attrValue ? attrValue : attrName;
+
+ default:
+ return attrValue;
+ }
+}
+
+/**
+ * Canonicalize the contents of an element tag.
+ *
+ * I.e. sorts the attributes alphabetically and canonicalizes their
+ * values where necessary. Also removes attributes we're not interested in.
+ *
+ * Note that this function relies on the spaces of the input string already
+ * having been normalized by canonicalizeSpaces!
+ *
+ * @param str {String} the contens of the element tag, excluding < and >.
+ * @param emitFlags {Object} flags used for this output
+ * @return {String} the canonicalized contents.
+ */
+function canonicalizeElementTag(str, emitFlags) {
+ // FIXME: lowercase only if emitFlags.lowercase is set
+ str = str.toLowerCase();
+
+ var pos = str.search(' ');
+
+ // element name only
+ if (pos == -1) {
+ return str;
+ }
+
+ var elemName = str.substr(0, pos);
+ str = str.substr(pos + 1);
+
+ // Even if emitFlags.emitAttrs is not set, we must iterate over the
+ // attributes to catch the special selection attribute and/or selection
+ // markers. :(
+
+ // Iterate over attributes, add them to an array, canonicalize their
+ // contents, and finally output the (remaining) attributes in sorted order.
+ // Note: We can't do a simple split on space here, because the value of,
+ // e.g., 'style' attributes may also contain spaces.
+ var attrs = [];
+ var selStartInTag = false;
+ var selEndInTag = false;
+
+ while (str) {
+ var attrName;
+ var attrValue = '';
+
+ pos = str.search(/[ =]/);
+ if (pos >= 0) {
+ attrName = str.substr(0, pos);
+ if (str.charAt(pos) == ' ') {
+ ++pos;
+ }
+ if (str.charAt(pos) == '=') {
+ ++pos;
+ if (str.charAt(pos) == ' ') {
+ ++pos;
+ }
+ str = str.substr(pos);
+ switch (str.charAt(0)) {
+ case '"':
+ case "'":
+ pos = str.indexOf(str.charAt(0), 1);
+ pos = (pos < 0) ? str.length : pos;
+ attrValue = str.substring(1, pos);
+ ++pos;
+ break;
+
+ default:
+ pos = str.indexOf(' ', 0);
+ pos = (pos < 0) ? str.length : pos;
+ attrValue = (pos == -1) ? str : str.substr(0, pos);
+ break;
+ }
+ attrValue = attrValue.replace(/^ /, '');
+ attrValue = attrValue.replace(/ $/, '');
+ }
+ } else {
+ attrName = str;
+ }
+ str = (pos == -1 || pos >= str.length) ? '' : str.substr(pos + 1);
+
+ // Remove special selection attributes.
+ switch (attrName) {
+ case ATTRNAME_SEL_START:
+ selStartInTag = true;
+ continue;
+
+ case ATTRNAME_SEL_END:
+ selEndInTag = true;
+ continue;
+ }
+
+ switch (attrName) {
+ case '':
+ case 'onload':
+ case 'xmlns':
+ break;
+
+ default:
+ if (!emitFlags.emitAttrs) {
+ break;
+ }
+ // >>> fall through >>>
+
+ case 'contenteditable':
+ attrValue = canonicalizeEntities(attrValue);
+ attrValue = canonicalizeSingleAttribute(elemName, attrName, attrValue, emitFlags);
+ if (attrValue !== null) {
+ attrs.push(attrName + '="' + attrValue + '"');
+ }
+ }
+ }
+
+ var result = elemName;
+
+ // Sort alphabetically (on full string rather than just attribute value for
+ // simplicity. Also, attribute names will differ when encountering the '=').
+ if (attrs.length > 0) {
+ attrs.sort();
+ result += ' ' + attrs.join(' ');
+ }
+
+ // Add intra-tag selection marker(s) or attribute(s), if any, at the end.
+ if (selStartInTag && selEndInTag) {
+ result += ' |';
+ } else if (selStartInTag) {
+ result += ' {';
+ } else if (selEndInTag) {
+ result += ' }';
+ }
+
+ return result;
+}
+
+/**
+ * Canonicalize elements and attributes to facilitate comparison to the
+ * expectation string: sort attributes, canonicalize values and remove chaff.
+ *
+ * Note that this function relies on the spaces of the input string already
+ * having been normalized by canonicalizeSpaces!
+ *
+ * @param str {String} the HTML string to be canonicalized
+ * @param emitFlags {Object} flags used for this output
+ * @return {String} the canonicalized string
+ */
+function canonicalizeElementsAndAttributes(str, emitFlags) {
+ var tagStart = str.indexOf('<');
+ var tagEnd = 0;
+ var result = '';
+
+ while (tagStart >= 0) {
+ ++tagStart;
+ if (str.charAt(tagStart) == '/') {
+ ++tagStart;
+ }
+ result = result + canonicalizeEntities(str.substring(tagEnd, tagStart));
+ tagEnd = str.indexOf('>', tagStart);
+ if (tagEnd < 0) {
+ tagEnd = str.length - 1;
+ }
+ if (str.charAt(tagEnd - 1) == '/') {
+ --tagEnd;
+ }
+ var elemStr = str.substring(tagStart, tagEnd);
+ elemStr = canonicalizeElementTag(elemStr, emitFlags);
+ result = result + elemStr;
+ tagStart = str.indexOf('<', tagEnd);
+ }
+ return result + canonicalizeEntities(str.substring(tagEnd));
+}
+
+/**
+ * Canonicalize an innerHTML string to uniform single whitespaces.
+ *
+ * FIXME: running this prevents testing for pre-formatted content
+ * and the CSS 'white-space' attribute.
+ *
+ * @param str {String} the HTML string to be canonicalized
+ * @return {String} the canonicalized string
+ */
+function canonicalizeSpaces(str) {
+ // Collapse sequential whitespace.
+ str = str.replace(/\s+/g, ' ');
+
+ // Remove spaces immediately inside angle brackets <, >, </ and />.
+ // While doing this also canonicalize <.../> to <...>.
+ str = str.replace(/\< ?/g, '<');
+ str = str.replace(/\<\/ ?/g, '</');
+ str = str.replace(/ ?\/?\>/g, '>');
+
+ return str;
+}
+
+/**
+ * Canonicalize an innerHTML string to uniform single whitespaces.
+ * Also remove comments to retain only embedded selection markers, and
+ * remove </br> and </hr> if present.
+ *
+ * FIXME: running this prevents testing for pre-formatted content
+ * and the CSS 'white-space' attribute.
+ *
+ * @param str {String} the HTML string to be canonicalized
+ * @return {String} the canonicalized string
+ */
+function initialCanonicalizationOf(str) {
+ str = canonicalizeSpaces(str);
+ str = str.replace(/ ?<!-- ?/g, '');
+ str = str.replace(/ ?--> ?/g, '');
+ str = str.replace(/<\/[bh]r>/g, '');
+
+ return str;
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js
new file mode 100644
index 000000000..be059cfc8
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js
@@ -0,0 +1,489 @@
+/**
+ * @fileoverview
+ * Comparison functions used in the RTE test suite.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+/**
+ * constants used only in the compare functions.
+ */
+var RESULT_DIFF = 0; // actual result doesn't match expectation
+var RESULT_SEL = 1; // actual result matches expectation in HTML only
+var RESULT_EQUAL = 2; // actual result matches expectation in both HTML and selection
+
+/**
+ * Gets the test expectations as an array from the passed-in field.
+ *
+ * @param {Array|String} the test expectation(s) as string or array.
+ * @return {Array} test expectations as an array.
+ */
+function getExpectationArray(expected) {
+ if (expected === undefined) {
+ return [];
+ }
+ if (expected === null) {
+ return [null];
+ }
+ switch (typeof expected) {
+ case 'string':
+ case 'boolean':
+ case 'number':
+ return [expected];
+ }
+ // Assume it's already an array.
+ return expected;
+}
+
+/**
+ * Compare a test result to a single expectation string.
+ *
+ * FIXME: add support for optional elements/attributes.
+ *
+ * @param expected {String} the already canonicalized (with the exception of selection marks) expectation string
+ * @param actual {String} the already canonicalized (with the exception of selection marks) actual result
+ * @return {Integer} one of the RESULT_... return values
+ * @see variables.js for return values
+ */
+function compareHTMLToSingleExpectation(expected, actual) {
+ // If the test checks the selection, then the actual string must match the
+ // expectation exactly.
+ if (expected == actual) {
+ return RESULT_EQUAL;
+ }
+
+ // Remove selection markers and see if strings match then.
+ expected = expected.replace(/ [{}\|]>/g, '>'); // intra-tag
+ expected = expected.replace(/[\[\]\^{}\|]/g, ''); // outside tag
+ actual = actual.replace(/ [{}\|]>/g, '>'); // intra-tag
+ actual = actual.replace(/[\[\]\^{}\|]/g, ''); // outside tag
+
+ return (expected == actual) ? RESULT_SEL : RESULT_DIFF;
+}
+
+/**
+ * Compare the current HTMLtest result to the expectation string(s).
+ *
+ * @param actual {String/Boolean} actual value
+ * @param expected {String/Array} expectation(s)
+ * @param emitFlags {Object} flags to use for canonicalization
+ * @return {Integer} one of the RESULT_... return values
+ * @see variables.js for return values
+ */
+function compareHTMLToExpectation(actual, expected, emitFlags) {
+ // Find the most favorable result among the possible expectation strings.
+ var expectedArr = getExpectationArray(expected);
+ var count = expectedArr ? expectedArr.length : 0;
+ var best = RESULT_DIFF;
+
+ for (var idx = 0; idx < count && best < RESULT_EQUAL; ++idx) {
+ var expected = expectedArr[idx];
+ expected = canonicalizeSpaces(expected);
+ expected = canonicalizeElementsAndAttributes(expected, emitFlags);
+
+ var singleResult = compareHTMLToSingleExpectation(expected, actual);
+
+ best = Math.max(best, singleResult);
+ }
+ return best;
+}
+
+/**
+ * Compare the current HTMLtest result to expected and acceptable results
+ *
+ * @param expected {String/Array} expected result(s)
+ * @param accepted {String/Array} accepted result(s)
+ * @param actual {String} actual result
+ * @param emitFlags {Object} how to canonicalize the HTML strings
+ * @param result {Object} [out] object recieving the result of the comparison.
+ */
+function compareHTMLTestResultTo(expected, accepted, actual, emitFlags, result) {
+ actual = actual.replace(/[\x60\xb4]/g, '');
+ actual = canonicalizeElementsAndAttributes(actual, emitFlags);
+
+ var bestExpected = compareHTMLToExpectation(actual, expected, emitFlags);
+
+ if (bestExpected == RESULT_EQUAL) {
+ // Shortcut - it doesn't get any better
+ result.valresult = VALRESULT_EQUAL;
+ result.selresult = SELRESULT_EQUAL;
+ return;
+ }
+
+ var bestAccepted = compareHTMLToExpectation(actual, accepted, emitFlags);
+
+ switch (bestExpected) {
+ case RESULT_SEL:
+ switch (bestAccepted) {
+ case RESULT_EQUAL:
+ // The HTML was equal to the/an expected HTML result as well
+ // (just not the selection there), therefore the difference
+ // between expected and accepted can only lie in the selection.
+ result.valresult = VALRESULT_EQUAL;
+ result.selresult = SELRESULT_ACCEPT;
+ return;
+
+ case RESULT_SEL:
+ case RESULT_DIFF:
+ // The acceptable expectations did not yield a better result
+ // -> stay with the original (i.e., comparison to 'expected') result.
+ result.valresult = VALRESULT_EQUAL;
+ result.selresult = SELRESULT_DIFF;
+ return;
+ }
+ break;
+
+ case RESULT_DIFF:
+ switch (bestAccepted) {
+ case RESULT_EQUAL:
+ result.valresult = VALRESULT_ACCEPT;
+ result.selresult = SELRESULT_EQUAL;
+ return;
+
+ case RESULT_SEL:
+ result.valresult = VALRESULT_ACCEPT;
+ result.selresult = SELRESULT_DIFF;
+ return;
+
+ case RESULT_DIFF:
+ result.valresult = VALRESULT_DIFF;
+ result.selresult = SELRESULT_NA;
+ return;
+ }
+ break;
+ }
+
+ throw INTERNAL_ERR + HTML_COMPARISON;
+}
+
+/**
+ * Verify that the canaries are unviolated.
+ *
+ * @param container {Object} the test container descriptor as object reference
+ * @param result {Object} object reference that contains the result data
+ * @return {Boolean} whether the canaries' HTML is OK (selection flagged, but not fatal)
+ */
+function verifyCanaries(container, result) {
+ if (!container.canary) {
+ return true;
+ }
+
+ var str = canonicalizeElementsAndAttributes(result.bodyInnerHTML, emitFlagsForCanary);
+
+ if (str.length < 2 * container.canary.length) {
+ result.valresult = VALRESULT_CANARY;
+ result.selresult = SELRESULT_NA;
+ result.output = result.bodyOuterHTML;
+ return false;
+ }
+
+ var strBefore = str.substr(0, container.canary.length);
+ var strAfter = str.substr(str.length - container.canary.length);
+
+ // Verify that the canary stretch doesn't contain any selection markers
+ if (SELECTION_MARKERS.test(strBefore) || SELECTION_MARKERS.test(strAfter)) {
+ str = str.replace(SELECTION_MARKERS, '');
+ if (str.length < 2 * container.canary.length) {
+ result.valresult = VALRESULT_CANARY;
+ result.selresult = SELRESULT_NA;
+ result.output = result.bodyOuterHTML;
+ return false;
+ }
+
+ // Selection escaped contentEditable element, but HTML may still be ok.
+ result.selresult = SELRESULT_CANARY;
+ strBefore = str.substr(0, container.canary.length);
+ strAfter = str.substr(str.length - container.canary.length);
+ }
+
+ if (strBefore !== container.canary || strAfter !== container.canary) {
+ result.valresult = VALRESULT_CANARY;
+ result.selresult = SELRESULT_NA;
+ result.output = result.bodyOuterHTML;
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Compare the current HTMLtest result to the expectation string(s).
+ * Sets the global result variables.
+ *
+ * @param suite {Object} the test suite as object reference
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} the test as object reference
+ * @param container {Object} the test container description
+ * @param result {Object} [in/out] the result description, incl. HTML strings
+ * @see variables.js for result values
+ */
+function compareHTMLTestResult(suite, group, test, container, result) {
+ if (!verifyCanaries(container, result)) {
+ return;
+ }
+
+ var emitFlags = {
+ emitAttrs: getTestParameter(suite, group, test, PARAM_CHECK_ATTRIBUTES),
+ emitStyle: getTestParameter(suite, group, test, PARAM_CHECK_STYLE),
+ emitClass: getTestParameter(suite, group, test, PARAM_CHECK_CLASS),
+ emitID: getTestParameter(suite, group, test, PARAM_CHECK_ID),
+ lowercase: true,
+ canonicalizeUnits: true
+ };
+
+ // 2a.) Compare opening tag -
+ // decide whether to compare vs. outer or inner HTML based on this.
+ var openingTagEnd = result.outerHTML.indexOf('>') + 1;
+ var openingTag = result.outerHTML.substr(0, openingTagEnd);
+
+ openingTag = canonicalizeElementsAndAttributes(openingTag, emitFlags);
+ var tagCmp = compareHTMLToExpectation(openingTag, container.tagOpen, emitFlags);
+
+ if (tagCmp == RESULT_EQUAL) {
+ result.output = result.innerHTML;
+ compareHTMLTestResultTo(
+ getTestParameter(suite, group, test, PARAM_EXPECTED),
+ getTestParameter(suite, group, test, PARAM_ACCEPT),
+ result.innerHTML,
+ emitFlags,
+ result)
+ } else {
+ result.output = result.outerHTML;
+ compareHTMLTestResultTo(
+ getContainerParameter(suite, group, test, container, PARAM_EXPECTED_OUTER),
+ getContainerParameter(suite, group, test, container, PARAM_ACCEPT_OUTER),
+ result.outerHTML,
+ emitFlags,
+ result)
+ }
+}
+
+/**
+ * Insert a selection position indicator.
+ *
+ * @param node {DOMNode} the node where to insert the selection indicator
+ * @param offs {Integer} the offset of the selection indicator
+ * @param textInd {String} the indicator to use if the node is a text node
+ * @param elemInd {String} the indicator to use if the node is an element node
+ */
+function insertSelectionIndicator(node, offs, textInd, elemInd) {
+ switch (node.nodeType) {
+ case DOM_NODE_TYPE_TEXT:
+ // Insert selection marker for text node into text content.
+ var text = node.data;
+ node.data = text.substring(0, offs) + textInd + text.substring(offs);
+ break;
+
+ case DOM_NODE_TYPE_ELEMENT:
+ var child = node.firstChild;
+ try {
+ // node has other children: insert marker as comment node
+ var comment = document.createComment(elemInd);
+ while (child && offs) {
+ --offs;
+ child = child.nextSibling;
+ }
+ if (child) {
+ node.insertBefore(comment, child);
+ } else {
+ node.appendChild(comment);
+ }
+ } catch (ex) {
+ // can't append child comment -> insert as special attribute(s)
+ switch (elemInd) {
+ case '|':
+ node.setAttribute(ATTRNAME_SEL_START, '1');
+ node.setAttribute(ATTRNAME_SEL_END, '1');
+ break;
+
+ case '{':
+ node.setAttribute(ATTRNAME_SEL_START, '1');
+ break;
+
+ case '}':
+ node.setAttribute(ATTRNAME_SEL_END, '1');
+ break;
+ }
+ }
+ break;
+ }
+}
+
+/**
+ * Adds quotes around all text nodes to show cases with non-normalized
+ * text nodes. Those are not a bug, but may still be usefil in helping to
+ * debug erroneous cases.
+ *
+ * @param node {DOMNode} root node from which to descend
+ */
+function encloseTextNodesWithQuotes(node) {
+ switch (node.nodeType) {
+ case DOM_NODE_TYPE_ELEMENT:
+ for (var i = 0; i < node.childNodes.length; ++i) {
+ encloseTextNodesWithQuotes(node.childNodes[i]);
+ }
+ break;
+
+ case DOM_NODE_TYPE_TEXT:
+ node.data = '\x60' + node.data + '\xb4';
+ break;
+ }
+}
+
+/**
+ * Retrieve the result of a test run and do some preliminary canonicalization.
+ *
+ * @param container {Object} the container where to retrieve the result from as object reference
+ * @param result {Object} object reference that contains the result data
+ * @return {String} a preliminarily canonicalized innerHTML with selection markers
+ */
+function prepareHTMLTestResult(container, result) {
+ // Start with empty strings in case any of the below throws.
+ result.innerHTML = '';
+ result.outerHTML = '';
+
+ // 1.) insert selection markers
+ var selRange = createFromWindow(container.win);
+ if (selRange) {
+ // save values, since range object gets auto-modified
+ var node1 = selRange.getAnchorNode();
+ var offs1 = selRange.getAnchorOffset();
+ var node2 = selRange.getFocusNode();
+ var offs2 = selRange.getFocusOffset();
+
+ // add markers
+ if (node1 && node1 == node2 && offs1 == offs2) {
+ // collapsed selection
+ insertSelectionIndicator(node1, offs1, '^', '|');
+ } else {
+ // Start point and end point are different
+ if (node1) {
+ insertSelectionIndicator(node1, offs1, '[', '{');
+ }
+
+ if (node2) {
+ if (node1 == node2 && offs1 < offs2) {
+ // Anchor indicator was inserted under the same node, so we need
+ // to shift the offset by 1
+ ++offs2;
+ }
+ insertSelectionIndicator(node2, offs2, ']', '}');
+ }
+ }
+ }
+
+ // 2.) insert markers for text node boundaries;
+ encloseTextNodesWithQuotes(container.editor);
+
+ // 3.) retrieve inner and outer HTML
+ result.innerHTML = initialCanonicalizationOf(container.editor.innerHTML);
+ result.bodyInnerHTML = initialCanonicalizationOf(container.body.innerHTML);
+ if (goog.userAgent.IE) {
+ result.outerHTML = initialCanonicalizationOf(container.editor.outerHTML);
+ result.bodyOuterHTML = initialCanonicalizationOf(container.body.outerHTML);
+ result.outerHTML = result.outerHTML.replace(/^\s+/, '');
+ result.outerHTML = result.outerHTML.replace(/\s+$/, '');
+ result.bodyOuterHTML = result.bodyOuterHTML.replace(/^\s+/, '');
+ result.bodyOuterHTML = result.bodyOuterHTML.replace(/\s+$/, '');
+ } else {
+ result.outerHTML = initialCanonicalizationOf(new XMLSerializer().serializeToString(container.editor));
+ result.bodyOuterHTML = initialCanonicalizationOf(new XMLSerializer().serializeToString(container.body));
+ }
+}
+
+/**
+ * Compare a text test result to the expectation string(s).
+ *
+ * @param suite {Object} the test suite as object reference
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} the test as object reference
+ * @param actual {String/Boolean} actual value
+ * @param expected {String/Array} expectation(s)
+ * @return {Boolean} whether we found a match
+ */
+function compareTextTestResultWith(suite, group, test, actual, expected) {
+ var expectedArr = getExpectationArray(expected);
+ // Find the most favorable result among the possible expectation strings.
+ var count = expectedArr.length;
+
+ // If the value matches the expectation exactly, then we're fine.
+ for (var idx = 0; idx < count; ++idx) {
+ if (actual === expectedArr[idx])
+ return true;
+ }
+
+ // Otherwise see if we should canonicalize specific value types.
+ //
+ // We only need to look at font name, color and size units if the originating
+ // test was both a) queryCommandValue and b) querying a font name/color/size
+ // specific criterion.
+ //
+ // TODO(rolandsteiner): This is ugly! Refactor!
+ switch (getTestParameter(suite, group, test, PARAM_QUERYCOMMANDVALUE)) {
+ case 'backcolor':
+ case 'forecolor':
+ case 'hilitecolor':
+ for (var idx = 0; idx < count; ++idx) {
+ if (new Color(actual).compare(new Color(expectedArr[idx])))
+ return true;
+ }
+ return false;
+
+ case 'fontname':
+ for (var idx = 0; idx < count; ++idx) {
+ if (new FontName(actual).compare(new FontName(expectedArr[idx])))
+ return true;
+ }
+ return false;
+
+ case 'fontsize':
+ for (var idx = 0; idx < count; ++idx) {
+ if (new FontSize(actual).compare(new FontSize(expectedArr[idx])))
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+}
+
+/**
+ * Compare the passed-in text test result to the expectation string(s).
+ * Sets the global result variables.
+ *
+ * @param suite {Object} the test suite as object reference
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} the test as object reference
+ * @param actual {String/Boolean} actual value
+ * @return {Integer} a RESUTLHTML... result value
+ * @see variables.js for result values
+ */
+function compareTextTestResult(suite, group, test, result) {
+ var expected = getTestParameter(suite, group, test, PARAM_EXPECTED);
+ if (compareTextTestResultWith(suite, group, test, result.output, expected)) {
+ result.valresult = VALRESULT_EQUAL;
+ return;
+ }
+ var accepted = getTestParameter(suite, group, test, PARAM_ACCEPT);
+ if (accepted && compareTextTestResultWith(suite, group, test, result.output, accepted)) {
+ result.valresult = VALRESULT_ACCEPT;
+ return;
+ }
+ result.valresult = VALRESULT_DIFF;
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/output.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/output.js
new file mode 100644
index 000000000..897efa011
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/output.js
@@ -0,0 +1,456 @@
+/**
+ * @fileoverview
+ * Functions used to format the test result output.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+/**
+ * Writes a fatal error to the output (replaces alert box)
+ *
+ * @param text {String} text to output
+ */
+function writeFatalError(text) {
+ var errorsStart = document.getElementById('errors');
+ var divider = document.getElementById('divider');
+ if (!errorsStart) {
+ errorsStart = document.createElement('hr');
+ errorsStart.id = 'errors';
+ divider.parentNode.insertBefore(errorsStart, divider);
+ }
+ var error = document.createElement('div');
+ error.className = 'fatalerror';
+ error.innerHTML = 'FATAL ERROR: ' + escapeOutput(text);
+ errorsStart.parentNode.insertBefore(error, divider);
+}
+
+/**
+ * Generates a unique ID for a given single test out of the suite ID and
+ * test ID.
+ *
+ * @param suiteID {string} ID string of the suite
+ * @param testID {string} ID string of the individual tests
+ * @return {string} globally unique ID
+ */
+function generateOutputID(suiteID, testID) {
+ return commonIDPrefix + '-' + suiteID + '_' + testID;
+}
+
+/**
+ * Function to highlight the selection markers
+ *
+ * @param str {String} a HTML string containing selection markers
+ * @return {String} the HTML string with highlighting tags around the markers
+ */
+function highlightSelectionMarkers(str) {
+ str = str.replace(/\[/g, '<span class="sel">[</span>');
+ str = str.replace(/\]/g, '<span class="sel">]</span>');
+ str = str.replace(/\^/g, '<span class="sel">^</span>');
+ str = str.replace(/{/g, '<span class="sel">{</span>');
+ str = str.replace(/}/g, '<span class="sel">}</span>');
+ str = str.replace(/\|/g, '<b class="sel">|</b>');
+ return str;
+}
+
+/**
+ * Function to highlight the selection markers
+ *
+ * @param str {String} a HTML string containing selection markers
+ * @return {String} the HTML string with highlighting tags around the markers
+ */
+function highlightSelectionMarkersAndTextNodes(str) {
+ str = highlightSelectionMarkers(str);
+ str = str.replace(/\x60/g, '<span class="txt">');
+ str = str.replace(/\xb4/g, '</span>');
+ return str;
+}
+
+/**
+ * Function to format output according to type
+ *
+ * @param value {String/Boolean} string or value to format
+ * @return {String} HTML-formatted string
+ */
+function formatValueOrString(value) {
+ if (value === undefined)
+ return '<i>undefined</i>';
+ if (value === null)
+ return '<i>null</i>';
+
+ switch (typeof value) {
+ case 'boolean':
+ return '<i>' + value.toString() + '</i>';
+
+ case 'number':
+ return value.toString();
+
+ case 'string':
+ return "'" + escapeOutput(value) + "'";
+
+ default:
+ return '<i>(' + escapeOutput(value.toString()) + ')</i>';
+ }
+}
+
+/**
+ * Function to highlight text nodes
+ *
+ * @param suite {Object} the suite the test belongs to
+ * @param group {Object} the group within the suite the test belongs to
+ * @param test {Object} the test description as object reference
+ * @param actual {String} a HTML string containing text nodes with markers
+ * @return {String} string with highlighting tags around the text node parts
+ */
+function formatActualResult(suite, group, test, actual) {
+ if (typeof actual != 'string')
+ return formatValueOrString(actual);
+
+ actual = escapeOutput(actual);
+
+ // Fade attributes (or just style) if not actually tested for
+ if (!getTestParameter(suite, group, test, PARAM_CHECK_ATTRIBUTES)) {
+ actual = actual.replace(/([^ =]+)=\x22([^\x22]*)\x22/g, '<span class="fade">$1="$2"</span>');
+ } else {
+ // NOTE: convert 'class="..."' first, before adding other <span class="fade">...</span> !!!
+ if (!getTestParameter(suite, group, test, PARAM_CHECK_CLASS)) {
+ actual = actual.replace(/class=\x22([^\x22]*)\x22/g, '<span class="fade">class="$1"</span>');
+ }
+ if (!getTestParameter(suite, group, test, PARAM_CHECK_STYLE)) {
+ actual = actual.replace(/style=\x22([^\x22]*)\x22/g, '<span class="fade">style="$1"</span>');
+ }
+ if (!getTestParameter(suite, group, test, PARAM_CHECK_ID)) {
+ actual = actual.replace(/id=\x22([^\x22]*)\x22/g, '<span class="fade">id="$1"</span>');
+ } else {
+ // fade out contenteditable host element's 'editor-<xyz>' ID.
+ actual = actual.replace(/id=\x22editor-([^\x22]*)\x22/g, '<span class="fade">id="editor-$1"</span>');
+ }
+ // grey out 'xmlns'
+ actual = actual.replace(/xmlns=\x22([^\x22]*)\x22/g, '<span class="fade">xmlns="$1"</span>');
+ // remove 'onload'
+ actual = actual.replace(/onload=\x22[^\x22]*\x22 ?/g, '');
+ }
+ // Highlight selection markers and text nodes.
+ actual = highlightSelectionMarkersAndTextNodes(actual);
+
+ return actual;
+}
+
+/**
+ * Escape text content for use with .innerHTML.
+ *
+ * @param str {String} HTML text to displayed
+ * @return {String} the escaped HTML
+ */
+function escapeOutput(str) {
+ return str ? str.replace(/\</g, '&lt;').replace(/\>/g, '&gt;') : '';
+}
+
+/**
+ * Fills in a single output table cell
+ *
+ * @param id {String} ID of the table cell
+ * @param val {String} inner HTML to set
+ * @param ttl {String, optional} value of the 'title' attribute
+ * @param cls {String, optional} class name for the cell
+ */
+function setTD(id, val, ttl, cls) {
+ var td = document.getElementById(id);
+ if (td) {
+ td.innerHTML = val;
+ if (ttl) {
+ td.title = ttl;
+ }
+ if (cls) {
+ td.className = cls;
+ }
+ }
+}
+
+/**
+ * Outputs the results of a single test suite
+ *
+ * @param suite {Object} test suite as object reference
+ * @param clsID {String} test class ID ('Proposed', 'RFC', 'Final')
+ * @param group {Object} the group of tests within the suite the test belongs to
+ * @param testIdx {Object} the test as object reference
+ */
+function outputTestResults(suite, clsID, group, test) {
+ var suiteID = suite.id;
+ var cls = suite[clsID];
+ var trID = generateOutputID(suiteID, test.id);
+ var testResult = results[suiteID][clsID][test.id];
+ var testValOut = VALOUTPUT[testResult.valresult];
+ var testSelOut = SELOUTPUT[testResult.selresult];
+
+ var suiteChecksSelOnly = !suiteChecksHTMLOrText(suite);
+ var testUsesHTML = !!getTestParameter(suite, group, test, PARAM_EXECCOMMAND) ||
+ !!getTestParameter(suite, group, test, PARAM_FUNCTION);
+
+ // Set background color for test ID
+ var td = document.getElementById(trID + IDOUT_TESTID);
+ if (td) {
+ td.className = (suiteChecksSelOnly && testResult.selresult != SELRESULT_NA) ? testSelOut.css : testValOut.css;
+ }
+
+ // Fill in "Command" and "Value" cells
+ var cmd;
+ var cmdOutput = '&nbsp;';
+ var valOutput = '&nbsp;';
+
+ if (cmd = getTestParameter(suite, group, test, PARAM_EXECCOMMAND)) {
+ cmdOutput = escapeOutput(cmd);
+ var val = getTestParameter(suite, group, test, PARAM_VALUE);
+ if (val !== undefined) {
+ valOutput = formatValueOrString(val);
+ }
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_FUNCTION)) {
+ cmdOutput = '<i>' + escapeOutput(cmd) + '</i>';
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDSUPPORTED)) {
+ cmdOutput = '<i>queryCommandSupported</i>';
+ valOutput = escapeOutput(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDENABLED)) {
+ cmdOutput = '<i>queryCommandEnabled</i>';
+ valOutput = escapeOutput(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDINDETERM)) {
+ cmdOutput = '<i>queryCommandIndeterm</i>';
+ valOutput = escapeOutput(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDSTATE)) {
+ cmdOutput = '<i>queryCommandState</i>';
+ valOutput = escapeOutput(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDVALUE)) {
+ cmdOutput = '<i>queryCommandValue</i>';
+ valOutput = escapeOutput(cmd);
+ } else {
+ cmdOutput = '<i>(none)</i>';
+ }
+ setTD(trID + IDOUT_COMMAND, cmdOutput);
+ setTD(trID + IDOUT_VALUE, valOutput);
+
+ // Fill in "Attribute checked?" and "Style checked?" cells
+ if (testUsesHTML) {
+ var checkAttrs = getTestParameter(suite, group, test, PARAM_CHECK_ATTRIBUTES);
+ var checkStyle = getTestParameter(suite, group, test, PARAM_CHECK_STYLE);
+
+ setTD(trID + IDOUT_CHECKATTRS,
+ checkAttrs ? OUTSTR_YES : OUTSTR_NO,
+ checkAttrs ? 'attributes must match' : 'attributes are ignored');
+
+ if (checkAttrs && checkStyle) {
+ setTD(trID + IDOUT_CHECKSTYLE, OUTSTR_YES, 'style attribute contents must match');
+ } else if (checkAttrs) {
+ setTD(trID + IDOUT_CHECKSTYLE, OUTSTR_NO, 'style attribute contents is ignored');
+ } else {
+ setTD(trID + IDOUT_CHECKSTYLE, OUTSTR_NO, 'all attributes (incl. style) are ignored');
+ }
+ } else {
+ setTD(trID + IDOUT_CHECKATTRS, OUTSTR_NA, 'attributes not applicable');
+ setTD(trID + IDOUT_CHECKSTYLE, OUTSTR_NA, 'style not applicable');
+ }
+
+ // Fill in test pad specification cell (initial HTML + selection markers)
+ setTD(trID + IDOUT_PAD, highlightSelectionMarkers(escapeOutput(getTestParameter(suite, group, test, PARAM_PAD))));
+
+ // Fill in expected result(s) cell
+ var expectedOutput = '';
+ var expectedArr = getExpectationArray(getTestParameter(suite, group, test, PARAM_EXPECTED));
+ for (var idx = 0; idx < expectedArr.length; ++idx) {
+ if (expectedOutput) {
+ expectedOutput += '\xA0\xA0\xA0<i>or</i><br>';
+ }
+ expectedOutput += testUsesHTML ? highlightSelectionMarkers(escapeOutput(expectedArr[idx]))
+ : formatValueOrString(expectedArr[idx]);
+ }
+ var acceptedArr = getExpectationArray(getTestParameter(suite, group, test, PARAM_ACCEPT));
+ for (var idx = 0; idx < acceptedArr.length; ++idx) {
+ expectedOutput += '<span class="accexp">\xA0\xA0\xA0<i>or</i></span><br><span class="accexp">';
+ expectedOutput += testUsesHTML ? highlightSelectionMarkers(escapeOutput(acceptedArr[idx]))
+ : formatValueOrString(acceptedArr[idx]);
+ expectedOutput += '</span>';
+ }
+ // TODO(rolandsteiner): THIS IS UGLY, relying on 'div' container being index 2,
+ // AND not allowing other containers to have 'outer' results - change!!!
+ var outerOutput = '';
+ expectedArr = getExpectationArray(getContainerParameter(suite, group, test, containers[2], PARAM_EXPECTED_OUTER));
+ for (var idx = 0; idx < expectedArr.length; ++idx) {
+ if (outerOutput) {
+ outerOutput += '\xA0\xA0\xA0<i>or</i><br>';
+ }
+ outerOutput += testUsesHTML ? highlightSelectionMarkers(escapeOutput(expectedArr[idx]))
+ : formatValueOrString(expectedArr[idx]);
+ }
+ acceptedArr = getExpectationArray(getContainerParameter(suite, group, test, containers[2], PARAM_ACCEPT_OUTER));
+ for (var idx = 0; idx < acceptedArr.length; ++idx) {
+ if (outerOutput) {
+ outerOutput += '<span class="accexp">\xA0\xA0\xA0<i>or</i></span><br>';
+ }
+ outerOutput += '<span class="accexp">';
+ outerOutput += testUsesHTML ? highlightSelectionMarkers(escapeOutput(acceptedArr[idx]))
+ : formatValueOrString(acceptedArr[idx]);
+ outerOutput += '</span>';
+ }
+ if (outerOutput) {
+ expectedOutput += '<hr>' + outerOutput;
+ }
+ setTD(trID + IDOUT_EXPECTED, expectedOutput);
+
+ // Iterate over the individual container results
+ for (var cntIdx = 0; cntIdx < containers.length; ++cntIdx) {
+ var cntID = containers[cntIdx].id;
+ var cntTD = document.getElementById(trID + IDOUT_CONTAINER + cntID);
+ var cntResult = testResult[cntID];
+ var cntValOut = VALOUTPUT[cntResult.valresult];
+ var cntSelOut = SELOUTPUT[cntResult.selresult];
+ var cssVal = cntValOut.css;
+ var cssSel = (!suiteChecksSelOnly || cntResult.selresult != SELRESULT_NA) ? cntSelOut.css : cssVal;
+ var cssCnt = cssVal;
+
+ // Fill in result status cell ("PASS", "ACC.", "FAIL", "EXC.", etc.)
+ setTD(trID + IDOUT_STATUSVAL + cntID, cntValOut.output, cntValOut.title, cssVal);
+
+ // Fill in selection status cell ("PASS", "ACC.", "FAIL", "N/A")
+ setTD(trID + IDOUT_STATUSSEL + cntID, cntSelOut.output, cntSelOut.title, cssSel);
+
+ // Fill in actual result
+ switch (cntResult.valresult) {
+ case VALRESULT_SETUP_EXCEPTION:
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ SETUP_EXCEPTION + '(mouseover)',
+ escapeOutput(cntResult.output),
+ cssVal);
+ break;
+
+ case VALRESULT_EXECUTION_EXCEPTION:
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ EXECUTION_EXCEPTION + '(mouseover)',
+ escapeOutput(cntResult.output.toString()),
+ cssVal);
+ break;
+
+ case VALRESULT_VERIFICATION_EXCEPTION:
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ VERIFICATION_EXCEPTION + '(mouseover)',
+ escapeOutput(cntResult.output.toString()),
+ cssVal);
+ break;
+
+ case VALRESULT_UNSUPPORTED:
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ escapeOutput(cntResult.output),
+ '',
+ cssVal);
+ break;
+
+ case VALRESULT_CANARY:
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ highlightSelectionMarkersAndTextNodes(escapeOutput(cntResult.output)),
+ '',
+ cssVal);
+ break;
+
+ case VALRESULT_DIFF:
+ case VALRESULT_ACCEPT:
+ case VALRESULT_EQUAL:
+ if (!testUsesHTML) {
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ formatValueOrString(cntResult.output),
+ '',
+ cssVal);
+ } else if (cntResult.selresult == SELRESULT_CANARY) {
+ cssCnt = suiteChecksSelOnly ? cssSel : cssVal;
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ highlightSelectionMarkersAndTextNodes(escapeOutput(cntResult.output)),
+ '',
+ cssCnt);
+ } else {
+ cssCnt = suiteChecksSelOnly ? cssSel : cssVal;
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ formatActualResult(suite, group, test, cntResult.output),
+ '',
+ cssCnt);
+ }
+ break;
+
+ default:
+ cssCnt = 'exception';
+ setTD(trID + IDOUT_ACTUAL + cntID,
+ INTERNAL_ERR + 'UNKNOWN RESULT VALUE',
+ '',
+ cssCnt);
+ }
+
+ if (cntTD) {
+ cntTD.className = cssCnt;
+ }
+ }
+}
+
+/**
+ * Outputs the results of a single test suite
+ *
+ * @param {Object} suite as object reference
+ */
+function outputTestSuiteResults(suite) {
+ var suiteID = suite.id;
+ var span;
+
+ span = document.getElementById(suiteID + '-score');
+ if (span) {
+ span.innerHTML = results[suiteID].valscore + '/' + results[suiteID].count;
+ }
+ span = document.getElementById(suiteID + '-selscore');
+ if (span) {
+ span.innerHTML = results[suiteID].selscore + '/' + results[suiteID].count;
+ }
+ span = document.getElementById(suiteID + '-time');
+ if (span) {
+ span.innerHTML = results[suiteID].time;
+ }
+ span = document.getElementById(suiteID + '-progress');
+ if (span) {
+ span.style.color = 'green';
+ }
+
+ for (var clsIdx = 0; clsIdx < testClassCount; ++clsIdx) {
+ var clsID = testClassIDs[clsIdx];
+ var cls = suite[clsID];
+ if (!cls)
+ continue;
+
+ span = document.getElementById(suiteID + '-' + clsID + '-score');
+ if (span) {
+ span.innerHTML = results[suiteID][clsID].valscore + '/' + results[suiteID][clsID].count;
+ }
+ span = document.getElementById(suiteID + '-' + clsID + '-selscore');
+ if (span) {
+ span.innerHTML = results[suiteID][clsID].selscore + '/' + results[suiteID][clsID].count;
+ }
+
+ var groupCount = cls.length;
+
+ for (var groupIdx = 0; groupIdx < groupCount; ++groupIdx) {
+ var group = cls[groupIdx];
+ var testCount = group.tests.length;
+
+ for (var testIdx = 0; testIdx < testCount; ++testIdx) {
+ var test = group.tests[testIdx];
+
+ outputTestResults(suite, clsID, group, test);
+ }
+ }
+ }
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/pad.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/pad.js
new file mode 100644
index 000000000..282f0d907
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/pad.js
@@ -0,0 +1,269 @@
+/**
+ * @fileoverview
+ * Functions used to handle test and expectation strings.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+/**
+ * Normalize text selection indicators and convert inter-element selection
+ * indicators to comments.
+ *
+ * Note that this function relies on the spaces of the input string already
+ * having been normalized by canonicalizeSpaces!
+ *
+ * @param pad {String} HTML string that includes selection marker characters
+ * @return {String} the HTML string with the selection markers converted
+ */
+function convertSelectionIndicators(pad) {
+ // Sanity check: Markers { } | only directly before or after an element,
+ // or just before a closing > (i.e., not within a text node).
+ // Note that intra-tag selection markers have already been converted to the
+ // special selection attribute(s) above.
+ if (/[^>][{}\|][^<>]/.test(pad) ||
+ /^[{}\|][^<]/.test(pad) ||
+ /[^>][{}\|]$/.test(pad) ||
+ /^[{}\|]*$/.test(pad)) {
+ throw SETUP_BAD_SELECTION_SPEC;
+ }
+
+ // Convert intra-tag selection markers to special attributes.
+ pad = pad.replace(/\{\>/g, ATTRNAME_SEL_START + '="1">');
+ pad = pad.replace(/\}\>/g, ATTRNAME_SEL_END + '="1">');
+ pad = pad.replace(/\|\>/g, ATTRNAME_SEL_START + '="1" ' +
+ ATTRNAME_SEL_END + '="1">');
+
+ // Convert remaining {, }, | to comments with '[' and ']' data.
+ pad = pad.replace('{', '<!--[-->');
+ pad = pad.replace('}', '<!--]-->');
+ pad = pad.replace('|', '<!--[--><!--]-->');
+
+ // Convert caret indicator ^ to empty selection indicator []
+ // (this simplifies further processing).
+ pad = pad.replace(/\^/, '[]');
+
+ return pad;
+}
+
+/**
+ * Derives one point of the selection from the indicators with the HTML tree:
+ * '[' or ']' within a text or comment node, or the special selection
+ * attributes within an element node.
+ *
+ * @param root {DOMNode} root node of the recursive search
+ * @param marker {String} which marker to look for: '[' or ']'
+ * @return {Object} a pair object: {node: {DOMNode}/null, offset: {Integer}}
+ */
+function deriveSelectionPoint(root, marker) {
+ switch (root.nodeType) {
+ case DOM_NODE_TYPE_ELEMENT:
+ if (root.attributes) {
+ // Note: getAttribute() is necessary for this to work on all browsers!
+ if (marker == '[' && root.getAttribute(ATTRNAME_SEL_START)) {
+ root.removeAttribute(ATTRNAME_SEL_START);
+ return {node: root, offs: 0};
+ }
+ if (marker == ']' && root.getAttribute(ATTRNAME_SEL_END)) {
+ root.removeAttribute(ATTRNAME_SEL_END);
+ return {node: root, offs: 0};
+ }
+ }
+ for (var i = 0; i < root.childNodes.length; ++i) {
+ var pair = deriveSelectionPoint(root.childNodes[i], marker);
+ if (pair.node) {
+ return pair;
+ }
+ }
+ break;
+
+ case DOM_NODE_TYPE_TEXT:
+ var pos = root.data.indexOf(marker);
+ if (pos != -1) {
+ // Remove selection marker from text.
+ var nodeText = root.data;
+ root.data = nodeText.substr(0, pos) + nodeText.substr(pos + 1);
+ return {node: root, offs: pos };
+ }
+ break;
+
+ case DOM_NODE_TYPE_COMMENT:
+ var pos = root.data.indexOf(marker);
+ if (pos != -1) {
+ // Remove comment node from parent.
+ var helper = root.previousSibling;
+
+ for (pos = 0; helper; ++pos ) {
+ helper = helper.previousSibling;
+ }
+ helper = root;
+ root = root.parentNode;
+ root.removeChild(helper);
+ return {node: root, offs: pos };
+ }
+ break;
+ }
+
+ return {node: null, offs: 0 };
+}
+
+/**
+ * Initialize the test HTML with the starting state specified in the test.
+ *
+ * The selection is specified "inline", using special characters:
+ * ^ a collapsed text caret selection (same as [])
+ * [ the selection start within a text node
+ * ] the selection end within a text node
+ * | collapsed selection between elements (same as {})
+ * { selection starting with the following element
+ * } selection ending with the preceding element
+ * {, } and | can also be used within an element tag, just before the closing
+ * angle bracket > to specify a selection [element, 0] where the element
+ * doesn't otherwise have any children. Ex.: <hr {>foobarbaz<hr }>
+ *
+ * Explicit and implicit specification can also be mixed between the 2 points.
+ *
+ * A pad string must only contain at most ONE of the above that is suitable for
+ * that start or end point, respectively, and must contain either both or none.
+ *
+ * @param suite {Object} suite that test originates in as object reference
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} test to be run as object reference
+ * @param container {Object} container descriptor as object reference
+ */
+function initContainer(suite, group, test, container) {
+ var pad = getTestParameter(suite, group, test, PARAM_PAD);
+ pad = canonicalizeSpaces(pad);
+ pad = convertSelectionIndicators(pad);
+
+ if (container.editorID) {
+ container.body.innerHTML = container.canary + container.tagOpen + pad + container.tagClose + container.canary;
+ container.editor = container.doc.getElementById(container.editorID);
+ } else {
+ container.body.innerHTML = pad;
+ container.editor = container.body;
+ }
+
+ win = container.win;
+ doc = container.doc;
+ body = container.body;
+ editor = container.editor;
+ sel = null;
+
+ if (!editor) {
+ throw SETUP_CONTAINER;
+ }
+
+ if (getTestParameter(suite, group, test, PARAM_STYLE_WITH_CSS)) {
+ try {
+ container.doc.execCommand('styleWithCSS', false, true);
+ } catch (ex) {
+ // ignore exception if unsupported
+ }
+ }
+
+ var selAnchor = deriveSelectionPoint(editor, '[');
+ var selFocus = deriveSelectionPoint(editor, ']');
+
+ // sanity check
+ if (!selAnchor || !selFocus) {
+ throw SETUP_SELECTION;
+ }
+
+ if (!selAnchor.node || !selFocus.node) {
+ if (selAnchor.node || selFocus.node) {
+ // Broken test: only one selection point was specified
+ throw SETUP_BAD_SELECTION_SPEC;
+ }
+ sel = null;
+ return;
+ }
+
+ if (selAnchor.node === selFocus.node) {
+ if (selAnchor.offs > selFocus.offs) {
+ // Both selection points are within the same node, the selection was
+ // specified inline (thus the end indicator element or character was
+ // removed), and the end point is before the start (reversed selection).
+ // Start offset that was derived is now off by 1 and needs adjustment.
+ --selAnchor.offs;
+ }
+
+ if (selAnchor.offs === selFocus.offs) {
+ createCaret(selAnchor.node, selAnchor.offs).select();
+ try {
+ sel = win.getSelection();
+ } catch (ex) {
+ sel = undefined;
+ }
+ return;
+ }
+ }
+
+ createFromNodes(selAnchor.node, selAnchor.offs, selFocus.node, selFocus.offs).select();
+
+ try {
+ sel = win.getSelection();
+ } catch (ex) {
+ sel = undefined;
+ }
+}
+
+/**
+ * Reset the editor element after a test is run.
+ *
+ * @param container {Object} container descriptor as object reference
+ */
+function resetContainer(container) {
+ // Remove errant styles and attributes that may have been set on the <body>.
+ container.body.removeAttribute('style');
+ container.body.removeAttribute('color');
+ container.body.removeAttribute('bgcolor');
+
+ try {
+ container.doc.execCommand('styleWithCSS', false, false);
+ } catch (ex) {
+ // Ignore exception if unsupported.
+ }
+}
+
+/**
+ * Initialize the editor document.
+ */
+function initEditorDocs() {
+ for (var c = 0; c < containers.length; ++c) {
+ var container = containers[c];
+
+ container.iframe = document.getElementById('iframe-' + container.id);
+ container.win = container.iframe.contentWindow;
+ container.doc = container.win.document;
+ container.body = container.doc.body;
+ // container.editor is set per test (changes on embedded editor elements).
+
+ // Some browsers require a selection to go with their 'styleWithCSS'.
+ try {
+ container.win.getSelection().selectAllChildren(editor);
+ } catch (ex) {
+ // ignore exception if unsupported
+ }
+ // Default styleWithCSS to false.
+ try {
+ container.doc.execCommand('styleWithCSS', false, false);
+ } catch (ex) {
+ // ignore exception if unsupported
+ }
+ }
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range-bootstrap.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range-bootstrap.js
new file mode 100644
index 000000000..24aef7ae9
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range-bootstrap.js
@@ -0,0 +1,5 @@
+goog.require('goog.dom.Range');
+
+window.createFromWindow = goog.dom.Range.createFromWindow;
+window.createFromNodes = goog.dom.Range.createFromNodes;
+window.createCaret = goog.dom.Range.createCaret;
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range.js
new file mode 100644
index 000000000..f323cf9b6
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/range.js
@@ -0,0 +1,6184 @@
+var COMPILED = false;
+var goog = goog || {};
+goog.global = this;
+goog.DEBUG = true;
+goog.LOCALE = "en";
+goog.evalWorksForGlobals_ = null;
+goog.provide = function(name) {
+ if(!COMPILED) {
+ if(goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+ var namespace = name;
+ while(namespace = namespace.substring(0, namespace.lastIndexOf("."))) {
+ goog.implicitNamespaces_[namespace] = true
+ }
+ }
+ goog.exportPath_(name)
+};
+if(!COMPILED) {
+ goog.implicitNamespaces_ = {}
+}
+goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split(".");
+ var cur = opt_objectToExportTo || goog.global;
+ if(!(parts[0] in cur) && cur.execScript) {
+ cur.execScript("var " + parts[0])
+ }
+ for(var part;parts.length && (part = parts.shift());) {
+ if(!parts.length && goog.isDef(opt_object)) {
+ cur[part] = opt_object
+ }else {
+ if(cur[part]) {
+ cur = cur[part]
+ }else {
+ cur = cur[part] = {}
+ }
+ }
+ }
+};
+goog.getObjectByName = function(name, opt_obj) {
+ var parts = name.split(".");
+ var cur = opt_obj || goog.global;
+ for(var part;part = parts.shift();) {
+ if(cur[part]) {
+ cur = cur[part]
+ }else {
+ return null
+ }
+ }
+ return cur
+};
+goog.globalize = function(obj, opt_global) {
+ var global = opt_global || goog.global;
+ for(var x in obj) {
+ global[x] = obj[x]
+ }
+};
+goog.addDependency = function(relPath, provides, requires) {
+ if(!COMPILED) {
+ var provide, require;
+ var path = relPath.replace(/\\/g, "/");
+ var deps = goog.dependencies_;
+ for(var i = 0;provide = provides[i];i++) {
+ deps.nameToPath[provide] = path;
+ if(!(path in deps.pathToNames)) {
+ deps.pathToNames[path] = {}
+ }
+ deps.pathToNames[path][provide] = true
+ }
+ for(var j = 0;require = requires[j];j++) {
+ if(!(path in deps.requires)) {
+ deps.requires[path] = {}
+ }
+ deps.requires[path][require] = true
+ }
+ }
+};
+goog.require = function(rule) {
+ if(!COMPILED) {
+ if(goog.getObjectByName(rule)) {
+ return
+ }
+ var path = goog.getPathFromDeps_(rule);
+ if(path) {
+ goog.included_[path] = true;
+ goog.writeScripts_()
+ }else {
+ var errorMessage = "goog.require could not find: " + rule;
+ if(goog.global.console) {
+ goog.global.console["error"](errorMessage)
+ }
+ throw Error(errorMessage);
+ }
+ }
+};
+goog.basePath = "";
+goog.global.CLOSURE_BASE_PATH;
+goog.nullFunction = function() {
+};
+goog.identityFunction = function(var_args) {
+ return arguments[0]
+};
+goog.abstractMethod = function() {
+ throw Error("unimplemented abstract method");
+};
+goog.addSingletonGetter = function(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor)
+ }
+};
+if(!COMPILED) {
+ goog.included_ = {};
+ goog.dependencies_ = {pathToNames:{}, nameToPath:{}, requires:{}, visited:{}, written:{}};
+ goog.inHtmlDocument_ = function() {
+ var doc = goog.global.document;
+ return typeof doc != "undefined" && "write" in doc
+ };
+ goog.findBasePath_ = function() {
+ if(!goog.inHtmlDocument_()) {
+ return
+ }
+ var doc = goog.global.document;
+ if(goog.global.CLOSURE_BASE_PATH) {
+ goog.basePath = goog.global.CLOSURE_BASE_PATH;
+ return
+ }
+ var scripts = doc.getElementsByTagName("script");
+ for(var i = scripts.length - 1;i >= 0;--i) {
+ var src = scripts[i].src;
+ var l = src.length;
+ if(src.substr(l - 7) == "base.js") {
+ goog.basePath = src.substr(0, l - 7);
+ return
+ }
+ }
+ };
+ goog.writeScriptTag_ = function(src) {
+ if(goog.inHtmlDocument_() && !goog.dependencies_.written[src]) {
+ goog.dependencies_.written[src] = true;
+ var doc = goog.global.document;
+ doc.write('<script type="text/javascript" src="' + src + '"></' + "script>")
+ }
+ };
+ goog.writeScripts_ = function() {
+ var scripts = [];
+ var seenScript = {};
+ var deps = goog.dependencies_;
+ function visitNode(path) {
+ if(path in deps.written) {
+ return
+ }
+ if(path in deps.visited) {
+ if(!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path)
+ }
+ return
+ }
+ deps.visited[path] = true;
+ if(path in deps.requires) {
+ for(var requireName in deps.requires[path]) {
+ if(requireName in deps.nameToPath) {
+ visitNode(deps.nameToPath[requireName])
+ }else {
+ if(!goog.getObjectByName(requireName)) {
+ throw Error("Undefined nameToPath for " + requireName);
+ }
+ }
+ }
+ }
+ if(!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path)
+ }
+ }
+ for(var path in goog.included_) {
+ if(!deps.written[path]) {
+ visitNode(path)
+ }
+ }
+ for(var i = 0;i < scripts.length;i++) {
+ if(scripts[i]) {
+ goog.writeScriptTag_(goog.basePath + scripts[i])
+ }else {
+ throw Error("Undefined script input");
+ }
+ }
+ };
+ goog.getPathFromDeps_ = function(rule) {
+ if(rule in goog.dependencies_.nameToPath) {
+ return goog.dependencies_.nameToPath[rule]
+ }else {
+ return null
+ }
+ };
+ goog.findBasePath_();
+}
+goog.typeOf = function(value) {
+ var s = typeof value;
+ if(s == "object") {
+ if(value) {
+ if(value instanceof Array || !(value instanceof Object) && Object.prototype.toString.call(value) == "[object Array]" || typeof value.length == "number" && typeof value.splice != "undefined" && typeof value.propertyIsEnumerable != "undefined" && !value.propertyIsEnumerable("splice")) {
+ return"array"
+ }
+ if(!(value instanceof Object) && (Object.prototype.toString.call(value) == "[object Function]" || typeof value.call != "undefined" && typeof value.propertyIsEnumerable != "undefined" && !value.propertyIsEnumerable("call"))) {
+ return"function"
+ }
+ }else {
+ return"null"
+ }
+ }else {
+ if(s == "function" && typeof value.call == "undefined") {
+ return"object"
+ }
+ }
+ return s
+};
+goog.propertyIsEnumerableCustom_ = function(object, propName) {
+ if(propName in object) {
+ for(var key in object) {
+ if(key == propName && Object.prototype.hasOwnProperty.call(object, propName)) {
+ return true
+ }
+ }
+ }
+ return false
+};
+goog.propertyIsEnumerable_ = function(object, propName) {
+ if(object instanceof Object) {
+ return Object.prototype.propertyIsEnumerable.call(object, propName)
+ }else {
+ return goog.propertyIsEnumerableCustom_(object, propName)
+ }
+};
+goog.isDef = function(val) {
+ return val !== undefined
+};
+goog.isNull = function(val) {
+ return val === null
+};
+goog.isDefAndNotNull = function(val) {
+ return val != null
+};
+goog.isArray = function(val) {
+ return goog.typeOf(val) == "array"
+};
+goog.isArrayLike = function(val) {
+ var type = goog.typeOf(val);
+ return type == "array" || type == "object" && typeof val.length == "number"
+};
+goog.isDateLike = function(val) {
+ return goog.isObject(val) && typeof val.getFullYear == "function"
+};
+goog.isString = function(val) {
+ return typeof val == "string"
+};
+goog.isBoolean = function(val) {
+ return typeof val == "boolean"
+};
+goog.isNumber = function(val) {
+ return typeof val == "number"
+};
+goog.isFunction = function(val) {
+ return goog.typeOf(val) == "function"
+};
+goog.isObject = function(val) {
+ var type = goog.typeOf(val);
+ return type == "object" || type == "array" || type == "function"
+};
+goog.getUid = function(obj) {
+ return obj[goog.UID_PROPERTY_] || (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_)
+};
+goog.removeUid = function(obj) {
+ if("removeAttribute" in obj) {
+ obj.removeAttribute(goog.UID_PROPERTY_)
+ }
+ try {
+ delete obj[goog.UID_PROPERTY_]
+ }catch(ex) {
+ }
+};
+goog.UID_PROPERTY_ = "closure_uid_" + Math.floor(Math.random() * 2147483648).toString(36);
+goog.uidCounter_ = 0;
+goog.getHashCode = goog.getUid;
+goog.removeHashCode = goog.removeUid;
+goog.cloneObject = function(obj) {
+ var type = goog.typeOf(obj);
+ if(type == "object" || type == "array") {
+ if(obj.clone) {
+ return obj.clone()
+ }
+ var clone = type == "array" ? [] : {};
+ for(var key in obj) {
+ clone[key] = goog.cloneObject(obj[key])
+ }
+ return clone
+ }
+ return obj
+};
+Object.prototype.clone;
+goog.bind = function(fn, selfObj, var_args) {
+ var context = selfObj || goog.global;
+ if(arguments.length > 2) {
+ var boundArgs = Array.prototype.slice.call(arguments, 2);
+ return function() {
+ var newArgs = Array.prototype.slice.call(arguments);
+ Array.prototype.unshift.apply(newArgs, boundArgs);
+ return fn.apply(context, newArgs)
+ }
+ }else {
+ return function() {
+ return fn.apply(context, arguments)
+ }
+ }
+};
+goog.partial = function(fn, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ var newArgs = Array.prototype.slice.call(arguments);
+ newArgs.unshift.apply(newArgs, args);
+ return fn.apply(this, newArgs)
+ }
+};
+goog.mixin = function(target, source) {
+ for(var x in source) {
+ target[x] = source[x]
+ }
+};
+goog.now = Date.now || function() {
+ return+new Date
+};
+goog.globalEval = function(script) {
+ if(goog.global.execScript) {
+ goog.global.execScript(script, "JavaScript")
+ }else {
+ if(goog.global.eval) {
+ if(goog.evalWorksForGlobals_ == null) {
+ goog.global.eval("var _et_ = 1;");
+ if(typeof goog.global["_et_"] != "undefined") {
+ delete goog.global["_et_"];
+ goog.evalWorksForGlobals_ = true
+ }else {
+ goog.evalWorksForGlobals_ = false
+ }
+ }
+ if(goog.evalWorksForGlobals_) {
+ goog.global.eval(script)
+ }else {
+ var doc = goog.global.document;
+ var scriptElt = doc.createElement("script");
+ scriptElt.type = "text/javascript";
+ scriptElt.defer = false;
+ scriptElt.appendChild(doc.createTextNode(script));
+ doc.body.appendChild(scriptElt);
+ doc.body.removeChild(scriptElt)
+ }
+ }else {
+ throw Error("goog.globalEval not available");
+ }
+ }
+};
+goog.typedef = true;
+goog.cssNameMapping_;
+goog.getCssName = function(className, opt_modifier) {
+ var cssName = className + (opt_modifier ? "-" + opt_modifier : "");
+ return goog.cssNameMapping_ && cssName in goog.cssNameMapping_ ? goog.cssNameMapping_[cssName] : cssName
+};
+goog.setCssNameMapping = function(mapping) {
+ goog.cssNameMapping_ = mapping
+};
+goog.getMsg = function(str, opt_values) {
+ var values = opt_values || {};
+ for(var key in values) {
+ var value = ("" + values[key]).replace(/\$/g, "$$$$");
+ str = str.replace(new RegExp("\\{\\$" + key + "\\}", "gi"), value)
+ }
+ return str
+};
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+ goog.exportPath_(publicPath, object, opt_objectToExportTo)
+};
+goog.exportProperty = function(object, publicName, symbol) {
+ object[publicName] = symbol
+};
+goog.inherits = function(childCtor, parentCtor) {
+ function tempCtor() {
+ }
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor;
+ childCtor.prototype.constructor = childCtor
+};
+goog.base = function(me, opt_methodName, var_args) {
+ var caller = arguments.callee.caller;
+ if(caller.superClass_) {
+ return caller.superClass_.constructor.apply(me, Array.prototype.slice.call(arguments, 1))
+ }
+ var args = Array.prototype.slice.call(arguments, 2);
+ var foundCaller = false;
+ for(var ctor = me.constructor;ctor;ctor = ctor.superClass_ && ctor.superClass_.constructor) {
+ if(ctor.prototype[opt_methodName] === caller) {
+ foundCaller = true
+ }else {
+ if(foundCaller) {
+ return ctor.prototype[opt_methodName].apply(me, args)
+ }
+ }
+ }
+ if(me[opt_methodName] === caller) {
+ return me.constructor.prototype[opt_methodName].apply(me, args)
+ }else {
+ throw Error("goog.base called from a method of one name " + "to a method of a different name");
+ }
+};
+goog.scope = function(fn) {
+ fn.call(goog.global)
+};
+goog.provide("goog.debug.Error");
+goog.debug.Error = function(opt_msg) {
+ this.stack = (new Error).stack || "";
+ if(opt_msg) {
+ this.message = String(opt_msg)
+ }
+};
+goog.inherits(goog.debug.Error, Error);
+goog.debug.Error.prototype.name = "CustomError";
+goog.provide("goog.string");
+goog.provide("goog.string.Unicode");
+goog.string.Unicode = {NBSP:"\u00a0"};
+goog.string.startsWith = function(str, prefix) {
+ return str.lastIndexOf(prefix, 0) == 0
+};
+goog.string.endsWith = function(str, suffix) {
+ var l = str.length - suffix.length;
+ return l >= 0 && str.indexOf(suffix, l) == l
+};
+goog.string.caseInsensitiveStartsWith = function(str, prefix) {
+ return goog.string.caseInsensitiveCompare(prefix, str.substr(0, prefix.length)) == 0
+};
+goog.string.caseInsensitiveEndsWith = function(str, suffix) {
+ return goog.string.caseInsensitiveCompare(suffix, str.substr(str.length - suffix.length, suffix.length)) == 0
+};
+goog.string.subs = function(str, var_args) {
+ for(var i = 1;i < arguments.length;i++) {
+ var replacement = String(arguments[i]).replace(/\$/g, "$$$$");
+ str = str.replace(/\%s/, replacement)
+ }
+ return str
+};
+goog.string.collapseWhitespace = function(str) {
+ return str.replace(/[\s\xa0]+/g, " ").replace(/^\s+|\s+$/g, "")
+};
+goog.string.isEmpty = function(str) {
+ return/^[\s\xa0]*$/.test(str)
+};
+goog.string.isEmptySafe = function(str) {
+ return goog.string.isEmpty(goog.string.makeSafe(str))
+};
+goog.string.isBreakingWhitespace = function(str) {
+ return!/[^\t\n\r ]/.test(str)
+};
+goog.string.isAlpha = function(str) {
+ return!/[^a-zA-Z]/.test(str)
+};
+goog.string.isNumeric = function(str) {
+ return!/[^0-9]/.test(str)
+};
+goog.string.isAlphaNumeric = function(str) {
+ return!/[^a-zA-Z0-9]/.test(str)
+};
+goog.string.isSpace = function(ch) {
+ return ch == " "
+};
+goog.string.isUnicodeChar = function(ch) {
+ return ch.length == 1 && ch >= " " && ch <= "~" || ch >= "\u0080" && ch <= "\ufffd"
+};
+goog.string.stripNewlines = function(str) {
+ return str.replace(/(\r\n|\r|\n)+/g, " ")
+};
+goog.string.canonicalizeNewlines = function(str) {
+ return str.replace(/(\r\n|\r|\n)/g, "\n")
+};
+goog.string.normalizeWhitespace = function(str) {
+ return str.replace(/\xa0|\s/g, " ")
+};
+goog.string.normalizeSpaces = function(str) {
+ return str.replace(/\xa0|[ \t]+/g, " ")
+};
+goog.string.trim = function(str) {
+ return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, "")
+};
+goog.string.trimLeft = function(str) {
+ return str.replace(/^[\s\xa0]+/, "")
+};
+goog.string.trimRight = function(str) {
+ return str.replace(/[\s\xa0]+$/, "")
+};
+goog.string.caseInsensitiveCompare = function(str1, str2) {
+ var test1 = String(str1).toLowerCase();
+ var test2 = String(str2).toLowerCase();
+ if(test1 < test2) {
+ return-1
+ }else {
+ if(test1 == test2) {
+ return 0
+ }else {
+ return 1
+ }
+ }
+};
+goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
+goog.string.numerateCompare = function(str1, str2) {
+ if(str1 == str2) {
+ return 0
+ }
+ if(!str1) {
+ return-1
+ }
+ if(!str2) {
+ return 1
+ }
+ var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
+ var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);
+ var count = Math.min(tokens1.length, tokens2.length);
+ for(var i = 0;i < count;i++) {
+ var a = tokens1[i];
+ var b = tokens2[i];
+ if(a != b) {
+ var num1 = parseInt(a, 10);
+ if(!isNaN(num1)) {
+ var num2 = parseInt(b, 10);
+ if(!isNaN(num2) && num1 - num2) {
+ return num1 - num2
+ }
+ }
+ return a < b ? -1 : 1
+ }
+ }
+ if(tokens1.length != tokens2.length) {
+ return tokens1.length - tokens2.length
+ }
+ return str1 < str2 ? -1 : 1
+};
+goog.string.encodeUriRegExp_ = /^[a-zA-Z0-9\-_.!~*'()]*$/;
+goog.string.urlEncode = function(str) {
+ str = String(str);
+ if(!goog.string.encodeUriRegExp_.test(str)) {
+ return encodeURIComponent(str)
+ }
+ return str
+};
+goog.string.urlDecode = function(str) {
+ return decodeURIComponent(str.replace(/\+/g, " "))
+};
+goog.string.newLineToBr = function(str, opt_xml) {
+ return str.replace(/(\r\n|\r|\n)/g, opt_xml ? "<br />" : "<br>")
+};
+goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
+ if(opt_isLikelyToContainHtmlChars) {
+ return str.replace(goog.string.amperRe_, "&amp;").replace(goog.string.ltRe_, "&lt;").replace(goog.string.gtRe_, "&gt;").replace(goog.string.quotRe_, "&quot;")
+ }else {
+ if(!goog.string.allRe_.test(str)) {
+ return str
+ }
+ if(str.indexOf("&") != -1) {
+ str = str.replace(goog.string.amperRe_, "&amp;")
+ }
+ if(str.indexOf("<") != -1) {
+ str = str.replace(goog.string.ltRe_, "&lt;")
+ }
+ if(str.indexOf(">") != -1) {
+ str = str.replace(goog.string.gtRe_, "&gt;")
+ }
+ if(str.indexOf('"') != -1) {
+ str = str.replace(goog.string.quotRe_, "&quot;")
+ }
+ return str
+ }
+};
+goog.string.amperRe_ = /&/g;
+goog.string.ltRe_ = /</g;
+goog.string.gtRe_ = />/g;
+goog.string.quotRe_ = /\"/g;
+goog.string.allRe_ = /[&<>\"]/;
+goog.string.unescapeEntities = function(str) {
+ if(goog.string.contains(str, "&")) {
+ if("document" in goog.global && !goog.string.contains(str, "<")) {
+ return goog.string.unescapeEntitiesUsingDom_(str)
+ }else {
+ return goog.string.unescapePureXmlEntities_(str)
+ }
+ }
+ return str
+};
+goog.string.unescapeEntitiesUsingDom_ = function(str) {
+ var el = goog.global["document"]["createElement"]("a");
+ el["innerHTML"] = str;
+ if(el[goog.string.NORMALIZE_FN_]) {
+ el[goog.string.NORMALIZE_FN_]()
+ }
+ str = el["firstChild"]["nodeValue"];
+ el["innerHTML"] = "";
+ return str
+};
+goog.string.unescapePureXmlEntities_ = function(str) {
+ return str.replace(/&([^;]+);/g, function(s, entity) {
+ switch(entity) {
+ case "amp":
+ return"&";
+ case "lt":
+ return"<";
+ case "gt":
+ return">";
+ case "quot":
+ return'"';
+ default:
+ if(entity.charAt(0) == "#") {
+ var n = Number("0" + entity.substr(1));
+ if(!isNaN(n)) {
+ return String.fromCharCode(n)
+ }
+ }
+ return s
+ }
+ })
+};
+goog.string.NORMALIZE_FN_ = "normalize";
+goog.string.whitespaceEscape = function(str, opt_xml) {
+ return goog.string.newLineToBr(str.replace(/ /g, " &#160;"), opt_xml)
+};
+goog.string.stripQuotes = function(str, quoteChars) {
+ var length = quoteChars.length;
+ for(var i = 0;i < length;i++) {
+ var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
+ if(str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
+ return str.substring(1, str.length - 1)
+ }
+ }
+ return str
+};
+goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
+ if(opt_protectEscapedCharacters) {
+ str = goog.string.unescapeEntities(str)
+ }
+ if(str.length > chars) {
+ str = str.substring(0, chars - 3) + "..."
+ }
+ if(opt_protectEscapedCharacters) {
+ str = goog.string.htmlEscape(str)
+ }
+ return str
+};
+goog.string.truncateMiddle = function(str, chars, opt_protectEscapedCharacters) {
+ if(opt_protectEscapedCharacters) {
+ str = goog.string.unescapeEntities(str)
+ }
+ if(str.length > chars) {
+ var half = Math.floor(chars / 2);
+ var endPos = str.length - half;
+ half += chars % 2;
+ str = str.substring(0, half) + "..." + str.substring(endPos)
+ }
+ if(opt_protectEscapedCharacters) {
+ str = goog.string.htmlEscape(str)
+ }
+ return str
+};
+goog.string.specialEscapeChars_ = {"\u0000":"\\0", "\u0008":"\\b", "\u000c":"\\f", "\n":"\\n", "\r":"\\r", "\t":"\\t", "\u000b":"\\x0B", '"':'\\"', "\\":"\\\\"};
+goog.string.jsEscapeCache_ = {"'":"\\'"};
+goog.string.quote = function(s) {
+ s = String(s);
+ if(s.quote) {
+ return s.quote()
+ }else {
+ var sb = ['"'];
+ for(var i = 0;i < s.length;i++) {
+ var ch = s.charAt(i);
+ var cc = ch.charCodeAt(0);
+ sb[i + 1] = goog.string.specialEscapeChars_[ch] || (cc > 31 && cc < 127 ? ch : goog.string.escapeChar(ch))
+ }
+ sb.push('"');
+ return sb.join("")
+ }
+};
+goog.string.escapeString = function(str) {
+ var sb = [];
+ for(var i = 0;i < str.length;i++) {
+ sb[i] = goog.string.escapeChar(str.charAt(i))
+ }
+ return sb.join("")
+};
+goog.string.escapeChar = function(c) {
+ if(c in goog.string.jsEscapeCache_) {
+ return goog.string.jsEscapeCache_[c]
+ }
+ if(c in goog.string.specialEscapeChars_) {
+ return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c]
+ }
+ var rv = c;
+ var cc = c.charCodeAt(0);
+ if(cc > 31 && cc < 127) {
+ rv = c
+ }else {
+ if(cc < 256) {
+ rv = "\\x";
+ if(cc < 16 || cc > 256) {
+ rv += "0"
+ }
+ }else {
+ rv = "\\u";
+ if(cc < 4096) {
+ rv += "0"
+ }
+ }
+ rv += cc.toString(16).toUpperCase()
+ }
+ return goog.string.jsEscapeCache_[c] = rv
+};
+goog.string.toMap = function(s) {
+ var rv = {};
+ for(var i = 0;i < s.length;i++) {
+ rv[s.charAt(i)] = true
+ }
+ return rv
+};
+goog.string.contains = function(s, ss) {
+ return s.indexOf(ss) != -1
+};
+goog.string.removeAt = function(s, index, stringLength) {
+ var resultStr = s;
+ if(index >= 0 && index < s.length && stringLength > 0) {
+ resultStr = s.substr(0, index) + s.substr(index + stringLength, s.length - index - stringLength)
+ }
+ return resultStr
+};
+goog.string.remove = function(s, ss) {
+ var re = new RegExp(goog.string.regExpEscape(ss), "");
+ return s.replace(re, "")
+};
+goog.string.removeAll = function(s, ss) {
+ var re = new RegExp(goog.string.regExpEscape(ss), "g");
+ return s.replace(re, "")
+};
+goog.string.regExpEscape = function(s) {
+ return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, "\\$1").replace(/\x08/g, "\\x08")
+};
+goog.string.repeat = function(string, length) {
+ return(new Array(length + 1)).join(string)
+};
+goog.string.padNumber = function(num, length, opt_precision) {
+ var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
+ var index = s.indexOf(".");
+ if(index == -1) {
+ index = s.length
+ }
+ return goog.string.repeat("0", Math.max(0, length - index)) + s
+};
+goog.string.makeSafe = function(obj) {
+ return obj == null ? "" : String(obj)
+};
+goog.string.buildString = function(var_args) {
+ return Array.prototype.join.call(arguments, "")
+};
+goog.string.getRandomString = function() {
+ return Math.floor(Math.random() * 2147483648).toString(36) + (Math.floor(Math.random() * 2147483648) ^ goog.now()).toString(36)
+};
+goog.string.compareVersions = function(version1, version2) {
+ var order = 0;
+ var v1Subs = goog.string.trim(String(version1)).split(".");
+ var v2Subs = goog.string.trim(String(version2)).split(".");
+ var subCount = Math.max(v1Subs.length, v2Subs.length);
+ for(var subIdx = 0;order == 0 && subIdx < subCount;subIdx++) {
+ var v1Sub = v1Subs[subIdx] || "";
+ var v2Sub = v2Subs[subIdx] || "";
+ var v1CompParser = new RegExp("(\\d*)(\\D*)", "g");
+ var v2CompParser = new RegExp("(\\d*)(\\D*)", "g");
+ do {
+ var v1Comp = v1CompParser.exec(v1Sub) || ["", "", ""];
+ var v2Comp = v2CompParser.exec(v2Sub) || ["", "", ""];
+ if(v1Comp[0].length == 0 && v2Comp[0].length == 0) {
+ break
+ }
+ var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
+ var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
+ order = goog.string.compareElements_(v1CompNum, v2CompNum) || goog.string.compareElements_(v1Comp[2].length == 0, v2Comp[2].length == 0) || goog.string.compareElements_(v1Comp[2], v2Comp[2])
+ }while(order == 0)
+ }
+ return order
+};
+goog.string.compareElements_ = function(left, right) {
+ if(left < right) {
+ return-1
+ }else {
+ if(left > right) {
+ return 1
+ }
+ }
+ return 0
+};
+goog.string.HASHCODE_MAX_ = 4294967296;
+goog.string.hashCode = function(str) {
+ var result = 0;
+ for(var i = 0;i < str.length;++i) {
+ result = 31 * result + str.charCodeAt(i);
+ result %= goog.string.HASHCODE_MAX_
+ }
+ return result
+};
+goog.string.uniqueStringCounter_ = Math.random() * 2147483648 | 0;
+goog.string.createUniqueString = function() {
+ return"goog_" + goog.string.uniqueStringCounter_++
+};
+goog.string.toNumber = function(str) {
+ var num = Number(str);
+ if(num == 0 && goog.string.isEmpty(str)) {
+ return NaN
+ }
+ return num
+};
+goog.provide("goog.asserts");
+goog.provide("goog.asserts.AssertionError");
+goog.require("goog.debug.Error");
+goog.require("goog.string");
+goog.asserts.ENABLE_ASSERTS = goog.DEBUG;
+goog.asserts.AssertionError = function(messagePattern, messageArgs) {
+ messageArgs.unshift(messagePattern);
+ goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
+ messageArgs.shift();
+ this.messagePattern = messagePattern
+};
+goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
+goog.asserts.AssertionError.prototype.name = "AssertionError";
+goog.asserts.doAssertFailure_ = function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
+ var message = "Assertion failed";
+ if(givenMessage) {
+ message += ": " + givenMessage;
+ var args = givenArgs
+ }else {
+ if(defaultMessage) {
+ message += ": " + defaultMessage;
+ args = defaultArgs
+ }
+ }
+ throw new goog.asserts.AssertionError("" + message, args || []);
+};
+goog.asserts.assert = function(condition, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !condition) {
+ goog.asserts.doAssertFailure_("", null, opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return condition
+};
+goog.asserts.fail = function(opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS) {
+ throw new goog.asserts.AssertionError("Failure" + (opt_message ? ": " + opt_message : ""), Array.prototype.slice.call(arguments, 1));
+ }
+};
+goog.asserts.assertNumber = function(value, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
+ goog.asserts.doAssertFailure_("Expected number but got %s: %s.", [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return value
+};
+goog.asserts.assertString = function(value, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
+ goog.asserts.doAssertFailure_("Expected string but got %s: %s.", [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return value
+};
+goog.asserts.assertFunction = function(value, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
+ goog.asserts.doAssertFailure_("Expected function but got %s: %s.", [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return value
+};
+goog.asserts.assertObject = function(value, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
+ goog.asserts.doAssertFailure_("Expected object but got %s: %s.", [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return value
+};
+goog.asserts.assertArray = function(value, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
+ goog.asserts.doAssertFailure_("Expected array but got %s: %s.", [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return value
+};
+goog.asserts.assertBoolean = function(value, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
+ goog.asserts.doAssertFailure_("Expected boolean but got %s: %s.", [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2))
+ }
+ return value
+};
+goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
+ if(goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
+ goog.asserts.doAssertFailure_("instanceof check failed.", null, opt_message, Array.prototype.slice.call(arguments, 3))
+ }
+};
+goog.provide("goog.array");
+goog.require("goog.asserts");
+goog.array.ArrayLike;
+goog.array.peek = function(array) {
+ return array[array.length - 1]
+};
+goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
+goog.array.indexOf = goog.array.ARRAY_PROTOTYPE_.indexOf ? function(arr, obj, opt_fromIndex) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex)
+} : function(arr, obj, opt_fromIndex) {
+ var fromIndex = opt_fromIndex == null ? 0 : opt_fromIndex < 0 ? Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex;
+ if(goog.isString(arr)) {
+ if(!goog.isString(obj) || obj.length != 1) {
+ return-1
+ }
+ return arr.indexOf(obj, fromIndex)
+ }
+ for(var i = fromIndex;i < arr.length;i++) {
+ if(i in arr && arr[i] === obj) {
+ return i
+ }
+ }
+ return-1
+};
+goog.array.lastIndexOf = goog.array.ARRAY_PROTOTYPE_.lastIndexOf ? function(arr, obj, opt_fromIndex) {
+ goog.asserts.assert(arr.length != null);
+ var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
+ return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex)
+} : function(arr, obj, opt_fromIndex) {
+ var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
+ if(fromIndex < 0) {
+ fromIndex = Math.max(0, arr.length + fromIndex)
+ }
+ if(goog.isString(arr)) {
+ if(!goog.isString(obj) || obj.length != 1) {
+ return-1
+ }
+ return arr.lastIndexOf(obj, fromIndex)
+ }
+ for(var i = fromIndex;i >= 0;i--) {
+ if(i in arr && arr[i] === obj) {
+ return i
+ }
+ }
+ return-1
+};
+goog.array.forEach = goog.array.ARRAY_PROTOTYPE_.forEach ? function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj)
+} : function(arr, f, opt_obj) {
+ var l = arr.length;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = 0;i < l;i++) {
+ if(i in arr2) {
+ f.call(opt_obj, arr2[i], i, arr)
+ }
+ }
+};
+goog.array.forEachRight = function(arr, f, opt_obj) {
+ var l = arr.length;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = l - 1;i >= 0;--i) {
+ if(i in arr2) {
+ f.call(opt_obj, arr2[i], i, arr)
+ }
+ }
+};
+goog.array.filter = goog.array.ARRAY_PROTOTYPE_.filter ? function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj)
+} : function(arr, f, opt_obj) {
+ var l = arr.length;
+ var res = [];
+ var resLength = 0;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = 0;i < l;i++) {
+ if(i in arr2) {
+ var val = arr2[i];
+ if(f.call(opt_obj, val, i, arr)) {
+ res[resLength++] = val
+ }
+ }
+ }
+ return res
+};
+goog.array.map = goog.array.ARRAY_PROTOTYPE_.map ? function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj)
+} : function(arr, f, opt_obj) {
+ var l = arr.length;
+ var res = new Array(l);
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = 0;i < l;i++) {
+ if(i in arr2) {
+ res[i] = f.call(opt_obj, arr2[i], i, arr)
+ }
+ }
+ return res
+};
+goog.array.reduce = function(arr, f, val, opt_obj) {
+ if(arr.reduce) {
+ if(opt_obj) {
+ return arr.reduce(goog.bind(f, opt_obj), val)
+ }else {
+ return arr.reduce(f, val)
+ }
+ }
+ var rval = val;
+ goog.array.forEach(arr, function(val, index) {
+ rval = f.call(opt_obj, rval, val, index, arr)
+ });
+ return rval
+};
+goog.array.reduceRight = function(arr, f, val, opt_obj) {
+ if(arr.reduceRight) {
+ if(opt_obj) {
+ return arr.reduceRight(goog.bind(f, opt_obj), val)
+ }else {
+ return arr.reduceRight(f, val)
+ }
+ }
+ var rval = val;
+ goog.array.forEachRight(arr, function(val, index) {
+ rval = f.call(opt_obj, rval, val, index, arr)
+ });
+ return rval
+};
+goog.array.some = goog.array.ARRAY_PROTOTYPE_.some ? function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj)
+} : function(arr, f, opt_obj) {
+ var l = arr.length;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = 0;i < l;i++) {
+ if(i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return true
+ }
+ }
+ return false
+};
+goog.array.every = goog.array.ARRAY_PROTOTYPE_.every ? function(arr, f, opt_obj) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj)
+} : function(arr, f, opt_obj) {
+ var l = arr.length;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = 0;i < l;i++) {
+ if(i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
+ return false
+ }
+ }
+ return true
+};
+goog.array.find = function(arr, f, opt_obj) {
+ var i = goog.array.findIndex(arr, f, opt_obj);
+ return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i]
+};
+goog.array.findIndex = function(arr, f, opt_obj) {
+ var l = arr.length;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = 0;i < l;i++) {
+ if(i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return i
+ }
+ }
+ return-1
+};
+goog.array.findRight = function(arr, f, opt_obj) {
+ var i = goog.array.findIndexRight(arr, f, opt_obj);
+ return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i]
+};
+goog.array.findIndexRight = function(arr, f, opt_obj) {
+ var l = arr.length;
+ var arr2 = goog.isString(arr) ? arr.split("") : arr;
+ for(var i = l - 1;i >= 0;i--) {
+ if(i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
+ return i
+ }
+ }
+ return-1
+};
+goog.array.contains = function(arr, obj) {
+ return goog.array.indexOf(arr, obj) >= 0
+};
+goog.array.isEmpty = function(arr) {
+ return arr.length == 0
+};
+goog.array.clear = function(arr) {
+ if(!goog.isArray(arr)) {
+ for(var i = arr.length - 1;i >= 0;i--) {
+ delete arr[i]
+ }
+ }
+ arr.length = 0
+};
+goog.array.insert = function(arr, obj) {
+ if(!goog.array.contains(arr, obj)) {
+ arr.push(obj)
+ }
+};
+goog.array.insertAt = function(arr, obj, opt_i) {
+ goog.array.splice(arr, opt_i, 0, obj)
+};
+goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
+ goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd)
+};
+goog.array.insertBefore = function(arr, obj, opt_obj2) {
+ var i;
+ if(arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
+ arr.push(obj)
+ }else {
+ goog.array.insertAt(arr, obj, i)
+ }
+};
+goog.array.remove = function(arr, obj) {
+ var i = goog.array.indexOf(arr, obj);
+ var rv;
+ if(rv = i >= 0) {
+ goog.array.removeAt(arr, i)
+ }
+ return rv
+};
+goog.array.removeAt = function(arr, i) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1
+};
+goog.array.removeIf = function(arr, f, opt_obj) {
+ var i = goog.array.findIndex(arr, f, opt_obj);
+ if(i >= 0) {
+ goog.array.removeAt(arr, i);
+ return true
+ }
+ return false
+};
+goog.array.concat = function(var_args) {
+ return goog.array.ARRAY_PROTOTYPE_.concat.apply(goog.array.ARRAY_PROTOTYPE_, arguments)
+};
+goog.array.clone = function(arr) {
+ if(goog.isArray(arr)) {
+ return goog.array.concat(arr)
+ }else {
+ var rv = [];
+ for(var i = 0, len = arr.length;i < len;i++) {
+ rv[i] = arr[i]
+ }
+ return rv
+ }
+};
+goog.array.toArray = function(object) {
+ if(goog.isArray(object)) {
+ return goog.array.concat(object)
+ }
+ return goog.array.clone(object)
+};
+goog.array.extend = function(arr1, var_args) {
+ for(var i = 1;i < arguments.length;i++) {
+ var arr2 = arguments[i];
+ var isArrayLike;
+ if(goog.isArray(arr2) || (isArrayLike = goog.isArrayLike(arr2)) && arr2.hasOwnProperty("callee")) {
+ arr1.push.apply(arr1, arr2)
+ }else {
+ if(isArrayLike) {
+ var len1 = arr1.length;
+ var len2 = arr2.length;
+ for(var j = 0;j < len2;j++) {
+ arr1[len1 + j] = arr2[j]
+ }
+ }else {
+ arr1.push(arr2)
+ }
+ }
+ }
+};
+goog.array.splice = function(arr, index, howMany, var_args) {
+ goog.asserts.assert(arr.length != null);
+ return goog.array.ARRAY_PROTOTYPE_.splice.apply(arr, goog.array.slice(arguments, 1))
+};
+goog.array.slice = function(arr, start, opt_end) {
+ goog.asserts.assert(arr.length != null);
+ if(arguments.length <= 2) {
+ return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start)
+ }else {
+ return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end)
+ }
+};
+goog.array.removeDuplicates = function(arr, opt_rv) {
+ var rv = opt_rv || arr;
+ var seen = {}, cursorInsert = 0, cursorRead = 0;
+ while(cursorRead < arr.length) {
+ var current = arr[cursorRead++];
+ var uid = goog.isObject(current) ? goog.getUid(current) : current;
+ if(!Object.prototype.hasOwnProperty.call(seen, uid)) {
+ seen[uid] = true;
+ rv[cursorInsert++] = current
+ }
+ }
+ rv.length = cursorInsert
+};
+goog.array.binarySearch = function(arr, target, opt_compareFn) {
+ return goog.array.binarySearch_(arr, opt_compareFn || goog.array.defaultCompare, false, target)
+};
+goog.array.binarySelect = function(arr, evaluator, opt_obj) {
+ return goog.array.binarySearch_(arr, evaluator, true, undefined, opt_obj)
+};
+goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target, opt_selfObj) {
+ var left = 0;
+ var right = arr.length;
+ var found;
+ while(left < right) {
+ var middle = left + right >> 1;
+ var compareResult;
+ if(isEvaluator) {
+ compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr)
+ }else {
+ compareResult = compareFn(opt_target, arr[middle])
+ }
+ if(compareResult > 0) {
+ left = middle + 1
+ }else {
+ right = middle;
+ found = !compareResult
+ }
+ }
+ return found ? left : ~left
+};
+goog.array.sort = function(arr, opt_compareFn) {
+ goog.asserts.assert(arr.length != null);
+ goog.array.ARRAY_PROTOTYPE_.sort.call(arr, opt_compareFn || goog.array.defaultCompare)
+};
+goog.array.stableSort = function(arr, opt_compareFn) {
+ for(var i = 0;i < arr.length;i++) {
+ arr[i] = {index:i, value:arr[i]}
+ }
+ var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
+ function stableCompareFn(obj1, obj2) {
+ return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index
+ }
+ goog.array.sort(arr, stableCompareFn);
+ for(var i = 0;i < arr.length;i++) {
+ arr[i] = arr[i].value
+ }
+};
+goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
+ var compare = opt_compareFn || goog.array.defaultCompare;
+ goog.array.sort(arr, function(a, b) {
+ return compare(a[key], b[key])
+ })
+};
+goog.array.equals = function(arr1, arr2, opt_equalsFn) {
+ if(!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) || arr1.length != arr2.length) {
+ return false
+ }
+ var l = arr1.length;
+ var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
+ for(var i = 0;i < l;i++) {
+ if(!equalsFn(arr1[i], arr2[i])) {
+ return false
+ }
+ }
+ return true
+};
+goog.array.compare = function(arr1, arr2, opt_equalsFn) {
+ return goog.array.equals(arr1, arr2, opt_equalsFn)
+};
+goog.array.defaultCompare = function(a, b) {
+ return a > b ? 1 : a < b ? -1 : 0
+};
+goog.array.defaultCompareEquality = function(a, b) {
+ return a === b
+};
+goog.array.binaryInsert = function(array, value, opt_compareFn) {
+ var index = goog.array.binarySearch(array, value, opt_compareFn);
+ if(index < 0) {
+ goog.array.insertAt(array, value, -(index + 1));
+ return true
+ }
+ return false
+};
+goog.array.binaryRemove = function(array, value, opt_compareFn) {
+ var index = goog.array.binarySearch(array, value, opt_compareFn);
+ return index >= 0 ? goog.array.removeAt(array, index) : false
+};
+goog.array.bucket = function(array, sorter) {
+ var buckets = {};
+ for(var i = 0;i < array.length;i++) {
+ var value = array[i];
+ var key = sorter(value, i, array);
+ if(goog.isDef(key)) {
+ var bucket = buckets[key] || (buckets[key] = []);
+ bucket.push(value)
+ }
+ }
+ return buckets
+};
+goog.array.repeat = function(value, n) {
+ var array = [];
+ for(var i = 0;i < n;i++) {
+ array[i] = value
+ }
+ return array
+};
+goog.array.flatten = function(var_args) {
+ var result = [];
+ for(var i = 0;i < arguments.length;i++) {
+ var element = arguments[i];
+ if(goog.isArray(element)) {
+ result.push.apply(result, goog.array.flatten.apply(null, element))
+ }else {
+ result.push(element)
+ }
+ }
+ return result
+};
+goog.array.rotate = function(array, n) {
+ goog.asserts.assert(array.length != null);
+ if(array.length) {
+ n %= array.length;
+ if(n > 0) {
+ goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n))
+ }else {
+ if(n < 0) {
+ goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n))
+ }
+ }
+ }
+ return array
+};
+goog.array.zip = function(var_args) {
+ if(!arguments.length) {
+ return[]
+ }
+ var result = [];
+ for(var i = 0;true;i++) {
+ var value = [];
+ for(var j = 0;j < arguments.length;j++) {
+ var arr = arguments[j];
+ if(i >= arr.length) {
+ return result
+ }
+ value.push(arr[i])
+ }
+ result.push(value)
+ }
+};
+goog.provide("goog.userAgent");
+goog.require("goog.string");
+goog.userAgent.ASSUME_IE = false;
+goog.userAgent.ASSUME_GECKO = false;
+goog.userAgent.ASSUME_WEBKIT = false;
+goog.userAgent.ASSUME_MOBILE_WEBKIT = false;
+goog.userAgent.ASSUME_OPERA = false;
+goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE || goog.userAgent.ASSUME_GECKO || goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_OPERA;
+goog.userAgent.getUserAgentString = function() {
+ return goog.global["navigator"] ? goog.global["navigator"].userAgent : null
+};
+goog.userAgent.getNavigator = function() {
+ return goog.global["navigator"]
+};
+goog.userAgent.init_ = function() {
+ goog.userAgent.detectedOpera_ = false;
+ goog.userAgent.detectedIe_ = false;
+ goog.userAgent.detectedWebkit_ = false;
+ goog.userAgent.detectedMobile_ = false;
+ goog.userAgent.detectedGecko_ = false;
+ var ua;
+ if(!goog.userAgent.BROWSER_KNOWN_ && (ua = goog.userAgent.getUserAgentString())) {
+ var navigator = goog.userAgent.getNavigator();
+ goog.userAgent.detectedOpera_ = ua.indexOf("Opera") == 0;
+ goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ && ua.indexOf("MSIE") != -1;
+ goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ && ua.indexOf("WebKit") != -1;
+ goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ && ua.indexOf("Mobile") != -1;
+ goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ && !goog.userAgent.detectedWebkit_ && navigator.product == "Gecko"
+ }
+};
+if(!goog.userAgent.BROWSER_KNOWN_) {
+ goog.userAgent.init_()
+}
+goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_;
+goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_;
+goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_GECKO : goog.userAgent.detectedGecko_;
+goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT : goog.userAgent.detectedWebkit_;
+goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.detectedMobile_;
+goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
+goog.userAgent.determinePlatform_ = function() {
+ var navigator = goog.userAgent.getNavigator();
+ return navigator && navigator.platform || ""
+};
+goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
+goog.userAgent.ASSUME_MAC = false;
+goog.userAgent.ASSUME_WINDOWS = false;
+goog.userAgent.ASSUME_LINUX = false;
+goog.userAgent.ASSUME_X11 = false;
+goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC || goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX || goog.userAgent.ASSUME_X11;
+goog.userAgent.initPlatform_ = function() {
+ goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM, "Mac");
+ goog.userAgent.detectedWindows_ = goog.string.contains(goog.userAgent.PLATFORM, "Win");
+ goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM, "Linux");
+ goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() && goog.string.contains(goog.userAgent.getNavigator()["appVersion"] || "", "X11")
+};
+if(!goog.userAgent.PLATFORM_KNOWN_) {
+ goog.userAgent.initPlatform_()
+}
+goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_;
+goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_;
+goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_;
+goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_;
+goog.userAgent.determineVersion_ = function() {
+ var version = "", re;
+ if(goog.userAgent.OPERA && goog.global["opera"]) {
+ var operaVersion = goog.global["opera"].version;
+ version = typeof operaVersion == "function" ? operaVersion() : operaVersion
+ }else {
+ if(goog.userAgent.GECKO) {
+ re = /rv\:([^\);]+)(\)|;)/
+ }else {
+ if(goog.userAgent.IE) {
+ re = /MSIE\s+([^\);]+)(\)|;)/
+ }else {
+ if(goog.userAgent.WEBKIT) {
+ re = /WebKit\/(\S+)/
+ }
+ }
+ }
+ if(re) {
+ var arr = re.exec(goog.userAgent.getUserAgentString());
+ version = arr ? arr[1] : ""
+ }
+ }
+ if(goog.userAgent.IE) {
+ var docMode = goog.userAgent.getDocumentMode_();
+ if(docMode > parseFloat(version)) {
+ return String(docMode)
+ }
+ }
+ return version
+};
+goog.userAgent.getDocumentMode_ = function() {
+ var doc = goog.global["document"];
+ return doc ? doc["documentMode"] : undefined
+};
+goog.userAgent.VERSION = goog.userAgent.determineVersion_();
+goog.userAgent.compare = function(v1, v2) {
+ return goog.string.compareVersions(v1, v2)
+};
+goog.userAgent.isVersionCache_ = {};
+goog.userAgent.isVersion = function(version) {
+ return goog.userAgent.isVersionCache_[version] || (goog.userAgent.isVersionCache_[version] = goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0)
+};
+goog.provide("goog.dom.BrowserFeature");
+goog.require("goog.userAgent");
+goog.dom.BrowserFeature = {
+ CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE || goog.userAgent.isVersion("9"),
+ CAN_USE_INNER_TEXT: goog.userAgent.IE && !goog.userAgent.isVersion("9"),
+ INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE
+};
+goog.provide("goog.dom.TagName");
+goog.dom.TagName = {A:"A", ABBR:"ABBR", ACRONYM:"ACRONYM", ADDRESS:"ADDRESS", APPLET:"APPLET", AREA:"AREA", B:"B", BASE:"BASE", BASEFONT:"BASEFONT", BDO:"BDO", BIG:"BIG", BLOCKQUOTE:"BLOCKQUOTE", BODY:"BODY", BR:"BR", BUTTON:"BUTTON", CANVAS:"CANVAS", CAPTION:"CAPTION", CENTER:"CENTER", CITE:"CITE", CODE:"CODE", COL:"COL", COLGROUP:"COLGROUP", DD:"DD", DEL:"DEL", DFN:"DFN", DIR:"DIR", DIV:"DIV", DL:"DL", DT:"DT", EM:"EM", FIELDSET:"FIELDSET", FONT:"FONT", FORM:"FORM", FRAME:"FRAME", FRAMESET:"FRAMESET",
+H1:"H1", H2:"H2", H3:"H3", H4:"H4", H5:"H5", H6:"H6", HEAD:"HEAD", HR:"HR", HTML:"HTML", I:"I", IFRAME:"IFRAME", IMG:"IMG", INPUT:"INPUT", INS:"INS", ISINDEX:"ISINDEX", KBD:"KBD", LABEL:"LABEL", LEGEND:"LEGEND", LI:"LI", LINK:"LINK", MAP:"MAP", MENU:"MENU", META:"META", NOFRAMES:"NOFRAMES", NOSCRIPT:"NOSCRIPT", OBJECT:"OBJECT", OL:"OL", OPTGROUP:"OPTGROUP", OPTION:"OPTION", P:"P", PARAM:"PARAM", PRE:"PRE", Q:"Q", S:"S", SAMP:"SAMP", SCRIPT:"SCRIPT", SELECT:"SELECT", SMALL:"SMALL", SPAN:"SPAN", STRIKE:"STRIKE",
+STRONG:"STRONG", STYLE:"STYLE", SUB:"SUB", SUP:"SUP", TABLE:"TABLE", TBODY:"TBODY", TD:"TD", TEXTAREA:"TEXTAREA", TFOOT:"TFOOT", TH:"TH", THEAD:"THEAD", TITLE:"TITLE", TR:"TR", TT:"TT", U:"U", UL:"UL", VAR:"VAR"};
+goog.provide("goog.dom.classes");
+goog.require("goog.array");
+goog.dom.classes.set = function(element, className) {
+ element.className = className
+};
+goog.dom.classes.get = function(element) {
+ var className = element.className;
+ return className && typeof className.split == "function" ? className.split(/\s+/) : []
+};
+goog.dom.classes.add = function(element, var_args) {
+ var classes = goog.dom.classes.get(element);
+ var args = goog.array.slice(arguments, 1);
+ var b = goog.dom.classes.add_(classes, args);
+ element.className = classes.join(" ");
+ return b
+};
+goog.dom.classes.remove = function(element, var_args) {
+ var classes = goog.dom.classes.get(element);
+ var args = goog.array.slice(arguments, 1);
+ var b = goog.dom.classes.remove_(classes, args);
+ element.className = classes.join(" ");
+ return b
+};
+goog.dom.classes.add_ = function(classes, args) {
+ var rv = 0;
+ for(var i = 0;i < args.length;i++) {
+ if(!goog.array.contains(classes, args[i])) {
+ classes.push(args[i]);
+ rv++
+ }
+ }
+ return rv == args.length
+};
+goog.dom.classes.remove_ = function(classes, args) {
+ var rv = 0;
+ for(var i = 0;i < classes.length;i++) {
+ if(goog.array.contains(args, classes[i])) {
+ goog.array.splice(classes, i--, 1);
+ rv++
+ }
+ }
+ return rv == args.length
+};
+goog.dom.classes.swap = function(element, fromClass, toClass) {
+ var classes = goog.dom.classes.get(element);
+ var removed = false;
+ for(var i = 0;i < classes.length;i++) {
+ if(classes[i] == fromClass) {
+ goog.array.splice(classes, i--, 1);
+ removed = true
+ }
+ }
+ if(removed) {
+ classes.push(toClass);
+ element.className = classes.join(" ")
+ }
+ return removed
+};
+goog.dom.classes.addRemove = function(element, classesToRemove, classesToAdd) {
+ var classes = goog.dom.classes.get(element);
+ if(goog.isString(classesToRemove)) {
+ goog.array.remove(classes, classesToRemove)
+ }else {
+ if(goog.isArray(classesToRemove)) {
+ goog.dom.classes.remove_(classes, classesToRemove)
+ }
+ }
+ if(goog.isString(classesToAdd) && !goog.array.contains(classes, classesToAdd)) {
+ classes.push(classesToAdd)
+ }else {
+ if(goog.isArray(classesToAdd)) {
+ goog.dom.classes.add_(classes, classesToAdd)
+ }
+ }
+ element.className = classes.join(" ")
+};
+goog.dom.classes.has = function(element, className) {
+ return goog.array.contains(goog.dom.classes.get(element), className)
+};
+goog.dom.classes.enable = function(element, className, enabled) {
+ if(enabled) {
+ goog.dom.classes.add(element, className)
+ }else {
+ goog.dom.classes.remove(element, className)
+ }
+};
+goog.dom.classes.toggle = function(element, className) {
+ var add = !goog.dom.classes.has(element, className);
+ goog.dom.classes.enable(element, className, add);
+ return add
+};
+goog.provide("goog.math.Coordinate");
+goog.math.Coordinate = function(opt_x, opt_y) {
+ this.x = goog.isDef(opt_x) ? opt_x : 0;
+ this.y = goog.isDef(opt_y) ? opt_y : 0
+};
+goog.math.Coordinate.prototype.clone = function() {
+ return new goog.math.Coordinate(this.x, this.y)
+};
+if(goog.DEBUG) {
+ goog.math.Coordinate.prototype.toString = function() {
+ return"(" + this.x + ", " + this.y + ")"
+ }
+}
+goog.math.Coordinate.equals = function(a, b) {
+ if(a == b) {
+ return true
+ }
+ if(!a || !b) {
+ return false
+ }
+ return a.x == b.x && a.y == b.y
+};
+goog.math.Coordinate.distance = function(a, b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return Math.sqrt(dx * dx + dy * dy)
+};
+goog.math.Coordinate.squaredDistance = function(a, b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return dx * dx + dy * dy
+};
+goog.math.Coordinate.difference = function(a, b) {
+ return new goog.math.Coordinate(a.x - b.x, a.y - b.y)
+};
+goog.math.Coordinate.sum = function(a, b) {
+ return new goog.math.Coordinate(a.x + b.x, a.y + b.y)
+};
+goog.provide("goog.math.Size");
+goog.math.Size = function(width, height) {
+ this.width = width;
+ this.height = height
+};
+goog.math.Size.equals = function(a, b) {
+ if(a == b) {
+ return true
+ }
+ if(!a || !b) {
+ return false
+ }
+ return a.width == b.width && a.height == b.height
+};
+goog.math.Size.prototype.clone = function() {
+ return new goog.math.Size(this.width, this.height)
+};
+if(goog.DEBUG) {
+ goog.math.Size.prototype.toString = function() {
+ return"(" + this.width + " x " + this.height + ")"
+ }
+}
+goog.math.Size.prototype.getLongest = function() {
+ return Math.max(this.width, this.height)
+};
+goog.math.Size.prototype.getShortest = function() {
+ return Math.min(this.width, this.height)
+};
+goog.math.Size.prototype.area = function() {
+ return this.width * this.height
+};
+goog.math.Size.prototype.perimeter = function() {
+ return(this.width + this.height) * 2
+};
+goog.math.Size.prototype.aspectRatio = function() {
+ return this.width / this.height
+};
+goog.math.Size.prototype.isEmpty = function() {
+ return!this.area()
+};
+goog.math.Size.prototype.ceil = function() {
+ this.width = Math.ceil(this.width);
+ this.height = Math.ceil(this.height);
+ return this
+};
+goog.math.Size.prototype.fitsInside = function(target) {
+ return this.width <= target.width && this.height <= target.height
+};
+goog.math.Size.prototype.floor = function() {
+ this.width = Math.floor(this.width);
+ this.height = Math.floor(this.height);
+ return this
+};
+goog.math.Size.prototype.round = function() {
+ this.width = Math.round(this.width);
+ this.height = Math.round(this.height);
+ return this
+};
+goog.math.Size.prototype.scale = function(s) {
+ this.width *= s;
+ this.height *= s;
+ return this
+};
+goog.math.Size.prototype.scaleToFit = function(target) {
+ var s = this.aspectRatio() > target.aspectRatio() ? target.width / this.width : target.height / this.height;
+ return this.scale(s)
+};
+goog.provide("goog.object");
+goog.object.forEach = function(obj, f, opt_obj) {
+ for(var key in obj) {
+ f.call(opt_obj, obj[key], key, obj)
+ }
+};
+goog.object.filter = function(obj, f, opt_obj) {
+ var res = {};
+ for(var key in obj) {
+ if(f.call(opt_obj, obj[key], key, obj)) {
+ res[key] = obj[key]
+ }
+ }
+ return res
+};
+goog.object.map = function(obj, f, opt_obj) {
+ var res = {};
+ for(var key in obj) {
+ res[key] = f.call(opt_obj, obj[key], key, obj)
+ }
+ return res
+};
+goog.object.some = function(obj, f, opt_obj) {
+ for(var key in obj) {
+ if(f.call(opt_obj, obj[key], key, obj)) {
+ return true
+ }
+ }
+ return false
+};
+goog.object.every = function(obj, f, opt_obj) {
+ for(var key in obj) {
+ if(!f.call(opt_obj, obj[key], key, obj)) {
+ return false
+ }
+ }
+ return true
+};
+goog.object.getCount = function(obj) {
+ var rv = 0;
+ for(var key in obj) {
+ rv++
+ }
+ return rv
+};
+goog.object.getAnyKey = function(obj) {
+ for(var key in obj) {
+ return key
+ }
+};
+goog.object.getAnyValue = function(obj) {
+ for(var key in obj) {
+ return obj[key]
+ }
+};
+goog.object.contains = function(obj, val) {
+ return goog.object.containsValue(obj, val)
+};
+goog.object.getValues = function(obj) {
+ var res = [];
+ var i = 0;
+ for(var key in obj) {
+ res[i++] = obj[key]
+ }
+ return res
+};
+goog.object.getKeys = function(obj) {
+ var res = [];
+ var i = 0;
+ for(var key in obj) {
+ res[i++] = key
+ }
+ return res
+};
+goog.object.containsKey = function(obj, key) {
+ return key in obj
+};
+goog.object.containsValue = function(obj, val) {
+ for(var key in obj) {
+ if(obj[key] == val) {
+ return true
+ }
+ }
+ return false
+};
+goog.object.findKey = function(obj, f, opt_this) {
+ for(var key in obj) {
+ if(f.call(opt_this, obj[key], key, obj)) {
+ return key
+ }
+ }
+ return undefined
+};
+goog.object.findValue = function(obj, f, opt_this) {
+ var key = goog.object.findKey(obj, f, opt_this);
+ return key && obj[key]
+};
+goog.object.isEmpty = function(obj) {
+ for(var key in obj) {
+ return false
+ }
+ return true
+};
+goog.object.clear = function(obj) {
+ var keys = goog.object.getKeys(obj);
+ for(var i = keys.length - 1;i >= 0;i--) {
+ goog.object.remove(obj, keys[i])
+ }
+};
+goog.object.remove = function(obj, key) {
+ var rv;
+ if(rv = key in obj) {
+ delete obj[key]
+ }
+ return rv
+};
+goog.object.add = function(obj, key, val) {
+ if(key in obj) {
+ throw Error('The object already contains the key "' + key + '"');
+ }
+ goog.object.set(obj, key, val)
+};
+goog.object.get = function(obj, key, opt_val) {
+ if(key in obj) {
+ return obj[key]
+ }
+ return opt_val
+};
+goog.object.set = function(obj, key, value) {
+ obj[key] = value
+};
+goog.object.setIfUndefined = function(obj, key, value) {
+ return key in obj ? obj[key] : obj[key] = value
+};
+goog.object.clone = function(obj) {
+ var res = {};
+ for(var key in obj) {
+ res[key] = obj[key]
+ }
+ return res
+};
+goog.object.transpose = function(obj) {
+ var transposed = {};
+ for(var key in obj) {
+ transposed[obj[key]] = key
+ }
+ return transposed
+};
+goog.object.PROTOTYPE_FIELDS_ = ["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf"];
+goog.object.extend = function(target, var_args) {
+ var key, source;
+ for(var i = 1;i < arguments.length;i++) {
+ source = arguments[i];
+ for(key in source) {
+ target[key] = source[key]
+ }
+ for(var j = 0;j < goog.object.PROTOTYPE_FIELDS_.length;j++) {
+ key = goog.object.PROTOTYPE_FIELDS_[j];
+ if(Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key]
+ }
+ }
+ }
+};
+goog.object.create = function(var_args) {
+ var argLength = arguments.length;
+ if(argLength == 1 && goog.isArray(arguments[0])) {
+ return goog.object.create.apply(null, arguments[0])
+ }
+ if(argLength % 2) {
+ throw Error("Uneven number of arguments");
+ }
+ var rv = {};
+ for(var i = 0;i < argLength;i += 2) {
+ rv[arguments[i]] = arguments[i + 1]
+ }
+ return rv
+};
+goog.object.createSet = function(var_args) {
+ var argLength = arguments.length;
+ if(argLength == 1 && goog.isArray(arguments[0])) {
+ return goog.object.createSet.apply(null, arguments[0])
+ }
+ var rv = {};
+ for(var i = 0;i < argLength;i++) {
+ rv[arguments[i]] = true
+ }
+ return rv
+};
+goog.provide("goog.dom");
+goog.provide("goog.dom.DomHelper");
+goog.provide("goog.dom.NodeType");
+goog.require("goog.array");
+goog.require("goog.dom.BrowserFeature");
+goog.require("goog.dom.TagName");
+goog.require("goog.dom.classes");
+goog.require("goog.math.Coordinate");
+goog.require("goog.math.Size");
+goog.require("goog.object");
+goog.require("goog.string");
+goog.require("goog.userAgent");
+goog.dom.ASSUME_QUIRKS_MODE = false;
+goog.dom.ASSUME_STANDARDS_MODE = false;
+goog.dom.COMPAT_MODE_KNOWN_ = goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
+goog.dom.NodeType = {ELEMENT:1, ATTRIBUTE:2, TEXT:3, CDATA_SECTION:4, ENTITY_REFERENCE:5, ENTITY:6, PROCESSING_INSTRUCTION:7, COMMENT:8, DOCUMENT:9, DOCUMENT_TYPE:10, DOCUMENT_FRAGMENT:11, NOTATION:12};
+goog.dom.getDomHelper = function(opt_element) {
+ return opt_element ? new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : goog.dom.defaultDomHelper_ || (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper)
+};
+goog.dom.defaultDomHelper_;
+goog.dom.getDocument = function() {
+ return document
+};
+goog.dom.getElement = function(element) {
+ return goog.isString(element) ? document.getElementById(element) : element
+};
+goog.dom.$ = goog.dom.getElement;
+goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
+ return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class, opt_el)
+};
+goog.dom.getElementsByClass = function(className, opt_el) {
+ var parent = opt_el || document;
+ if(goog.dom.canUseQuerySelector_(parent)) {
+ return parent.querySelectorAll("." + className)
+ }else {
+ if(parent.getElementsByClassName) {
+ return parent.getElementsByClassName(className)
+ }
+ }
+ return goog.dom.getElementsByTagNameAndClass_(document, "*", className, opt_el)
+};
+goog.dom.getElementByClass = function(className, opt_el) {
+ var parent = opt_el || document;
+ var retVal = null;
+ if(goog.dom.canUseQuerySelector_(parent)) {
+ retVal = parent.querySelector("." + className)
+ }else {
+ retVal = goog.dom.getElementsByClass(className, opt_el)[0]
+ }
+ return retVal || null
+};
+goog.dom.canUseQuerySelector_ = function(parent) {
+ return parent.querySelectorAll && parent.querySelector && (!goog.userAgent.WEBKIT || goog.dom.isCss1CompatMode_(document) || goog.userAgent.isVersion("528"))
+};
+goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class, opt_el) {
+ var parent = opt_el || doc;
+ var tagName = opt_tag && opt_tag != "*" ? opt_tag.toUpperCase() : "";
+ if(goog.dom.canUseQuerySelector_(parent) && (tagName || opt_class)) {
+ var query = tagName + (opt_class ? "." + opt_class : "");
+ return parent.querySelectorAll(query)
+ }
+ if(opt_class && parent.getElementsByClassName) {
+ var els = parent.getElementsByClassName(opt_class);
+ if(tagName) {
+ var arrayLike = {};
+ var len = 0;
+ for(var i = 0, el;el = els[i];i++) {
+ if(tagName == el.nodeName) {
+ arrayLike[len++] = el
+ }
+ }
+ arrayLike.length = len;
+ return arrayLike
+ }else {
+ return els
+ }
+ }
+ var els = parent.getElementsByTagName(tagName || "*");
+ if(opt_class) {
+ var arrayLike = {};
+ var len = 0;
+ for(var i = 0, el;el = els[i];i++) {
+ var className = el.className;
+ if(typeof className.split == "function" && goog.array.contains(className.split(/\s+/), opt_class)) {
+ arrayLike[len++] = el
+ }
+ }
+ arrayLike.length = len;
+ return arrayLike
+ }else {
+ return els
+ }
+};
+goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
+goog.dom.setProperties = function(element, properties) {
+ goog.object.forEach(properties, function(val, key) {
+ if(key == "style") {
+ element.style.cssText = val
+ }else {
+ if(key == "class") {
+ element.className = val
+ }else {
+ if(key == "for") {
+ element.htmlFor = val
+ }else {
+ if(key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
+ element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val)
+ }else {
+ element[key] = val
+ }
+ }
+ }
+ }
+ })
+};
+goog.dom.DIRECT_ATTRIBUTE_MAP_ = {cellpadding:"cellPadding", cellspacing:"cellSpacing", colspan:"colSpan", rowspan:"rowSpan", valign:"vAlign", height:"height", width:"width", usemap:"useMap", frameborder:"frameBorder", type:"type"};
+goog.dom.getViewportSize = function(opt_window) {
+ return goog.dom.getViewportSize_(opt_window || window)
+};
+goog.dom.getViewportSize_ = function(win) {
+ var doc = win.document;
+ if(goog.userAgent.WEBKIT && !goog.userAgent.isVersion("500") && !goog.userAgent.MOBILE) {
+ if(typeof win.innerHeight == "undefined") {
+ win = window
+ }
+ var innerHeight = win.innerHeight;
+ var scrollHeight = win.document.documentElement.scrollHeight;
+ if(win == win.top) {
+ if(scrollHeight < innerHeight) {
+ innerHeight -= 15
+ }
+ }
+ return new goog.math.Size(win.innerWidth, innerHeight)
+ }
+ var readsFromDocumentElement = goog.dom.isCss1CompatMode_(doc);
+ if(goog.userAgent.OPERA && !goog.userAgent.isVersion("9.50")) {
+ readsFromDocumentElement = false
+ }
+ var el = readsFromDocumentElement ? doc.documentElement : doc.body;
+ return new goog.math.Size(el.clientWidth, el.clientHeight)
+};
+goog.dom.getDocumentHeight = function() {
+ return goog.dom.getDocumentHeight_(window)
+};
+goog.dom.getDocumentHeight_ = function(win) {
+ var doc = win.document;
+ var height = 0;
+ if(doc) {
+ var vh = goog.dom.getViewportSize_(win).height;
+ var body = doc.body;
+ var docEl = doc.documentElement;
+ if(goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
+ height = docEl.scrollHeight != vh ? docEl.scrollHeight : docEl.offsetHeight
+ }else {
+ var sh = docEl.scrollHeight;
+ var oh = docEl.offsetHeight;
+ if(docEl.clientHeight != oh) {
+ sh = body.scrollHeight;
+ oh = body.offsetHeight
+ }
+ if(sh > vh) {
+ height = sh > oh ? sh : oh
+ }else {
+ height = sh < oh ? sh : oh
+ }
+ }
+ }
+ return height
+};
+goog.dom.getPageScroll = function(opt_window) {
+ var win = opt_window || goog.global || window;
+ return goog.dom.getDomHelper(win.document).getDocumentScroll()
+};
+goog.dom.getDocumentScroll = function() {
+ return goog.dom.getDocumentScroll_(document)
+};
+goog.dom.getDocumentScroll_ = function(doc) {
+ var el = goog.dom.getDocumentScrollElement_(doc);
+ return new goog.math.Coordinate(el.scrollLeft, el.scrollTop)
+};
+goog.dom.getDocumentScrollElement = function() {
+ return goog.dom.getDocumentScrollElement_(document)
+};
+goog.dom.getDocumentScrollElement_ = function(doc) {
+ return!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body
+};
+goog.dom.getWindow = function(opt_doc) {
+ return opt_doc ? goog.dom.getWindow_(opt_doc) : window
+};
+goog.dom.getWindow_ = function(doc) {
+ return doc.parentWindow || doc.defaultView
+};
+goog.dom.createDom = function(tagName, opt_attributes, var_args) {
+ return goog.dom.createDom_(document, arguments)
+};
+goog.dom.createDom_ = function(doc, args) {
+ var tagName = args[0];
+ var attributes = args[1];
+ if(!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes && (attributes.name || attributes.type)) {
+ var tagNameArr = ["<", tagName];
+ if(attributes.name) {
+ tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), '"')
+ }
+ if(attributes.type) {
+ tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), '"');
+ var clone = {};
+ goog.object.extend(clone, attributes);
+ attributes = clone;
+ delete attributes.type
+ }
+ tagNameArr.push(">");
+ tagName = tagNameArr.join("")
+ }
+ var element = doc.createElement(tagName);
+ if(attributes) {
+ if(goog.isString(attributes)) {
+ element.className = attributes
+ }else {
+ if(goog.isArray(attributes)) {
+ goog.dom.classes.add.apply(null, [element].concat(attributes))
+ }else {
+ goog.dom.setProperties(element, attributes)
+ }
+ }
+ }
+ if(args.length > 2) {
+ goog.dom.append_(doc, element, args, 2)
+ }
+ return element
+};
+goog.dom.append_ = function(doc, parent, args, startIndex) {
+ function childHandler(child) {
+ if(child) {
+ parent.appendChild(goog.isString(child) ? doc.createTextNode(child) : child)
+ }
+ }
+ for(var i = startIndex;i < args.length;i++) {
+ var arg = args[i];
+ if(goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
+ goog.array.forEach(goog.dom.isNodeList(arg) ? goog.array.clone(arg) : arg, childHandler)
+ }else {
+ childHandler(arg)
+ }
+ }
+};
+goog.dom.$dom = goog.dom.createDom;
+goog.dom.createElement = function(name) {
+ return document.createElement(name)
+};
+goog.dom.createTextNode = function(content) {
+ return document.createTextNode(content)
+};
+goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
+ return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp)
+};
+goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
+ var rowHtml = ["<tr>"];
+ for(var i = 0;i < columns;i++) {
+ rowHtml.push(fillWithNbsp ? "<td>&nbsp;</td>" : "<td></td>")
+ }
+ rowHtml.push("</tr>");
+ rowHtml = rowHtml.join("");
+ var totalHtml = ["<table>"];
+ for(i = 0;i < rows;i++) {
+ totalHtml.push(rowHtml)
+ }
+ totalHtml.push("</table>");
+ var elem = doc.createElement(goog.dom.TagName.DIV);
+ elem.innerHTML = totalHtml.join("");
+ return elem.removeChild(elem.firstChild)
+};
+goog.dom.htmlToDocumentFragment = function(htmlString) {
+ return goog.dom.htmlToDocumentFragment_(document, htmlString)
+};
+goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
+ var tempDiv = doc.createElement("div");
+ if(goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
+ tempDiv.innerHTML = "<br>" + htmlString;
+ tempDiv.removeChild(tempDiv.firstChild)
+ }else {
+ tempDiv.innerHTML = htmlString
+ }
+ if(tempDiv.childNodes.length == 1) {
+ return tempDiv.removeChild(tempDiv.firstChild)
+ }else {
+ var fragment = doc.createDocumentFragment();
+ while(tempDiv.firstChild) {
+ fragment.appendChild(tempDiv.firstChild)
+ }
+ return fragment
+ }
+};
+goog.dom.getCompatMode = function() {
+ return goog.dom.isCss1CompatMode() ? "CSS1Compat" : "BackCompat"
+};
+goog.dom.isCss1CompatMode = function() {
+ return goog.dom.isCss1CompatMode_(document)
+};
+goog.dom.isCss1CompatMode_ = function(doc) {
+ if(goog.dom.COMPAT_MODE_KNOWN_) {
+ return goog.dom.ASSUME_STANDARDS_MODE
+ }
+ return doc.compatMode == "CSS1Compat"
+};
+goog.dom.canHaveChildren = function(node) {
+ if(node.nodeType != goog.dom.NodeType.ELEMENT) {
+ return false
+ }
+ switch(node.tagName) {
+ case goog.dom.TagName.APPLET:
+ ;
+ case goog.dom.TagName.AREA:
+ ;
+ case goog.dom.TagName.BASE:
+ ;
+ case goog.dom.TagName.BR:
+ ;
+ case goog.dom.TagName.COL:
+ ;
+ case goog.dom.TagName.FRAME:
+ ;
+ case goog.dom.TagName.HR:
+ ;
+ case goog.dom.TagName.IMG:
+ ;
+ case goog.dom.TagName.INPUT:
+ ;
+ case goog.dom.TagName.IFRAME:
+ ;
+ case goog.dom.TagName.ISINDEX:
+ ;
+ case goog.dom.TagName.LINK:
+ ;
+ case goog.dom.TagName.NOFRAMES:
+ ;
+ case goog.dom.TagName.NOSCRIPT:
+ ;
+ case goog.dom.TagName.META:
+ ;
+ case goog.dom.TagName.OBJECT:
+ ;
+ case goog.dom.TagName.PARAM:
+ ;
+ case goog.dom.TagName.SCRIPT:
+ ;
+ case goog.dom.TagName.STYLE:
+ return false
+ }
+ return true
+};
+goog.dom.appendChild = function(parent, child) {
+ parent.appendChild(child)
+};
+goog.dom.append = function(parent, var_args) {
+ goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1)
+};
+goog.dom.removeChildren = function(node) {
+ var child;
+ while(child = node.firstChild) {
+ node.removeChild(child)
+ }
+};
+goog.dom.insertSiblingBefore = function(newNode, refNode) {
+ if(refNode.parentNode) {
+ refNode.parentNode.insertBefore(newNode, refNode)
+ }
+};
+goog.dom.insertSiblingAfter = function(newNode, refNode) {
+ if(refNode.parentNode) {
+ refNode.parentNode.insertBefore(newNode, refNode.nextSibling)
+ }
+};
+goog.dom.removeNode = function(node) {
+ return node && node.parentNode ? node.parentNode.removeChild(node) : null
+};
+goog.dom.replaceNode = function(newNode, oldNode) {
+ var parent = oldNode.parentNode;
+ if(parent) {
+ parent.replaceChild(newNode, oldNode)
+ }
+};
+goog.dom.flattenElement = function(element) {
+ var child, parent = element.parentNode;
+ if(parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
+ if(element.removeNode) {
+ return element.removeNode(false)
+ }else {
+ while(child = element.firstChild) {
+ parent.insertBefore(child, element)
+ }
+ return goog.dom.removeNode(element)
+ }
+ }
+};
+goog.dom.getFirstElementChild = function(node) {
+ return goog.dom.getNextElementNode_(node.firstChild, true)
+};
+goog.dom.getLastElementChild = function(node) {
+ return goog.dom.getNextElementNode_(node.lastChild, false)
+};
+goog.dom.getNextElementSibling = function(node) {
+ return goog.dom.getNextElementNode_(node.nextSibling, true)
+};
+goog.dom.getPreviousElementSibling = function(node) {
+ return goog.dom.getNextElementNode_(node.previousSibling, false)
+};
+goog.dom.getNextElementNode_ = function(node, forward) {
+ while(node && node.nodeType != goog.dom.NodeType.ELEMENT) {
+ node = forward ? node.nextSibling : node.previousSibling
+ }
+ return node
+};
+goog.dom.getNextNode = function(node) {
+ if(!node) {
+ return null
+ }
+ if(node.firstChild) {
+ return node.firstChild
+ }
+ while(node && !node.nextSibling) {
+ node = node.parentNode
+ }
+ return node ? node.nextSibling : null
+};
+goog.dom.getPreviousNode = function(node) {
+ if(!node) {
+ return null
+ }
+ if(!node.previousSibling) {
+ return node.parentNode
+ }
+ node = node.previousSibling;
+ while(node && node.lastChild) {
+ node = node.lastChild
+ }
+ return node
+};
+goog.dom.isNodeLike = function(obj) {
+ return goog.isObject(obj) && obj.nodeType > 0
+};
+goog.dom.contains = function(parent, descendant) {
+ if(parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
+ return parent == descendant || parent.contains(descendant)
+ }
+ if(typeof parent.compareDocumentPosition != "undefined") {
+ return parent == descendant || Boolean(parent.compareDocumentPosition(descendant) & 16)
+ }
+ while(descendant && parent != descendant) {
+ descendant = descendant.parentNode
+ }
+ return descendant == parent
+};
+goog.dom.compareNodeOrder = function(node1, node2) {
+ if(node1 == node2) {
+ return 0
+ }
+ if(node1.compareDocumentPosition) {
+ return node1.compareDocumentPosition(node2) & 2 ? 1 : -1
+ }
+ if("sourceIndex" in node1 || node1.parentNode && "sourceIndex" in node1.parentNode) {
+ var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
+ var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
+ if(isElement1 && isElement2) {
+ return node1.sourceIndex - node2.sourceIndex
+ }else {
+ var parent1 = node1.parentNode;
+ var parent2 = node2.parentNode;
+ if(parent1 == parent2) {
+ return goog.dom.compareSiblingOrder_(node1, node2)
+ }
+ if(!isElement1 && goog.dom.contains(parent1, node2)) {
+ return-1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2)
+ }
+ if(!isElement2 && goog.dom.contains(parent2, node1)) {
+ return goog.dom.compareParentsDescendantNodeIe_(node2, node1)
+ }
+ return(isElement1 ? node1.sourceIndex : parent1.sourceIndex) - (isElement2 ? node2.sourceIndex : parent2.sourceIndex)
+ }
+ }
+ var doc = goog.dom.getOwnerDocument(node1);
+ var range1, range2;
+ range1 = doc.createRange();
+ range1.selectNode(node1);
+ range1.collapse(true);
+ range2 = doc.createRange();
+ range2.selectNode(node2);
+ range2.collapse(true);
+ return range1.compareBoundaryPoints(goog.global["Range"].START_TO_END, range2)
+};
+goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
+ var parent = textNode.parentNode;
+ if(parent == node) {
+ return-1
+ }
+ var sibling = node;
+ while(sibling.parentNode != parent) {
+ sibling = sibling.parentNode
+ }
+ return goog.dom.compareSiblingOrder_(sibling, textNode)
+};
+goog.dom.compareSiblingOrder_ = function(node1, node2) {
+ var s = node2;
+ while(s = s.previousSibling) {
+ if(s == node1) {
+ return-1
+ }
+ }
+ return 1
+};
+goog.dom.findCommonAncestor = function(var_args) {
+ var i, count = arguments.length;
+ if(!count) {
+ return null
+ }else {
+ if(count == 1) {
+ return arguments[0]
+ }
+ }
+ var paths = [];
+ var minLength = Infinity;
+ for(i = 0;i < count;i++) {
+ var ancestors = [];
+ var node = arguments[i];
+ while(node) {
+ ancestors.unshift(node);
+ node = node.parentNode
+ }
+ paths.push(ancestors);
+ minLength = Math.min(minLength, ancestors.length)
+ }
+ var output = null;
+ for(i = 0;i < minLength;i++) {
+ var first = paths[0][i];
+ for(var j = 1;j < count;j++) {
+ if(first != paths[j][i]) {
+ return output
+ }
+ }
+ output = first
+ }
+ return output
+};
+goog.dom.getOwnerDocument = function(node) {
+ return node.nodeType == goog.dom.NodeType.DOCUMENT ? node : node.ownerDocument || node.document
+};
+goog.dom.getFrameContentDocument = function(frame) {
+ var doc;
+ if(goog.userAgent.WEBKIT) {
+ doc = frame.document || frame.contentWindow.document
+ }else {
+ doc = frame.contentDocument || frame.contentWindow.document
+ }
+ return doc
+};
+goog.dom.getFrameContentWindow = function(frame) {
+ return frame.contentWindow || goog.dom.getWindow_(goog.dom.getFrameContentDocument(frame))
+};
+goog.dom.setTextContent = function(element, text) {
+ if("textContent" in element) {
+ element.textContent = text
+ }else {
+ if(element.firstChild && element.firstChild.nodeType == goog.dom.NodeType.TEXT) {
+ while(element.lastChild != element.firstChild) {
+ element.removeChild(element.lastChild)
+ }
+ element.firstChild.data = text
+ }else {
+ goog.dom.removeChildren(element);
+ var doc = goog.dom.getOwnerDocument(element);
+ element.appendChild(doc.createTextNode(text))
+ }
+ }
+};
+goog.dom.getOuterHtml = function(element) {
+ if("outerHTML" in element) {
+ return element.outerHTML
+ }else {
+ var doc = goog.dom.getOwnerDocument(element);
+ var div = doc.createElement("div");
+ div.appendChild(element.cloneNode(true));
+ return div.innerHTML
+ }
+};
+goog.dom.findNode = function(root, p) {
+ var rv = [];
+ var found = goog.dom.findNodes_(root, p, rv, true);
+ return found ? rv[0] : undefined
+};
+goog.dom.findNodes = function(root, p) {
+ var rv = [];
+ goog.dom.findNodes_(root, p, rv, false);
+ return rv
+};
+goog.dom.findNodes_ = function(root, p, rv, findOne) {
+ if(root != null) {
+ for(var i = 0, child;child = root.childNodes[i];i++) {
+ if(p(child)) {
+ rv.push(child);
+ if(findOne) {
+ return true
+ }
+ }
+ if(goog.dom.findNodes_(child, p, rv, findOne)) {
+ return true
+ }
+ }
+ }
+ return false
+};
+goog.dom.TAGS_TO_IGNORE_ = {SCRIPT:1, STYLE:1, HEAD:1, IFRAME:1, OBJECT:1};
+goog.dom.PREDEFINED_TAG_VALUES_ = {IMG:" ", BR:"\n"};
+goog.dom.isFocusableTabIndex = function(element) {
+ var attrNode = element.getAttributeNode("tabindex");
+ if(attrNode && attrNode.specified) {
+ var index = element.tabIndex;
+ return goog.isNumber(index) && index >= 0
+ }
+ return false
+};
+goog.dom.setFocusableTabIndex = function(element, enable) {
+ if(enable) {
+ element.tabIndex = 0
+ }else {
+ element.removeAttribute("tabIndex")
+ }
+};
+goog.dom.getTextContent = function(node) {
+ var textContent;
+ if(goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && "innerText" in node) {
+ textContent = goog.string.canonicalizeNewlines(node.innerText)
+ }else {
+ var buf = [];
+ goog.dom.getTextContent_(node, buf, true);
+ textContent = buf.join("")
+ }
+ textContent = textContent.replace(/ \xAD /g, " ").replace(/\xAD/g, "");
+ if(!goog.userAgent.IE) {
+ textContent = textContent.replace(/ +/g, " ")
+ }
+ if(textContent != " ") {
+ textContent = textContent.replace(/^\s*/, "")
+ }
+ return textContent
+};
+goog.dom.getRawTextContent = function(node) {
+ var buf = [];
+ goog.dom.getTextContent_(node, buf, false);
+ return buf.join("")
+};
+goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
+ if(node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
+ }else {
+ if(node.nodeType == goog.dom.NodeType.TEXT) {
+ if(normalizeWhitespace) {
+ buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ""))
+ }else {
+ buf.push(node.nodeValue)
+ }
+ }else {
+ if(node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
+ buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName])
+ }else {
+ var child = node.firstChild;
+ while(child) {
+ goog.dom.getTextContent_(child, buf, normalizeWhitespace);
+ child = child.nextSibling
+ }
+ }
+ }
+ }
+};
+goog.dom.getNodeTextLength = function(node) {
+ return goog.dom.getTextContent(node).length
+};
+goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
+ var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
+ var buf = [];
+ while(node && node != root) {
+ var cur = node;
+ while(cur = cur.previousSibling) {
+ buf.unshift(goog.dom.getTextContent(cur))
+ }
+ node = node.parentNode
+ }
+ return goog.string.trimLeft(buf.join("")).replace(/ +/g, " ").length
+};
+goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
+ var stack = [parent], pos = 0, cur;
+ while(stack.length > 0 && pos < offset) {
+ cur = stack.pop();
+ if(cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
+ }else {
+ if(cur.nodeType == goog.dom.NodeType.TEXT) {
+ var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, "").replace(/ +/g, " ");
+ pos += text.length
+ }else {
+ if(cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
+ pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length
+ }else {
+ for(var i = cur.childNodes.length - 1;i >= 0;i--) {
+ stack.push(cur.childNodes[i])
+ }
+ }
+ }
+ }
+ }
+ if(goog.isObject(opt_result)) {
+ opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
+ opt_result.node = cur
+ }
+ return cur
+};
+goog.dom.isNodeList = function(val) {
+ if(val && typeof val.length == "number") {
+ if(goog.isObject(val)) {
+ return typeof val.item == "function" || typeof val.item == "string"
+ }else {
+ if(goog.isFunction(val)) {
+ return typeof val.item == "function"
+ }
+ }
+ }
+ return false
+};
+goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class) {
+ var tagName = opt_tag ? opt_tag.toUpperCase() : null;
+ return goog.dom.getAncestor(element, function(node) {
+ return(!tagName || node.nodeName == tagName) && (!opt_class || goog.dom.classes.has(node, opt_class))
+ }, true)
+};
+goog.dom.getAncestor = function(element, matcher, opt_includeNode, opt_maxSearchSteps) {
+ if(!opt_includeNode) {
+ element = element.parentNode
+ }
+ var ignoreSearchSteps = opt_maxSearchSteps == null;
+ var steps = 0;
+ while(element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) {
+ if(matcher(element)) {
+ return element
+ }
+ element = element.parentNode;
+ steps++
+ }
+ return null
+};
+goog.dom.DomHelper = function(opt_document) {
+ this.document_ = opt_document || goog.global.document || document
+};
+goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
+goog.dom.DomHelper.prototype.setDocument = function(document) {
+ this.document_ = document
+};
+goog.dom.DomHelper.prototype.getDocument = function() {
+ return this.document_
+};
+goog.dom.DomHelper.prototype.getElement = function(element) {
+ if(goog.isString(element)) {
+ return this.document_.getElementById(element)
+ }else {
+ return element
+ }
+};
+goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
+goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
+ return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag, opt_class, opt_el)
+};
+goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
+ var doc = opt_el || this.document_;
+ return goog.dom.getElementsByClass(className, doc)
+};
+goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
+ var doc = opt_el || this.document_;
+ return goog.dom.getElementByClass(className, doc)
+};
+goog.dom.DomHelper.prototype.$$ = goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
+goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
+goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
+ return goog.dom.getViewportSize(opt_window || this.getWindow())
+};
+goog.dom.DomHelper.prototype.getDocumentHeight = function() {
+ return goog.dom.getDocumentHeight_(this.getWindow())
+};
+goog.dom.Appendable;
+goog.dom.DomHelper.prototype.createDom = function(tagName, opt_attributes, var_args) {
+ return goog.dom.createDom_(this.document_, arguments)
+};
+goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
+goog.dom.DomHelper.prototype.createElement = function(name) {
+ return this.document_.createElement(name)
+};
+goog.dom.DomHelper.prototype.createTextNode = function(content) {
+ return this.document_.createTextNode(content)
+};
+goog.dom.DomHelper.prototype.createTable = function(rows, columns, opt_fillWithNbsp) {
+ return goog.dom.createTable_(this.document_, rows, columns, !!opt_fillWithNbsp)
+};
+goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) {
+ return goog.dom.htmlToDocumentFragment_(this.document_, htmlString)
+};
+goog.dom.DomHelper.prototype.getCompatMode = function() {
+ return this.isCss1CompatMode() ? "CSS1Compat" : "BackCompat"
+};
+goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
+ return goog.dom.isCss1CompatMode_(this.document_)
+};
+goog.dom.DomHelper.prototype.getWindow = function() {
+ return goog.dom.getWindow_(this.document_)
+};
+goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
+ return goog.dom.getDocumentScrollElement_(this.document_)
+};
+goog.dom.DomHelper.prototype.getDocumentScroll = function() {
+ return goog.dom.getDocumentScroll_(this.document_)
+};
+goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
+goog.dom.DomHelper.prototype.append = goog.dom.append;
+goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
+goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
+goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
+goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
+goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
+goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
+goog.dom.DomHelper.prototype.getFirstElementChild = goog.dom.getFirstElementChild;
+goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;
+goog.dom.DomHelper.prototype.getNextElementSibling = goog.dom.getNextElementSibling;
+goog.dom.DomHelper.prototype.getPreviousElementSibling = goog.dom.getPreviousElementSibling;
+goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
+goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
+goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
+goog.dom.DomHelper.prototype.contains = goog.dom.contains;
+goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
+goog.dom.DomHelper.prototype.getFrameContentDocument = goog.dom.getFrameContentDocument;
+goog.dom.DomHelper.prototype.getFrameContentWindow = goog.dom.getFrameContentWindow;
+goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
+goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;
+goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
+goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
+goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
+goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
+goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass = goog.dom.getAncestorByTagNameAndClass;
+goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;
+goog.provide("goog.Disposable");
+goog.provide("goog.dispose");
+goog.Disposable = function() {
+};
+goog.Disposable.prototype.disposed_ = false;
+goog.Disposable.prototype.isDisposed = function() {
+ return this.disposed_
+};
+goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
+goog.Disposable.prototype.dispose = function() {
+ if(!this.disposed_) {
+ this.disposed_ = true;
+ this.disposeInternal()
+ }
+};
+goog.Disposable.prototype.disposeInternal = function() {
+};
+goog.dispose = function(obj) {
+ if(obj && typeof obj.dispose == "function") {
+ obj.dispose()
+ }
+};
+goog.provide("goog.structs");
+goog.require("goog.array");
+goog.require("goog.object");
+goog.structs.getCount = function(col) {
+ if(typeof col.getCount == "function") {
+ return col.getCount()
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return col.length
+ }
+ return goog.object.getCount(col)
+};
+goog.structs.getValues = function(col) {
+ if(typeof col.getValues == "function") {
+ return col.getValues()
+ }
+ if(goog.isString(col)) {
+ return col.split("")
+ }
+ if(goog.isArrayLike(col)) {
+ var rv = [];
+ var l = col.length;
+ for(var i = 0;i < l;i++) {
+ rv.push(col[i])
+ }
+ return rv
+ }
+ return goog.object.getValues(col)
+};
+goog.structs.getKeys = function(col) {
+ if(typeof col.getKeys == "function") {
+ return col.getKeys()
+ }
+ if(typeof col.getValues == "function") {
+ return undefined
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ var rv = [];
+ var l = col.length;
+ for(var i = 0;i < l;i++) {
+ rv.push(i)
+ }
+ return rv
+ }
+ return goog.object.getKeys(col)
+};
+goog.structs.contains = function(col, val) {
+ if(typeof col.contains == "function") {
+ return col.contains(val)
+ }
+ if(typeof col.containsValue == "function") {
+ return col.containsValue(val)
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.contains(col, val)
+ }
+ return goog.object.containsValue(col, val)
+};
+goog.structs.isEmpty = function(col) {
+ if(typeof col.isEmpty == "function") {
+ return col.isEmpty()
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.isEmpty(col)
+ }
+ return goog.object.isEmpty(col)
+};
+goog.structs.clear = function(col) {
+ if(typeof col.clear == "function") {
+ col.clear()
+ }else {
+ if(goog.isArrayLike(col)) {
+ goog.array.clear(col)
+ }else {
+ goog.object.clear(col)
+ }
+ }
+};
+goog.structs.forEach = function(col, f, opt_obj) {
+ if(typeof col.forEach == "function") {
+ col.forEach(f, opt_obj)
+ }else {
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ goog.array.forEach(col, f, opt_obj)
+ }else {
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for(var i = 0;i < l;i++) {
+ f.call(opt_obj, values[i], keys && keys[i], col)
+ }
+ }
+ }
+};
+goog.structs.filter = function(col, f, opt_obj) {
+ if(typeof col.filter == "function") {
+ return col.filter(f, opt_obj)
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.filter(col, f, opt_obj)
+ }
+ var rv;
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ if(keys) {
+ rv = {};
+ for(var i = 0;i < l;i++) {
+ if(f.call(opt_obj, values[i], keys[i], col)) {
+ rv[keys[i]] = values[i]
+ }
+ }
+ }else {
+ rv = [];
+ for(var i = 0;i < l;i++) {
+ if(f.call(opt_obj, values[i], undefined, col)) {
+ rv.push(values[i])
+ }
+ }
+ }
+ return rv
+};
+goog.structs.map = function(col, f, opt_obj) {
+ if(typeof col.map == "function") {
+ return col.map(f, opt_obj)
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.map(col, f, opt_obj)
+ }
+ var rv;
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ if(keys) {
+ rv = {};
+ for(var i = 0;i < l;i++) {
+ rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col)
+ }
+ }else {
+ rv = [];
+ for(var i = 0;i < l;i++) {
+ rv[i] = f.call(opt_obj, values[i], undefined, col)
+ }
+ }
+ return rv
+};
+goog.structs.some = function(col, f, opt_obj) {
+ if(typeof col.some == "function") {
+ return col.some(f, opt_obj)
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.some(col, f, opt_obj)
+ }
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for(var i = 0;i < l;i++) {
+ if(f.call(opt_obj, values[i], keys && keys[i], col)) {
+ return true
+ }
+ }
+ return false
+};
+goog.structs.every = function(col, f, opt_obj) {
+ if(typeof col.every == "function") {
+ return col.every(f, opt_obj)
+ }
+ if(goog.isArrayLike(col) || goog.isString(col)) {
+ return goog.array.every(col, f, opt_obj)
+ }
+ var keys = goog.structs.getKeys(col);
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for(var i = 0;i < l;i++) {
+ if(!f.call(opt_obj, values[i], keys && keys[i], col)) {
+ return false
+ }
+ }
+ return true
+};
+goog.provide("goog.iter");
+goog.provide("goog.iter.Iterator");
+goog.provide("goog.iter.StopIteration");
+goog.require("goog.array");
+goog.iter.Iterable;
+if("StopIteration" in goog.global) {
+ goog.iter.StopIteration = goog.global["StopIteration"]
+}else {
+ goog.iter.StopIteration = Error("StopIteration")
+}
+goog.iter.Iterator = function() {
+};
+goog.iter.Iterator.prototype.next = function() {
+ throw goog.iter.StopIteration;
+};
+goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
+ return this
+};
+goog.iter.toIterator = function(iterable) {
+ if(iterable instanceof goog.iter.Iterator) {
+ return iterable
+ }
+ if(typeof iterable.__iterator__ == "function") {
+ return iterable.__iterator__(false)
+ }
+ if(goog.isArrayLike(iterable)) {
+ var i = 0;
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while(true) {
+ if(i >= iterable.length) {
+ throw goog.iter.StopIteration;
+ }
+ if(!(i in iterable)) {
+ i++;
+ continue
+ }
+ return iterable[i++]
+ }
+ };
+ return newIter
+ }
+ throw Error("Not implemented");
+};
+goog.iter.forEach = function(iterable, f, opt_obj) {
+ if(goog.isArrayLike(iterable)) {
+ try {
+ goog.array.forEach(iterable, f, opt_obj)
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ }else {
+ iterable = goog.iter.toIterator(iterable);
+ try {
+ while(true) {
+ f.call(opt_obj, iterable.next(), undefined, iterable)
+ }
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ }
+};
+goog.iter.filter = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while(true) {
+ var val = iterable.next();
+ if(f.call(opt_obj, val, undefined, iterable)) {
+ return val
+ }
+ }
+ };
+ return newIter
+};
+goog.iter.range = function(startOrStop, opt_stop, opt_step) {
+ var start = 0;
+ var stop = startOrStop;
+ var step = opt_step || 1;
+ if(arguments.length > 1) {
+ start = startOrStop;
+ stop = opt_stop
+ }
+ if(step == 0) {
+ throw Error("Range step argument must not be zero");
+ }
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ if(step > 0 && start >= stop || step < 0 && start <= stop) {
+ throw goog.iter.StopIteration;
+ }
+ var rv = start;
+ start += step;
+ return rv
+ };
+ return newIter
+};
+goog.iter.join = function(iterable, deliminator) {
+ return goog.iter.toArray(iterable).join(deliminator)
+};
+goog.iter.map = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while(true) {
+ var val = iterable.next();
+ return f.call(opt_obj, val, undefined, iterable)
+ }
+ };
+ return newIter
+};
+goog.iter.reduce = function(iterable, f, val, opt_obj) {
+ var rval = val;
+ goog.iter.forEach(iterable, function(val) {
+ rval = f.call(opt_obj, rval, val)
+ });
+ return rval
+};
+goog.iter.some = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ try {
+ while(true) {
+ if(f.call(opt_obj, iterable.next(), undefined, iterable)) {
+ return true
+ }
+ }
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ return false
+};
+goog.iter.every = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ try {
+ while(true) {
+ if(!f.call(opt_obj, iterable.next(), undefined, iterable)) {
+ return false
+ }
+ }
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration) {
+ throw ex;
+ }
+ }
+ return true
+};
+goog.iter.chain = function(var_args) {
+ var args = arguments;
+ var length = args.length;
+ var i = 0;
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ try {
+ if(i >= length) {
+ throw goog.iter.StopIteration;
+ }
+ var current = goog.iter.toIterator(args[i]);
+ return current.next()
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration || i >= length) {
+ throw ex;
+ }else {
+ i++;
+ return this.next()
+ }
+ }
+ };
+ return newIter
+};
+goog.iter.dropWhile = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ var dropping = true;
+ newIter.next = function() {
+ while(true) {
+ var val = iterable.next();
+ if(dropping && f.call(opt_obj, val, undefined, iterable)) {
+ continue
+ }else {
+ dropping = false
+ }
+ return val
+ }
+ };
+ return newIter
+};
+goog.iter.takeWhile = function(iterable, f, opt_obj) {
+ iterable = goog.iter.toIterator(iterable);
+ var newIter = new goog.iter.Iterator;
+ var taking = true;
+ newIter.next = function() {
+ while(true) {
+ if(taking) {
+ var val = iterable.next();
+ if(f.call(opt_obj, val, undefined, iterable)) {
+ return val
+ }else {
+ taking = false
+ }
+ }else {
+ throw goog.iter.StopIteration;
+ }
+ }
+ };
+ return newIter
+};
+goog.iter.toArray = function(iterable) {
+ if(goog.isArrayLike(iterable)) {
+ return goog.array.toArray(iterable)
+ }
+ iterable = goog.iter.toIterator(iterable);
+ var array = [];
+ goog.iter.forEach(iterable, function(val) {
+ array.push(val)
+ });
+ return array
+};
+goog.iter.equals = function(iterable1, iterable2) {
+ iterable1 = goog.iter.toIterator(iterable1);
+ iterable2 = goog.iter.toIterator(iterable2);
+ var b1, b2;
+ try {
+ while(true) {
+ b1 = b2 = false;
+ var val1 = iterable1.next();
+ b1 = true;
+ var val2 = iterable2.next();
+ b2 = true;
+ if(val1 != val2) {
+ return false
+ }
+ }
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration) {
+ throw ex;
+ }else {
+ if(b1 && !b2) {
+ return false
+ }
+ if(!b2) {
+ try {
+ val2 = iterable2.next();
+ return false
+ }catch(ex1) {
+ if(ex1 !== goog.iter.StopIteration) {
+ throw ex1;
+ }
+ return true
+ }
+ }
+ }
+ }
+ return false
+};
+goog.iter.nextOrValue = function(iterable, defaultValue) {
+ try {
+ return goog.iter.toIterator(iterable).next()
+ }catch(e) {
+ if(e != goog.iter.StopIteration) {
+ throw e;
+ }
+ return defaultValue
+ }
+};
+goog.provide("goog.structs.Map");
+goog.require("goog.iter.Iterator");
+goog.require("goog.iter.StopIteration");
+goog.require("goog.object");
+goog.require("goog.structs");
+goog.structs.Map = function(opt_map, var_args) {
+ this.map_ = {};
+ this.keys_ = [];
+ var argLength = arguments.length;
+ if(argLength > 1) {
+ if(argLength % 2) {
+ throw Error("Uneven number of arguments");
+ }
+ for(var i = 0;i < argLength;i += 2) {
+ this.set(arguments[i], arguments[i + 1])
+ }
+ }else {
+ if(opt_map) {
+ this.addAll(opt_map)
+ }
+ }
+};
+goog.structs.Map.prototype.count_ = 0;
+goog.structs.Map.prototype.version_ = 0;
+goog.structs.Map.prototype.getCount = function() {
+ return this.count_
+};
+goog.structs.Map.prototype.getValues = function() {
+ this.cleanupKeysArray_();
+ var rv = [];
+ for(var i = 0;i < this.keys_.length;i++) {
+ var key = this.keys_[i];
+ rv.push(this.map_[key])
+ }
+ return rv
+};
+goog.structs.Map.prototype.getKeys = function() {
+ this.cleanupKeysArray_();
+ return this.keys_.concat()
+};
+goog.structs.Map.prototype.containsKey = function(key) {
+ return goog.structs.Map.hasKey_(this.map_, key)
+};
+goog.structs.Map.prototype.containsValue = function(val) {
+ for(var i = 0;i < this.keys_.length;i++) {
+ var key = this.keys_[i];
+ if(goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
+ return true
+ }
+ }
+ return false
+};
+goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
+ if(this === otherMap) {
+ return true
+ }
+ if(this.count_ != otherMap.getCount()) {
+ return false
+ }
+ var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
+ this.cleanupKeysArray_();
+ for(var key, i = 0;key = this.keys_[i];i++) {
+ if(!equalityFn(this.get(key), otherMap.get(key))) {
+ return false
+ }
+ }
+ return true
+};
+goog.structs.Map.defaultEquals = function(a, b) {
+ return a === b
+};
+goog.structs.Map.prototype.isEmpty = function() {
+ return this.count_ == 0
+};
+goog.structs.Map.prototype.clear = function() {
+ this.map_ = {};
+ this.keys_.length = 0;
+ this.count_ = 0;
+ this.version_ = 0
+};
+goog.structs.Map.prototype.remove = function(key) {
+ if(goog.structs.Map.hasKey_(this.map_, key)) {
+ delete this.map_[key];
+ this.count_--;
+ this.version_++;
+ if(this.keys_.length > 2 * this.count_) {
+ this.cleanupKeysArray_()
+ }
+ return true
+ }
+ return false
+};
+goog.structs.Map.prototype.cleanupKeysArray_ = function() {
+ if(this.count_ != this.keys_.length) {
+ var srcIndex = 0;
+ var destIndex = 0;
+ while(srcIndex < this.keys_.length) {
+ var key = this.keys_[srcIndex];
+ if(goog.structs.Map.hasKey_(this.map_, key)) {
+ this.keys_[destIndex++] = key
+ }
+ srcIndex++
+ }
+ this.keys_.length = destIndex
+ }
+ if(this.count_ != this.keys_.length) {
+ var seen = {};
+ var srcIndex = 0;
+ var destIndex = 0;
+ while(srcIndex < this.keys_.length) {
+ var key = this.keys_[srcIndex];
+ if(!goog.structs.Map.hasKey_(seen, key)) {
+ this.keys_[destIndex++] = key;
+ seen[key] = 1
+ }
+ srcIndex++
+ }
+ this.keys_.length = destIndex
+ }
+};
+goog.structs.Map.prototype.get = function(key, opt_val) {
+ if(goog.structs.Map.hasKey_(this.map_, key)) {
+ return this.map_[key]
+ }
+ return opt_val
+};
+goog.structs.Map.prototype.set = function(key, value) {
+ if(!goog.structs.Map.hasKey_(this.map_, key)) {
+ this.count_++;
+ this.keys_.push(key);
+ this.version_++
+ }
+ this.map_[key] = value
+};
+goog.structs.Map.prototype.addAll = function(map) {
+ var keys, values;
+ if(map instanceof goog.structs.Map) {
+ keys = map.getKeys();
+ values = map.getValues()
+ }else {
+ keys = goog.object.getKeys(map);
+ values = goog.object.getValues(map)
+ }
+ for(var i = 0;i < keys.length;i++) {
+ this.set(keys[i], values[i])
+ }
+};
+goog.structs.Map.prototype.clone = function() {
+ return new goog.structs.Map(this)
+};
+goog.structs.Map.prototype.transpose = function() {
+ var transposed = new goog.structs.Map;
+ for(var i = 0;i < this.keys_.length;i++) {
+ var key = this.keys_[i];
+ var value = this.map_[key];
+ transposed.set(value, key)
+ }
+ return transposed
+};
+goog.structs.Map.prototype.toObject = function() {
+ this.cleanupKeysArray_();
+ var obj = {};
+ for(var i = 0;i < this.keys_.length;i++) {
+ var key = this.keys_[i];
+ obj[key] = this.map_[key]
+ }
+ return obj
+};
+goog.structs.Map.prototype.getKeyIterator = function() {
+ return this.__iterator__(true)
+};
+goog.structs.Map.prototype.getValueIterator = function() {
+ return this.__iterator__(false)
+};
+goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
+ this.cleanupKeysArray_();
+ var i = 0;
+ var keys = this.keys_;
+ var map = this.map_;
+ var version = this.version_;
+ var selfObj = this;
+ var newIter = new goog.iter.Iterator;
+ newIter.next = function() {
+ while(true) {
+ if(version != selfObj.version_) {
+ throw Error("The map has changed since the iterator was created");
+ }
+ if(i >= keys.length) {
+ throw goog.iter.StopIteration;
+ }
+ var key = keys[i++];
+ return opt_keys ? key : map[key]
+ }
+ };
+ return newIter
+};
+goog.structs.Map.hasKey_ = function(obj, key) {
+ return Object.prototype.hasOwnProperty.call(obj, key)
+};
+goog.provide("goog.structs.Set");
+goog.require("goog.structs");
+goog.require("goog.structs.Map");
+goog.structs.Set = function(opt_values) {
+ this.map_ = new goog.structs.Map;
+ if(opt_values) {
+ this.addAll(opt_values)
+ }
+};
+goog.structs.Set.getKey_ = function(val) {
+ var type = typeof val;
+ if(type == "object" && val || type == "function") {
+ return"o" + goog.getUid(val)
+ }else {
+ return type.substr(0, 1) + val
+ }
+};
+goog.structs.Set.prototype.getCount = function() {
+ return this.map_.getCount()
+};
+goog.structs.Set.prototype.add = function(element) {
+ this.map_.set(goog.structs.Set.getKey_(element), element)
+};
+goog.structs.Set.prototype.addAll = function(col) {
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for(var i = 0;i < l;i++) {
+ this.add(values[i])
+ }
+};
+goog.structs.Set.prototype.removeAll = function(col) {
+ var values = goog.structs.getValues(col);
+ var l = values.length;
+ for(var i = 0;i < l;i++) {
+ this.remove(values[i])
+ }
+};
+goog.structs.Set.prototype.remove = function(element) {
+ return this.map_.remove(goog.structs.Set.getKey_(element))
+};
+goog.structs.Set.prototype.clear = function() {
+ this.map_.clear()
+};
+goog.structs.Set.prototype.isEmpty = function() {
+ return this.map_.isEmpty()
+};
+goog.structs.Set.prototype.contains = function(element) {
+ return this.map_.containsKey(goog.structs.Set.getKey_(element))
+};
+goog.structs.Set.prototype.containsAll = function(col) {
+ return goog.structs.every(col, this.contains, this)
+};
+goog.structs.Set.prototype.intersection = function(col) {
+ var result = new goog.structs.Set;
+ var values = goog.structs.getValues(col);
+ for(var i = 0;i < values.length;i++) {
+ var value = values[i];
+ if(this.contains(value)) {
+ result.add(value)
+ }
+ }
+ return result
+};
+goog.structs.Set.prototype.getValues = function() {
+ return this.map_.getValues()
+};
+goog.structs.Set.prototype.clone = function() {
+ return new goog.structs.Set(this)
+};
+goog.structs.Set.prototype.equals = function(col) {
+ return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col)
+};
+goog.structs.Set.prototype.isSubsetOf = function(col) {
+ var colCount = goog.structs.getCount(col);
+ if(this.getCount() > colCount) {
+ return false
+ }
+ if(!(col instanceof goog.structs.Set) && colCount > 5) {
+ col = new goog.structs.Set(col)
+ }
+ return goog.structs.every(this, function(value) {
+ return goog.structs.contains(col, value)
+ })
+};
+goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
+ return this.map_.__iterator__(false)
+};
+goog.provide("goog.debug");
+goog.require("goog.array");
+goog.require("goog.string");
+goog.require("goog.structs.Set");
+goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
+ var target = opt_target || goog.global;
+ var oldErrorHandler = target.onerror;
+ target.onerror = function(message, url, line) {
+ if(oldErrorHandler) {
+ oldErrorHandler(message, url, line)
+ }
+ logFunc({message:message, fileName:url, line:line});
+ return Boolean(opt_cancel)
+ }
+};
+goog.debug.expose = function(obj, opt_showFn) {
+ if(typeof obj == "undefined") {
+ return"undefined"
+ }
+ if(obj == null) {
+ return"NULL"
+ }
+ var str = [];
+ for(var x in obj) {
+ if(!opt_showFn && goog.isFunction(obj[x])) {
+ continue
+ }
+ var s = x + " = ";
+ try {
+ s += obj[x]
+ }catch(e) {
+ s += "*** " + e + " ***"
+ }
+ str.push(s)
+ }
+ return str.join("\n")
+};
+goog.debug.deepExpose = function(obj, opt_showFn) {
+ var previous = new goog.structs.Set;
+ var str = [];
+ var helper = function(obj, space) {
+ var nestspace = space + " ";
+ var indentMultiline = function(str) {
+ return str.replace(/\n/g, "\n" + space)
+ };
+ try {
+ if(!goog.isDef(obj)) {
+ str.push("undefined")
+ }else {
+ if(goog.isNull(obj)) {
+ str.push("NULL")
+ }else {
+ if(goog.isString(obj)) {
+ str.push('"' + indentMultiline(obj) + '"')
+ }else {
+ if(goog.isFunction(obj)) {
+ str.push(indentMultiline(String(obj)))
+ }else {
+ if(goog.isObject(obj)) {
+ if(previous.contains(obj)) {
+ str.push("*** reference loop detected ***")
+ }else {
+ previous.add(obj);
+ str.push("{");
+ for(var x in obj) {
+ if(!opt_showFn && goog.isFunction(obj[x])) {
+ continue
+ }
+ str.push("\n");
+ str.push(nestspace);
+ str.push(x + " = ");
+ helper(obj[x], nestspace)
+ }
+ str.push("\n" + space + "}")
+ }
+ }else {
+ str.push(obj)
+ }
+ }
+ }
+ }
+ }
+ }catch(e) {
+ str.push("*** " + e + " ***")
+ }
+ };
+ helper(obj, "");
+ return str.join("")
+};
+goog.debug.exposeArray = function(arr) {
+ var str = [];
+ for(var i = 0;i < arr.length;i++) {
+ if(goog.isArray(arr[i])) {
+ str.push(goog.debug.exposeArray(arr[i]))
+ }else {
+ str.push(arr[i])
+ }
+ }
+ return"[ " + str.join(", ") + " ]"
+};
+goog.debug.exposeException = function(err, opt_fn) {
+ try {
+ var e = goog.debug.normalizeErrorObject(err);
+ var error = "Message: " + goog.string.htmlEscape(e.message) + '\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' + e.fileName + "</a>\nLine: " + e.lineNumber + "\n\nBrowser stack:\n" + goog.string.htmlEscape(e.stack + "-> ") + "[end]\n\nJS stack traversal:\n" + goog.string.htmlEscape(goog.debug.getStacktrace(opt_fn) + "-> ");
+ return error
+ }catch(e2) {
+ return"Exception trying to expose exception! You win, we lose. " + e2
+ }
+};
+goog.debug.normalizeErrorObject = function(err) {
+ var href = goog.getObjectByName("window.location.href");
+ return typeof err == "string" ? {message:err, name:"Unknown error", lineNumber:"Not available", fileName:href, stack:"Not available"} : !err.lineNumber || !err.fileName || !err.stack ? {message:err.message, name:err.name, lineNumber:err.lineNumber || err.line || "Not available", fileName:err.fileName || err.filename || err.sourceURL || href, stack:err.stack || "Not available"} : err
+};
+goog.debug.enhanceError = function(err, opt_message) {
+ var error = typeof err == "string" ? Error(err) : err;
+ if(!error.stack) {
+ error.stack = goog.debug.getStacktrace(arguments.callee.caller)
+ }
+ if(opt_message) {
+ var x = 0;
+ while(error["message" + x]) {
+ ++x
+ }
+ error["message" + x] = String(opt_message)
+ }
+ return error
+};
+goog.debug.getStacktraceSimple = function(opt_depth) {
+ var sb = [];
+ var fn = arguments.callee.caller;
+ var depth = 0;
+ while(fn && (!opt_depth || depth < opt_depth)) {
+ sb.push(goog.debug.getFunctionName(fn));
+ sb.push("()\n");
+ try {
+ fn = fn.caller
+ }catch(e) {
+ sb.push("[exception trying to get caller]\n");
+ break
+ }
+ depth++;
+ if(depth >= goog.debug.MAX_STACK_DEPTH) {
+ sb.push("[...long stack...]");
+ break
+ }
+ }
+ if(opt_depth && depth >= opt_depth) {
+ sb.push("[...reached max depth limit...]")
+ }else {
+ sb.push("[end]")
+ }
+ return sb.join("")
+};
+goog.debug.MAX_STACK_DEPTH = 50;
+goog.debug.getStacktrace = function(opt_fn) {
+ return goog.debug.getStacktraceHelper_(opt_fn || arguments.callee.caller, [])
+};
+goog.debug.getStacktraceHelper_ = function(fn, visited) {
+ var sb = [];
+ if(goog.array.contains(visited, fn)) {
+ sb.push("[...circular reference...]")
+ }else {
+ if(fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
+ sb.push(goog.debug.getFunctionName(fn) + "(");
+ var args = fn.arguments;
+ for(var i = 0;i < args.length;i++) {
+ if(i > 0) {
+ sb.push(", ")
+ }
+ var argDesc;
+ var arg = args[i];
+ switch(typeof arg) {
+ case "object":
+ argDesc = arg ? "object" : "null";
+ break;
+ case "string":
+ argDesc = arg;
+ break;
+ case "number":
+ argDesc = String(arg);
+ break;
+ case "boolean":
+ argDesc = arg ? "true" : "false";
+ break;
+ case "function":
+ argDesc = goog.debug.getFunctionName(arg);
+ argDesc = argDesc ? argDesc : "[fn]";
+ break;
+ case "undefined":
+ ;
+ default:
+ argDesc = typeof arg;
+ break
+ }
+ if(argDesc.length > 40) {
+ argDesc = argDesc.substr(0, 40) + "..."
+ }
+ sb.push(argDesc)
+ }
+ visited.push(fn);
+ sb.push(")\n");
+ try {
+ sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited))
+ }catch(e) {
+ sb.push("[exception trying to get caller]\n")
+ }
+ }else {
+ if(fn) {
+ sb.push("[...long stack...]")
+ }else {
+ sb.push("[end]")
+ }
+ }
+ }
+ return sb.join("")
+};
+goog.debug.getFunctionName = function(fn) {
+ var functionSource = String(fn);
+ if(!goog.debug.fnNameCache_[functionSource]) {
+ var matches = /function ([^\(]+)/.exec(functionSource);
+ if(matches) {
+ var method = matches[1];
+ goog.debug.fnNameCache_[functionSource] = method
+ }else {
+ goog.debug.fnNameCache_[functionSource] = "[Anonymous]"
+ }
+ }
+ return goog.debug.fnNameCache_[functionSource]
+};
+goog.debug.makeWhitespaceVisible = function(string) {
+ return string.replace(/ /g, "[_]").replace(/\f/g, "[f]").replace(/\n/g, "[n]\n").replace(/\r/g, "[r]").replace(/\t/g, "[t]")
+};
+goog.debug.fnNameCache_ = {};
+goog.provide("goog.debug.LogRecord");
+goog.debug.LogRecord = function(level, msg, loggerName, opt_time, opt_sequenceNumber) {
+ this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber)
+};
+goog.debug.LogRecord.prototype.time_;
+goog.debug.LogRecord.prototype.level_;
+goog.debug.LogRecord.prototype.msg_;
+goog.debug.LogRecord.prototype.loggerName_;
+goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
+goog.debug.LogRecord.prototype.exception_ = null;
+goog.debug.LogRecord.prototype.exceptionText_ = null;
+goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS = true;
+goog.debug.LogRecord.nextSequenceNumber_ = 0;
+goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName, opt_time, opt_sequenceNumber) {
+ if(goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
+ this.sequenceNumber_ = typeof opt_sequenceNumber == "number" ? opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++
+ }
+ this.time_ = opt_time || goog.now();
+ this.level_ = level;
+ this.msg_ = msg;
+ this.loggerName_ = loggerName;
+ delete this.exception_;
+ delete this.exceptionText_
+};
+goog.debug.LogRecord.prototype.getLoggerName = function() {
+ return this.loggerName_
+};
+goog.debug.LogRecord.prototype.getException = function() {
+ return this.exception_
+};
+goog.debug.LogRecord.prototype.setException = function(exception) {
+ this.exception_ = exception
+};
+goog.debug.LogRecord.prototype.getExceptionText = function() {
+ return this.exceptionText_
+};
+goog.debug.LogRecord.prototype.setExceptionText = function(text) {
+ this.exceptionText_ = text
+};
+goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
+ this.loggerName_ = loggerName
+};
+goog.debug.LogRecord.prototype.getLevel = function() {
+ return this.level_
+};
+goog.debug.LogRecord.prototype.setLevel = function(level) {
+ this.level_ = level
+};
+goog.debug.LogRecord.prototype.getMessage = function() {
+ return this.msg_
+};
+goog.debug.LogRecord.prototype.setMessage = function(msg) {
+ this.msg_ = msg
+};
+goog.debug.LogRecord.prototype.getMillis = function() {
+ return this.time_
+};
+goog.debug.LogRecord.prototype.setMillis = function(time) {
+ this.time_ = time
+};
+goog.debug.LogRecord.prototype.getSequenceNumber = function() {
+ return this.sequenceNumber_
+};
+goog.provide("goog.debug.LogBuffer");
+goog.require("goog.asserts");
+goog.require("goog.debug.LogRecord");
+goog.debug.LogBuffer = function() {
+ goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(), "Cannot use goog.debug.LogBuffer without defining " + "goog.debug.LogBuffer.CAPACITY.");
+ this.clear()
+};
+goog.debug.LogBuffer.getInstance = function() {
+ if(!goog.debug.LogBuffer.instance_) {
+ goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer
+ }
+ return goog.debug.LogBuffer.instance_
+};
+goog.debug.LogBuffer.CAPACITY = 0;
+goog.debug.LogBuffer.prototype.buffer_;
+goog.debug.LogBuffer.prototype.curIndex_;
+goog.debug.LogBuffer.prototype.isFull_;
+goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
+ var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
+ this.curIndex_ = curIndex;
+ if(this.isFull_) {
+ var ret = this.buffer_[curIndex];
+ ret.reset(level, msg, loggerName);
+ return ret
+ }
+ this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
+ return this.buffer_[curIndex] = new goog.debug.LogRecord(level, msg, loggerName)
+};
+goog.debug.LogBuffer.isBufferingEnabled = function() {
+ return goog.debug.LogBuffer.CAPACITY > 0
+};
+goog.debug.LogBuffer.prototype.clear = function() {
+ this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
+ this.curIndex_ = -1;
+ this.isFull_ = false
+};
+goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
+ var buffer = this.buffer_;
+ if(!buffer[0]) {
+ return
+ }
+ var curIndex = this.curIndex_;
+ var i = this.isFull_ ? curIndex : -1;
+ do {
+ i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
+ func(buffer[i])
+ }while(i != curIndex)
+};
+goog.provide("goog.debug.LogManager");
+goog.provide("goog.debug.Logger");
+goog.provide("goog.debug.Logger.Level");
+goog.require("goog.array");
+goog.require("goog.asserts");
+goog.require("goog.debug");
+goog.require("goog.debug.LogBuffer");
+goog.require("goog.debug.LogRecord");
+goog.debug.Logger = function(name) {
+ this.name_ = name
+};
+goog.debug.Logger.prototype.parent_ = null;
+goog.debug.Logger.prototype.level_ = null;
+goog.debug.Logger.prototype.children_ = null;
+goog.debug.Logger.prototype.handlers_ = null;
+goog.debug.Logger.ENABLE_HIERARCHY = true;
+if(!goog.debug.Logger.ENABLE_HIERARCHY) {
+ goog.debug.Logger.rootHandlers_ = [];
+ goog.debug.Logger.rootLevel_
+}
+goog.debug.Logger.Level = function(name, value) {
+ this.name = name;
+ this.value = value
+};
+goog.debug.Logger.Level.prototype.toString = function() {
+ return this.name
+};
+goog.debug.Logger.Level.OFF = new goog.debug.Logger.Level("OFF", Infinity);
+goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level("SHOUT", 1200);
+goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level("SEVERE", 1E3);
+goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level("WARNING", 900);
+goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level("INFO", 800);
+goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level("CONFIG", 700);
+goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level("FINE", 500);
+goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level("FINER", 400);
+goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level("FINEST", 300);
+goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level("ALL", 0);
+goog.debug.Logger.Level.PREDEFINED_LEVELS = [goog.debug.Logger.Level.OFF, goog.debug.Logger.Level.SHOUT, goog.debug.Logger.Level.SEVERE, goog.debug.Logger.Level.WARNING, goog.debug.Logger.Level.INFO, goog.debug.Logger.Level.CONFIG, goog.debug.Logger.Level.FINE, goog.debug.Logger.Level.FINER, goog.debug.Logger.Level.FINEST, goog.debug.Logger.Level.ALL];
+goog.debug.Logger.Level.predefinedLevelsCache_ = null;
+goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
+ goog.debug.Logger.Level.predefinedLevelsCache_ = {};
+ for(var i = 0, level;level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];i++) {
+ goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
+ goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level
+ }
+};
+goog.debug.Logger.Level.getPredefinedLevel = function(name) {
+ if(!goog.debug.Logger.Level.predefinedLevelsCache_) {
+ goog.debug.Logger.Level.createPredefinedLevelsCache_()
+ }
+ return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null
+};
+goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
+ if(!goog.debug.Logger.Level.predefinedLevelsCache_) {
+ goog.debug.Logger.Level.createPredefinedLevelsCache_()
+ }
+ if(value in goog.debug.Logger.Level.predefinedLevelsCache_) {
+ return goog.debug.Logger.Level.predefinedLevelsCache_[value]
+ }
+ for(var i = 0;i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length;++i) {
+ var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
+ if(level.value <= value) {
+ return level
+ }
+ }
+ return null
+};
+goog.debug.Logger.getLogger = function(name) {
+ return goog.debug.LogManager.getLogger(name)
+};
+goog.debug.Logger.prototype.getName = function() {
+ return this.name_
+};
+goog.debug.Logger.prototype.addHandler = function(handler) {
+ if(goog.debug.Logger.ENABLE_HIERARCHY) {
+ if(!this.handlers_) {
+ this.handlers_ = []
+ }
+ this.handlers_.push(handler)
+ }else {
+ goog.asserts.assert(!this.name_, "Cannot call addHandler on a non-root logger when " + "goog.debug.Logger.ENABLE_HIERARCHY is false.");
+ goog.debug.Logger.rootHandlers_.push(handler)
+ }
+};
+goog.debug.Logger.prototype.removeHandler = function(handler) {
+ var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ : goog.debug.Logger.rootHandlers_;
+ return!!handlers && goog.array.remove(handlers, handler)
+};
+goog.debug.Logger.prototype.getParent = function() {
+ return this.parent_
+};
+goog.debug.Logger.prototype.getChildren = function() {
+ if(!this.children_) {
+ this.children_ = {}
+ }
+ return this.children_
+};
+goog.debug.Logger.prototype.setLevel = function(level) {
+ if(goog.debug.Logger.ENABLE_HIERARCHY) {
+ this.level_ = level
+ }else {
+ goog.asserts.assert(!this.name_, "Cannot call setLevel() on a non-root logger when " + "goog.debug.Logger.ENABLE_HIERARCHY is false.");
+ goog.debug.Logger.rootLevel_ = level
+ }
+};
+goog.debug.Logger.prototype.getLevel = function() {
+ return this.level_
+};
+goog.debug.Logger.prototype.getEffectiveLevel = function() {
+ if(!goog.debug.Logger.ENABLE_HIERARCHY) {
+ return goog.debug.Logger.rootLevel_
+ }
+ if(this.level_) {
+ return this.level_
+ }
+ if(this.parent_) {
+ return this.parent_.getEffectiveLevel()
+ }
+ goog.asserts.fail("Root logger has no level set.");
+ return null
+};
+goog.debug.Logger.prototype.isLoggable = function(level) {
+ return level.value >= this.getEffectiveLevel().value
+};
+goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
+ if(this.isLoggable(level)) {
+ this.doLogRecord_(this.getLogRecord(level, msg, opt_exception))
+ }
+};
+goog.debug.Logger.prototype.getLogRecord = function(level, msg, opt_exception) {
+ if(goog.debug.LogBuffer.isBufferingEnabled()) {
+ var logRecord = goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_)
+ }else {
+ logRecord = new goog.debug.LogRecord(level, String(msg), this.name_)
+ }
+ if(opt_exception) {
+ logRecord.setException(opt_exception);
+ logRecord.setExceptionText(goog.debug.exposeException(opt_exception, arguments.callee.caller))
+ }
+ return logRecord
+};
+goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception)
+};
+goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception)
+};
+goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception)
+};
+goog.debug.Logger.prototype.info = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.INFO, msg, opt_exception)
+};
+goog.debug.Logger.prototype.config = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception)
+};
+goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.FINE, msg, opt_exception)
+};
+goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.FINER, msg, opt_exception)
+};
+goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
+ this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception)
+};
+goog.debug.Logger.prototype.logRecord = function(logRecord) {
+ if(this.isLoggable(logRecord.getLevel())) {
+ this.doLogRecord_(logRecord)
+ }
+};
+goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
+ if(goog.debug.Logger.ENABLE_HIERARCHY) {
+ var target = this;
+ while(target) {
+ target.callPublish_(logRecord);
+ target = target.getParent()
+ }
+ }else {
+ for(var i = 0, handler;handler = goog.debug.Logger.rootHandlers_[i++];) {
+ handler(logRecord)
+ }
+ }
+};
+goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
+ if(this.handlers_) {
+ for(var i = 0, handler;handler = this.handlers_[i];i++) {
+ handler(logRecord)
+ }
+ }
+};
+goog.debug.Logger.prototype.setParent_ = function(parent) {
+ this.parent_ = parent
+};
+goog.debug.Logger.prototype.addChild_ = function(name, logger) {
+ this.getChildren()[name] = logger
+};
+goog.debug.LogManager = {};
+goog.debug.LogManager.loggers_ = {};
+goog.debug.LogManager.rootLogger_ = null;
+goog.debug.LogManager.initialize = function() {
+ if(!goog.debug.LogManager.rootLogger_) {
+ goog.debug.LogManager.rootLogger_ = new goog.debug.Logger("");
+ goog.debug.LogManager.loggers_[""] = goog.debug.LogManager.rootLogger_;
+ goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG)
+ }
+};
+goog.debug.LogManager.getLoggers = function() {
+ return goog.debug.LogManager.loggers_
+};
+goog.debug.LogManager.getRoot = function() {
+ goog.debug.LogManager.initialize();
+ return goog.debug.LogManager.rootLogger_
+};
+goog.debug.LogManager.getLogger = function(name) {
+ goog.debug.LogManager.initialize();
+ var ret = goog.debug.LogManager.loggers_[name];
+ return ret || goog.debug.LogManager.createLogger_(name)
+};
+goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
+ return function(info) {
+ var logger = opt_logger || goog.debug.LogManager.getRoot();
+ logger.severe("Error: " + info.message + " (" + info.fileName + " @ Line: " + info.line + ")")
+ }
+};
+goog.debug.LogManager.createLogger_ = function(name) {
+ var logger = new goog.debug.Logger(name);
+ if(goog.debug.Logger.ENABLE_HIERARCHY) {
+ var lastDotIndex = name.lastIndexOf(".");
+ var parentName = name.substr(0, lastDotIndex);
+ var leafName = name.substr(lastDotIndex + 1);
+ var parentLogger = goog.debug.LogManager.getLogger(parentName);
+ parentLogger.addChild_(leafName, logger);
+ logger.setParent_(parentLogger)
+ }
+ goog.debug.LogManager.loggers_[name] = logger;
+ return logger
+};
+goog.provide("goog.dom.SavedRange");
+goog.require("goog.Disposable");
+goog.require("goog.debug.Logger");
+goog.dom.SavedRange = function() {
+ goog.Disposable.call(this)
+};
+goog.inherits(goog.dom.SavedRange, goog.Disposable);
+goog.dom.SavedRange.logger_ = goog.debug.Logger.getLogger("goog.dom.SavedRange");
+goog.dom.SavedRange.prototype.restore = function(opt_stayAlive) {
+ if(this.isDisposed()) {
+ goog.dom.SavedRange.logger_.severe("Disposed SavedRange objects cannot be restored.")
+ }
+ var range = this.restoreInternal();
+ if(!opt_stayAlive) {
+ this.dispose()
+ }
+ return range
+};
+goog.dom.SavedRange.prototype.restoreInternal = goog.abstractMethod;
+goog.provide("goog.dom.SavedCaretRange");
+goog.require("goog.array");
+goog.require("goog.dom");
+goog.require("goog.dom.SavedRange");
+goog.require("goog.dom.TagName");
+goog.require("goog.string");
+goog.dom.SavedCaretRange = function(range) {
+ goog.dom.SavedRange.call(this);
+ this.startCaretId_ = goog.string.createUniqueString();
+ this.endCaretId_ = goog.string.createUniqueString();
+ this.dom_ = goog.dom.getDomHelper(range.getDocument());
+ range.surroundWithNodes(this.createCaret_(true), this.createCaret_(false))
+};
+goog.inherits(goog.dom.SavedCaretRange, goog.dom.SavedRange);
+goog.dom.SavedCaretRange.prototype.toAbstractRange = function() {
+ var range = null;
+ var startCaret = this.getCaret(true);
+ var endCaret = this.getCaret(false);
+ if(startCaret && endCaret) {
+ range = goog.dom.Range.createFromNodes(startCaret, 0, endCaret, 0)
+ }
+ return range
+};
+goog.dom.SavedCaretRange.prototype.getCaret = function(start) {
+ return this.dom_.getElement(start ? this.startCaretId_ : this.endCaretId_)
+};
+goog.dom.SavedCaretRange.prototype.removeCarets = function(opt_range) {
+ goog.dom.removeNode(this.getCaret(true));
+ goog.dom.removeNode(this.getCaret(false));
+ return opt_range
+};
+goog.dom.SavedCaretRange.prototype.setRestorationDocument = function(doc) {
+ this.dom_.setDocument(doc)
+};
+goog.dom.SavedCaretRange.prototype.restoreInternal = function() {
+ var range = null;
+ var startCaret = this.getCaret(true);
+ var endCaret = this.getCaret(false);
+ if(startCaret && endCaret) {
+ var startNode = startCaret.parentNode;
+ var startOffset = goog.array.indexOf(startNode.childNodes, startCaret);
+ var endNode = endCaret.parentNode;
+ var endOffset = goog.array.indexOf(endNode.childNodes, endCaret);
+ if(endNode == startNode) {
+ endOffset -= 1
+ }
+ range = goog.dom.Range.createFromNodes(startNode, startOffset, endNode, endOffset);
+ range = this.removeCarets(range);
+ range.select()
+ }else {
+ this.removeCarets()
+ }
+ return range
+};
+goog.dom.SavedCaretRange.prototype.disposeInternal = function() {
+ this.removeCarets();
+ this.dom_ = null
+};
+goog.dom.SavedCaretRange.prototype.createCaret_ = function(start) {
+ return this.dom_.createDom(goog.dom.TagName.SPAN, {id:start ? this.startCaretId_ : this.endCaretId_})
+};
+goog.dom.SavedCaretRange.CARET_REGEX = /<span\s+id="?goog_\d+"?><\/span>/ig;
+goog.dom.SavedCaretRange.htmlEqual = function(str1, str2) {
+ return str1 == str2 || str1.replace(goog.dom.SavedCaretRange.CARET_REGEX, "") == str2.replace(goog.dom.SavedCaretRange.CARET_REGEX, "")
+};
+goog.provide("goog.dom.TagIterator");
+goog.provide("goog.dom.TagWalkType");
+goog.require("goog.dom.NodeType");
+goog.require("goog.iter.Iterator");
+goog.require("goog.iter.StopIteration");
+goog.dom.TagWalkType = {START_TAG:1, OTHER:0, END_TAG:-1};
+goog.dom.TagIterator = function(opt_node, opt_reversed, opt_unconstrained, opt_tagType, opt_depth) {
+ this.reversed = !!opt_reversed;
+ if(opt_node) {
+ this.setPosition(opt_node, opt_tagType)
+ }
+ this.depth = opt_depth != undefined ? opt_depth : this.tagType || 0;
+ if(this.reversed) {
+ this.depth *= -1
+ }
+ this.constrained = !opt_unconstrained
+};
+goog.inherits(goog.dom.TagIterator, goog.iter.Iterator);
+goog.dom.TagIterator.prototype.node = null;
+goog.dom.TagIterator.prototype.tagType = goog.dom.TagWalkType.OTHER;
+goog.dom.TagIterator.prototype.depth;
+goog.dom.TagIterator.prototype.reversed;
+goog.dom.TagIterator.prototype.constrained;
+goog.dom.TagIterator.prototype.started_ = false;
+goog.dom.TagIterator.prototype.setPosition = function(node, opt_tagType, opt_depth) {
+ this.node = node;
+ if(node) {
+ if(goog.isNumber(opt_tagType)) {
+ this.tagType = opt_tagType
+ }else {
+ this.tagType = this.node.nodeType != goog.dom.NodeType.ELEMENT ? goog.dom.TagWalkType.OTHER : this.reversed ? goog.dom.TagWalkType.END_TAG : goog.dom.TagWalkType.START_TAG
+ }
+ }
+ if(goog.isNumber(opt_depth)) {
+ this.depth = opt_depth
+ }
+};
+goog.dom.TagIterator.prototype.copyFrom = function(other) {
+ this.node = other.node;
+ this.tagType = other.tagType;
+ this.depth = other.depth;
+ this.reversed = other.reversed;
+ this.constrained = other.constrained
+};
+goog.dom.TagIterator.prototype.clone = function() {
+ return new goog.dom.TagIterator(this.node, this.reversed, !this.constrained, this.tagType, this.depth)
+};
+goog.dom.TagIterator.prototype.skipTag = function() {
+ var check = this.reversed ? goog.dom.TagWalkType.END_TAG : goog.dom.TagWalkType.START_TAG;
+ if(this.tagType == check) {
+ this.tagType = check * -1;
+ this.depth += this.tagType * (this.reversed ? -1 : 1)
+ }
+};
+goog.dom.TagIterator.prototype.restartTag = function() {
+ var check = this.reversed ? goog.dom.TagWalkType.START_TAG : goog.dom.TagWalkType.END_TAG;
+ if(this.tagType == check) {
+ this.tagType = check * -1;
+ this.depth += this.tagType * (this.reversed ? -1 : 1)
+ }
+};
+goog.dom.TagIterator.prototype.next = function() {
+ var node;
+ if(this.started_) {
+ if(!this.node || this.constrained && this.depth == 0) {
+ throw goog.iter.StopIteration;
+ }
+ node = this.node;
+ var startType = this.reversed ? goog.dom.TagWalkType.END_TAG : goog.dom.TagWalkType.START_TAG;
+ if(this.tagType == startType) {
+ var child = this.reversed ? node.lastChild : node.firstChild;
+ if(child) {
+ this.setPosition(child)
+ }else {
+ this.setPosition(node, startType * -1)
+ }
+ }else {
+ var sibling = this.reversed ? node.previousSibling : node.nextSibling;
+ if(sibling) {
+ this.setPosition(sibling)
+ }else {
+ this.setPosition(node.parentNode, startType * -1)
+ }
+ }
+ this.depth += this.tagType * (this.reversed ? -1 : 1)
+ }else {
+ this.started_ = true
+ }
+ node = this.node;
+ if(!this.node) {
+ throw goog.iter.StopIteration;
+ }
+ return node
+};
+goog.dom.TagIterator.prototype.isStarted = function() {
+ return this.started_
+};
+goog.dom.TagIterator.prototype.isStartTag = function() {
+ return this.tagType == goog.dom.TagWalkType.START_TAG
+};
+goog.dom.TagIterator.prototype.isEndTag = function() {
+ return this.tagType == goog.dom.TagWalkType.END_TAG
+};
+goog.dom.TagIterator.prototype.isNonElement = function() {
+ return this.tagType == goog.dom.TagWalkType.OTHER
+};
+goog.dom.TagIterator.prototype.equals = function(other) {
+ return other.node == this.node && (!this.node || other.tagType == this.tagType)
+};
+goog.dom.TagIterator.prototype.splice = function(var_args) {
+ var node = this.node;
+ this.restartTag();
+ this.reversed = !this.reversed;
+ goog.dom.TagIterator.prototype.next.call(this);
+ this.reversed = !this.reversed;
+ var arr = goog.isArrayLike(arguments[0]) ? arguments[0] : arguments;
+ for(var i = arr.length - 1;i >= 0;i--) {
+ goog.dom.insertSiblingAfter(arr[i], node)
+ }
+ goog.dom.removeNode(node)
+};
+goog.provide("goog.dom.AbstractRange");
+goog.provide("goog.dom.RangeIterator");
+goog.provide("goog.dom.RangeType");
+goog.require("goog.dom");
+goog.require("goog.dom.NodeType");
+goog.require("goog.dom.SavedCaretRange");
+goog.require("goog.dom.TagIterator");
+goog.require("goog.userAgent");
+goog.dom.RangeType = {TEXT:"text", CONTROL:"control", MULTI:"mutli"};
+goog.dom.AbstractRange = function() {
+};
+goog.dom.AbstractRange.getBrowserSelectionForWindow = function(win) {
+ if(win.getSelection) {
+ return win.getSelection()
+ }else {
+ var doc = win.document;
+ var sel = doc.selection;
+ if(sel) {
+ try {
+ var range = sel.createRange();
+ if(range.parentElement) {
+ if(range.parentElement().document != doc) {
+ return null
+ }
+ }else {
+ if(!range.length || range.item(0).document != doc) {
+ return null
+ }
+ }
+ }catch(e) {
+ return null
+ }
+ return sel
+ }
+ return null
+ }
+};
+goog.dom.AbstractRange.isNativeControlRange = function(range) {
+ return!!range && !!range.addElement
+};
+goog.dom.AbstractRange.prototype.clone = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getType = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getBrowserRangeObject = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.setBrowserRangeObject = function(nativeRange) {
+ return false
+};
+goog.dom.AbstractRange.prototype.getTextRangeCount = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getTextRange = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getTextRanges = function() {
+ var output = [];
+ for(var i = 0, len = this.getTextRangeCount();i < len;i++) {
+ output.push(this.getTextRange(i))
+ }
+ return output
+};
+goog.dom.AbstractRange.prototype.getContainer = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getContainerElement = function() {
+ var node = this.getContainer();
+ return node.nodeType == goog.dom.NodeType.ELEMENT ? node : node.parentNode
+};
+goog.dom.AbstractRange.prototype.getStartNode = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getStartOffset = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getEndNode = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getEndOffset = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getAnchorNode = function() {
+ return this.isReversed() ? this.getEndNode() : this.getStartNode()
+};
+goog.dom.AbstractRange.prototype.getAnchorOffset = function() {
+ return this.isReversed() ? this.getEndOffset() : this.getStartOffset()
+};
+goog.dom.AbstractRange.prototype.getFocusNode = function() {
+ return this.isReversed() ? this.getStartNode() : this.getEndNode()
+};
+goog.dom.AbstractRange.prototype.getFocusOffset = function() {
+ return this.isReversed() ? this.getStartOffset() : this.getEndOffset()
+};
+goog.dom.AbstractRange.prototype.isReversed = function() {
+ return false
+};
+goog.dom.AbstractRange.prototype.getDocument = function() {
+ return goog.dom.getOwnerDocument(goog.userAgent.IE ? this.getContainer() : this.getStartNode())
+};
+goog.dom.AbstractRange.prototype.getWindow = function() {
+ return goog.dom.getWindow(this.getDocument())
+};
+goog.dom.AbstractRange.prototype.containsRange = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.containsNode = function(node, opt_allowPartial) {
+ return this.containsRange(goog.dom.Range.createFromNodeContents(node), opt_allowPartial)
+};
+goog.dom.AbstractRange.prototype.isRangeInDocument = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getText = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getHtmlFragment = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getValidHtml = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.getPastableHtml = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.__iterator__ = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.select = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.removeContents = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.insertNode = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.replaceContentsWithNode = function(node) {
+ if(!this.isCollapsed()) {
+ this.removeContents()
+ }
+ return this.insertNode(node, true)
+};
+goog.dom.AbstractRange.prototype.surroundWithNodes = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.saveUsingDom = goog.abstractMethod;
+goog.dom.AbstractRange.prototype.saveUsingCarets = function() {
+ return this.getStartNode() && this.getEndNode() ? new goog.dom.SavedCaretRange(this) : null
+};
+goog.dom.AbstractRange.prototype.collapse = goog.abstractMethod;
+goog.dom.RangeIterator = function(node, opt_reverse) {
+ goog.dom.TagIterator.call(this, node, opt_reverse, true)
+};
+goog.inherits(goog.dom.RangeIterator, goog.dom.TagIterator);
+goog.dom.RangeIterator.prototype.getStartTextOffset = goog.abstractMethod;
+goog.dom.RangeIterator.prototype.getEndTextOffset = goog.abstractMethod;
+goog.dom.RangeIterator.prototype.getStartNode = goog.abstractMethod;
+goog.dom.RangeIterator.prototype.getEndNode = goog.abstractMethod;
+goog.dom.RangeIterator.prototype.isLast = goog.abstractMethod;
+goog.provide("goog.dom.AbstractMultiRange");
+goog.require("goog.array");
+goog.require("goog.dom");
+goog.require("goog.dom.AbstractRange");
+goog.dom.AbstractMultiRange = function() {
+};
+goog.inherits(goog.dom.AbstractMultiRange, goog.dom.AbstractRange);
+goog.dom.AbstractMultiRange.prototype.containsRange = function(otherRange, opt_allowPartial) {
+ var ranges = this.getTextRanges();
+ var otherRanges = otherRange.getTextRanges();
+ var fn = opt_allowPartial ? goog.array.some : goog.array.every;
+ return fn(otherRanges, function(otherRange) {
+ return goog.array.some(ranges, function(range) {
+ return range.containsRange(otherRange, opt_allowPartial)
+ })
+ })
+};
+goog.dom.AbstractMultiRange.prototype.insertNode = function(node, before) {
+ if(before) {
+ goog.dom.insertSiblingBefore(node, this.getStartNode())
+ }else {
+ goog.dom.insertSiblingAfter(node, this.getEndNode())
+ }
+ return node
+};
+goog.dom.AbstractMultiRange.prototype.surroundWithNodes = function(startNode, endNode) {
+ this.insertNode(startNode, true);
+ this.insertNode(endNode, false)
+};
+goog.provide("goog.dom.TextRangeIterator");
+goog.require("goog.array");
+goog.require("goog.dom.NodeType");
+goog.require("goog.dom.RangeIterator");
+goog.require("goog.dom.TagName");
+goog.require("goog.iter.StopIteration");
+goog.dom.TextRangeIterator = function(startNode, startOffset, endNode, endOffset, opt_reverse) {
+ var goNext;
+ if(startNode) {
+ this.startNode_ = startNode;
+ this.startOffset_ = startOffset;
+ this.endNode_ = endNode;
+ this.endOffset_ = endOffset;
+ if(startNode.nodeType == goog.dom.NodeType.ELEMENT && startNode.tagName != goog.dom.TagName.BR) {
+ var startChildren = startNode.childNodes;
+ var candidate = startChildren[startOffset];
+ if(candidate) {
+ this.startNode_ = candidate;
+ this.startOffset_ = 0
+ }else {
+ if(startChildren.length) {
+ this.startNode_ = goog.array.peek(startChildren)
+ }
+ goNext = true
+ }
+ }
+ if(endNode.nodeType == goog.dom.NodeType.ELEMENT) {
+ this.endNode_ = endNode.childNodes[endOffset];
+ if(this.endNode_) {
+ this.endOffset_ = 0
+ }else {
+ this.endNode_ = endNode
+ }
+ }
+ }
+ goog.dom.RangeIterator.call(this, opt_reverse ? this.endNode_ : this.startNode_, opt_reverse);
+ if(goNext) {
+ try {
+ this.next()
+ }catch(e) {
+ if(e != goog.iter.StopIteration) {
+ throw e;
+ }
+ }
+ }
+};
+goog.inherits(goog.dom.TextRangeIterator, goog.dom.RangeIterator);
+goog.dom.TextRangeIterator.prototype.startNode_ = null;
+goog.dom.TextRangeIterator.prototype.endNode_ = null;
+goog.dom.TextRangeIterator.prototype.startOffset_ = 0;
+goog.dom.TextRangeIterator.prototype.endOffset_ = 0;
+goog.dom.TextRangeIterator.prototype.getStartTextOffset = function() {
+ return this.node.nodeType != goog.dom.NodeType.TEXT ? -1 : this.node == this.startNode_ ? this.startOffset_ : 0
+};
+goog.dom.TextRangeIterator.prototype.getEndTextOffset = function() {
+ return this.node.nodeType != goog.dom.NodeType.TEXT ? -1 : this.node == this.endNode_ ? this.endOffset_ : this.node.nodeValue.length
+};
+goog.dom.TextRangeIterator.prototype.getStartNode = function() {
+ return this.startNode_
+};
+goog.dom.TextRangeIterator.prototype.setStartNode = function(node) {
+ if(!this.isStarted()) {
+ this.setPosition(node)
+ }
+ this.startNode_ = node;
+ this.startOffset_ = 0
+};
+goog.dom.TextRangeIterator.prototype.getEndNode = function() {
+ return this.endNode_
+};
+goog.dom.TextRangeIterator.prototype.setEndNode = function(node) {
+ this.endNode_ = node;
+ this.endOffset_ = 0
+};
+goog.dom.TextRangeIterator.prototype.isLast = function() {
+ return this.isStarted() && this.node == this.endNode_ && (!this.endOffset_ || !this.isStartTag())
+};
+goog.dom.TextRangeIterator.prototype.next = function() {
+ if(this.isLast()) {
+ throw goog.iter.StopIteration;
+ }
+ return goog.dom.TextRangeIterator.superClass_.next.call(this)
+};
+goog.dom.TextRangeIterator.prototype.skipTag = function() {
+ goog.dom.TextRangeIterator.superClass_.skipTag.apply(this);
+ if(goog.dom.contains(this.node, this.endNode_)) {
+ throw goog.iter.StopIteration;
+ }
+};
+goog.dom.TextRangeIterator.prototype.copyFrom = function(other) {
+ this.startNode_ = other.startNode_;
+ this.endNode_ = other.endNode_;
+ this.startOffset_ = other.startOffset_;
+ this.endOffset_ = other.endOffset_;
+ this.isReversed_ = other.isReversed_;
+ goog.dom.TextRangeIterator.superClass_.copyFrom.call(this, other)
+};
+goog.dom.TextRangeIterator.prototype.clone = function() {
+ var copy = new goog.dom.TextRangeIterator(this.startNode_, this.startOffset_, this.endNode_, this.endOffset_, this.isReversed_);
+ copy.copyFrom(this);
+ return copy
+};
+goog.provide("goog.dom.RangeEndpoint");
+goog.dom.RangeEndpoint = {START:1, END:0};
+goog.provide("goog.userAgent.jscript");
+goog.require("goog.string");
+goog.userAgent.jscript.ASSUME_NO_JSCRIPT = false;
+goog.userAgent.jscript.init_ = function() {
+ var hasScriptEngine = "ScriptEngine" in goog.global;
+ goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_ = hasScriptEngine && goog.global["ScriptEngine"]() == "JScript";
+ goog.userAgent.jscript.DETECTED_VERSION_ = goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_ ? goog.global["ScriptEngineMajorVersion"]() + "." + goog.global["ScriptEngineMinorVersion"]() + "." + goog.global["ScriptEngineBuildVersion"]() : "0"
+};
+if(!goog.userAgent.jscript.ASSUME_NO_JSCRIPT) {
+ goog.userAgent.jscript.init_()
+}
+goog.userAgent.jscript.HAS_JSCRIPT = goog.userAgent.jscript.ASSUME_NO_JSCRIPT ? false : goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_;
+goog.userAgent.jscript.VERSION = goog.userAgent.jscript.ASSUME_NO_JSCRIPT ? "0" : goog.userAgent.jscript.DETECTED_VERSION_;
+goog.userAgent.jscript.isVersion = function(version) {
+ return goog.string.compareVersions(goog.userAgent.jscript.VERSION, version) >= 0
+};
+goog.provide("goog.string.StringBuffer");
+goog.require("goog.userAgent.jscript");
+goog.string.StringBuffer = function(opt_a1, var_args) {
+ this.buffer_ = goog.userAgent.jscript.HAS_JSCRIPT ? [] : "";
+ if(opt_a1 != null) {
+ this.append.apply(this, arguments)
+ }
+};
+goog.string.StringBuffer.prototype.set = function(s) {
+ this.clear();
+ this.append(s)
+};
+if(goog.userAgent.jscript.HAS_JSCRIPT) {
+ goog.string.StringBuffer.prototype.bufferLength_ = 0;
+ goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
+ if(opt_a2 == null) {
+ this.buffer_[this.bufferLength_++] = a1
+ }else {
+ this.buffer_.push.apply(this.buffer_, arguments);
+ this.bufferLength_ = this.buffer_.length
+ }
+ return this
+ }
+}else {
+ goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
+ this.buffer_ += a1;
+ if(opt_a2 != null) {
+ for(var i = 1;i < arguments.length;i++) {
+ this.buffer_ += arguments[i]
+ }
+ }
+ return this
+ }
+}
+goog.string.StringBuffer.prototype.clear = function() {
+ if(goog.userAgent.jscript.HAS_JSCRIPT) {
+ this.buffer_.length = 0;
+ this.bufferLength_ = 0
+ }else {
+ this.buffer_ = ""
+ }
+};
+goog.string.StringBuffer.prototype.getLength = function() {
+ return this.toString().length
+};
+goog.string.StringBuffer.prototype.toString = function() {
+ if(goog.userAgent.jscript.HAS_JSCRIPT) {
+ var str = this.buffer_.join("");
+ this.clear();
+ if(str) {
+ this.append(str)
+ }
+ return str
+ }else {
+ return this.buffer_
+ }
+};
+goog.provide("goog.dom.browserrange.AbstractRange");
+goog.require("goog.dom");
+goog.require("goog.dom.NodeType");
+goog.require("goog.dom.RangeEndpoint");
+goog.require("goog.dom.TagName");
+goog.require("goog.dom.TextRangeIterator");
+goog.require("goog.iter");
+goog.require("goog.string");
+goog.require("goog.string.StringBuffer");
+goog.require("goog.userAgent");
+goog.dom.browserrange.AbstractRange = function() {
+};
+goog.dom.browserrange.AbstractRange.prototype.clone = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getBrowserRange = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getContainer = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getStartNode = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getStartOffset = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getEndNode = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getEndOffset = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.compareBrowserRangeEndpoints = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.containsRange = function(abstractRange, opt_allowPartial) {
+ var checkPartial = opt_allowPartial && !abstractRange.isCollapsed();
+ var range = abstractRange.getBrowserRange();
+ var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
+ try {
+ if(checkPartial) {
+ return this.compareBrowserRangeEndpoints(range, end, start) >= 0 && this.compareBrowserRangeEndpoints(range, start, end) <= 0
+ }else {
+ return this.compareBrowserRangeEndpoints(range, end, end) >= 0 && this.compareBrowserRangeEndpoints(range, start, start) <= 0
+ }
+ }catch(e) {
+ if(!goog.userAgent.IE) {
+ throw e;
+ }
+ return false
+ }
+};
+goog.dom.browserrange.AbstractRange.prototype.containsNode = function(node, opt_allowPartial) {
+ return this.containsRange(goog.dom.browserrange.createRangeFromNodeContents(node), opt_allowPartial)
+};
+goog.dom.browserrange.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getText = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.getHtmlFragment = function() {
+ var output = new goog.string.StringBuffer;
+ goog.iter.forEach(this, function(node, ignore, it) {
+ if(node.nodeType == goog.dom.NodeType.TEXT) {
+ output.append(goog.string.htmlEscape(node.nodeValue.substring(it.getStartTextOffset(), it.getEndTextOffset())))
+ }else {
+ if(node.nodeType == goog.dom.NodeType.ELEMENT) {
+ if(it.isEndTag()) {
+ if(goog.dom.canHaveChildren(node)) {
+ output.append("</" + node.tagName + ">")
+ }
+ }else {
+ var shallow = node.cloneNode(false);
+ var html = goog.dom.getOuterHtml(shallow);
+ if(goog.userAgent.IE && node.tagName == goog.dom.TagName.LI) {
+ output.append(html)
+ }else {
+ var index = html.lastIndexOf("<");
+ output.append(index ? html.substr(0, index) : html)
+ }
+ }
+ }
+ }
+ }, this);
+ return output.toString()
+};
+goog.dom.browserrange.AbstractRange.prototype.getValidHtml = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.__iterator__ = function(opt_keys) {
+ return new goog.dom.TextRangeIterator(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset())
+};
+goog.dom.browserrange.AbstractRange.prototype.select = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.removeContents = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.surroundContents = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.insertNode = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.surroundWithNodes = goog.abstractMethod;
+goog.dom.browserrange.AbstractRange.prototype.collapse = goog.abstractMethod;
+goog.provide("goog.dom.browserrange.W3cRange");
+goog.require("goog.dom");
+goog.require("goog.dom.NodeType");
+goog.require("goog.dom.RangeEndpoint");
+goog.require("goog.dom.browserrange.AbstractRange");
+goog.require("goog.string");
+goog.dom.browserrange.W3cRange = function(range) {
+ this.range_ = range
+};
+goog.inherits(goog.dom.browserrange.W3cRange, goog.dom.browserrange.AbstractRange);
+goog.dom.browserrange.W3cRange.getBrowserRangeForNode = function(node) {
+ var nodeRange = goog.dom.getOwnerDocument(node).createRange();
+ if(node.nodeType == goog.dom.NodeType.TEXT) {
+ nodeRange.setStart(node, 0);
+ nodeRange.setEnd(node, node.length)
+ }else {
+ if(!goog.dom.browserrange.canContainRangeEndpoint(node)) {
+ var rangeParent = node.parentNode;
+ var rangeStartOffset = goog.array.indexOf(rangeParent.childNodes, node);
+ nodeRange.setStart(rangeParent, rangeStartOffset);
+ nodeRange.setEnd(rangeParent, rangeStartOffset + 1)
+ }else {
+ var tempNode, leaf = node;
+ while((tempNode = leaf.firstChild) && goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
+ leaf = tempNode
+ }
+ nodeRange.setStart(leaf, 0);
+ leaf = node;
+ while((tempNode = leaf.lastChild) && goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
+ leaf = tempNode
+ }
+ nodeRange.setEnd(leaf, leaf.nodeType == goog.dom.NodeType.ELEMENT ? leaf.childNodes.length : leaf.length)
+ }
+ }
+ return nodeRange
+};
+goog.dom.browserrange.W3cRange.getBrowserRangeForNodes = function(startNode, startOffset, endNode, endOffset) {
+ var nodeRange = goog.dom.getOwnerDocument(startNode).createRange();
+ nodeRange.setStart(startNode, startOffset);
+ nodeRange.setEnd(endNode, endOffset);
+ return nodeRange
+};
+goog.dom.browserrange.W3cRange.createFromNodeContents = function(node) {
+ return new goog.dom.browserrange.W3cRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node))
+};
+goog.dom.browserrange.W3cRange.createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return new goog.dom.browserrange.W3cRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode, startOffset, endNode, endOffset))
+};
+goog.dom.browserrange.W3cRange.prototype.clone = function() {
+ return new this.constructor(this.range_.cloneRange())
+};
+goog.dom.browserrange.W3cRange.prototype.getBrowserRange = function() {
+ return this.range_
+};
+goog.dom.browserrange.W3cRange.prototype.getContainer = function() {
+ return this.range_.commonAncestorContainer
+};
+goog.dom.browserrange.W3cRange.prototype.getStartNode = function() {
+ return this.range_.startContainer
+};
+goog.dom.browserrange.W3cRange.prototype.getStartOffset = function() {
+ return this.range_.startOffset
+};
+goog.dom.browserrange.W3cRange.prototype.getEndNode = function() {
+ return this.range_.endContainer
+};
+goog.dom.browserrange.W3cRange.prototype.getEndOffset = function() {
+ return this.range_.endOffset
+};
+goog.dom.browserrange.W3cRange.prototype.compareBrowserRangeEndpoints = function(range, thisEndpoint, otherEndpoint) {
+ return this.range_.compareBoundaryPoints(otherEndpoint == goog.dom.RangeEndpoint.START ? thisEndpoint == goog.dom.RangeEndpoint.START ? goog.global["Range"].START_TO_START : goog.global["Range"].START_TO_END : thisEndpoint == goog.dom.RangeEndpoint.START ? goog.global["Range"].END_TO_START : goog.global["Range"].END_TO_END, range)
+};
+goog.dom.browserrange.W3cRange.prototype.isCollapsed = function() {
+ return this.range_.collapsed
+};
+goog.dom.browserrange.W3cRange.prototype.getText = function() {
+ return this.range_.toString()
+};
+goog.dom.browserrange.W3cRange.prototype.getValidHtml = function() {
+ var div = goog.dom.getDomHelper(this.range_.startContainer).createDom("div");
+ div.appendChild(this.range_.cloneContents());
+ var result = div.innerHTML;
+ if(goog.string.startsWith(result, "<") || !this.isCollapsed() && !goog.string.contains(result, "<")) {
+ return result
+ }
+ var container = this.getContainer();
+ container = container.nodeType == goog.dom.NodeType.ELEMENT ? container : container.parentNode;
+ var html = goog.dom.getOuterHtml(container.cloneNode(false));
+ return html.replace(">", ">" + result)
+};
+goog.dom.browserrange.W3cRange.prototype.select = function(reverse) {
+ var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
+ this.selectInternal(win.getSelection(), reverse)
+};
+goog.dom.browserrange.W3cRange.prototype.selectInternal = function(selection, reverse) {
+ selection.removeAllRanges();
+ selection.addRange(this.range_)
+};
+goog.dom.browserrange.W3cRange.prototype.removeContents = function() {
+ var range = this.range_;
+ range.extractContents();
+ if(range.startContainer.hasChildNodes()) {
+ var rangeStartContainer = range.startContainer.childNodes[range.startOffset];
+ if(rangeStartContainer) {
+ var rangePrevious = rangeStartContainer.previousSibling;
+ if(goog.dom.getRawTextContent(rangeStartContainer) == "") {
+ goog.dom.removeNode(rangeStartContainer)
+ }
+ if(rangePrevious && goog.dom.getRawTextContent(rangePrevious) == "") {
+ goog.dom.removeNode(rangePrevious)
+ }
+ }
+ }
+};
+goog.dom.browserrange.W3cRange.prototype.surroundContents = function(element) {
+ this.range_.surroundContents(element);
+ return element
+};
+goog.dom.browserrange.W3cRange.prototype.insertNode = function(node, before) {
+ var range = this.range_.cloneRange();
+ range.collapse(before);
+ range.insertNode(node);
+ range.detach();
+ return node
+};
+goog.dom.browserrange.W3cRange.prototype.surroundWithNodes = function(startNode, endNode) {
+ var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
+ var selectionRange = goog.dom.Range.createFromWindow(win);
+ if(selectionRange) {
+ var sNode = selectionRange.getStartNode();
+ var eNode = selectionRange.getEndNode();
+ var sOffset = selectionRange.getStartOffset();
+ var eOffset = selectionRange.getEndOffset()
+ }
+ var clone1 = this.range_.cloneRange();
+ var clone2 = this.range_.cloneRange();
+ clone1.collapse(false);
+ clone2.collapse(true);
+ clone1.insertNode(endNode);
+ clone2.insertNode(startNode);
+ clone1.detach();
+ clone2.detach();
+ if(selectionRange) {
+ var isInsertedNode = function(n) {
+ return n == startNode || n == endNode
+ };
+ if(sNode.nodeType == goog.dom.NodeType.TEXT) {
+ while(sOffset > sNode.length) {
+ sOffset -= sNode.length;
+ do {
+ sNode = sNode.nextSibling
+ }while(isInsertedNode(sNode))
+ }
+ }
+ if(eNode.nodeType == goog.dom.NodeType.TEXT) {
+ while(eOffset > eNode.length) {
+ eOffset -= eNode.length;
+ do {
+ eNode = eNode.nextSibling
+ }while(isInsertedNode(eNode))
+ }
+ }
+ goog.dom.Range.createFromNodes(sNode, sOffset, eNode, eOffset).select()
+ }
+};
+goog.dom.browserrange.W3cRange.prototype.collapse = function(toStart) {
+ this.range_.collapse(toStart)
+};
+goog.provide("goog.dom.browserrange.GeckoRange");
+goog.require("goog.dom.browserrange.W3cRange");
+goog.dom.browserrange.GeckoRange = function(range) {
+ goog.dom.browserrange.W3cRange.call(this, range)
+};
+goog.inherits(goog.dom.browserrange.GeckoRange, goog.dom.browserrange.W3cRange);
+goog.dom.browserrange.GeckoRange.createFromNodeContents = function(node) {
+ return new goog.dom.browserrange.GeckoRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node))
+};
+goog.dom.browserrange.GeckoRange.createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return new goog.dom.browserrange.GeckoRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode, startOffset, endNode, endOffset))
+};
+goog.dom.browserrange.GeckoRange.prototype.selectInternal = function(selection, reversed) {
+ var anchorNode = reversed ? this.getEndNode() : this.getStartNode();
+ var anchorOffset = reversed ? this.getEndOffset() : this.getStartOffset();
+ var focusNode = reversed ? this.getStartNode() : this.getEndNode();
+ var focusOffset = reversed ? this.getStartOffset() : this.getEndOffset();
+ selection.collapse(anchorNode, anchorOffset);
+ if(anchorNode != focusNode || anchorOffset != focusOffset) {
+ selection.extend(focusNode, focusOffset)
+ }
+};
+goog.provide("goog.dom.NodeIterator");
+goog.require("goog.dom.TagIterator");
+goog.dom.NodeIterator = function(opt_node, opt_reversed, opt_unconstrained, opt_depth) {
+ goog.dom.TagIterator.call(this, opt_node, opt_reversed, opt_unconstrained, null, opt_depth)
+};
+goog.inherits(goog.dom.NodeIterator, goog.dom.TagIterator);
+goog.dom.NodeIterator.prototype.next = function() {
+ do {
+ goog.dom.NodeIterator.superClass_.next.call(this)
+ }while(this.isEndTag());
+ return this.node
+};
+goog.provide("goog.dom.browserrange.IeRange");
+goog.require("goog.array");
+goog.require("goog.debug.Logger");
+goog.require("goog.dom");
+goog.require("goog.dom.NodeIterator");
+goog.require("goog.dom.NodeType");
+goog.require("goog.dom.RangeEndpoint");
+goog.require("goog.dom.TagName");
+goog.require("goog.dom.browserrange.AbstractRange");
+goog.require("goog.iter");
+goog.require("goog.iter.StopIteration");
+goog.require("goog.string");
+goog.dom.browserrange.IeRange = function(range, doc) {
+ this.range_ = range;
+ this.doc_ = doc
+};
+goog.inherits(goog.dom.browserrange.IeRange, goog.dom.browserrange.AbstractRange);
+goog.dom.browserrange.IeRange.logger_ = goog.debug.Logger.getLogger("goog.dom.browserrange.IeRange");
+goog.dom.browserrange.IeRange.getBrowserRangeForNode_ = function(node) {
+ var nodeRange = goog.dom.getOwnerDocument(node).body.createTextRange();
+ if(node.nodeType == goog.dom.NodeType.ELEMENT) {
+ nodeRange.moveToElementText(node);
+ if(goog.dom.browserrange.canContainRangeEndpoint(node) && !node.childNodes.length) {
+ nodeRange.collapse(false)
+ }
+ }else {
+ var offset = 0;
+ var sibling = node;
+ while(sibling = sibling.previousSibling) {
+ var nodeType = sibling.nodeType;
+ if(nodeType == goog.dom.NodeType.TEXT) {
+ offset += sibling.length
+ }else {
+ if(nodeType == goog.dom.NodeType.ELEMENT) {
+ nodeRange.moveToElementText(sibling);
+ break
+ }
+ }
+ }
+ if(!sibling) {
+ nodeRange.moveToElementText(node.parentNode)
+ }
+ nodeRange.collapse(!sibling);
+ if(offset) {
+ nodeRange.move("character", offset)
+ }
+ nodeRange.moveEnd("character", node.length)
+ }
+ return nodeRange
+};
+goog.dom.browserrange.IeRange.getBrowserRangeForNodes_ = function(startNode, startOffset, endNode, endOffset) {
+ var child, collapse = false;
+ if(startNode.nodeType == goog.dom.NodeType.ELEMENT) {
+ if(startOffset > startNode.childNodes.length) {
+ goog.dom.browserrange.IeRange.logger_.severe("Cannot have startOffset > startNode child count")
+ }
+ child = startNode.childNodes[startOffset];
+ collapse = !child;
+ startNode = child || startNode.lastChild || startNode;
+ startOffset = 0
+ }
+ var leftRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(startNode);
+ if(startOffset) {
+ leftRange.move("character", startOffset)
+ }
+ if(startNode == endNode && startOffset == endOffset) {
+ leftRange.collapse(true);
+ return leftRange
+ }
+ if(collapse) {
+ leftRange.collapse(false)
+ }
+ collapse = false;
+ if(endNode.nodeType == goog.dom.NodeType.ELEMENT) {
+ if(endOffset > endNode.childNodes.length) {
+ goog.dom.browserrange.IeRange.logger_.severe("Cannot have endOffset > endNode child count")
+ }
+ child = endNode.childNodes[endOffset];
+ endNode = child || endNode.lastChild || endNode;
+ endOffset = 0;
+ collapse = !child
+ }
+ var rightRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(endNode);
+ rightRange.collapse(!collapse);
+ if(endOffset) {
+ rightRange.moveEnd("character", endOffset)
+ }
+ leftRange.setEndPoint("EndToEnd", rightRange);
+ return leftRange
+};
+goog.dom.browserrange.IeRange.createFromNodeContents = function(node) {
+ var range = new goog.dom.browserrange.IeRange(goog.dom.browserrange.IeRange.getBrowserRangeForNode_(node), goog.dom.getOwnerDocument(node));
+ if(!goog.dom.browserrange.canContainRangeEndpoint(node)) {
+ range.startNode_ = range.endNode_ = range.parentNode_ = node.parentNode;
+ range.startOffset_ = goog.array.indexOf(range.parentNode_.childNodes, node);
+ range.endOffset_ = range.startOffset_ + 1
+ }else {
+ var tempNode, leaf = node;
+ while((tempNode = leaf.firstChild) && goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
+ leaf = tempNode
+ }
+ range.startNode_ = leaf;
+ range.startOffset_ = 0;
+ leaf = node;
+ while((tempNode = leaf.lastChild) && goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
+ leaf = tempNode
+ }
+ range.endNode_ = leaf;
+ range.endOffset_ = leaf.nodeType == goog.dom.NodeType.ELEMENT ? leaf.childNodes.length : leaf.length;
+ range.parentNode_ = node
+ }
+ return range
+};
+goog.dom.browserrange.IeRange.createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ var range = new goog.dom.browserrange.IeRange(goog.dom.browserrange.IeRange.getBrowserRangeForNodes_(startNode, startOffset, endNode, endOffset), goog.dom.getOwnerDocument(startNode));
+ range.startNode_ = startNode;
+ range.startOffset_ = startOffset;
+ range.endNode_ = endNode;
+ range.endOffset_ = endOffset;
+ return range
+};
+goog.dom.browserrange.IeRange.prototype.parentNode_ = null;
+goog.dom.browserrange.IeRange.prototype.startNode_ = null;
+goog.dom.browserrange.IeRange.prototype.endNode_ = null;
+goog.dom.browserrange.IeRange.prototype.startOffset_ = -1;
+goog.dom.browserrange.IeRange.prototype.endOffset_ = -1;
+goog.dom.browserrange.IeRange.prototype.clone = function() {
+ var range = new goog.dom.browserrange.IeRange(this.range_.duplicate(), this.doc_);
+ range.parentNode_ = this.parentNode_;
+ range.startNode_ = this.startNode_;
+ range.endNode_ = this.endNode_;
+ return range
+};
+goog.dom.browserrange.IeRange.prototype.getBrowserRange = function() {
+ return this.range_
+};
+goog.dom.browserrange.IeRange.prototype.clearCachedValues_ = function() {
+ this.parentNode_ = this.startNode_ = this.endNode_ = null;
+ this.startOffset_ = this.endOffset_ = -1
+};
+goog.dom.browserrange.IeRange.prototype.getContainer = function() {
+ if(!this.parentNode_) {
+ var selectText = this.range_.text;
+ var range = this.range_.duplicate();
+ var rightTrimmedSelectText = selectText.replace(/ +$/, "");
+ var numSpacesAtEnd = selectText.length - rightTrimmedSelectText.length;
+ if(numSpacesAtEnd) {
+ range.moveEnd("character", -numSpacesAtEnd)
+ }
+ var parent = range.parentElement();
+ var htmlText = range.htmlText;
+ var htmlTextLen = goog.string.stripNewlines(htmlText).length;
+ if(this.isCollapsed() && htmlTextLen > 0) {
+ return this.parentNode_ = parent
+ }
+ while(htmlTextLen > goog.string.stripNewlines(parent.outerHTML).length) {
+ parent = parent.parentNode
+ }
+ while(parent.childNodes.length == 1 && parent.innerText == goog.dom.browserrange.IeRange.getNodeText_(parent.firstChild)) {
+ if(!goog.dom.browserrange.canContainRangeEndpoint(parent.firstChild)) {
+ break
+ }
+ parent = parent.firstChild
+ }
+ if(selectText.length == 0) {
+ parent = this.findDeepestContainer_(parent)
+ }
+ this.parentNode_ = parent
+ }
+ return this.parentNode_
+};
+goog.dom.browserrange.IeRange.prototype.findDeepestContainer_ = function(node) {
+ var childNodes = node.childNodes;
+ for(var i = 0, len = childNodes.length;i < len;i++) {
+ var child = childNodes[i];
+ if(goog.dom.browserrange.canContainRangeEndpoint(child)) {
+ var childRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(child);
+ var start = goog.dom.RangeEndpoint.START;
+ var end = goog.dom.RangeEndpoint.END;
+ var isChildRangeErratic = childRange.htmlText != child.outerHTML;
+ var isNativeInRangeErratic = this.isCollapsed() && isChildRangeErratic;
+ var inChildRange = isNativeInRangeErratic ? this.compareBrowserRangeEndpoints(childRange, start, start) >= 0 && this.compareBrowserRangeEndpoints(childRange, start, end) <= 0 : this.range_.inRange(childRange);
+ if(inChildRange) {
+ return this.findDeepestContainer_(child)
+ }
+ }
+ }
+ return node
+};
+goog.dom.browserrange.IeRange.prototype.getStartNode = function() {
+ if(!this.startNode_) {
+ this.startNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.START);
+ if(this.isCollapsed()) {
+ this.endNode_ = this.startNode_
+ }
+ }
+ return this.startNode_
+};
+goog.dom.browserrange.IeRange.prototype.getStartOffset = function() {
+ if(this.startOffset_ < 0) {
+ this.startOffset_ = this.getOffset_(goog.dom.RangeEndpoint.START);
+ if(this.isCollapsed()) {
+ this.endOffset_ = this.startOffset_
+ }
+ }
+ return this.startOffset_
+};
+goog.dom.browserrange.IeRange.prototype.getEndNode = function() {
+ if(this.isCollapsed()) {
+ return this.getStartNode()
+ }
+ if(!this.endNode_) {
+ this.endNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.END)
+ }
+ return this.endNode_
+};
+goog.dom.browserrange.IeRange.prototype.getEndOffset = function() {
+ if(this.isCollapsed()) {
+ return this.getStartOffset()
+ }
+ if(this.endOffset_ < 0) {
+ this.endOffset_ = this.getOffset_(goog.dom.RangeEndpoint.END);
+ if(this.isCollapsed()) {
+ this.startOffset_ = this.endOffset_
+ }
+ }
+ return this.endOffset_
+};
+goog.dom.browserrange.IeRange.prototype.compareBrowserRangeEndpoints = function(range, thisEndpoint, otherEndpoint) {
+ return this.range_.compareEndPoints((thisEndpoint == goog.dom.RangeEndpoint.START ? "Start" : "End") + "To" + (otherEndpoint == goog.dom.RangeEndpoint.START ? "Start" : "End"), range)
+};
+goog.dom.browserrange.IeRange.prototype.getEndpointNode_ = function(endpoint, opt_node) {
+ var node = opt_node || this.getContainer();
+ if(!node || !node.firstChild) {
+ return node
+ }
+ var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
+ var isStartEndpoint = endpoint == start;
+ for(var j = 0, length = node.childNodes.length;j < length;j++) {
+ var i = isStartEndpoint ? j : length - j - 1;
+ var child = node.childNodes[i];
+ var childRange;
+ try {
+ childRange = goog.dom.browserrange.createRangeFromNodeContents(child)
+ }catch(e) {
+ continue
+ }
+ var ieRange = childRange.getBrowserRange();
+ if(this.isCollapsed()) {
+ if(!goog.dom.browserrange.canContainRangeEndpoint(child)) {
+ if(this.compareBrowserRangeEndpoints(ieRange, start, start) == 0) {
+ this.startOffset_ = this.endOffset_ = i;
+ return node
+ }
+ }else {
+ if(childRange.containsRange(this)) {
+ return this.getEndpointNode_(endpoint, child)
+ }
+ }
+ }else {
+ if(this.containsRange(childRange)) {
+ if(!goog.dom.browserrange.canContainRangeEndpoint(child)) {
+ if(isStartEndpoint) {
+ this.startOffset_ = i
+ }else {
+ this.endOffset_ = i + 1
+ }
+ return node
+ }
+ return this.getEndpointNode_(endpoint, child)
+ }else {
+ if(this.compareBrowserRangeEndpoints(ieRange, start, end) < 0 && this.compareBrowserRangeEndpoints(ieRange, end, start) > 0) {
+ return this.getEndpointNode_(endpoint, child)
+ }
+ }
+ }
+ }
+ return node
+};
+goog.dom.browserrange.IeRange.prototype.compareNodeEndpoints_ = function(node, thisEndpoint, otherEndpoint) {
+ return this.range_.compareEndPoints((thisEndpoint == goog.dom.RangeEndpoint.START ? "Start" : "End") + "To" + (otherEndpoint == goog.dom.RangeEndpoint.START ? "Start" : "End"), goog.dom.browserrange.createRangeFromNodeContents(node).getBrowserRange())
+};
+goog.dom.browserrange.IeRange.prototype.getOffset_ = function(endpoint, opt_container) {
+ var isStartEndpoint = endpoint == goog.dom.RangeEndpoint.START;
+ var container = opt_container || (isStartEndpoint ? this.getStartNode() : this.getEndNode());
+ if(container.nodeType == goog.dom.NodeType.ELEMENT) {
+ var children = container.childNodes;
+ var len = children.length;
+ var edge = isStartEndpoint ? 0 : len - 1;
+ var sign = isStartEndpoint ? 1 : -1;
+ for(var i = edge;i >= 0 && i < len;i += sign) {
+ var child = children[i];
+ if(goog.dom.browserrange.canContainRangeEndpoint(child)) {
+ continue
+ }
+ var endPointCompare = this.compareNodeEndpoints_(child, endpoint, endpoint);
+ if(endPointCompare == 0) {
+ return isStartEndpoint ? i : i + 1
+ }
+ }
+ return i == -1 ? 0 : i
+ }else {
+ var range = this.range_.duplicate();
+ var nodeRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(container);
+ range.setEndPoint(isStartEndpoint ? "EndToEnd" : "StartToStart", nodeRange);
+ var rangeLength = range.text.length;
+ return isStartEndpoint ? container.length - rangeLength : rangeLength
+ }
+};
+goog.dom.browserrange.IeRange.getNodeText_ = function(node) {
+ return node.nodeType == goog.dom.NodeType.TEXT ? node.nodeValue : node.innerText
+};
+goog.dom.browserrange.IeRange.prototype.isRangeInDocument = function() {
+ var range = this.doc_.body.createTextRange();
+ range.moveToElementText(this.doc_.body);
+ return this.containsRange(new goog.dom.browserrange.IeRange(range, this.doc_), true)
+};
+goog.dom.browserrange.IeRange.prototype.isCollapsed = function() {
+ return this.range_.compareEndPoints("StartToEnd", this.range_) == 0
+};
+goog.dom.browserrange.IeRange.prototype.getText = function() {
+ return this.range_.text
+};
+goog.dom.browserrange.IeRange.prototype.getValidHtml = function() {
+ return this.range_.htmlText
+};
+goog.dom.browserrange.IeRange.prototype.select = function(opt_reverse) {
+ this.range_.select()
+};
+goog.dom.browserrange.IeRange.prototype.removeContents = function() {
+ if(this.range_.htmlText) {
+ var startNode = this.getStartNode();
+ var endNode = this.getEndNode();
+ var oldText = this.range_.text;
+ var clone = this.range_.duplicate();
+ clone.moveStart("character", 1);
+ clone.moveStart("character", -1);
+ if(clone.text != oldText) {
+ var iter = new goog.dom.NodeIterator(startNode, false, true);
+ var toDelete = [];
+ goog.iter.forEach(iter, function(node) {
+ if(node.nodeType != goog.dom.NodeType.TEXT && this.containsNode(node)) {
+ toDelete.push(node);
+ iter.skipTag()
+ }
+ if(node == endNode) {
+ throw goog.iter.StopIteration;
+ }
+ });
+ this.collapse(true);
+ goog.array.forEach(toDelete, goog.dom.removeNode);
+ this.clearCachedValues_();
+ return
+ }
+ this.range_ = clone;
+ this.range_.text = "";
+ this.clearCachedValues_();
+ var newStartNode = this.getStartNode();
+ var newStartOffset = this.getStartOffset();
+ try {
+ var sibling = startNode.nextSibling;
+ if(startNode == endNode && startNode.parentNode && startNode.nodeType == goog.dom.NodeType.TEXT && sibling && sibling.nodeType == goog.dom.NodeType.TEXT) {
+ startNode.nodeValue += sibling.nodeValue;
+ goog.dom.removeNode(sibling);
+ this.range_ = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(newStartNode);
+ this.range_.move("character", newStartOffset);
+ this.clearCachedValues_()
+ }
+ }catch(e) {
+ }
+ }
+};
+goog.dom.browserrange.IeRange.getDomHelper_ = function(range) {
+ return goog.dom.getDomHelper(range.parentElement())
+};
+goog.dom.browserrange.IeRange.pasteElement_ = function(range, element, opt_domHelper) {
+ opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_(range);
+ var id;
+ var originalId = id = element.id;
+ if(!id) {
+ id = element.id = goog.string.createUniqueString()
+ }
+ range.pasteHTML(element.outerHTML);
+ element = opt_domHelper.getElement(id);
+ if(element) {
+ if(!originalId) {
+ element.removeAttribute("id")
+ }
+ }
+ return element
+};
+goog.dom.browserrange.IeRange.prototype.surroundContents = function(element) {
+ goog.dom.removeNode(element);
+ element.innerHTML = this.range_.htmlText;
+ element = goog.dom.browserrange.IeRange.pasteElement_(this.range_, element);
+ if(element) {
+ this.range_.moveToElementText(element)
+ }
+ this.clearCachedValues_();
+ return element
+};
+goog.dom.browserrange.IeRange.insertNode_ = function(clone, node, before, opt_domHelper) {
+ opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_(clone);
+ var isNonElement;
+ if(node.nodeType != goog.dom.NodeType.ELEMENT) {
+ isNonElement = true;
+ node = opt_domHelper.createDom(goog.dom.TagName.DIV, null, node)
+ }
+ clone.collapse(before);
+ node = goog.dom.browserrange.IeRange.pasteElement_(clone, node, opt_domHelper);
+ if(isNonElement) {
+ var newNonElement = node.firstChild;
+ opt_domHelper.flattenElement(node);
+ node = newNonElement
+ }
+ return node
+};
+goog.dom.browserrange.IeRange.prototype.insertNode = function(node, before) {
+ var output = goog.dom.browserrange.IeRange.insertNode_(this.range_.duplicate(), node, before);
+ this.clearCachedValues_();
+ return output
+};
+goog.dom.browserrange.IeRange.prototype.surroundWithNodes = function(startNode, endNode) {
+ var clone1 = this.range_.duplicate();
+ var clone2 = this.range_.duplicate();
+ goog.dom.browserrange.IeRange.insertNode_(clone1, startNode, true);
+ goog.dom.browserrange.IeRange.insertNode_(clone2, endNode, false);
+ this.clearCachedValues_()
+};
+goog.dom.browserrange.IeRange.prototype.collapse = function(toStart) {
+ this.range_.collapse(toStart);
+ if(toStart) {
+ this.endNode_ = this.startNode_;
+ this.endOffset_ = this.startOffset_
+ }else {
+ this.startNode_ = this.endNode_;
+ this.startOffset_ = this.endOffset_
+ }
+};
+goog.provide("goog.dom.browserrange.OperaRange");
+goog.require("goog.dom.browserrange.W3cRange");
+goog.dom.browserrange.OperaRange = function(range) {
+ goog.dom.browserrange.W3cRange.call(this, range)
+};
+goog.inherits(goog.dom.browserrange.OperaRange, goog.dom.browserrange.W3cRange);
+goog.dom.browserrange.OperaRange.createFromNodeContents = function(node) {
+ return new goog.dom.browserrange.OperaRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node))
+};
+goog.dom.browserrange.OperaRange.createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return new goog.dom.browserrange.OperaRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode, startOffset, endNode, endOffset))
+};
+goog.dom.browserrange.OperaRange.prototype.selectInternal = function(selection, reversed) {
+ selection.collapse(this.getStartNode(), this.getStartOffset());
+ if(this.getEndNode() != this.getStartNode() || this.getEndOffset() != this.getStartOffset()) {
+ selection.extend(this.getEndNode(), this.getEndOffset())
+ }
+ if(selection.rangeCount == 0) {
+ selection.addRange(this.range_)
+ }
+};
+goog.provide("goog.dom.browserrange.WebKitRange");
+goog.require("goog.dom.RangeEndpoint");
+goog.require("goog.dom.browserrange.W3cRange");
+goog.require("goog.userAgent");
+goog.dom.browserrange.WebKitRange = function(range) {
+ goog.dom.browserrange.W3cRange.call(this, range)
+};
+goog.inherits(goog.dom.browserrange.WebKitRange, goog.dom.browserrange.W3cRange);
+goog.dom.browserrange.WebKitRange.createFromNodeContents = function(node) {
+ return new goog.dom.browserrange.WebKitRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node))
+};
+goog.dom.browserrange.WebKitRange.createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return new goog.dom.browserrange.WebKitRange(goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode, startOffset, endNode, endOffset))
+};
+goog.dom.browserrange.WebKitRange.prototype.compareBrowserRangeEndpoints = function(range, thisEndpoint, otherEndpoint) {
+ if(goog.userAgent.isVersion("528")) {
+ return goog.dom.browserrange.WebKitRange.superClass_.compareBrowserRangeEndpoints.call(this, range, thisEndpoint, otherEndpoint)
+ }
+ return this.range_.compareBoundaryPoints(otherEndpoint == goog.dom.RangeEndpoint.START ? thisEndpoint == goog.dom.RangeEndpoint.START ? goog.global["Range"].START_TO_START : goog.global["Range"].END_TO_START : thisEndpoint == goog.dom.RangeEndpoint.START ? goog.global["Range"].START_TO_END : goog.global["Range"].END_TO_END, range)
+};
+goog.dom.browserrange.WebKitRange.prototype.selectInternal = function(selection, reversed) {
+ selection.removeAllRanges();
+ if(reversed) {
+ selection.setBaseAndExtent(this.getEndNode(), this.getEndOffset(), this.getStartNode(), this.getStartOffset())
+ }else {
+ selection.setBaseAndExtent(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset())
+ }
+};
+goog.provide("goog.dom.browserrange");
+goog.provide("goog.dom.browserrange.Error");
+goog.require("goog.dom");
+goog.require("goog.dom.browserrange.GeckoRange");
+goog.require("goog.dom.browserrange.IeRange");
+goog.require("goog.dom.browserrange.OperaRange");
+goog.require("goog.dom.browserrange.W3cRange");
+goog.require("goog.dom.browserrange.WebKitRange");
+goog.require("goog.userAgent");
+goog.dom.browserrange.Error = {NOT_IMPLEMENTED:"Not Implemented"};
+goog.dom.browserrange.createRange = function(range) {
+ if(goog.userAgent.IE && !goog.userAgent.isVersion("9")) {
+ return new goog.dom.browserrange.IeRange(range, goog.dom.getOwnerDocument(range.parentElement()))
+ }else {
+ if(goog.userAgent.WEBKIT) {
+ return new goog.dom.browserrange.WebKitRange(range)
+ }else {
+ if(goog.userAgent.GECKO) {
+ return new goog.dom.browserrange.GeckoRange(range)
+ }else {
+ if(goog.userAgent.OPERA) {
+ return new goog.dom.browserrange.OperaRange(range)
+ }else {
+ return new goog.dom.browserrange.W3cRange(range)
+ }
+ }
+ }
+ }
+};
+goog.dom.browserrange.createRangeFromNodeContents = function(node) {
+ if(goog.userAgent.IE && !goog.userAgent.isVersion("9")) {
+ return goog.dom.browserrange.IeRange.createFromNodeContents(node)
+ }else {
+ if(goog.userAgent.WEBKIT) {
+ return goog.dom.browserrange.WebKitRange.createFromNodeContents(node)
+ }else {
+ if(goog.userAgent.GECKO) {
+ return goog.dom.browserrange.GeckoRange.createFromNodeContents(node)
+ }else {
+ if(goog.userAgent.OPERA) {
+ return goog.dom.browserrange.OperaRange.createFromNodeContents(node)
+ }else {
+ return goog.dom.browserrange.W3cRange.createFromNodeContents(node)
+ }
+ }
+ }
+ }
+};
+goog.dom.browserrange.createRangeFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ if(goog.userAgent.IE && !goog.userAgent.isVersion("9")) {
+ return goog.dom.browserrange.IeRange.createFromNodes(startNode, startOffset, endNode, endOffset)
+ }else {
+ if(goog.userAgent.WEBKIT) {
+ return goog.dom.browserrange.WebKitRange.createFromNodes(startNode, startOffset, endNode, endOffset)
+ }else {
+ if(goog.userAgent.GECKO) {
+ return goog.dom.browserrange.GeckoRange.createFromNodes(startNode, startOffset, endNode, endOffset)
+ }else {
+ if(goog.userAgent.OPERA) {
+ return goog.dom.browserrange.OperaRange.createFromNodes(startNode, startOffset, endNode, endOffset)
+ }else {
+ return goog.dom.browserrange.W3cRange.createFromNodes(startNode, startOffset, endNode, endOffset)
+ }
+ }
+ }
+ }
+};
+goog.dom.browserrange.canContainRangeEndpoint = function(node) {
+ return goog.dom.canHaveChildren(node) || node.nodeType == goog.dom.NodeType.TEXT
+};
+goog.provide("goog.dom.TextRange");
+goog.require("goog.array");
+goog.require("goog.dom");
+goog.require("goog.dom.AbstractRange");
+goog.require("goog.dom.RangeType");
+goog.require("goog.dom.SavedRange");
+goog.require("goog.dom.TagName");
+goog.require("goog.dom.TextRangeIterator");
+goog.require("goog.dom.browserrange");
+goog.require("goog.string");
+goog.require("goog.userAgent");
+goog.dom.TextRange = function() {
+};
+goog.inherits(goog.dom.TextRange, goog.dom.AbstractRange);
+goog.dom.TextRange.createFromBrowserRange = function(range, opt_isReversed) {
+ return goog.dom.TextRange.createFromBrowserRangeWrapper_(goog.dom.browserrange.createRange(range), opt_isReversed)
+};
+goog.dom.TextRange.createFromBrowserRangeWrapper_ = function(browserRange, opt_isReversed) {
+ var range = new goog.dom.TextRange;
+ range.browserRangeWrapper_ = browserRange;
+ range.isReversed_ = !!opt_isReversed;
+ return range
+};
+goog.dom.TextRange.createFromNodeContents = function(node, opt_isReversed) {
+ return goog.dom.TextRange.createFromBrowserRangeWrapper_(goog.dom.browserrange.createRangeFromNodeContents(node), opt_isReversed)
+};
+goog.dom.TextRange.createFromNodes = function(anchorNode, anchorOffset, focusNode, focusOffset) {
+ var range = new goog.dom.TextRange;
+ range.isReversed_ = goog.dom.Range.isReversed(anchorNode, anchorOffset, focusNode, focusOffset);
+ if(anchorNode.tagName == "BR") {
+ var parent = anchorNode.parentNode;
+ anchorOffset = goog.array.indexOf(parent.childNodes, anchorNode);
+ anchorNode = parent
+ }
+ if(focusNode.tagName == "BR") {
+ var parent = focusNode.parentNode;
+ focusOffset = goog.array.indexOf(parent.childNodes, focusNode);
+ focusNode = parent
+ }
+ if(range.isReversed_) {
+ range.startNode_ = focusNode;
+ range.startOffset_ = focusOffset;
+ range.endNode_ = anchorNode;
+ range.endOffset_ = anchorOffset
+ }else {
+ range.startNode_ = anchorNode;
+ range.startOffset_ = anchorOffset;
+ range.endNode_ = focusNode;
+ range.endOffset_ = focusOffset
+ }
+ return range
+};
+goog.dom.TextRange.prototype.browserRangeWrapper_ = null;
+goog.dom.TextRange.prototype.startNode_ = null;
+goog.dom.TextRange.prototype.startOffset_ = null;
+goog.dom.TextRange.prototype.endNode_ = null;
+goog.dom.TextRange.prototype.endOffset_ = null;
+goog.dom.TextRange.prototype.isReversed_ = false;
+goog.dom.TextRange.prototype.clone = function() {
+ var range = new goog.dom.TextRange;
+ range.browserRangeWrapper_ = this.browserRangeWrapper_;
+ range.startNode_ = this.startNode_;
+ range.startOffset_ = this.startOffset_;
+ range.endNode_ = this.endNode_;
+ range.endOffset_ = this.endOffset_;
+ range.isReversed_ = this.isReversed_;
+ return range
+};
+goog.dom.TextRange.prototype.getType = function() {
+ return goog.dom.RangeType.TEXT
+};
+goog.dom.TextRange.prototype.getBrowserRangeObject = function() {
+ return this.getBrowserRangeWrapper_().getBrowserRange()
+};
+goog.dom.TextRange.prototype.setBrowserRangeObject = function(nativeRange) {
+ if(goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
+ return false
+ }
+ this.browserRangeWrapper_ = goog.dom.browserrange.createRange(nativeRange);
+ this.clearCachedValues_();
+ return true
+};
+goog.dom.TextRange.prototype.clearCachedValues_ = function() {
+ this.startNode_ = this.startOffset_ = this.endNode_ = this.endOffset_ = null
+};
+goog.dom.TextRange.prototype.getTextRangeCount = function() {
+ return 1
+};
+goog.dom.TextRange.prototype.getTextRange = function(i) {
+ return this
+};
+goog.dom.TextRange.prototype.getBrowserRangeWrapper_ = function() {
+ return this.browserRangeWrapper_ || (this.browserRangeWrapper_ = goog.dom.browserrange.createRangeFromNodes(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset()))
+};
+goog.dom.TextRange.prototype.getContainer = function() {
+ return this.getBrowserRangeWrapper_().getContainer()
+};
+goog.dom.TextRange.prototype.getStartNode = function() {
+ return this.startNode_ || (this.startNode_ = this.getBrowserRangeWrapper_().getStartNode())
+};
+goog.dom.TextRange.prototype.getStartOffset = function() {
+ return this.startOffset_ != null ? this.startOffset_ : this.startOffset_ = this.getBrowserRangeWrapper_().getStartOffset()
+};
+goog.dom.TextRange.prototype.getEndNode = function() {
+ return this.endNode_ || (this.endNode_ = this.getBrowserRangeWrapper_().getEndNode())
+};
+goog.dom.TextRange.prototype.getEndOffset = function() {
+ return this.endOffset_ != null ? this.endOffset_ : this.endOffset_ = this.getBrowserRangeWrapper_().getEndOffset()
+};
+goog.dom.TextRange.prototype.moveToNodes = function(startNode, startOffset, endNode, endOffset, isReversed) {
+ this.startNode_ = startNode;
+ this.startOffset_ = startOffset;
+ this.endNode_ = endNode;
+ this.endOffset_ = endOffset;
+ this.isReversed_ = isReversed;
+ this.browserRangeWrapper_ = null
+};
+goog.dom.TextRange.prototype.isReversed = function() {
+ return this.isReversed_
+};
+goog.dom.TextRange.prototype.containsRange = function(otherRange, opt_allowPartial) {
+ var otherRangeType = otherRange.getType();
+ if(otherRangeType == goog.dom.RangeType.TEXT) {
+ return this.getBrowserRangeWrapper_().containsRange(otherRange.getBrowserRangeWrapper_(), opt_allowPartial)
+ }else {
+ if(otherRangeType == goog.dom.RangeType.CONTROL) {
+ var elements = otherRange.getElements();
+ var fn = opt_allowPartial ? goog.array.some : goog.array.every;
+ return fn(elements, function(el) {
+ return this.containsNode(el, opt_allowPartial)
+ }, this)
+ }
+ }
+ return false
+};
+goog.dom.TextRange.isAttachedNode = function(node) {
+ if(goog.userAgent.IE) {
+ var returnValue = false;
+ try {
+ returnValue = node.parentNode
+ }catch(e) {
+ }
+ return!!returnValue
+ }else {
+ return goog.dom.contains(node.ownerDocument.body, node)
+ }
+};
+goog.dom.TextRange.prototype.isRangeInDocument = function() {
+ return(!this.startNode_ || goog.dom.TextRange.isAttachedNode(this.startNode_)) && (!this.endNode_ || goog.dom.TextRange.isAttachedNode(this.endNode_)) && (!goog.userAgent.IE || this.getBrowserRangeWrapper_().isRangeInDocument())
+};
+goog.dom.TextRange.prototype.isCollapsed = function() {
+ return this.getBrowserRangeWrapper_().isCollapsed()
+};
+goog.dom.TextRange.prototype.getText = function() {
+ return this.getBrowserRangeWrapper_().getText()
+};
+goog.dom.TextRange.prototype.getHtmlFragment = function() {
+ return this.getBrowserRangeWrapper_().getHtmlFragment()
+};
+goog.dom.TextRange.prototype.getValidHtml = function() {
+ return this.getBrowserRangeWrapper_().getValidHtml()
+};
+goog.dom.TextRange.prototype.getPastableHtml = function() {
+ var html = this.getValidHtml();
+ if(html.match(/^\s*<td\b/i)) {
+ html = "<table><tbody><tr>" + html + "</tr></tbody></table>"
+ }else {
+ if(html.match(/^\s*<tr\b/i)) {
+ html = "<table><tbody>" + html + "</tbody></table>"
+ }else {
+ if(html.match(/^\s*<tbody\b/i)) {
+ html = "<table>" + html + "</table>"
+ }else {
+ if(html.match(/^\s*<li\b/i)) {
+ var container = this.getContainer();
+ var tagType = goog.dom.TagName.UL;
+ while(container) {
+ if(container.tagName == goog.dom.TagName.OL) {
+ tagType = goog.dom.TagName.OL;
+ break
+ }else {
+ if(container.tagName == goog.dom.TagName.UL) {
+ break
+ }
+ }
+ container = container.parentNode
+ }
+ html = goog.string.buildString("<", tagType, ">", html, "</", tagType, ">")
+ }
+ }
+ }
+ }
+ return html
+};
+goog.dom.TextRange.prototype.__iterator__ = function(opt_keys) {
+ return new goog.dom.TextRangeIterator(this.getStartNode(), this.getStartOffset(), this.getEndNode(), this.getEndOffset())
+};
+goog.dom.TextRange.prototype.select = function() {
+ this.getBrowserRangeWrapper_().select(this.isReversed_)
+};
+goog.dom.TextRange.prototype.removeContents = function() {
+ this.getBrowserRangeWrapper_().removeContents();
+ this.clearCachedValues_()
+};
+goog.dom.TextRange.prototype.surroundContents = function(element) {
+ var output = this.getBrowserRangeWrapper_().surroundContents(element);
+ this.clearCachedValues_();
+ return output
+};
+goog.dom.TextRange.prototype.insertNode = function(node, before) {
+ var output = this.getBrowserRangeWrapper_().insertNode(node, before);
+ this.clearCachedValues_();
+ return output
+};
+goog.dom.TextRange.prototype.surroundWithNodes = function(startNode, endNode) {
+ this.getBrowserRangeWrapper_().surroundWithNodes(startNode, endNode);
+ this.clearCachedValues_()
+};
+goog.dom.TextRange.prototype.saveUsingDom = function() {
+ return new goog.dom.DomSavedTextRange_(this)
+};
+goog.dom.TextRange.prototype.collapse = function(toAnchor) {
+ var toStart = this.isReversed() ? !toAnchor : toAnchor;
+ if(this.browserRangeWrapper_) {
+ this.browserRangeWrapper_.collapse(toStart)
+ }
+ if(toStart) {
+ this.endNode_ = this.startNode_;
+ this.endOffset_ = this.startOffset_
+ }else {
+ this.startNode_ = this.endNode_;
+ this.startOffset_ = this.endOffset_
+ }
+ this.isReversed_ = false
+};
+goog.dom.DomSavedTextRange_ = function(range) {
+ this.anchorNode_ = range.getAnchorNode();
+ this.anchorOffset_ = range.getAnchorOffset();
+ this.focusNode_ = range.getFocusNode();
+ this.focusOffset_ = range.getFocusOffset()
+};
+goog.inherits(goog.dom.DomSavedTextRange_, goog.dom.SavedRange);
+goog.dom.DomSavedTextRange_.prototype.restoreInternal = function() {
+ return goog.dom.Range.createFromNodes(this.anchorNode_, this.anchorOffset_, this.focusNode_, this.focusOffset_)
+};
+goog.dom.DomSavedTextRange_.prototype.disposeInternal = function() {
+ goog.dom.DomSavedTextRange_.superClass_.disposeInternal.call(this);
+ this.anchorNode_ = null;
+ this.focusNode_ = null
+};
+goog.provide("goog.dom.ControlRange");
+goog.provide("goog.dom.ControlRangeIterator");
+goog.require("goog.array");
+goog.require("goog.dom");
+goog.require("goog.dom.AbstractMultiRange");
+goog.require("goog.dom.AbstractRange");
+goog.require("goog.dom.RangeIterator");
+goog.require("goog.dom.RangeType");
+goog.require("goog.dom.SavedRange");
+goog.require("goog.dom.TagWalkType");
+goog.require("goog.dom.TextRange");
+goog.require("goog.iter.StopIteration");
+goog.require("goog.userAgent");
+goog.dom.ControlRange = function() {
+};
+goog.inherits(goog.dom.ControlRange, goog.dom.AbstractMultiRange);
+goog.dom.ControlRange.createFromBrowserRange = function(controlRange) {
+ var range = new goog.dom.ControlRange;
+ range.range_ = controlRange;
+ return range
+};
+goog.dom.ControlRange.createFromElements = function(var_args) {
+ var range = goog.dom.getOwnerDocument(arguments[0]).body.createControlRange();
+ for(var i = 0, len = arguments.length;i < len;i++) {
+ range.addElement(arguments[i])
+ }
+ return goog.dom.ControlRange.createFromBrowserRange(range)
+};
+goog.dom.ControlRange.prototype.range_ = null;
+goog.dom.ControlRange.prototype.elements_ = null;
+goog.dom.ControlRange.prototype.sortedElements_ = null;
+goog.dom.ControlRange.prototype.clearCachedValues_ = function() {
+ this.elements_ = null;
+ this.sortedElements_ = null
+};
+goog.dom.ControlRange.prototype.clone = function() {
+ return goog.dom.ControlRange.createFromElements.apply(this, this.getElements())
+};
+goog.dom.ControlRange.prototype.getType = function() {
+ return goog.dom.RangeType.CONTROL
+};
+goog.dom.ControlRange.prototype.getBrowserRangeObject = function() {
+ return this.range_ || document.body.createControlRange()
+};
+goog.dom.ControlRange.prototype.setBrowserRangeObject = function(nativeRange) {
+ if(!goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
+ return false
+ }
+ this.range_ = nativeRange;
+ return true
+};
+goog.dom.ControlRange.prototype.getTextRangeCount = function() {
+ return this.range_ ? this.range_.length : 0
+};
+goog.dom.ControlRange.prototype.getTextRange = function(i) {
+ return goog.dom.TextRange.createFromNodeContents(this.range_.item(i))
+};
+goog.dom.ControlRange.prototype.getContainer = function() {
+ return goog.dom.findCommonAncestor.apply(null, this.getElements())
+};
+goog.dom.ControlRange.prototype.getStartNode = function() {
+ return this.getSortedElements()[0]
+};
+goog.dom.ControlRange.prototype.getStartOffset = function() {
+ return 0
+};
+goog.dom.ControlRange.prototype.getEndNode = function() {
+ var sorted = this.getSortedElements();
+ var startsLast = goog.array.peek(sorted);
+ return goog.array.find(sorted, function(el) {
+ return goog.dom.contains(el, startsLast)
+ })
+};
+goog.dom.ControlRange.prototype.getEndOffset = function() {
+ return this.getEndNode().childNodes.length
+};
+goog.dom.ControlRange.prototype.getElements = function() {
+ if(!this.elements_) {
+ this.elements_ = [];
+ if(this.range_) {
+ for(var i = 0;i < this.range_.length;i++) {
+ this.elements_.push(this.range_.item(i))
+ }
+ }
+ }
+ return this.elements_
+};
+goog.dom.ControlRange.prototype.getSortedElements = function() {
+ if(!this.sortedElements_) {
+ this.sortedElements_ = this.getElements().concat();
+ this.sortedElements_.sort(function(a, b) {
+ return a.sourceIndex - b.sourceIndex
+ })
+ }
+ return this.sortedElements_
+};
+goog.dom.ControlRange.prototype.isRangeInDocument = function() {
+ var returnValue = false;
+ try {
+ returnValue = goog.array.every(this.getElements(), function(element) {
+ return goog.userAgent.IE ? element.parentNode : goog.dom.contains(element.ownerDocument.body, element)
+ })
+ }catch(e) {
+ }
+ return returnValue
+};
+goog.dom.ControlRange.prototype.isCollapsed = function() {
+ return!this.range_ || !this.range_.length
+};
+goog.dom.ControlRange.prototype.getText = function() {
+ return""
+};
+goog.dom.ControlRange.prototype.getHtmlFragment = function() {
+ return goog.array.map(this.getSortedElements(), goog.dom.getOuterHtml).join("")
+};
+goog.dom.ControlRange.prototype.getValidHtml = function() {
+ return this.getHtmlFragment()
+};
+goog.dom.ControlRange.prototype.getPastableHtml = goog.dom.ControlRange.prototype.getValidHtml;
+goog.dom.ControlRange.prototype.__iterator__ = function(opt_keys) {
+ return new goog.dom.ControlRangeIterator(this)
+};
+goog.dom.ControlRange.prototype.select = function() {
+ if(this.range_) {
+ this.range_.select()
+ }
+};
+goog.dom.ControlRange.prototype.removeContents = function() {
+ if(this.range_) {
+ var nodes = [];
+ for(var i = 0, len = this.range_.length;i < len;i++) {
+ nodes.push(this.range_.item(i))
+ }
+ goog.array.forEach(nodes, goog.dom.removeNode);
+ this.collapse(false)
+ }
+};
+goog.dom.ControlRange.prototype.replaceContentsWithNode = function(node) {
+ var result = this.insertNode(node, true);
+ if(!this.isCollapsed()) {
+ this.removeContents()
+ }
+ return result
+};
+goog.dom.ControlRange.prototype.saveUsingDom = function() {
+ return new goog.dom.DomSavedControlRange_(this)
+};
+goog.dom.ControlRange.prototype.collapse = function(toAnchor) {
+ this.range_ = null;
+ this.clearCachedValues_()
+};
+goog.dom.DomSavedControlRange_ = function(range) {
+ this.elements_ = range.getElements()
+};
+goog.inherits(goog.dom.DomSavedControlRange_, goog.dom.SavedRange);
+goog.dom.DomSavedControlRange_.prototype.restoreInternal = function() {
+ var doc = this.elements_.length ? goog.dom.getOwnerDocument(this.elements_[0]) : document;
+ var controlRange = doc.body.createControlRange();
+ for(var i = 0, len = this.elements_.length;i < len;i++) {
+ controlRange.addElement(this.elements_[i])
+ }
+ return goog.dom.ControlRange.createFromBrowserRange(controlRange)
+};
+goog.dom.DomSavedControlRange_.prototype.disposeInternal = function() {
+ goog.dom.DomSavedControlRange_.superClass_.disposeInternal.call(this);
+ delete this.elements_
+};
+goog.dom.ControlRangeIterator = function(range) {
+ if(range) {
+ this.elements_ = range.getSortedElements();
+ this.startNode_ = this.elements_.shift();
+ this.endNode_ = goog.array.peek(this.elements_) || this.startNode_
+ }
+ goog.dom.RangeIterator.call(this, this.startNode_, false)
+};
+goog.inherits(goog.dom.ControlRangeIterator, goog.dom.RangeIterator);
+goog.dom.ControlRangeIterator.prototype.startNode_ = null;
+goog.dom.ControlRangeIterator.prototype.endNode_ = null;
+goog.dom.ControlRangeIterator.prototype.elements_ = null;
+goog.dom.ControlRangeIterator.prototype.getStartTextOffset = function() {
+ return 0
+};
+goog.dom.ControlRangeIterator.prototype.getEndTextOffset = function() {
+ return 0
+};
+goog.dom.ControlRangeIterator.prototype.getStartNode = function() {
+ return this.startNode_
+};
+goog.dom.ControlRangeIterator.prototype.getEndNode = function() {
+ return this.endNode_
+};
+goog.dom.ControlRangeIterator.prototype.isLast = function() {
+ return!this.depth && !this.elements_.length
+};
+goog.dom.ControlRangeIterator.prototype.next = function() {
+ if(this.isLast()) {
+ throw goog.iter.StopIteration;
+ }else {
+ if(!this.depth) {
+ var el = this.elements_.shift();
+ this.setPosition(el, goog.dom.TagWalkType.START_TAG, goog.dom.TagWalkType.START_TAG);
+ return el
+ }
+ }
+ return goog.dom.ControlRangeIterator.superClass_.next.call(this)
+};
+goog.dom.ControlRangeIterator.prototype.copyFrom = function(other) {
+ this.elements_ = other.elements_;
+ this.startNode_ = other.startNode_;
+ this.endNode_ = other.endNode_;
+ goog.dom.ControlRangeIterator.superClass_.copyFrom.call(this, other)
+};
+goog.dom.ControlRangeIterator.prototype.clone = function() {
+ var copy = new goog.dom.ControlRangeIterator(null);
+ copy.copyFrom(this);
+ return copy
+};
+goog.provide("goog.dom.MultiRange");
+goog.provide("goog.dom.MultiRangeIterator");
+goog.require("goog.array");
+goog.require("goog.debug.Logger");
+goog.require("goog.dom.AbstractMultiRange");
+goog.require("goog.dom.AbstractRange");
+goog.require("goog.dom.RangeIterator");
+goog.require("goog.dom.RangeType");
+goog.require("goog.dom.SavedRange");
+goog.require("goog.dom.TextRange");
+goog.require("goog.iter.StopIteration");
+goog.dom.MultiRange = function() {
+ this.browserRanges_ = [];
+ this.ranges_ = [];
+ this.sortedRanges_ = null;
+ this.container_ = null
+};
+goog.inherits(goog.dom.MultiRange, goog.dom.AbstractMultiRange);
+goog.dom.MultiRange.createFromBrowserSelection = function(selection) {
+ var range = new goog.dom.MultiRange;
+ for(var i = 0, len = selection.rangeCount;i < len;i++) {
+ range.browserRanges_.push(selection.getRangeAt(i))
+ }
+ return range
+};
+goog.dom.MultiRange.createFromBrowserRanges = function(browserRanges) {
+ var range = new goog.dom.MultiRange;
+ range.browserRanges_ = goog.array.clone(browserRanges);
+ return range
+};
+goog.dom.MultiRange.createFromTextRanges = function(textRanges) {
+ var range = new goog.dom.MultiRange;
+ range.ranges_ = textRanges;
+ range.browserRanges_ = goog.array.map(textRanges, function(range) {
+ return range.getBrowserRangeObject()
+ });
+ return range
+};
+goog.dom.MultiRange.prototype.logger_ = goog.debug.Logger.getLogger("goog.dom.MultiRange");
+goog.dom.MultiRange.prototype.clearCachedValues_ = function() {
+ this.ranges_ = [];
+ this.sortedRanges_ = null;
+ this.container_ = null
+};
+goog.dom.MultiRange.prototype.clone = function() {
+ return goog.dom.MultiRange.createFromBrowserRanges(this.browserRanges_)
+};
+goog.dom.MultiRange.prototype.getType = function() {
+ return goog.dom.RangeType.MULTI
+};
+goog.dom.MultiRange.prototype.getBrowserRangeObject = function() {
+ if(this.browserRanges_.length > 1) {
+ this.logger_.warning("getBrowserRangeObject called on MultiRange with more than 1 range")
+ }
+ return this.browserRanges_[0]
+};
+goog.dom.MultiRange.prototype.setBrowserRangeObject = function(nativeRange) {
+ return false
+};
+goog.dom.MultiRange.prototype.getTextRangeCount = function() {
+ return this.browserRanges_.length
+};
+goog.dom.MultiRange.prototype.getTextRange = function(i) {
+ if(!this.ranges_[i]) {
+ this.ranges_[i] = goog.dom.TextRange.createFromBrowserRange(this.browserRanges_[i])
+ }
+ return this.ranges_[i]
+};
+goog.dom.MultiRange.prototype.getContainer = function() {
+ if(!this.container_) {
+ var nodes = [];
+ for(var i = 0, len = this.getTextRangeCount();i < len;i++) {
+ nodes.push(this.getTextRange(i).getContainer())
+ }
+ this.container_ = goog.dom.findCommonAncestor.apply(null, nodes)
+ }
+ return this.container_
+};
+goog.dom.MultiRange.prototype.getSortedRanges = function() {
+ if(!this.sortedRanges_) {
+ this.sortedRanges_ = this.getTextRanges();
+ this.sortedRanges_.sort(function(a, b) {
+ var aStartNode = a.getStartNode();
+ var aStartOffset = a.getStartOffset();
+ var bStartNode = b.getStartNode();
+ var bStartOffset = b.getStartOffset();
+ if(aStartNode == bStartNode && aStartOffset == bStartOffset) {
+ return 0
+ }
+ return goog.dom.Range.isReversed(aStartNode, aStartOffset, bStartNode, bStartOffset) ? 1 : -1
+ })
+ }
+ return this.sortedRanges_
+};
+goog.dom.MultiRange.prototype.getStartNode = function() {
+ return this.getSortedRanges()[0].getStartNode()
+};
+goog.dom.MultiRange.prototype.getStartOffset = function() {
+ return this.getSortedRanges()[0].getStartOffset()
+};
+goog.dom.MultiRange.prototype.getEndNode = function() {
+ return goog.array.peek(this.getSortedRanges()).getEndNode()
+};
+goog.dom.MultiRange.prototype.getEndOffset = function() {
+ return goog.array.peek(this.getSortedRanges()).getEndOffset()
+};
+/*
+goog.dom.MultiRange.prototype.isRangeInDocument = function() {
+ return goog.array.every(this.getTextRanges(), function(range) {
+ return range.isRangeInDocument()
+ })
+};
+*/
+goog.dom.MultiRange.prototype.isCollapsed = function() {
+ return this.browserRanges_.length == 0 || this.browserRanges_.length == 1 && this.getTextRange(0).isCollapsed()
+};
+goog.dom.MultiRange.prototype.getText = function() {
+ return goog.array.map(this.getTextRanges(), function(range) {
+ return range.getText()
+ }).join("")
+};
+goog.dom.MultiRange.prototype.getHtmlFragment = function() {
+ return this.getValidHtml()
+};
+goog.dom.MultiRange.prototype.getValidHtml = function() {
+ return goog.array.map(this.getTextRanges(), function(range) {
+ return range.getValidHtml()
+ }).join("")
+};
+goog.dom.MultiRange.prototype.getPastableHtml = function() {
+ return this.getValidHtml()
+};
+goog.dom.MultiRange.prototype.__iterator__ = function(opt_keys) {
+ return new goog.dom.MultiRangeIterator(this)
+};
+goog.dom.MultiRange.prototype.select = function() {
+ var selection = goog.dom.AbstractRange.getBrowserSelectionForWindow(this.getWindow());
+ selection.removeAllRanges();
+ for(var i = 0, len = this.getTextRangeCount();i < len;i++) {
+ selection.addRange(this.getTextRange(i).getBrowserRangeObject())
+ }
+};
+goog.dom.MultiRange.prototype.removeContents = function() {
+ goog.array.forEach(this.getTextRanges(), function(range) {
+ range.removeContents()
+ })
+};
+goog.dom.MultiRange.prototype.saveUsingDom = function() {
+ return new goog.dom.DomSavedMultiRange_(this)
+};
+goog.dom.MultiRange.prototype.collapse = function(toAnchor) {
+ if(!this.isCollapsed()) {
+ var range = toAnchor ? this.getTextRange(0) : this.getTextRange(this.getTextRangeCount() - 1);
+ this.clearCachedValues_();
+ range.collapse(toAnchor);
+ this.ranges_ = [range];
+ this.sortedRanges_ = [range];
+ this.browserRanges_ = [range.getBrowserRangeObject()]
+ }
+};
+goog.dom.DomSavedMultiRange_ = function(range) {
+ this.savedRanges_ = goog.array.map(range.getTextRanges(), function(range) {
+ return range.saveUsingDom()
+ })
+};
+goog.inherits(goog.dom.DomSavedMultiRange_, goog.dom.SavedRange);
+goog.dom.DomSavedMultiRange_.prototype.restoreInternal = function() {
+ var ranges = goog.array.map(this.savedRanges_, function(savedRange) {
+ return savedRange.restore()
+ });
+ return goog.dom.MultiRange.createFromTextRanges(ranges)
+};
+goog.dom.DomSavedMultiRange_.prototype.disposeInternal = function() {
+ goog.dom.DomSavedMultiRange_.superClass_.disposeInternal.call(this);
+ goog.array.forEach(this.savedRanges_, function(savedRange) {
+ savedRange.dispose()
+ });
+ delete this.savedRanges_
+};
+goog.dom.MultiRangeIterator = function(range) {
+ if(range) {
+ this.iterators_ = goog.array.map(range.getSortedRanges(), function(r) {
+ return goog.iter.toIterator(r)
+ })
+ }
+ goog.dom.RangeIterator.call(this, range ? this.getStartNode() : null, false)
+};
+goog.inherits(goog.dom.MultiRangeIterator, goog.dom.RangeIterator);
+goog.dom.MultiRangeIterator.prototype.iterators_ = null;
+goog.dom.MultiRangeIterator.prototype.currentIdx_ = 0;
+goog.dom.MultiRangeIterator.prototype.getStartTextOffset = function() {
+ return this.iterators_[this.currentIdx_].getStartTextOffset()
+};
+goog.dom.MultiRangeIterator.prototype.getEndTextOffset = function() {
+ return this.iterators_[this.currentIdx_].getEndTextOffset()
+};
+goog.dom.MultiRangeIterator.prototype.getStartNode = function() {
+ return this.iterators_[0].getStartNode()
+};
+goog.dom.MultiRangeIterator.prototype.getEndNode = function() {
+ return goog.array.peek(this.iterators_).getEndNode()
+};
+goog.dom.MultiRangeIterator.prototype.isLast = function() {
+ return this.iterators_[this.currentIdx_].isLast()
+};
+goog.dom.MultiRangeIterator.prototype.next = function() {
+ try {
+ var it = this.iterators_[this.currentIdx_];
+ var next = it.next();
+ this.setPosition(it.node, it.tagType, it.depth);
+ return next
+ }catch(ex) {
+ if(ex !== goog.iter.StopIteration || this.iterators_.length - 1 == this.currentIdx_) {
+ throw ex;
+ }else {
+ this.currentIdx_++;
+ return this.next()
+ }
+ }
+};
+goog.dom.MultiRangeIterator.prototype.copyFrom = function(other) {
+ this.iterators_ = goog.array.clone(other.iterators_);
+ goog.dom.MultiRangeIterator.superClass_.copyFrom.call(this, other)
+};
+goog.dom.MultiRangeIterator.prototype.clone = function() {
+ var copy = new goog.dom.MultiRangeIterator(null);
+ copy.copyFrom(this);
+ return copy
+};
+goog.provide("goog.dom.Range");
+goog.require("goog.dom");
+goog.require("goog.dom.AbstractRange");
+goog.require("goog.dom.ControlRange");
+goog.require("goog.dom.MultiRange");
+goog.require("goog.dom.NodeType");
+goog.require("goog.dom.TextRange");
+goog.require("goog.userAgent");
+goog.dom.Range.createFromWindow = function(opt_win) {
+ var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow(opt_win || window);
+ return sel && goog.dom.Range.createFromBrowserSelection(sel)
+};
+goog.dom.Range.createFromBrowserSelection = function(selection) {
+ var range;
+ var isReversed = false;
+ if(selection.createRange) {
+ try {
+ range = selection.createRange()
+ }catch(e) {
+ return null
+ }
+ }else {
+ if(selection.rangeCount) {
+ if(selection.rangeCount > 1) {
+ return goog.dom.MultiRange.createFromBrowserSelection(selection)
+ }else {
+ range = selection.getRangeAt(0);
+ isReversed = goog.dom.Range.isReversed(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset)
+ }
+ }else {
+ return null
+ }
+ }
+ return goog.dom.Range.createFromBrowserRange(range, isReversed)
+};
+goog.dom.Range.createFromBrowserRange = function(range, opt_isReversed) {
+ return goog.dom.AbstractRange.isNativeControlRange(range) ? goog.dom.ControlRange.createFromBrowserRange(range) : goog.dom.TextRange.createFromBrowserRange(range, opt_isReversed)
+};
+goog.dom.Range.createFromNodeContents = function(node, opt_isReversed) {
+ return goog.dom.TextRange.createFromNodeContents(node, opt_isReversed)
+};
+goog.dom.Range.createCaret = function(node, offset) {
+ return goog.dom.TextRange.createFromNodes(node, offset, node, offset)
+};
+goog.dom.Range.createFromNodes = function(startNode, startOffset, endNode, endOffset) {
+ return goog.dom.TextRange.createFromNodes(startNode, startOffset, endNode, endOffset)
+};
+goog.dom.Range.clearSelection = function(opt_win) {
+ var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow(opt_win || window);
+ if(!sel) {
+ return
+ }
+ if(sel.empty) {
+ try {
+ sel.empty()
+ }catch(e) {
+ }
+ }else {
+ sel.removeAllRanges()
+ }
+};
+goog.dom.Range.hasSelection = function(opt_win) {
+ var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow(opt_win || window);
+ return!!sel && (goog.userAgent.IE ? sel.type != "None" : !!sel.rangeCount)
+};
+goog.dom.Range.isReversed = function(anchorNode, anchorOffset, focusNode, focusOffset) {
+ if(anchorNode == focusNode) {
+ return focusOffset < anchorOffset
+ }
+ var child;
+ if(anchorNode.nodeType == goog.dom.NodeType.ELEMENT && anchorOffset) {
+ child = anchorNode.childNodes[anchorOffset];
+ if(child) {
+ anchorNode = child;
+ anchorOffset = 0
+ }else {
+ if(goog.dom.contains(anchorNode, focusNode)) {
+ return true
+ }
+ }
+ }
+ if(focusNode.nodeType == goog.dom.NodeType.ELEMENT && focusOffset) {
+ child = focusNode.childNodes[focusOffset];
+ if(child) {
+ focusNode = child;
+ focusOffset = 0
+ }else {
+ if(goog.dom.contains(focusNode, anchorNode)) {
+ return false
+ }
+ }
+ }
+ return(goog.dom.compareNodeOrder(anchorNode, focusNode) || anchorOffset - focusOffset) > 0
+};
+window.createFromWindow = goog.dom.Range.createFromWindow;
+window.createFromNodes = goog.dom.Range.createFromNodes;
+window.createCaret = goog.dom.Range.createCaret; \ No newline at end of file
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/run.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/run.js
new file mode 100644
index 000000000..6e2acd937
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/run.js
@@ -0,0 +1,383 @@
+/**
+ * @fileoverview
+ * Main functions used in running the RTE test suite.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+/**
+ * Info function: returns true if the suite (mainly) tests the result HTML/Text.
+ *
+ * @param suite {String} the test suite
+ * @return {boolean} Whether the suite main focus is the output HTML/Text
+ */
+function suiteChecksHTMLOrText(suite) {
+ return suite.id[0] != 'S';
+}
+
+/**
+ * Info function: returns true if the suite checks the result selection.
+ *
+ * @param suite {String} the test suite
+ * @return {boolean} Whether the suite checks the selection
+ */
+function suiteChecksSelection(suite) {
+ return suite.id[0] != 'Q';
+}
+
+/**
+ * Helper function returning the effective value of a test parameter.
+ *
+ * @param suite {Object} the test suite
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} the test
+ * @param param {String} the test parameter to be checked
+ * @return {Any} the effective value of the parameter (can be undefined)
+ */
+function getTestParameter(suite, group, test, param) {
+ var val = test[param];
+ if (val === undefined) {
+ val = group[param];
+ }
+ if (val === undefined) {
+ val = suite[param];
+ }
+ return val;
+}
+
+/**
+ * Helper function returning the effective value of a container/test parameter.
+ *
+ * @param suite {Object} the test suite
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} the test
+ * @param container {Object} the container descriptor object
+ * @param param {String} the test parameter to be checked
+ * @return {Any} the effective value of the parameter (can be undefined)
+ */
+function getContainerParameter(suite, group, test, container, param) {
+ var val = undefined;
+ if (test[container.id]) {
+ val = test[container.id][param];
+ }
+ if (val === undefined) {
+ val = test[param];
+ }
+ if (val === undefined) {
+ val = group[param];
+ }
+ if (val === undefined) {
+ val = suite[param];
+ }
+ return val;
+}
+
+/**
+ * Initializes the global variables before any tests are run.
+ */
+function initVariables() {
+ results = {
+ count: 0,
+ valscore: 0,
+ selscore: 0
+ };
+}
+
+/**
+ * Runs a single test - outputs and sets the result variables.
+ *
+ * @param suite {Object} suite that test originates in as object reference
+ * @param group {Object} group of tests within the suite the test belongs to
+ * @param test {Object} test to be run as object reference
+ * @param container {Object} container descriptor as object reference
+ * @see variables.js for RESULT... values
+ */
+function runSingleTest(suite, group, test, container) {
+ var result = {
+ valscore: 0,
+ selscore: 0,
+ valresult: VALRESULT_NOT_RUN,
+ selresult: SELRESULT_NOT_RUN,
+ output: ''
+ };
+
+ // 1.) Populate the editor element with the initial test setup HTML.
+ try {
+ initContainer(suite, group, test, container);
+ } catch(ex) {
+ result.valresult = VALRESULT_SETUP_EXCEPTION;
+ result.selresult = SELRESULT_NA;
+ result.output = SETUP_EXCEPTION + ex.toString();
+ return result;
+ }
+
+ // 2.) Run the test command, general function or query function.
+ var isHTMLTest = false;
+
+ try {
+ var cmd = undefined;
+
+ if (cmd = getTestParameter(suite, group, test, PARAM_EXECCOMMAND)) {
+ isHTMLTest = true;
+ // Note: "getTestParameter(suite, group, test, PARAM_VALUE) || null"
+ // doesn't work, since value might be the empty string, e.g., for 'insertText'!
+ var value = getTestParameter(suite, group, test, PARAM_VALUE);
+ if (value === undefined) {
+ value = null;
+ }
+ container.doc.execCommand(cmd, false, value);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_FUNCTION)) {
+ isHTMLTest = true;
+ eval(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDSUPPORTED)) {
+ result.output = container.doc.queryCommandSupported(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDENABLED)) {
+ result.output = container.doc.queryCommandEnabled(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDINDETERM)) {
+ result.output = container.doc.queryCommandIndeterm(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDSTATE)) {
+ result.output = container.doc.queryCommandState(cmd);
+ } else if (cmd = getTestParameter(suite, group, test, PARAM_QUERYCOMMANDVALUE)) {
+ result.output = container.doc.queryCommandValue(cmd);
+ if (result.output === false) {
+ // A return value of boolean 'false' for queryCommandValue means 'not supported'.
+ result.valresult = VALRESULT_UNSUPPORTED;
+ result.selresult = SELRESULT_NA;
+ result.output = UNSUPPORTED;
+ return result;
+ }
+ } else {
+ result.valresult = VALRESULT_SETUP_EXCEPTION;
+ result.selresult = SELRESULT_NA;
+ result.output = SETUP_EXCEPTION + SETUP_NOCOMMAND;
+ return result;
+ }
+ } catch (ex) {
+ result.valresult = VALRESULT_EXECUTION_EXCEPTION;
+ result.selresult = SELRESULT_NA;
+ result.output = EXECUTION_EXCEPTION + ex.toString();
+ return result;
+ }
+
+ // 4.) Verify test result
+ try {
+ if (isHTMLTest) {
+ // First, retrieve HTML from container
+ prepareHTMLTestResult(container, result);
+
+ // Compare result to expectations
+ compareHTMLTestResult(suite, group, test, container, result);
+
+ result.valscore = (result.valresult === VALRESULT_EQUAL) ? 1 : 0;
+ result.selscore = (result.selresult === SELRESULT_EQUAL) ? 1 : 0;
+ } else {
+ compareTextTestResult(suite, group, test, result);
+
+ result.selresult = SELRESULT_NA;
+ result.valscore = (result.valresult === VALRESULT_EQUAL) ? 1 : 0;
+ }
+ } catch (ex) {
+ result.valresult = VALRESULT_VERIFICATION_EXCEPTION;
+ result.selresult = SELRESULT_NA;
+ result.output = VERIFICATION_EXCEPTION + ex.toString();
+ return result;
+ }
+
+ return result;
+}
+
+/**
+ * Initializes the results dictionary for a given test suite.
+ * (for all classes -> tests -> containers)
+ *
+ * @param {Object} suite as object reference
+ */
+function initTestSuiteResults(suite) {
+ var suiteID = suite.id;
+
+ // Initialize results entries for this suite
+ results[suiteID] = {
+ count: 0,
+ valscore: 0,
+ selscore: 0,
+ time: 0
+ };
+ var totalTestCount = 0;
+
+ for (var clsIdx = 0; clsIdx < testClassCount; ++clsIdx) {
+ var clsID = testClassIDs[clsIdx];
+ var cls = suite[clsID];
+ if (!cls)
+ continue;
+
+ results[suiteID][clsID] = {
+ count: 0,
+ valscore: 0,
+ selscore: 0
+ };
+ var clsTestCount = 0;
+
+ var groupCount = cls.length;
+ for (var groupIdx = 0; groupIdx < groupCount; ++groupIdx) {
+ var group = cls[groupIdx];
+ var testCount = group.tests.length;
+
+ clsTestCount += testCount;
+ totalTestCount += testCount;
+
+ for (var testIdx = 0; testIdx < testCount; ++testIdx) {
+ var test = group.tests[testIdx];
+
+ results[suiteID][clsID ][test.id] = {
+ valscore: 0,
+ selscore: 0,
+ valresult: VALRESULT_NOT_RUN,
+ selresult: SELRESULT_NOT_RUN
+ };
+ for (var cntIdx = 0; cntIdx < containers.length; ++cntIdx) {
+ var cntID = containers[cntIdx].id;
+
+ results[suiteID][clsID][test.id][cntID] = {
+ valscore: 0,
+ selscore: 0,
+ valresult: VALRESULT_NOT_RUN,
+ selresult: SELRESULT_NOT_RUN,
+ output: ''
+ }
+ }
+ }
+ }
+ results[suiteID][clsID].count = clsTestCount;
+ }
+ results[suiteID].count = totalTestCount;
+}
+
+/**
+ * Runs a single test suite (such as DELETE tests or INSERT tests).
+ *
+ * @param suite {Object} suite as object reference
+ */
+function runTestSuite(suite) {
+ var suiteID = suite.id;
+ var suiteStartTime = new Date().getTime();
+
+ initTestSuiteResults(suite);
+
+ for (var clsIdx = 0; clsIdx < testClassCount; ++clsIdx) {
+ var clsID = testClassIDs[clsIdx];
+ var cls = suite[clsID];
+ if (!cls)
+ continue;
+
+ var groupCount = cls.length;
+
+ for (var groupIdx = 0; groupIdx < groupCount; ++groupIdx) {
+ var group = cls[groupIdx];
+ var testCount = group.tests.length;
+
+ for (var testIdx = 0; testIdx < testCount; ++testIdx) {
+ var test = group.tests[testIdx];
+
+ var valscore = 1;
+ var selscore = 1;
+ var valresult = VALRESULT_EQUAL;
+ var selresult = SELRESULT_EQUAL;
+
+ for (var cntIdx = 0; cntIdx < containers.length; ++cntIdx) {
+ var container = containers[cntIdx];
+ var cntID = container.id;
+
+ var result = runSingleTest(suite, group, test, container);
+
+ results[suiteID][clsID][test.id][cntID] = result;
+
+ valscore = Math.min(valscore, result.valscore);
+ selscore = Math.min(selscore, result.selscore);
+ valresult = Math.min(valresult, result.valresult);
+ selresult = Math.min(selresult, result.selresult);
+
+ resetContainer(container);
+ }
+
+ results[suiteID][clsID][test.id].valscore = valscore;
+ results[suiteID][clsID][test.id].selscore = selscore;
+ results[suiteID][clsID][test.id].valresult = valresult;
+ results[suiteID][clsID][test.id].selresult = selresult;
+
+ results[suiteID][clsID].valscore += valscore;
+ results[suiteID][clsID].selscore += selscore;
+ results[suiteID].valscore += valscore;
+ results[suiteID].selscore += selscore;
+ results.valscore += valscore;
+ results.selscore += selscore;
+ }
+ }
+ }
+
+ results[suiteID].time = new Date().getTime() - suiteStartTime;
+}
+
+/**
+ * Runs a single test suite (such as DELETE tests or INSERT tests)
+ * and updates the output HTML.
+ *
+ * @param {Object} suite as object reference
+ */
+function runAndOutputTestSuite(suite) {
+ runTestSuite(suite);
+ outputTestSuiteResults(suite);
+}
+
+/**
+ * Fills the beacon with the test results.
+ */
+function fillResults() {
+ // Result totals of the individual categories
+ categoryTotals = [
+ 'selection=' + results['S'].selscore,
+ 'apply=' + results['A'].valscore,
+ 'applyCSS=' + results['AC'].valscore,
+ 'change=' + results['C'].valscore,
+ 'changeCSS=' + results['CC'].valscore,
+ 'unapply=' + results['U'].valscore,
+ 'unapplyCSS=' + results['UC'].valscore,
+ 'delete=' + results['D'].valscore,
+ 'forwarddelete=' + results['FD'].valscore,
+ 'insert=' + results['I'].valscore,
+ 'selectionResult=' + (results['A'].selscore +
+ results['AC'].selscore +
+ results['C'].selscore +
+ results['CC'].selscore +
+ results['U'].selscore +
+ results['UC'].selscore +
+ results['D'].selscore +
+ results['FD'].selscore +
+ results['I'].selscore),
+ 'querySupported=' + results['Q'].valscore,
+ 'queryEnabled=' + results['QE'].valscore,
+ 'queryIndeterm=' + results['QI'].valscore,
+ 'queryState=' + results['QS'].valscore,
+ 'queryStateCSS=' + results['QSC'].valscore,
+ 'queryValue=' + results['QV'].valscore,
+ 'queryValueCSS=' + results['QVC'].valscore
+ ];
+
+ // Beacon copies category results
+ beacon = categoryTotals.slice(0);
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/units.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/units.js
new file mode 100644
index 000000000..f2c23fbe5
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/units.js
@@ -0,0 +1,416 @@
+/**
+ * @fileoverview
+ * Common constants and variables used in the RTE test suite.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+// All colors defined in CSS3.
+var colorChart = {
+ 'aliceblue': {red: 0xF0, green: 0xF8, blue: 0xFF},
+ 'antiquewhite': {red: 0xFA, green: 0xEB, blue: 0xD7},
+ 'aqua': {red: 0x00, green: 0xFF, blue: 0xFF},
+ 'aquamarine': {red: 0x7F, green: 0xFF, blue: 0xD4},
+ 'azure': {red: 0xF0, green: 0xFF, blue: 0xFF},
+ 'beige': {red: 0xF5, green: 0xF5, blue: 0xDC},
+ 'bisque': {red: 0xFF, green: 0xE4, blue: 0xC4},
+ 'black': {red: 0x00, green: 0x00, blue: 0x00},
+ 'blanchedalmond': {red: 0xFF, green: 0xEB, blue: 0xCD},
+ 'blue': {red: 0x00, green: 0x00, blue: 0xFF},
+ 'blueviolet': {red: 0x8A, green: 0x2B, blue: 0xE2},
+ 'brown': {red: 0xA5, green: 0x2A, blue: 0x2A},
+ 'burlywood': {red: 0xDE, green: 0xB8, blue: 0x87},
+ 'cadetblue': {red: 0x5F, green: 0x9E, blue: 0xA0},
+ 'chartreuse': {red: 0x7F, green: 0xFF, blue: 0x00},
+ 'chocolate': {red: 0xD2, green: 0x69, blue: 0x1E},
+ 'coral': {red: 0xFF, green: 0x7F, blue: 0x50},
+ 'cornflowerblue': {red: 0x64, green: 0x95, blue: 0xED},
+ 'cornsilk': {red: 0xFF, green: 0xF8, blue: 0xDC},
+ 'crimson': {red: 0xDC, green: 0x14, blue: 0x3C},
+ 'cyan': {red: 0x00, green: 0xFF, blue: 0xFF},
+ 'darkblue': {red: 0x00, green: 0x00, blue: 0x8B},
+ 'darkcyan': {red: 0x00, green: 0x8B, blue: 0x8B},
+ 'darkgoldenrod': {red: 0xB8, green: 0x86, blue: 0x0B},
+ 'darkgray': {red: 0xA9, green: 0xA9, blue: 0xA9},
+ 'darkgreen': {red: 0x00, green: 0x64, blue: 0x00},
+ 'darkgrey': {red: 0xA9, green: 0xA9, blue: 0xA9},
+ 'darkkhaki': {red: 0xBD, green: 0xB7, blue: 0x6B},
+ 'darkmagenta': {red: 0x8B, green: 0x00, blue: 0x8B},
+ 'darkolivegreen': {red: 0x55, green: 0x6B, blue: 0x2F},
+ 'darkorange': {red: 0xFF, green: 0x8C, blue: 0x00},
+ 'darkorchid': {red: 0x99, green: 0x32, blue: 0xCC},
+ 'darkred': {red: 0x8B, green: 0x00, blue: 0x00},
+ 'darksalmon': {red: 0xE9, green: 0x96, blue: 0x7A},
+ 'darkseagreen': {red: 0x8F, green: 0xBC, blue: 0x8F},
+ 'darkslateblue': {red: 0x48, green: 0x3D, blue: 0x8B},
+ 'darkslategray': {red: 0x2F, green: 0x4F, blue: 0x4F},
+ 'darkslategrey': {red: 0x2F, green: 0x4F, blue: 0x4F},
+ 'darkturquoise': {red: 0x00, green: 0xCE, blue: 0xD1},
+ 'darkviolet': {red: 0x94, green: 0x00, blue: 0xD3},
+ 'deeppink': {red: 0xFF, green: 0x14, blue: 0x93},
+ 'deepskyblue': {red: 0x00, green: 0xBF, blue: 0xFF},
+ 'dimgray': {red: 0x69, green: 0x69, blue: 0x69},
+ 'dimgrey': {red: 0x69, green: 0x69, blue: 0x69},
+ 'dodgerblue': {red: 0x1E, green: 0x90, blue: 0xFF},
+ 'firebrick': {red: 0xB2, green: 0x22, blue: 0x22},
+ 'floralwhite': {red: 0xFF, green: 0xFA, blue: 0xF0},
+ 'forestgreen': {red: 0x22, green: 0x8B, blue: 0x22},
+ 'fuchsia': {red: 0xFF, green: 0x00, blue: 0xFF},
+ 'gainsboro': {red: 0xDC, green: 0xDC, blue: 0xDC},
+ 'ghostwhite': {red: 0xF8, green: 0xF8, blue: 0xFF},
+ 'gold': {red: 0xFF, green: 0xD7, blue: 0x00},
+ 'goldenrod': {red: 0xDA, green: 0xA5, blue: 0x20},
+ 'gray': {red: 0x80, green: 0x80, blue: 0x80},
+ 'green': {red: 0x00, green: 0x80, blue: 0x00},
+ 'greenyellow': {red: 0xAD, green: 0xFF, blue: 0x2F},
+ 'grey': {red: 0x80, green: 0x80, blue: 0x80},
+ 'honeydew': {red: 0xF0, green: 0xFF, blue: 0xF0},
+ 'hotpink': {red: 0xFF, green: 0x69, blue: 0xB4},
+ 'indianred': {red: 0xCD, green: 0x5C, blue: 0x5C},
+ 'indigo': {red: 0x4B, green: 0x00, blue: 0x82},
+ 'ivory': {red: 0xFF, green: 0xFF, blue: 0xF0},
+ 'khaki': {red: 0xF0, green: 0xE6, blue: 0x8C},
+ 'lavender': {red: 0xE6, green: 0xE6, blue: 0xFA},
+ 'lavenderblush': {red: 0xFF, green: 0xF0, blue: 0xF5},
+ 'lawngreen': {red: 0x7C, green: 0xFC, blue: 0x00},
+ 'lemonchiffon': {red: 0xFF, green: 0xFA, blue: 0xCD},
+ 'lightblue': {red: 0xAD, green: 0xD8, blue: 0xE6},
+ 'lightcoral': {red: 0xF0, green: 0x80, blue: 0x80},
+ 'lightcyan': {red: 0xE0, green: 0xFF, blue: 0xFF},
+ 'lightgoldenrodyellow': {red: 0xFA, green: 0xFA, blue: 0xD2},
+ 'lightgray': {red: 0xD3, green: 0xD3, blue: 0xD3},
+ 'lightgreen': {red: 0x90, green: 0xEE, blue: 0x90},
+ 'lightgrey': {red: 0xD3, green: 0xD3, blue: 0xD3},
+ 'lightpink': {red: 0xFF, green: 0xB6, blue: 0xC1},
+ 'lightsalmon': {red: 0xFF, green: 0xA0, blue: 0x7A},
+ 'lightseagreen': {red: 0x20, green: 0xB2, blue: 0xAA},
+ 'lightskyblue': {red: 0x87, green: 0xCE, blue: 0xFA},
+ 'lightslategray': {red: 0x77, green: 0x88, blue: 0x99},
+ 'lightslategrey': {red: 0x77, green: 0x88, blue: 0x99},
+ 'lightsteelblue': {red: 0xB0, green: 0xC4, blue: 0xDE},
+ 'lightyellow': {red: 0xFF, green: 0xFF, blue: 0xE0},
+ 'lime': {red: 0x00, green: 0xFF, blue: 0x00},
+ 'limegreen': {red: 0x32, green: 0xCD, blue: 0x32},
+ 'linen': {red: 0xFA, green: 0xF0, blue: 0xE6},
+ 'magenta': {red: 0xFF, green: 0x00, blue: 0xFF},
+ 'maroon': {red: 0x80, green: 0x00, blue: 0x00},
+ 'mediumaquamarine': {red: 0x66, green: 0xCD, blue: 0xAA},
+ 'mediumblue': {red: 0x00, green: 0x00, blue: 0xCD},
+ 'mediumorchid': {red: 0xBA, green: 0x55, blue: 0xD3},
+ 'mediumpurple': {red: 0x93, green: 0x70, blue: 0xDB},
+ 'mediumseagreen': {red: 0x3C, green: 0xB3, blue: 0x71},
+ 'mediumslateblue': {red: 0x7B, green: 0x68, blue: 0xEE},
+ 'mediumspringgreen': {red: 0x00, green: 0xFA, blue: 0x9A},
+ 'mediumturquoise': {red: 0x48, green: 0xD1, blue: 0xCC},
+ 'mediumvioletred': {red: 0xC7, green: 0x15, blue: 0x85},
+ 'midnightblue': {red: 0x19, green: 0x19, blue: 0x70},
+ 'mintcream': {red: 0xF5, green: 0xFF, blue: 0xFA},
+ 'mistyrose': {red: 0xFF, green: 0xE4, blue: 0xE1},
+ 'moccasin': {red: 0xFF, green: 0xE4, blue: 0xB5},
+ 'navajowhite': {red: 0xFF, green: 0xDE, blue: 0xAD},
+ 'navy': {red: 0x00, green: 0x00, blue: 0x80},
+ 'oldlace': {red: 0xFD, green: 0xF5, blue: 0xE6},
+ 'olive': {red: 0x80, green: 0x80, blue: 0x00},
+ 'olivedrab': {red: 0x6B, green: 0x8E, blue: 0x23},
+ 'orange': {red: 0xFF, green: 0xA5, blue: 0x00},
+ 'orangered': {red: 0xFF, green: 0x45, blue: 0x00},
+ 'orchid': {red: 0xDA, green: 0x70, blue: 0xD6},
+ 'palegoldenrod': {red: 0xEE, green: 0xE8, blue: 0xAA},
+ 'palegreen': {red: 0x98, green: 0xFB, blue: 0x98},
+ 'paleturquoise': {red: 0xAF, green: 0xEE, blue: 0xEE},
+ 'palevioletred': {red: 0xDB, green: 0x70, blue: 0x93},
+ 'papayawhip': {red: 0xFF, green: 0xEF, blue: 0xD5},
+ 'peachpuff': {red: 0xFF, green: 0xDA, blue: 0xB9},
+ 'peru': {red: 0xCD, green: 0x85, blue: 0x3F},
+ 'pink': {red: 0xFF, green: 0xC0, blue: 0xCB},
+ 'plum': {red: 0xDD, green: 0xA0, blue: 0xDD},
+ 'powderblue': {red: 0xB0, green: 0xE0, blue: 0xE6},
+ 'purple': {red: 0x80, green: 0x00, blue: 0x80},
+ 'red': {red: 0xFF, green: 0x00, blue: 0x00},
+ 'rosybrown': {red: 0xBC, green: 0x8F, blue: 0x8F},
+ 'royalblue': {red: 0x41, green: 0x69, blue: 0xE1},
+ 'saddlebrown': {red: 0x8B, green: 0x45, blue: 0x13},
+ 'salmon': {red: 0xFA, green: 0x80, blue: 0x72},
+ 'sandybrown': {red: 0xF4, green: 0xA4, blue: 0x60},
+ 'seagreen': {red: 0x2E, green: 0x8B, blue: 0x57},
+ 'seashell': {red: 0xFF, green: 0xF5, blue: 0xEE},
+ 'sienna': {red: 0xA0, green: 0x52, blue: 0x2D},
+ 'silver': {red: 0xC0, green: 0xC0, blue: 0xC0},
+ 'skyblue': {red: 0x87, green: 0xCE, blue: 0xEB},
+ 'slateblue': {red: 0x6A, green: 0x5A, blue: 0xCD},
+ 'slategray': {red: 0x70, green: 0x80, blue: 0x90},
+ 'slategrey': {red: 0x70, green: 0x80, blue: 0x90},
+ 'snow': {red: 0xFF, green: 0xFA, blue: 0xFA},
+ 'springgreen': {red: 0x00, green: 0xFF, blue: 0x7F},
+ 'steelblue': {red: 0x46, green: 0x82, blue: 0xB4},
+ 'tan': {red: 0xD2, green: 0xB4, blue: 0x8C},
+ 'teal': {red: 0x00, green: 0x80, blue: 0x80},
+ 'thistle': {red: 0xD8, green: 0xBF, blue: 0xD8},
+ 'tomato': {red: 0xFF, green: 0x63, blue: 0x47},
+ 'turquoise': {red: 0x40, green: 0xE0, blue: 0xD0},
+ 'violet': {red: 0xEE, green: 0x82, blue: 0xEE},
+ 'wheat': {red: 0xF5, green: 0xDE, blue: 0xB3},
+ 'white': {red: 0xFF, green: 0xFF, blue: 0xFF},
+ 'whitesmoke': {red: 0xF5, green: 0xF5, blue: 0xF5},
+ 'yellow': {red: 0xFF, green: 0xFF, blue: 0x00},
+ 'yellowgreen': {red: 0x9A, green: 0xCD, blue: 0x32},
+
+ 'transparent': {red: 0x00, green: 0x00, blue: 0x00, alpha: 0.0}
+};
+
+/**
+ * Color class allows cross-browser comparison of values, which can
+ * be returned from queryCommandValue in several formats:
+ * #ff00ff
+ * #f0f
+ * rgb(255, 0, 0)
+ * rgb(100%, 0%, 28%) // disabled for the time being (see below)
+ * rgba(127, 0, 64, 0.25)
+ * rgba(50%, 0%, 10%, 0.65) // disabled for the time being (see below)
+ * palegoldenrod
+ * transparent
+ *
+ * @constructor
+ * @param value {String} original value
+ */
+function Color(value) {
+ this.compare = function(other) {
+ if (!this.valid || !other.valid) {
+ return false;
+ }
+ if (this.alpha != other.alpha) {
+ return false;
+ }
+ if (this.alpha == 0.0) {
+ // both are fully transparent -> ignore the specific color information
+ return true;
+ }
+ // TODO(rolandsteiner): handle hsl/hsla values
+ return this.red == other.red && this.green == other.green && this.blue == other.blue;
+ }
+ this.parse = function(value) {
+ if (!value)
+ return false;
+ value = String(value).toLowerCase();
+ var match;
+ // '#' + 6 hex digits, e.g., #ff3300
+ match = value.match(/#([0-9a-f]{6})/i);
+ if (match) {
+ this.red = parseInt(match[1].substring(0, 2), 16);
+ this.green = parseInt(match[1].substring(2, 4), 16);
+ this.blue = parseInt(match[1].substring(4, 6), 16);
+ this.alpha = 1.0;
+ return true;
+ }
+ // '#' + 3 hex digits, e.g., #f30
+ match = value.match(/#([0-9a-f]{3})/i);
+ if (match) {
+ this.red = parseInt(match[1].substring(0, 1), 16) * 16;
+ this.green = parseInt(match[1].substring(1, 2), 16) * 16;
+ this.blue = parseInt(match[1].substring(2, 3), 16) * 16;
+ this.alpha = 1.0;
+ return true;
+ }
+ // a color name, e.g., springgreen
+ match = colorChart[value];
+ if (match) {
+ this.red = match.red;
+ this.green = match.green;
+ this.blue = match.blue;
+ this.alpha = (match.alpha === undefined) ? 1.0 : match.alpha;
+ return true;
+ }
+ // rgb(r, g, b), e.g., rgb(128, 12, 217)
+ match = value.match(/rgb\(([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/i);
+ if (match) {
+ this.red = Number(match[1]);
+ this.green = Number(match[2]);
+ this.blue = Number(match[3]);
+ this.alpha = 1.0;
+ return true;
+ }
+ // rgb(r%, g%, b%), e.g., rgb(100%, 0%, 50%)
+// Commented out for the time being, since it seems likely that the resulting
+// decimal values will create false negatives when compared with non-% values.
+//
+// => store as separate percent values and do exact matching when compared with % values
+// and fuzzy matching when compared with non-% values?
+//
+// match = value.match(/rgb\(([0-9]{0,3}(?:\.[0-9]+)?)%\s*,\s*([0-9]{0,3}(?:\.[0-9]+)?)%\s*,\s*([0-9]{0,3}(?:\.[0-9]+)?)%\s*\)/i);
+// if (match) {
+// this.red = Number(match[1]) * 255 / 100;
+// this.green = Number(match[2]) * 255 / 100;
+// this.blue = Number(match[3]) * 255 / 100;
+// this.alpha = 1.0;
+// return true;
+// }
+ // rgba(r, g, b, a), e.g., rgb(128, 12, 217, 0.2)
+ match = value.match(/rgba\(([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/i);
+ if (match) {
+ this.red = Number(match[1]);
+ this.green = Number(match[2]);
+ this.blue = Number(match[3]);
+ this.alpha = Number(match[4]);
+ return true;
+ }
+ // rgba(r%, g%, b%, a), e.g., rgb(100%, 0%, 50%, 0.3)
+// Commented out for the time being (cf. rgb() matching above)
+// match = value.match(/rgba\(([0-9]{0,3}(?:\.[0-9]+)?)%\s*,\s*([0-9]{0,3}(?:\.[0-9]+)?)%\s*,\s*([0-9]{0,3}(?:\.[0-9]+)?)%,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/i);
+// if (match) {
+// this.red = Number(match[1]) * 255 / 100;
+// this.green = Number(match[2]) * 255 / 100;
+// this.blue = Number(match[3]) * 255 / 100;
+// this.alpha = Number(match[4]);
+// return true;
+// }
+ // TODO(rolandsteiner): handle "hsl(h, s, l)" and "hsla(h, s, l, a)" notation
+ return false;
+ }
+ this.toString = function() {
+ return this.valid ? this.red + ',' + this.green + ',' + this.blue : '(invalid)';
+ }
+ this.toHexString = function() {
+ if (!this.valid)
+ return '(invalid)';
+ return ((this.red < 16) ? '0' : '') + this.red.toString(16) +
+ ((this.green < 16) ? '0' : '') + this.green.toString(16) +
+ ((this.blue < 16) ? '0' : '') + this.blue.toString(16);
+ }
+ this.valid = this.parse(value);
+}
+
+/**
+ * Utility class for converting font sizes to the size
+ * attribute in a font tag. Currently only converts px because
+ * only the sizes and px ever come from queryCommandValue.
+ *
+ * @constructor
+ * @param value {String} original value
+ */
+function FontSize(value) {
+ this.parse = function(str) {
+ if (!str)
+ this.valid = false;
+ var match;
+ if (match = String(str).match(/([0-9]+)px/)) {
+ var px = Number(match[1]);
+ if (px <= 0 || px > 47)
+ return false;
+ if (px <= 10) {
+ this.size = '1';
+ } else if (px <= 13) {
+ this.size = '2';
+ } else if (px <= 16) {
+ this.size = '3';
+ } else if (px <= 18) {
+ this.size = '4';
+ } else if (px <= 24) {
+ this.size = '5';
+ } else if (px <= 32) {
+ this.size = '6';
+ } else {
+ this.size = '7';
+ }
+ return true;
+ }
+ if (match = String(str).match(/([+-][0-9]+)/)) {
+ this.size = match[1];
+ return this.size >= 1 && this.size <= 7;
+ }
+ if (Number(str)) {
+ this.size = String(Number(str));
+ return this.size >= 1 && this.size <= 7;
+ }
+ switch (str) {
+ case 'x-small':
+ this.size = '1';
+ return true;
+ case 'small':
+ this.size = '2';
+ return true;
+ case 'medium':
+ this.size = '3';
+ return true;
+ case 'large':
+ this.size = '4';
+ return true;
+ case 'x-large':
+ this.size = '5';
+ return true;
+ case 'xx-large':
+ this.size = '6';
+ return true;
+ case 'xxx-large':
+ this.size = '7';
+ return true;
+ case '-webkit-xxx-large':
+ this.size = '7';
+ return true;
+ case 'larger':
+ this.size = '+1';
+ return true;
+ case 'smaller':
+ this.size = '-1';
+ return true;
+ }
+ return false;
+ }
+ this.compare = function(other) {
+ return this.valid && other.valid && this.size === other.size;
+ }
+ this.toString = function() {
+ return this.valid ? this.size : '(invalid)';
+ }
+ this.valid = this.parse(value);
+}
+
+/**
+ * Utility class for converting & canonicalizing font names.
+ *
+ * @constructor
+ * @param value {String} original value
+ */
+function FontName(value) {
+ this.parse = function(str) {
+ if (!str)
+ return false;
+ str = String(str).toLowerCase();
+ switch (str) {
+ case 'arial new':
+ this.fontname = 'arial';
+ return true;
+ case 'courier new':
+ this.fontname = 'courier';
+ return true;
+ case 'times new':
+ case 'times roman':
+ case 'times new roman':
+ this.fontname = 'times';
+ return true;
+ }
+ this.fontname = value;
+ return true;
+ }
+ this.compare = function(other) {
+ return this.valid && other.valid && this.fontname === other.fontname;
+ }
+ this.toString = function() {
+ return this.valid ? this.fontname : '(invalid)';
+ }
+ this.valid = this.parse(value);
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/variables.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/variables.js
new file mode 100644
index 000000000..cdc6f1e92
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/variables.js
@@ -0,0 +1,227 @@
+/**
+ * @fileoverview
+ * Common constants and variables used in the RTE test suite.
+ *
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @version 0.1
+ * @author rolandsteiner@google.com
+ */
+
+// Constant for indicating a test setup is unsupported or incorrect
+// (threw exception).
+var INTERNAL_ERR = 'INTERNAL ERROR: ';
+var SETUP_EXCEPTION = 'SETUP EXCEPTION: ';
+var EXECUTION_EXCEPTION = 'EXECUTION EXCEPTION: ';
+var VERIFICATION_EXCEPTION = 'VERIFICATION EXCEPTION: ';
+
+var SETUP_CONTAINER = 'WHEN INITIALIZING TEST CONTAINER';
+var SETUP_BAD_SELECTION_SPEC = 'BAD SELECTION SPECIFICATION IN TEST OR EXPECTATION STRING';
+var SETUP_HTML = 'WHEN SETTING TEST HTML';
+var SETUP_SELECTION = 'WHEN SETTING SELECTION';
+var SETUP_NOCOMMAND = 'NO COMMAND, GENERAL FUNCTION OR QUERY FUNCTION GIVEN';
+var HTML_COMPARISON = 'WHEN COMPARING OUTPUT HTML';
+
+// Exceptiona to be thrown on unsupported selection operations
+var SELMODIFY_UNSUPPORTED = 'UNSUPPORTED selection.modify()';
+var SELALLCHILDREN_UNSUPPORTED = 'UNSUPPORTED selection.selectAllChildren()';
+
+// Output string for unsupported functions
+// (returning bool 'false' as opposed to throwing an exception)
+var UNSUPPORTED = '<i>false</i> (UNSUPPORTED)';
+
+// HTML comparison result contants.
+var VALRESULT_NOT_RUN = 0; // test hasn't been run yet
+var VALRESULT_SETUP_EXCEPTION = 1;
+var VALRESULT_EXECUTION_EXCEPTION = 2;
+var VALRESULT_VERIFICATION_EXCEPTION = 3;
+var VALRESULT_UNSUPPORTED = 4;
+var VALRESULT_CANARY = 5; // HTML changes bled into the canary.
+var VALRESULT_DIFF = 6;
+var VALRESULT_ACCEPT = 7; // HTML technically correct, but not ideal.
+var VALRESULT_EQUAL = 8;
+
+var VALOUTPUT = [ // IMPORTANT: this array MUST be coordinated with the values above!!
+ {css: 'grey', output: '???', title: 'The test has not been run yet.'}, // VALRESULT_NOT_RUN
+ {css: 'exception', output: 'EXC.', title: 'Exception was thrown during setup.'}, // VALRESULT_SETUP_EXCEPTION
+ {css: 'exception', output: 'EXC.', title: 'Exception was thrown during execution.'}, // VALRESULT_EXECUTION_EXCEPTION
+ {css: 'exception', output: 'EXC.', title: 'Exception was thrown during result verification.'}, // VALRESULT_VERIFICATION_EXCEPTION
+ {css: 'unsupported', output: 'UNS.', title: 'Unsupported command or value'}, // VALRESULT_UNSUPPORTED
+ {css: 'canary', output: 'CANARY', title: 'The command affected the contentEditable root element, or outside HTML.'}, // VALRESULT_CANARY
+ {css: 'fail', output: 'FAIL', title: 'The result differs from the expectation(s).'}, // VALRESULT_DIFF
+ {css: 'accept', output: 'ACC.', title: 'The result is technically correct, but sub-optimal.'}, // VALRESULT_ACCEPT
+ {css: 'pass', output: 'PASS', title: 'The test result matches the expectation.'} // VALRESULT_EQUAL
+]
+
+// Selection comparison result contants.
+var SELRESULT_NOT_RUN = 0; // test hasn't been run yet
+var SELRESULT_CANARY = 1; // selection escapes the contentEditable element
+var SELRESULT_DIFF = 2;
+var SELRESULT_NA = 3;
+var SELRESULT_ACCEPT = 4; // Selection is acceptable, but not ideal.
+var SELRESULT_EQUAL = 5;
+
+var SELOUTPUT = [ // IMPORTANT: this array MUST be coordinated with the values above!!
+ {css: 'grey', output: 'grey', title: 'The test has not been run yet.'}, // SELRESULT_NOT_RUN
+ {css: 'canary', output: 'CANARY', title: 'The selection escaped the contentEditable boundary!'}, // SELRESULT_CANARY
+ {css: 'fail', output: 'FAIL', title: 'The selection differs from the expectation(s).'}, // SELRESULT_DIFF
+ {css: 'na', output: 'N/A', title: 'The correctness of the selection could not be verified.'}, // SELRESULT_NA
+ {css: 'accept', output: 'ACC.', title: 'The selection is technically correct, but sub-optimal.'}, // SELRESULT_ACCEPT
+ {css: 'pass', output: 'PASS', title: 'The selection matches the expectation.'} // SELRESULT_EQUAL
+];
+
+// RegExp for selection markers
+var SELECTION_MARKERS = /[\[\]\{\}\|\^]/;
+
+// Special attributes used to mark selections within elements that otherwise
+// have no children. Important: attribute name MUST be lower case!
+var ATTRNAME_SEL_START = 'bsselstart';
+var ATTRNAME_SEL_END = 'bsselend';
+
+// DOM node type constants.
+var DOM_NODE_TYPE_ELEMENT = 1;
+var DOM_NODE_TYPE_TEXT = 3;
+var DOM_NODE_TYPE_COMMENT = 8;
+
+// Test parameter names
+var PARAM_DESCRIPTION = 'desc';
+var PARAM_PAD = 'pad';
+var PARAM_EXECCOMMAND = 'command';
+var PARAM_FUNCTION = 'function';
+var PARAM_QUERYCOMMANDSUPPORTED = 'qcsupported';
+var PARAM_QUERYCOMMANDENABLED = 'qcenabled';
+var PARAM_QUERYCOMMANDINDETERM = 'qcindeterm';
+var PARAM_QUERYCOMMANDSTATE = 'qcstate';
+var PARAM_QUERYCOMMANDVALUE = 'qcvalue';
+var PARAM_VALUE = 'value';
+var PARAM_EXPECTED = 'expected';
+var PARAM_EXPECTED_OUTER = 'expOuter';
+var PARAM_ACCEPT = 'accept';
+var PARAM_ACCEPT_OUTER = 'accOuter';
+var PARAM_CHECK_ATTRIBUTES = 'checkAttrs';
+var PARAM_CHECK_STYLE = 'checkStyle';
+var PARAM_CHECK_CLASS = 'checkClass';
+var PARAM_CHECK_ID = 'checkID';
+var PARAM_STYLE_WITH_CSS = 'styleWithCSS';
+
+// ID suffixes for the output columns
+var IDOUT_TR = '_:TR:'; // per container
+var IDOUT_TESTID = '_:tid'; // per test
+var IDOUT_COMMAND = '_:cmd'; // per test
+var IDOUT_VALUE = '_:val'; // per test
+var IDOUT_CHECKATTRS = '_:att'; // per test
+var IDOUT_CHECKSTYLE = '_:sty'; // per test
+var IDOUT_CONTAINER = '_:cnt:'; // per container
+var IDOUT_STATUSVAL = '_:sta:'; // per container
+var IDOUT_STATUSSEL = '_:sel:'; // per container
+var IDOUT_PAD = '_:pad'; // per test
+var IDOUT_EXPECTED = '_:exp'; // per test
+var IDOUT_ACTUAL = '_:act:'; // per container
+
+// Output strings to use for yes/no/NA
+var OUTSTR_YES = '&#x25CF;';
+var OUTSTR_NO = '&#x25CB;';
+var OUTSTR_NA = '-';
+
+// Tags at the start of HTML strings where they were taken from
+var HTMLTAG_BODY = 'B:';
+var HTMLTAG_OUTER = 'O:';
+var HTMLTAG_INNER = 'I:';
+
+// What to use for the canary
+var CANARY = 'CAN<br>ARY';
+
+// Containers for tests, and their associated DOM elements:
+// iframe, win, doc, body, elem
+var containers = [
+ { id: 'dM',
+ iframe: null,
+ win: null,
+ doc: null,
+ body: null,
+ editor: null,
+ tagOpen: '<body>',
+ tagClose: '</body>',
+ editorID: null,
+ canary: '',
+ },
+ { id: 'body',
+ iframe: null,
+ win: null,
+ doc: null,
+ body: null,
+ editor: null,
+ tagOpen: '<body contenteditable="true">',
+ tagClose: '</body>',
+ editorID: null,
+ canary: ''
+ },
+ { id: 'div',
+ iframe: null,
+ win: null,
+ doc: null,
+ body: null,
+ editor: null,
+ tagOpen: '<div contenteditable="true" id="editor-div">',
+ tagClose: '</div>',
+ editorID: 'editor-div',
+ canary: CANARY
+ }
+];
+
+// Helper variables to use in test functions
+var win = null; // window object to use for test functions
+var doc = null; // document object to use for test functions
+var body = null; // The <body> element of the current document
+var editor = null; // The contentEditable element (i.e., the <body> or <div>)
+var sel = null; // The current selection after the pad is set up
+
+// Canonicalization emit flags for various purposes
+var emitFlagsForCanary = {
+ emitAttrs: true,
+ emitStyle: true,
+ emitClass: true,
+ emitID: true,
+ lowercase: true,
+ canonicalizeUnits: true
+};
+var emitFlagsForOutput = {
+ emitAttrs: true,
+ emitStyle: true,
+ emitClass: true,
+ emitID: true,
+ lowercase: false,
+ canonicalizeUnits: false
+};
+
+// Shades of output colors
+var colorShades = ['Lo', 'Hi'];
+
+// Classes of tests
+var testClassIDs = ['Finalized', 'RFC', 'Proposed'];
+var testClassCount = testClassIDs.length;
+
+// Dictionary storing the detailed test results.
+var results = {
+ count: 0,
+ score: 0
+};
+
+// Results - populated by the fillResults() function.
+var beacon = [];
+
+// "compatibility" between Python and JS for test quines
+var True = true;
+var False = false;
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/output.html b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/output.html
new file mode 100644
index 000000000..62d917d69
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/output.html
@@ -0,0 +1,138 @@
+<!-- Legend -->
+<TABLE CLASS="legend framed">
+ <THEAD>
+ <TR><TH COLSPAN=3 CLASS="legendHdr">Result Description</TH></TR>
+ <TR><TH>Status</TH><TH ALIGN="LEFT">Meaning</TH><TH ALIGN="LEFT">Explanation</TH><TH>Scoring</TH></TR>
+ </THEAD>
+ <TBODY>
+ <TR CLASS="lo"><TD CLASS="pass" ALIGN="CENTER">&nbsp;PASS&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Passed</TD><TD CLASS="legend" ROWSPAN=2>The result matches the expectation.</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="pass">PASS (+1)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="pass" ALIGN="CENTER">&nbsp;PASS&nbsp;</TD></TR>
+ <TR CLASS="lo"><TD CLASS="accept" ALIGN="CENTER">&nbsp;ACC.&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Acceptable</TD><TD CLASS="legend" ROWSPAN=2>The result is technically correct, but not ideal (too verbose, deprecated usage, etc.) - for informative purposes only.</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="fail">FAIL (+0)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="accept" ALIGN="CENTER">&nbsp;ACC.&nbsp;</TD></TR>
+ <TR CLASS="lo"><TD CLASS="fail" ALIGN="CENTER">&nbsp;FAIL&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Failure</TD><TD CLASS="legend" ROWSPAN=2>The result does not match any given expectation.</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="fail">FAIL (+0)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="fail" ALIGN="CENTER">&nbsp;FAIL&nbsp;</TD></TR>
+ <TR CLASS="lo"><TD CLASS="canary" ALIGN="CENTER">&nbsp;CANARY&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Canary</TD><TD CLASS="legend" ROWSPAN=2>The result changes HTML other than children of the contentEditable element.</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="fail">FAIL (+0)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="canary" ALIGN="CENTER">&nbsp;CANARY&nbsp;</TD></TR>
+ <TR CLASS="lo"><TD CLASS="unsupported" ALIGN="CENTER">&nbsp;UNS.&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Unsupported</TD><TD CLASS="legend" ROWSPAN=2>The specific function or value is unsupported (returned boolean 'false').</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="fail">FAIL (+0)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="unsupported" ALIGN="CENTER">&nbsp;UNS.&nbsp;</TD></TR>
+ <TR CLASS="lo"><TD CLASS="exception" ALIGN="CENTER">&nbsp;EXC.&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Exception</TD><TD CLASS="legend" ROWSPAN=2>An unexpected exception was thrown during the execution of the test.</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="fail">FAIL (+0)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="exception" ALIGN="CENTER">&nbsp;EXC.&nbsp;</TD></TR>
+ <TR CLASS="lo"><TD CLASS="na" ALIGN="CENTER">&nbsp;N/A&nbsp;</TD><TD CLASS="legend" ROWSPAN=2>Not Applicable</TD><TD CLASS="legend" ROWSPAN=2>The selection could not be tested, because the tested function failed to return a known result.</TD><TD ROWSPAN=2 ALIGN="CENTER" CLASS="fail">FAIL (+0)</TD></TR>
+ <TR CLASS="hi"><TD CLASS="na" ALIGN="CENTER">&nbsp;N/A&nbsp;</TD></TR>
+ </TBODY>
+</TABLE>
+<TABLE CLASS="legend framed">
+ <THEAD>
+ <TR><TH COLSPAN=2 CLASS="legendHdr">Selection and Result Display</TH></TR>
+ <TR><TH>Character</TH><TH ALIGN="LEFT">Explanation</TH></TR>
+ </THEAD>
+ <TBODY>
+ <TR><TD CLASS="sel" ALIGN="CENTER">[</TD><TD>Start of selection - selection point is within a text node.</TD></TR>
+ <TR><TD CLASS="sel" ALIGN="CENTER">]</TD><TD>End of selection - selection point is within a text node.</TD></TR>
+ <TR><TD CLASS="sel" ALIGN="CENTER">^</TD><TD>Collapsed selection - selection point is within a text node.</TD></TR>
+ <TR><TD COLSPAN=2>&nbsp;</TD></TR>
+ <TR><TD CLASS="sel" ALIGN="CENTER">{</TD><TD>Start of selection - selection point is within an element node.</TD></TR>
+ <TR><TD CLASS="sel" ALIGN="CENTER">}</TD><TD>End of selection - selection point is within an element node.</TD></TR>
+ <TR><TD CLASS="sel" ALIGN="CENTER">|</TD><TD>Collapsed selection - selection point is within an element node.</TD></TR>
+ <TR><TD COLSPAN=2>&nbsp;</TD></TR>
+ <TR><TD ALIGN="CENTER"><SPAN CLASS="fade">foo</SPAN></TD><TD>Greyed text indicates parts of the output that are ignored for the purposes of checking the result.</TD></TR>
+ <TR><TD ALIGN="CENTER"><SPAN CLASS="txt">foo</SPAN></TD><TD>Grey border indicates extent of text nodes in the result.</TD></TR>
+ </TBODY>
+</TABLE>
+<!-- progress meter -->
+<HR ID="divider">
+<H1>Running Test Suites: {% for s in suites %}<A HREF="#{{ s.id }}" ID="{{ s.id }}-progress" STYLE="color: #eeeeee">{{ s.id }}</A> {% endfor %}<SPAN ID="done">&nbsp;</SPAN></H1>
+<HR>
+<!-- main output -->
+{% for s in suites %}
+ <H1 ID="{{ s.id }}"><A NAME="{{ s.id }}" HREF="#{{ s.id }}">{{ s.id }}</A> - {{ s.caption }}:
+ <SPAN ID="{{ s.id }}-{% ifequal s.id.0 'S' %}sel{% endifequal %}score">?/?</SPAN>
+ {% ifnotequal s.id.0 "Q" %}{% ifnotequal s.id.0 "S" %}
+ (Selection: <SPAN ID="{{ s.id }}-selscore">?/?</SPAN>)
+ {% endifnotequal %}{% endifnotequal %}
+ (time: <SPAN ID="{{ s.id }}-time">?</SPAN>&nbsp;ms)
+ </H1>
+ {% if s.comment %}
+ <DIV CLASS="comment">{{ s.comment|safe }}</DIV>
+ {% endif %}
+ {% for cls in classes %}{% for pk, pv in s.items %}{% ifequal pk cls %}
+ <H2 ID="{{ s.id }}-{{ cls }}"><A NAME="{{ s.id }}-{{ cls }}" HREF="#{{ s.id }}-{{ cls }}">{{ cls }} Tests</A>:
+ <SPAN ID="{{ s.id }}-{{ cls }}-{% ifequal s.id.0 'S' %}sel{% endifequal %}score">?/?</SPAN>
+ {% ifnotequal s.id.0 "Q" %}{% ifnotequal s.id.0 "S" %}
+ (Selection: <SPAN ID="{{ s.id }}-{{ cls }}-selscore">?/?</SPAN>)
+ {% endifnotequal %}{% endifnotequal %}
+ </H2>
+ <TABLE WIDTH=100%>
+ <THEAD>
+ <TR>
+ <TH TITLE="Unique ID of the test" ALIGN="LEFT">ID</TH>
+ <TH TITLE="Command or function used in the test" ALIGN="LEFT">Command</TH>
+ <TH TITLE="Value field for commands" ALIGN="LEFT">Value</TH>
+ {% ifnotequal s.id.0 "S" %}{% ifnotequal s.id.0 "Q" %}{% comment %} Don't output attribute and style columns for selection and "queryCommand..." tests. {% endcomment %}
+ <TH TITLE="check Atributes?">A</TH>
+ <TH TITLE="check Style">S</TH>
+ {% endifnotequal %}{% endifnotequal %}
+ <TH TITLE="Testing HTML Element">Env.</TH>
+ {% ifnotequal s.id.0 "S" %}{% comment %} Don't output HTML status column for selection tests. {% endcomment %}
+ <TH TITLE="State of the test">Status</TH>
+ {% endifnotequal %}
+ {% ifnotequal s.id.0 "Q" %}{% comment %} Don't output selection result column for "queryCommand..." tests. {% endcomment %}
+ <TH TITLE="State of the test regarding the selection">Selection</TH>
+ {% endifnotequal %}
+ <TH TITLE="Initial HTML and selection" ALIGN="LEFT">Initial</TH>
+ <TH TITLE="Expected HTML and selection" ALIGN="LEFT">Expected</TH>
+ <TH TITLE="Actual result HTML and selection" ALIGN="LEFT">Actual (lower case, canonicalized, selection marks)</TH>
+ <TH TITLE="Short description of the test" ALIGN="LEFT">Description</TH>
+ </TR>
+ </THEAD>
+ <TBODY>
+ {% for g in pv %}{% for t in g.tests %}
+ <TR ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:TR:dM" CLASS="{% cycle 'lo' 'lo' 'lo' 'hi' 'hi' 'hi' as shade %}">
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:tid"><A CLASS="idLabel" NAME="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}" HREF="#{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}">{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}</A></TD>
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:cmd">&nbsp;</TD>
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:val">&nbsp;</TD>
+ {% ifnotequal s.id.0 "S" %}{% ifnotequal s.id.0 "Q" %}{% comment %} Don't output attribute and style columns for selection and "queryCommand..." tests. {% endcomment %}
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:att" ALIGN="CENTER">&nbsp;</TD>
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sty" ALIGN="CENTER">&nbsp;</TD>
+ {% endifnotequal %}{% endifnotequal %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:cnt:dM" TITLE="designMode=&quot;on&quot;" ALIGN="CENTER">dM</TD>
+ {% ifnotequal s.id.0 "S" %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sta:dM" ALIGN="CENTER">NONE</TD>
+ {% endifnotequal %}
+ {% ifnotequal s.id.0 "Q" %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sel:dM" ALIGN="CENTER">NONE</TD>
+ {% endifnotequal %}
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:pad">&nbsp;</TD>
+ <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:exp">&nbsp;</TD>
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:act:dM"><I>Processing...</I></TD>
+ <TD ROWSPAN=3>{{ t.desc|default:"&nbsp;" }}</TD>
+ </TR>
+ <TR ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:TR:body" CLASS="{% cycle shade %}">
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:cnt:body" TITLE="&lt;body contentEditable=&quot;true&quot;&gt;" ALIGN="CENTER">body</TD>
+ {% ifnotequal s.id.0 "S" %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sta:body" ALIGN="CENTER">NONE</TD>
+ {% endifnotequal %}
+ {% ifnotequal s.id.0 "Q" %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sel:body" ALIGN="CENTER">NONE</TD>
+ {% endifnotequal %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:act:body"><I>Processing...</I></TD>
+ </TR>
+ <TR ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:TR:div" CLASS="{% cycle shade %}">
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:cnt:div" TITLE="&lt;div contentEditable=&quot;true&quot;&gt;" ALIGN="CENTER">div</TD>
+ {% ifnotequal s.id.0 "S" %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sta:div" ALIGN="CENTER">NONE</TD>
+ {% endifnotequal %}
+ {% ifnotequal s.id.0 "Q" %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sel:div" ALIGN="CENTER">NONE</TD>
+ {% endifnotequal %}
+ <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:act:div"><I>Processing...</I></TD>
+ </TR>
+ {% endfor %}{% endfor %}
+ </TBODY>
+ </TABLE>
+ {% endifequal %}{% endfor %}{% endfor %}
+{% endfor %}
+
+
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/richtext2.html b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/richtext2.html
new file mode 100644
index 000000000..98de8796d
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/templates/richtext2.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+
+ <title>New Rich Text Tests</title>
+
+ <link rel="stylesheet" href="static/common.css" type="text/css">
+ <link rel="stylesheet" href="static/editable.css" type="text/css">
+
+ <!-- utility scripts -->
+ <script src="static/js/variables.js"></script>
+
+ <script src="static/js/canonicalize.js"></script>
+ <script src="static/js/compare.js"></script>
+ <script src="static/js/output.js"></script>
+ <script src="static/js/pad.js"></script>
+ <script src="static/js/range.js"></script>
+ <script src="static/js/units.js"></script>
+
+ <script src="static/js/run.js"></script>
+
+ <!-- new tests -->
+ <script type="text/javascript">
+ {% autoescape off %}
+
+ var commonIDPrefix = '{{ commonIDPrefix }}';
+ {% for s in suites %}
+ var {{ s.id }}_TESTS = {{ s }};
+ {% endfor %}
+
+ /**
+ * Stuff to do after all tests are run:
+ * - write a nice "DONE!" at the end of the progress meter
+ * - beacon the results
+ * - remove the testing <iframe>s
+ */
+ function finish() {
+ var span = document.getElementById('done');
+ if (span)
+ span.innerHTML = ' ... DONE!';
+
+ fillResults();
+ parent.sendScore(beacon, categoryTotals);
+
+ cleanUp();
+ }
+
+ /**
+ * Run every individual suite, with a a brief timeout in between
+ * to allow for screen updates.
+ */
+{% for s in suites %}
+ {% if not forloop.first %}
+ setTimeout("runSuite{{ s.id }}()", 100);
+ }
+ {% endif %}
+
+ function runSuite{{ s.id }}() {
+ runAndOutputTestSuite({{ s.id }}_TESTS);
+{% endfor %}
+ finish();
+ }
+
+ /**
+ * Runs all tests in all suites.
+ */
+ function doRunTests() {
+ initVariables();
+ initEditorDocs();
+
+ // Start with the first test suite
+ runSuite{{ suites.0.id }}();
+ }
+
+ /**
+ * Runs after allowing for some time to have everything loaded
+ * (aka. horrible IE9 kludge)
+ */
+ function runTests() {
+ setTimeout("doRunTests()", 1500);
+ }
+
+ /**
+ * Removes the <iframe>s after all tests are finished
+ */
+ function cleanUp() {
+ var e = document.getElementById('iframe-dM');
+ e.parentNode.removeChild(e);
+ e = document.getElementById('iframe-body');
+ e.parentNode.removeChild(e);
+ e = document.getElementById('iframe-div');
+ e.parentNode.removeChild(e);
+ }
+ {% endautoescape %}
+ </script>
+</head>
+
+<body onload="runTests()">
+ {% include "richtext2/templates/output.html" %}
+ <hr>
+ <iframe name="iframe-dM" id="iframe-dM" src="static/editable-dM.html"></iframe>
+ <iframe name="iframe-body" id="iframe-body" src="static/editable-body.html"></iframe>
+ <iframe name="iframe-div" id="iframe-div" src="static/editable-div.html"></iframe>
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/__init__.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/__init__.py
new file mode 100644
index 000000000..a1f5279ad
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/__init__.py
@@ -0,0 +1,17 @@
+__all__ = [
+ 'apply',
+ 'applyCSS',
+ 'change',
+ 'changeCSS',
+ 'delete',
+ 'forwarddelete',
+ 'insert',
+ 'queryEnabled',
+ 'queryIndeterm',
+ 'queryState',
+ 'querySupported',
+ 'queryValue',
+ 'selection',
+ 'unapply',
+ 'unapplyCSS'
+] \ No newline at end of file
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/apply.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/apply.py
new file mode 100644
index 000000000..3eb465c84
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/apply.py
@@ -0,0 +1,364 @@
+
+APPLY_TESTS = {
+ 'id': 'A',
+ 'caption': 'Apply Formatting Tests',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[HTML5] bold',
+ 'command': 'bold',
+ 'tests': [
+ { 'id': 'B_TEXT-1_SI',
+ 'rte1-id': 'a-bold-0',
+ 'desc': 'Bold selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<b>[bar]</b>baz',
+ 'foo<strong>[bar]</strong>baz' ] },
+
+ { 'id': 'B_TEXT-1_SIR',
+ 'desc': 'Bold reversed selection',
+ 'pad': 'foo]bar[baz',
+ 'expected': [ 'foo<b>[bar]</b>baz',
+ 'foo<strong>[bar]</strong>baz' ] },
+
+ { 'id': 'B_I-1_SL',
+ 'desc': 'Bold selection, partially including italic',
+ 'pad': 'foo[bar<i>baz]qoz</i>quz',
+ 'expected': [ 'foo<b>[bar</b><i><b>baz]</b>qoz</i>quz',
+ 'foo<b>[bar<i>baz]</i></b><i>qoz</i>quz',
+ 'foo<strong>[bar</strong><i><strong>baz]</strong>qoz</i>quz',
+ 'foo<strong>[bar<i>baz]</i></strong><i>qoz</i>quz' ] }
+ ]
+ },
+
+ { 'desc': '[HTML5] italic',
+ 'command': 'italic',
+ 'tests': [
+ { 'id': 'I_TEXT-1_SI',
+ 'rte1-id': 'a-italic-0',
+ 'desc': 'Italicize selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<i>[bar]</i>baz',
+ 'foo<em>[bar]</em>baz' ] }
+ ]
+ },
+
+ { 'desc': '[HTML5] underline',
+ 'command': 'underline',
+ 'tests': [
+ { 'id': 'U_TEXT-1_SI',
+ 'rte1-id': 'a-underline-0',
+ 'desc': 'Underline selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<u>[bar]</u>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] strikethrough',
+ 'command': 'strikethrough',
+ 'tests': [
+ { 'id': 'S_TEXT-1_SI',
+ 'rte1-id': 'a-strikethrough-0',
+ 'desc': 'Strike-through selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<s>[bar]</s>baz',
+ 'foo<strike>[bar]</strike>baz',
+ 'foo<del>[bar]</del>baz' ] }
+ ]
+ },
+
+ { 'desc': '[HTML5] subscript',
+ 'command': 'subscript',
+ 'tests': [
+ { 'id': 'SUB_TEXT-1_SI',
+ 'rte1-id': 'a-subscript-0',
+ 'desc': 'Change selection to subscript',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<sub>[bar]</sub>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] superscript',
+ 'command': 'superscript',
+ 'tests': [
+ { 'id': 'SUP_TEXT-1_SI',
+ 'rte1-id': 'a-superscript-0',
+ 'desc': 'Change selection to superscript',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<sup>[bar]</sup>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] createlink',
+ 'command': 'createlink',
+ 'tests': [
+ { 'id': 'CL:url_TEXT-1_SI',
+ 'rte1-id': 'a-createlink-0',
+ 'desc': 'create a link around the selection',
+ 'value': '#foo',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<a href="#foo">[bar]</a>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] formatBlock',
+ 'command': 'formatblock',
+ 'tests': [
+ { 'id': 'FB:H1_TEXT-1_SI',
+ 'rte1-id': 'a-formatblock-0',
+ 'desc': 'format the selection into a block: use <h1>',
+ 'value': 'h1',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<h1>foo[bar]baz</h1>' },
+
+ { 'id': 'FB:P_TEXT-1_SI',
+ 'desc': 'format the selection into a block: use <p>',
+ 'value': 'p',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<p>foo[bar]baz</p>' },
+
+ { 'id': 'FB:PRE_TEXT-1_SI',
+ 'desc': 'format the selection into a block: use <pre>',
+ 'value': 'pre',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<pre>foo[bar]baz</pre>' },
+
+ { 'id': 'FB:ADDRESS_TEXT-1_SI',
+ 'desc': 'format the selection into a block: use <address>',
+ 'value': 'address',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<address>foo[bar]baz</address>' },
+
+ { 'id': 'FB:BQ_TEXT-1_SI',
+ 'desc': 'format the selection into a block: use <blockquote>',
+ 'value': 'blockquote',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<blockquote>foo[bar]baz</blockquote>' },
+
+ { 'id': 'FB:BQ_BR.BR-1_SM',
+ 'desc': 'format a multi-line selection into a block: use <blockquote>',
+ 'command': 'formatblock',
+ 'value': 'blockquote',
+ 'pad': 'fo[o<br>bar<br>b]az',
+ 'expected': '<blockquote>fo[o<br>bar<br>b]az</blockquote>' }
+ ]
+ },
+
+
+ { 'desc': '[MIDAS] backcolor',
+ 'command': 'backcolor',
+ 'tests': [
+ { 'id': 'BC:blue_TEXT-1_SI',
+ 'rte1-id': 'a-backcolor-0',
+ 'desc': 'Change background color (note: no non-CSS variant available)',
+ 'value': 'blue',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="background-color: blue">[bar]</span>baz',
+ 'foo<font style="background-color: blue">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] forecolor',
+ 'command': 'forecolor',
+ 'tests': [
+ { 'id': 'FC:blue_TEXT-1_SI',
+ 'rte1-id': 'a-forecolor-0',
+ 'desc': 'Change the text color',
+ 'value': 'blue',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<font color="blue">[bar]</font>baz' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] hilitecolor',
+ 'command': 'hilitecolor',
+ 'tests': [
+ { 'id': 'HC:blue_TEXT-1_SI',
+ 'rte1-id': 'a-hilitecolor-0',
+ 'desc': 'Change the hilite color',
+ 'value': 'blue',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="background-color: blue">[bar]</span>baz',
+ 'foo<font style="background-color: blue">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontname',
+ 'command': 'fontname',
+ 'tests': [
+ { 'id': 'FN:a_TEXT-1_SI',
+ 'rte1-id': 'a-fontname-0',
+ 'desc': 'Change the font name',
+ 'value': 'arial',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<font face="arial">[bar]</font>baz' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontsize',
+ 'command': 'fontsize',
+ 'tests': [
+ { 'id': 'FS:2_TEXT-1_SI',
+ 'rte1-id': 'a-fontsize-0',
+ 'desc': 'Change the font size to "2"',
+ 'value': '2',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<font size="2">[bar]</font>baz' },
+
+ { 'id': 'FS:18px_TEXT-1_SI',
+ 'desc': 'Change the font size to "18px"',
+ 'value': '18px',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<font size="18px">[bar]</font>baz' },
+
+ { 'id': 'FS:large_TEXT-1_SI',
+ 'desc': 'Change the font size to "large"',
+ 'value': 'large',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<font size="large">[bar]</font>baz' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] increasefontsize',
+ 'command': 'increasefontsize',
+ 'tests': [
+ { 'id': 'INCFS:2_TEXT-1_SI',
+ 'desc': 'Decrease the font size (to small)',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<font size="4">[bar]</font>baz',
+ 'foo<font size="+1">[bar]</font>baz',
+ 'foo<big>[bar]</big>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] decreasefontsize',
+ 'command': 'decreasefontsize',
+ 'tests': [
+ { 'id': 'DECFS:2_TEXT-1_SI',
+ 'rte1-id': 'a-decreasefontsize-0',
+ 'desc': 'Decrease the font size (to small)',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<font size="2">[bar]</font>baz',
+ 'foo<font size="-1">[bar]</font>baz',
+ 'foo<small>[bar]</small>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] indent (note: accept the de-facto standard indent of 40px)',
+ 'command': 'indent',
+ 'tests': [
+ { 'id': 'IND_TEXT-1_SI',
+ 'rte1-id': 'a-indent-0',
+ 'desc': 'Indent the text (accept the de-facto standard of 40px indent)',
+ 'pad': 'foo[bar]baz',
+ 'checkAttrs': False,
+ 'expected': [ '<blockquote>foo[bar]baz</blockquote>',
+ '<div style="margin-left: 40px">foo[bar]baz</div>' ],
+ 'div': {
+ 'accOuter': '<div contenteditable="true" style="margin-left: 40px">foo[bar]baz</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] outdent (-> unapply tests)',
+ 'command': 'outdent',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifycenter',
+ 'command': 'justifycenter',
+ 'tests': [
+ { 'id': 'JC_TEXT-1_SC',
+ 'rte1-id': 'a-justifycenter-0',
+ 'desc': 'justify the text centrally',
+ 'pad': 'foo^bar',
+ 'expected': [ '<center>foo^bar</center>',
+ '<p align="center">foo^bar</p>',
+ '<p align="middle">foo^bar</p>',
+ '<div align="center">foo^bar</div>',
+ '<div align="middle">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': [ '<div align="center" contenteditable="true">foo^bar</div>',
+ '<div align="middle" contenteditable="true">foo^bar</div>' ] } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifyfull',
+ 'command': 'justifyfull',
+ 'tests': [
+ { 'id': 'JF_TEXT-1_SC',
+ 'rte1-id': 'a-justifyfull-0',
+ 'desc': 'justify the text fully',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p align="justify">foo^bar</p>',
+ '<div align="justify">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div align="justify" contenteditable="true">foo^bar</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifyleft',
+ 'command': 'justifyleft',
+ 'tests': [
+ { 'id': 'JL_TEXT-1_SC',
+ 'rte1-id': 'a-justifyleft-0',
+ 'desc': 'justify the text left',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p align="left">foo^bar</p>',
+ '<div align="left">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div align="left" contenteditable="true">foo^bar</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifyright',
+ 'command': 'justifyright',
+ 'tests': [
+ { 'id': 'JR_TEXT-1_SC',
+ 'rte1-id': 'a-justifyright-0',
+ 'desc': 'justify the text right',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p align="right">foo^bar</p>',
+ '<div align="right">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div align="right" contenteditable="true">foo^bar</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] heading',
+ 'command': 'heading',
+ 'tests': [
+ { 'id': 'H:H1_TEXT-1_SC',
+ 'desc': 'create a heading from the paragraph that contains the selection',
+ 'value': 'h1',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<h1>foo[bar]baz</h1>' }
+ ]
+ },
+
+
+ { 'desc': '[Other] createbookmark',
+ 'command': 'createbookmark',
+ 'tests': [
+ { 'id': 'CB:name_TEXT-1_SI',
+ 'rte1-id': 'a-createbookmark-0',
+ 'desc': 'create a bookmark (named link) around selection',
+ 'value': 'created',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<a name="created">[bar]</a>baz' }
+ ]
+ }
+ ]
+}
+
+
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/applyCSS.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/applyCSS.py
new file mode 100644
index 000000000..94cdad83f
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/applyCSS.py
@@ -0,0 +1,244 @@
+
+APPLY_TESTS_CSS = {
+ 'id': 'AC',
+ 'caption': 'Apply Formatting Tests, using styleWithCSS',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': True,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[HTML5] bold',
+ 'command': 'bold',
+ 'tests': [
+ { 'id': 'B_TEXT-1_SI',
+ 'rte1-id': 'a-bold-1',
+ 'desc': 'Bold selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span style="font-weight: bold">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] italic',
+ 'command': 'italic',
+ 'tests': [
+ { 'id': 'I_TEXT-1_SI',
+ 'rte1-id': 'a-italic-1',
+ 'desc': 'Italicize selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span style="font-style: italic">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] underline',
+ 'command': 'underline',
+ 'tests': [
+ { 'id': 'U_TEXT-1_SI',
+ 'rte1-id': 'a-underline-1',
+ 'desc': 'Underline selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span style="text-decoration: underline">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] strikethrough',
+ 'command': 'strikethrough',
+ 'tests': [
+ { 'id': 'S_TEXT-1_SI',
+ 'rte1-id': 'a-strikethrough-1',
+ 'desc': 'Strike-through selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span style="text-decoration: line-through">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] subscript',
+ 'command': 'subscript',
+ 'tests': [
+ { 'id': 'SUB_TEXT-1_SI',
+ 'rte1-id': 'a-subscript-1',
+ 'desc': 'Change selection to subscript',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span style="vertical-align: sub">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': '[HTML5] superscript',
+ 'command': 'superscript',
+ 'tests': [
+ { 'id': 'SUP_TEXT-1_SI',
+ 'rte1-id': 'a-superscript-1',
+ 'desc': 'Change selection to superscript',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span style="vertical-align: super">[bar]</span>baz' }
+ ]
+ },
+
+
+ { 'desc': '[MIDAS] backcolor',
+ 'command': 'backcolor',
+ 'tests': [
+ { 'id': 'BC:blue_TEXT-1_SI',
+ 'rte1-id': 'a-backcolor-1',
+ 'desc': 'Change background color',
+ 'value': 'blue',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="background-color: blue">[bar]</span>baz',
+ 'foo<font style="background-color: blue">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] forecolor',
+ 'command': 'forecolor',
+ 'tests': [
+ { 'id': 'FC:blue_TEXT-1_SI',
+ 'rte1-id': 'a-forecolor-1',
+ 'desc': 'Change the text color',
+ 'value': 'blue',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="color: blue">[bar]</span>baz',
+ 'foo<font style="color: blue">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] hilitecolor',
+ 'command': 'hilitecolor',
+ 'tests': [
+ { 'id': 'HC:blue_TEXT-1_SI',
+ 'rte1-id': 'a-hilitecolor-1',
+ 'desc': 'Change the hilite color',
+ 'value': 'blue',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="background-color: blue">[bar]</span>baz',
+ 'foo<font style="background-color: blue">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontname',
+ 'command': 'fontname',
+ 'tests': [
+ { 'id': 'FN:a_TEXT-1_SI',
+ 'rte1-id': 'a-fontname-1',
+ 'desc': 'Change the font name',
+ 'value': 'arial',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="font-family: arial">[bar]</span>baz',
+ 'foo<font style="font-family: blue">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontsize',
+ 'command': 'fontsize',
+ 'tests': [
+ { 'id': 'FS:2_TEXT-1_SI',
+ 'rte1-id': 'a-fontsize-1',
+ 'desc': 'Change the font size to "2"',
+ 'value': '2',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="font-size: small">[bar]</span>baz',
+ 'foo<font style="font-size: small">[bar]</font>baz' ] },
+
+ { 'id': 'FS:18px_TEXT-1_SI',
+ 'desc': 'Change the font size to "18px"',
+ 'value': '18px',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="font-size: 18px">[bar]</span>baz',
+ 'foo<font style="font-size: 18px">[bar]</font>baz' ] },
+
+ { 'id': 'FS:large_TEXT-1_SI',
+ 'desc': 'Change the font size to "large"',
+ 'value': 'large',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<span style="font-size: large">[bar]</span>baz',
+ 'foo<font style="font-size: large">[bar]</font>baz' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] indent',
+ 'command': 'indent',
+ 'tests': [
+ { 'id': 'IND_TEXT-1_SI',
+ 'rte1-id': 'a-indent-1',
+ 'desc': 'Indent the text (assume "standard" 40px)',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ '<div style="margin-left: 40px">foo[bar]baz</div>',
+ '<div style="margin: 0 0 0 40px">foo[bar]baz</div>',
+ '<blockquote style="margin-left: 40px">foo[bar]baz</blockquote>',
+ '<blockquote style="margin: 0 0 0 40px">foo[bar]baz</blockquote>' ],
+ 'div': {
+ 'accOuter': [ '<div contenteditable="true" style="margin-left: 40px">foo[bar]baz</div>',
+ '<div contenteditable="true" style="margin: 0 0 0 40px">foo[bar]baz</div>' ] } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] outdent (-> unapply tests)',
+ 'command': 'outdent',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifycenter',
+ 'command': 'justifycenter',
+ 'tests': [
+ { 'id': 'JC_TEXT-1_SC',
+ 'rte1-id': 'a-justifycenter-1',
+ 'desc': 'justify the text centrally',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p style="text-align: center">foo^bar</p>',
+ '<div style="text-align: center">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div contenteditable="true" style="text-align: center">foo^bar</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifyfull',
+ 'command': 'justifyfull',
+ 'tests': [
+ { 'id': 'JF_TEXT-1_SC',
+ 'rte1-id': 'a-justifyfull-1',
+ 'desc': 'justify the text fully',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p style="text-align: justify">foo^bar</p>',
+ '<div style="text-align: justify">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div contenteditable="true" style="text-align: justify">foo^bar</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifyleft',
+ 'command': 'justifyleft',
+ 'tests': [
+ { 'id': 'JL_TEXT-1_SC',
+ 'rte1-id': 'a-justifyleft-1',
+ 'desc': 'justify the text left',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p style="text-align: left">foo^bar</p>',
+ '<div style="text-align: left">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div contenteditable="true" style="text-align: left">foo^bar</div>' } }
+ ]
+ },
+
+ { 'desc': '[MIDAS] justifyright',
+ 'command': 'justifyright',
+ 'tests': [
+ { 'id': 'JR_TEXT-1_SC',
+ 'rte1-id': 'a-justifyright-1',
+ 'desc': 'justify the text right',
+ 'pad': 'foo^bar',
+ 'expected': [ '<p style="text-align: right">foo^bar</p>',
+ '<div style="text-align: right">foo^bar</div>' ],
+ 'div': {
+ 'accOuter': '<div contenteditable="true" style="text-align: right">foo^bar</div>' } }
+ ]
+ }
+ ]
+}
+
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/change.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/change.py
new file mode 100644
index 000000000..6a76d3d5f
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/change.py
@@ -0,0 +1,273 @@
+
+CHANGE_TESTS = {
+ 'id': 'C',
+ 'caption': 'Change Existing Format to Different Format Tests',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[HTML5] italic',
+ 'command': 'italic',
+ 'tests': [
+ { 'id': 'I_I-1_SL',
+ 'desc': 'Italicize partially italicized text',
+ 'pad': 'foo[bar<i>baz]</i>qoz',
+ 'expected': 'foo<i>[barbaz]</i>qoz' },
+
+ { 'id': 'I_B-I-1_SO',
+ 'desc': 'Italicize partially italicized text in bold context',
+ 'pad': '<b>foo[bar<i>baz</i>}</b>',
+ 'expected': '<b>foo<i>[barbaz]</i></b>' }
+ ]
+ },
+
+ { 'desc': '[HTML5] underline',
+ 'command': 'underline',
+ 'tests': [
+ { 'id': 'U_U-1_SO',
+ 'desc': 'Underline partially underlined text',
+ 'pad': 'foo[bar<u>baz</u>qoz]quz',
+ 'expected': 'foo<u>[barbazqoz]</u>quz' },
+
+ { 'id': 'U_U-1_SL',
+ 'desc': 'Underline partially underlined text',
+ 'pad': 'foo[bar<u>baz]qoz</u>quz',
+ 'expected': 'foo<u>[barbaz]qoz</u>quz' },
+
+ { 'id': 'U_S-U-1_SO',
+ 'desc': 'Underline partially underlined text in striked context',
+ 'pad': '<s>foo[bar<u>baz</u>}</s>',
+ 'expected': '<s>foo<u>[barbaz]</u></s>' }
+ ]
+ },
+
+
+ { 'desc': '[MIDAS] backcolor',
+ 'command': 'backcolor',
+ 'tests': [
+ { 'id': 'BC:842_FONTs:bc:fca-1_SW',
+ 'rte1-id': 'c-backcolor-0',
+ 'desc': 'Change background color to new color',
+ 'value': '#884422',
+ 'pad': '<font style="background-color: #ffccaa">[foobarbaz]</font>',
+ 'expected': [ '<font style="background-color: #884422">[foobarbaz]</font>',
+ '<span style="background-color: #884422">[foobarbaz]</span>' ] },
+
+ { 'id': 'BC:00f_SPANs:bc:f00-1_SW',
+ 'rte1-id': 'c-backcolor-2',
+ 'desc': 'Change background color to new color',
+ 'value': '#0000ff',
+ 'pad': '<span style="background-color: #ff0000">[foobarbaz]</span>',
+ 'expected': [ '<font style="background-color: #0000ff">[foobarbaz]</font>',
+ '<span style="background-color: #0000ff">[foobarbaz]</span>' ] },
+
+ { 'id': 'BC:ace_FONT.ass.s:bc:rgb-1_SW',
+ 'rte1-id': 'c-backcolor-1',
+ 'desc': 'Change background color in styled span to new color',
+ 'value': '#aaccee',
+ 'pad': '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0)">[foobarbaz]</span>',
+ 'expected': [ '<font style="background-color: #aaccee">[foobarbaz]</font>',
+ '<span style="background-color: #aaccee">[foobarbaz]</span>' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] forecolor',
+ 'command': 'forecolor',
+ 'tests': [
+ { 'id': 'FC:g_FONTc:b-1_SW',
+ 'rte1-id': 'c-forecolor-0',
+ 'desc': 'Change the text color (without CSS)',
+ 'value': 'green',
+ 'pad': '<font color="blue">[foobarbaz]</font>',
+ 'expected': '<font color="green">[foobarbaz]</font>' },
+
+ { 'id': 'FC:g_SPANs:c:g-1_SW',
+ 'rte1-id': 'c-forecolor-1',
+ 'desc': 'Change the text color from a styled span (without CSS)',
+ 'value': 'green',
+ 'pad': '<span style="color: blue">[foobarbaz]</span>',
+ 'expected': '<font color="green">[foobarbaz]</font>' },
+
+ { 'id': 'FC:g_FONTc:b.s:c:r-1_SW',
+ 'rte1-id': 'c-forecolor-2',
+ 'desc': 'Change the text color from conflicting color and style (without CSS)',
+ 'value': 'green',
+ 'pad': '<font color="blue" style="color: red">[foobarbaz]</font>',
+ 'expected': '<font color="green">[foobarbaz]</font>' },
+
+ { 'id': 'FC:g_FONTc:b.sz:6-1_SI',
+ 'desc': 'Change the font color in content with a different font size and font color',
+ 'value': 'green',
+ 'pad': '<font color="blue" size="6">foo[bar]baz</font>',
+ 'expected': [ '<font color="blue" size="6">foo<font color="green">[bar]</font>baz</font>',
+ '<font size="6"><font color="blue">foo<font color="green">[bar]</font><font color="blue">baz</font></font>' ] }
+ ]
+ },
+
+ { 'desc': '[MIDAS] hilitecolor',
+ 'command': 'hilitecolor',
+ 'tests': [
+ { 'id': 'HC:g_FONTs:c:b-1_SW',
+ 'rte1-id': 'c-hilitecolor-0',
+ 'desc': 'Change the hilite color (without CSS)',
+ 'value': 'green',
+ 'pad': '<font style="background-color: blue">[foobarbaz]</font>',
+ 'expected': [ '<font style="background-color: green">[foobarbaz]</font>',
+ '<span style="background-color: green">[foobarbaz]</span>' ] },
+
+ { 'id': 'HC:g_SPANs:c:g-1_SW',
+ 'rte1-id': 'c-hilitecolor-2',
+ 'desc': 'Change the hilite color from a styled span (without CSS)',
+ 'value': 'green',
+ 'pad': '<span style="background-color: blue">[foobarbaz]</span>',
+ 'expected': '<span style="background-color: green">[foobarbaz]</span>' },
+
+ { 'id': 'HC:g_SPAN.ass.s:c:rgb-1_SW',
+ 'rte1-id': 'c-hilitecolor-1',
+ 'desc': 'Change the hilite color from a styled span (without CSS)',
+ 'value': 'green',
+ 'pad': '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">[foobarbaz]</span>',
+ 'expected': '<span style="background-color: green">[foobarbaz]</span>' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontname',
+ 'command': 'fontname',
+ 'tests': [
+ { 'id': 'FN:c_FONTf:a-1_SW',
+ 'rte1-id': 'c-fontname-0',
+ 'desc': 'Change existing font name to new font name (without CSS)',
+ 'value': 'courier',
+ 'pad': '<font face="arial">[foobarbaz]</font>',
+ 'expected': '<font face="courier">[foobarbaz]</font>' },
+
+ { 'id': 'FN:c_SPANs:ff:a-1_SW',
+ 'rte1-id': 'c-fontname-1',
+ 'desc': 'Change existing font name from style to new font name (without CSS)',
+ 'value': 'courier',
+ 'pad': '<span style="font-family: arial">[foobarbaz]</span>',
+ 'expected': '<font face="courier">[foobarbaz]</font>' },
+
+ { 'id': 'FN:c_FONTf:a.s:ff:v-1_SW',
+ 'rte1-id': 'c-fontname-2',
+ 'desc': 'Change existing font name with conflicting face and style to new font name (without CSS)',
+ 'value': 'courier',
+ 'pad': '<font face="arial" style="font-family: verdana">[foobarbaz]</font>',
+ 'expected': '<font face="courier">[foobarbaz]</font>' },
+
+ { 'id': 'FN:c_FONTf:a-1_SI',
+ 'desc': 'Change existing font name to new font name, text partially selected',
+ 'value': 'courier',
+ 'pad': '<font face="arial">foo[bar]baz</font>',
+ 'expected': '<font face="arial">foo</font><font face="courier">[bar]</font><font face="arial">baz</font>',
+ 'accept': '<font face="arial">foo<font face="courier">[bar]</font>baz</font>' },
+
+ { 'id': 'FN:c_FONTf:a-2_SL',
+ 'desc': 'Change existing font name to new font name, using CSS styling',
+ 'value': 'courier',
+ 'pad': 'foo[bar<font face="arial">baz]qoz</font>',
+ 'expected': 'foo<font face="courier">[barbaz]</font><font face="arial">qoz</font>' },
+
+ { 'id': 'FN:c_FONTf:v-FONTf:a-1_SW',
+ 'rte1-id': 'c-fontname-3',
+ 'desc': 'Change existing font name in nested <font> tags to new font name (without CSS)',
+ 'value': 'courier',
+ 'pad': '<font face="verdana"><font face="arial">[foobarbaz]</font></font>',
+ 'expected': '<font face="courier">[foobarbaz]</font>',
+ 'accept': '<font face="verdana"><font face="courier">[foobarbaz]</font></font>' },
+
+ { 'id': 'FN:c_SPANs:ff:v-FONTf:a-1_SW',
+ 'rte1-id': 'c-fontname-4',
+ 'desc': 'Change existing font name in nested mixed tags to new font name (without CSS)',
+ 'value': 'courier',
+ 'pad': '<span style="font-family: verdana"><font face="arial">[foobarbaz]</font></span>',
+ 'expected': '<font face="courier">[foobarbaz]</font>',
+ 'accept': '<span style="font-family: verdana"><font face="courier">[foobarbaz]</font></span>' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontsize',
+ 'command': 'fontsize',
+ 'tests': [
+ { 'id': 'FS:1_FONTsz:4-1_SW',
+ 'rte1-id': 'c-fontsize-0',
+ 'desc': 'Change existing font size to new size (without CSS)',
+ 'value': '1',
+ 'pad': '<font size="4">[foobarbaz]</font>',
+ 'expected': '<font size="1">[foobarbaz]</font>' },
+
+ { 'id': 'FS:1_SPAN.ass.s:fs:large-1_SW',
+ 'rte1-id': 'c-fontsize-1',
+ 'desc': 'Change existing font size from styled span to new size (without CSS)',
+ 'value': '1',
+ 'pad': '<span class="Apple-style-span" style="font-size: large">[foobarbaz]</span>',
+ 'expected': '<font size="1">[foobarbaz]</font>' },
+
+ { 'id': 'FS:5_FONTsz:1.s:fs:xs-1_SW',
+ 'rte1-id': 'c-fontsize-2',
+ 'desc': 'Change existing font size from tag with conflicting size and style to new size (without CSS)',
+ 'value': '5',
+ 'pad': '<font size="1" style="font-size:x-small">[foobarbaz]</font>',
+ 'expected': '<font size="5">[foobarbaz]</font>' },
+
+ { 'id': 'FS:2_FONTc:b.sz:6-1_SI',
+ 'desc': 'Change the font size in content with a different font size and font color',
+ 'value': '2',
+ 'pad': '<font color="blue" size="6">foo[bar]baz</font>',
+ 'expected': [ '<font color="blue" size="6">foo<font size="2">[bar]</font>baz</font>',
+ '<font color="blue"><font size="6">foo</font><font size="2">[bar]</font><font size="6">baz</font></font>' ] },
+
+ { 'id': 'FS:larger_FONTsz:4',
+ 'desc': 'Change selection to use next larger font',
+ 'value': 'larger',
+ 'pad': '<font size="4">foo[bar]baz</font>',
+ 'expected': '<font size="4">foo<font size="larger">[bar]</font>baz</font>',
+ 'accept': '<font size="4">foo</font><font size="5">[bar]</font><font size="4">baz</font>' },
+
+ { 'id': 'FS:smaller_FONTsz:4',
+ 'desc': 'Change selection to use next smaller font',
+ 'value': 'smaller',
+ 'pad': '<font size="4">foo[bar]baz</font>',
+ 'expected': '<font size="4">foo<font size="smaller">[bar]</font>baz</font>',
+ 'accept': '<font size="4">foo</font><font size="3">[bar]</font><font size="4">baz</font>' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] formatblock',
+ 'command': 'formatblock',
+ 'tests': [
+ { 'id': 'FB:h1_ADDRESS-1_SW',
+ 'desc': 'change block from <address> to <h1>',
+ 'value': 'h1',
+ 'pad': '<address>foo [bar] baz</address>',
+ 'expected': '<h1>foo [bar] baz</h1>' },
+
+ { 'id': 'FB:h1_ADDRESS-FONTsz:4-1_SO',
+ 'desc': 'change block from <address> with partially formatted content to <h1>',
+ 'value': 'h1',
+ 'pad': '<address>foo [<font size="4">bar</font>] baz</address>',
+ 'expected': '<h1>foo [bar] baz</h1>' },
+
+ { 'id': 'FB:h1_ADDRESS-FONTsz:4-1_SW',
+ 'desc': 'change block from <address> with partially formatted content to <h1>',
+ 'value': 'h1',
+ 'pad': '<address>foo <font size="4">[bar]</font> baz</address>',
+ 'expected': '<h1>foo [bar] baz</h1>' },
+
+ { 'id': 'FB:h1_ADDRESS-FONT.ass.sz:4-1_SW',
+ 'desc': 'change block from <address> with partially formatted content to <h1>',
+ 'value': 'h1',
+ 'pad': '<address>foo <font class="Apple-style-span" size="4">[bar]</font> baz</address>',
+ 'expected': '<h1>foo [bar] baz</h1>' }
+ ]
+ }
+ ]
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/changeCSS.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/changeCSS.py
new file mode 100644
index 000000000..4862b9b73
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/changeCSS.py
@@ -0,0 +1,210 @@
+
+CHANGE_TESTS_CSS = {
+ 'id': 'CC',
+ 'caption': 'Change Existing Format to Different Format Tests, using styleWithCSS',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': True,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[HTML5] italic',
+ 'command': 'italic',
+ 'tests': [
+ { 'id': 'I_I-1_SL',
+ 'desc': 'Italicize partially italicized text',
+ 'pad': 'foo[bar<i>baz]</i>qoz',
+ 'expected': 'foo<span style="font-style: italic">[barbaz]</span>qoz' },
+
+ { 'id': 'I_B-1_SL',
+ 'desc': 'Italicize partially bolded text',
+ 'pad': 'foo[bar<b>baz]</b>qoz',
+ 'expected': 'foo<span style="font-style: italic">[bar<b>baz]</b></span>qoz',
+ 'accept': 'foo<span style="font-style: italic">[bar<b>baz</b>}</span>qoz' },
+
+ { 'id': 'I_B-1_SW',
+ 'desc': 'Italicize bold text, ideally combining both',
+ 'pad': 'foobar<b>[baz]</b>qoz',
+ 'expected': 'foobar<span style="font-style: italic; font-weight: bold">[baz]</span>qoz',
+ 'accept': 'foobar<b><span style="font-style: italic">[baz]</span></b>qoz' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] backcolor',
+ 'command': 'backcolor',
+ 'tests': [
+ { 'id': 'BC:gray_SPANs:bc:b-1_SW',
+ 'desc': 'Change background color from blue to gray',
+ 'value': 'gray',
+ 'pad': '<span style="background-color: blue">[foobarbaz]</span>',
+ 'expected': '<span style="background-color: gray">[foobarbaz]</span>' },
+
+ { 'id': 'BC:gray_SPANs:bc:b-1_SO',
+ 'desc': 'Change background color from blue to gray',
+ 'value': 'gray',
+ 'pad': '{<span style="background-color: blue">foobarbaz</span>}',
+ 'expected': [ '{<span style="background-color: gray">foobarbaz</span>}',
+ '<span style="background-color: gray">[foobarbaz]</span>' ] },
+
+ { 'id': 'BC:gray_SPANs:bc:b-1_SI',
+ 'desc': 'Change background color from blue to gray',
+ 'value': 'gray',
+ 'pad': '<span style="background-color: blue">foo[bar]baz</span>',
+ 'expected': '<span style="background-color: blue">foo</span><span style="background-color: gray">[bar]</span><span style="background-color: blue">baz</span>',
+ 'accept': '<span style="background-color: blue">foo<span style="background-color: gray">[bar]</span>baz</span>' },
+
+ { 'id': 'BC:gray_P-SPANs:bc:b-1_SW',
+ 'desc': 'Change background color within a paragraph from blue to gray',
+ 'value': 'gray',
+ 'pad': '<p><span style="background-color: blue">[foobarbaz]</span></p>',
+ 'expected': [ '<p><span style="background-color: gray">[foobarbaz]</span></p>',
+ '<p style="background-color: gray">[foobarbaz]</p>' ] },
+
+ { 'id': 'BC:gray_P-SPANs:bc:b-2_SW',
+ 'desc': 'Change background color within a paragraph from blue to gray',
+ 'value': 'gray',
+ 'pad': '<p>foo<span style="background-color: blue">[bar]</span>baz</p>',
+ 'expected': '<p>foo<span style="background-color: gray">[bar]</span>baz</p>' },
+
+ { 'id': 'BC:gray_P-SPANs:bc:b-3_SO',
+ 'desc': 'Change background color within a paragraph from blue to gray (selection encloses more than previous span)',
+ 'value': 'gray',
+ 'pad': '<p>[foo<span style="background-color: blue">barbaz</span>qoz]quz</p>',
+ 'expected': '<p><span style="background-color: gray">[foobarbazqoz]</span>quz</p>' },
+
+ { 'id': 'BC:gray_P-SPANs:bc:b-3_SL',
+ 'desc': 'Change background color within a paragraph from blue to gray (previous span partially selected)',
+ 'value': 'gray',
+ 'pad': '<p>[foo<span style="background-color: blue">bar]baz</span>qozquz</p>',
+ 'expected': '<p><span style="background-color: gray">[foobar]</span><span style="background-color: blue">baz</span>qozquz</p>' },
+
+ { 'id': 'BC:gray_SPANs:bc:b-2_SL',
+ 'desc': 'Change background color from blue to gray on partially covered span, selection extends left',
+ 'value': 'gray',
+ 'pad': 'foo [bar <span style="background-color: blue">baz] qoz</span> quz sic',
+ 'expected': 'foo <span style="background-color: gray">[bar baz]</span><span style="background-color: blue"> qoz</span> quz sic' },
+
+ { 'id': 'BC:gray_SPANs:bc:b-2_SR',
+ 'desc': 'Change background color from blue to gray on partially covered span, selection extends right',
+ 'value': 'gray',
+ 'pad': 'foo bar <span style="background-color: blue">baz [qoz</span> quz] sic',
+ 'expected': 'foo bar <span style="background-color: blue">baz </span><span style="background-color: gray">[qoz quz]</span> sic' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontname',
+ 'command': 'fontname',
+ 'tests': [
+ { 'id': 'FN:c_SPANs:ff:a-1_SW',
+ 'desc': 'Change existing font name to new font name, using CSS styling',
+ 'value': 'courier',
+ 'pad': '<span style="font-family: arial">[foobarbaz]</span>',
+ 'expected': '<span style="font-family: courier">[foobarbaz]</span>' },
+
+ { 'id': 'FN:c_FONTf:a-1_SW',
+ 'desc': 'Change existing font name to new font name, using CSS styling',
+ 'value': 'courier',
+ 'pad': '<font face="arial">[foobarbaz]</font>',
+ 'expected': [ '<font style="font-family: courier">[foobarbaz]</font>',
+ '<span style="font-family: courier">[foobarbaz]</span>' ] },
+
+ { 'id': 'FN:c_FONTf:a-1_SI',
+ 'desc': 'Change existing font name to new font name, using CSS styling',
+ 'value': 'courier',
+ 'pad': '<font face="arial">foo[bar]baz</font>',
+ 'expected': '<font face="arial">foo</font><span style="font-family: courier">[bar]</span><font face="arial">baz</font>' },
+
+ { 'id': 'FN:a_FONTf:a-1_SI',
+ 'desc': 'Change existing font name to same font name, using CSS styling (should be noop)',
+ 'value': 'arial',
+ 'pad': '<font face="arial">foo[bar]baz</font>',
+ 'expected': '<font face="arial">foo[bar]baz</font>' },
+
+ { 'id': 'FN:a_FONTf:a-1_SW',
+ 'desc': 'Change existing font name to same font name, using CSS styling (should be noop or perhaps change tag)',
+ 'value': 'arial',
+ 'pad': '<font face="arial">[foobarbaz]</font>',
+ 'expected': [ '<font face="arial">[foobarbaz]</font>',
+ '<span style="font-family: arial">[foobarbaz]</span>' ] },
+
+ { 'id': 'FN:a_FONTf:a-1_SO',
+ 'desc': 'Change existing font name to same font name, using CSS styling (should be noop or perhaps change tag)',
+ 'value': 'arial',
+ 'pad': '{<font face="arial">foobarbaz</font>}',
+ 'expected': [ '{<font face="arial">foobarbaz</font>}',
+ '<font face="arial">[foobarbaz]</font>',
+ '{<span style="font-family: arial">foobarbaz</span>}',
+ '<span style="font-family: arial">[foobarbaz]</span>' ] },
+
+ { 'id': 'FN:a_SPANs:ff:a-1_SI',
+ 'desc': 'Change existing font name to same font name, using CSS styling (should be noop)',
+ 'value': 'arial',
+ 'pad': '<span style="font-family: arial">[foobarbaz]</span>',
+ 'expected': '<span style="font-family: arial">[foobarbaz]</span>' },
+
+ { 'id': 'FN:c_FONTf:a-2_SL',
+ 'desc': 'Change existing font name to new font name, using CSS styling',
+ 'value': 'courier',
+ 'pad': 'foo[bar<font face="arial">baz]qoz</font>',
+ 'expected': 'foo<span style="font-family: courier">[barbaz]</span><font face="arial">qoz</font>' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] fontsize',
+ 'command': 'fontsize',
+ 'tests': [
+ { 'id': 'FS:1_SPANs:fs:l-1_SW',
+ 'desc': 'Change existing font size to new size, using CSS styling',
+ 'value': '1',
+ 'pad': '<span style="font-size: large">[foobarbaz]</span>',
+ 'expected': '<span style="font-size: x-small">[foobarbaz]</span>' },
+
+ { 'id': 'FS:large_SPANs:fs:l-1_SW',
+ 'desc': 'Change existing font size to same size (should be noop)',
+ 'value': 'large',
+ 'pad': '<span style="font-size: large">[foobarbaz]</span>',
+ 'expected': '<span style="font-size: large">[foobarbaz]</span>' },
+
+ { 'id': 'FS:18px_SPANs:fs:l-1_SW',
+ 'desc': 'Change existing font size to equivalent px size (should be noop, or change unit)',
+ 'value': '18px',
+ 'pad': '<span style="font-size: large">[foobarbaz]</span>',
+ 'expected': [ '<span style="font-size: 18px">[foobarbaz]</span>',
+ '<span style="font-size: large">[foobarbaz]</span>' ] },
+
+ { 'id': 'FS:4_SPANs:fs:l-1_SW',
+ 'desc': 'Change existing font size to equivalent numeric size (should be noop)',
+ 'value': '4',
+ 'pad': '<span style="font-size: large">[foobarbaz]</span>',
+ 'expected': '<span style="font-size: large">[foobarbaz]</span>' },
+
+ { 'id': 'FS:4_SPANs:fs:18px-1_SW',
+ 'desc': 'Change existing font size to equivalent numeric size (should be noop)',
+ 'value': '4',
+ 'pad': '<span style="font-size: 18px">[foobarbaz]</span>',
+ 'expected': '<span style="font-size: 18px">[foobarbaz]</span>' },
+
+ { 'id': 'FS:larger_SPANs:fs:l-1_SI',
+ 'desc': 'Change selection to use next larger font',
+ 'value': 'larger',
+ 'pad': '<span style="font-size: large">foo[bar]baz</span>',
+ 'expected': [ '<span style="font-size: large">foo<span style="font-size: x-large">[bar]</span>baz</span>',
+ '<span style="font-size: large">foo</span><span style="font-size: x-large">[bar]</span><span style="font-size: large">baz</span>' ],
+ 'accept': '<span style="font-size: large">foo<font size="larger">[bar]</font>baz</span>' },
+
+ { 'id': 'FS:smaller_SPANs:fs:l-1_SI',
+ 'desc': 'Change selection to use next smaller font',
+ 'value': 'smaller',
+ 'pad': '<span style="font-size: large">foo[bar]baz</span>',
+ 'expected': [ '<span style="font-size: large">foo<span style="font-size: medium">[bar]</span>baz</span>',
+ '<span style="font-size: large">foo</span><span style="font-size: medium">[bar]</span><span style="font-size: large">baz</span>' ],
+ 'accept': '<span style="font-size: large">foo<font size="smaller">[bar]</font>baz</span>' }
+ ]
+ }
+ ]
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/delete.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/delete.py
new file mode 100644
index 000000000..0cc659225
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/delete.py
@@ -0,0 +1,330 @@
+
+DELETE_TESTS = {
+ 'id': 'D',
+ 'caption': 'Delete Tests',
+ 'command': 'delete',
+ 'checkAttrs': True,
+ 'checkStyle': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'delete single characters',
+ 'tests': [
+ { 'id': 'CHAR-1_SC',
+ 'desc': 'Delete 1 character',
+ 'pad': 'foo^barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-2_SC',
+ 'desc': 'Delete 1 pre-composed character o with diaeresis',
+ 'pad': 'fo&#xF6;^barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-3_SC',
+ 'desc': 'Delete 1 character with combining diaeresis above',
+ 'pad': 'foo&#x0308;^barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-4_SC',
+ 'desc': 'Delete 1 character with combining diaeresis below',
+ 'pad': 'foo&#x0324;^barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-5_SC',
+ 'desc': 'Delete 1 character with combining diaeresis above and below',
+ 'pad': 'foo&#x0308;&#x0324;^barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-5_SI-1',
+ 'desc': 'Delete 1 character with combining diaeresis above and below, selection on diaeresis above',
+ 'pad': 'foo[&#x0308;]&#x0324;barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-5_SI-2',
+ 'desc': 'Delete 1 character with combining diaeresis above and below, selection on diaeresis below',
+ 'pad': 'foo&#x0308;[&#x0324;]barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-5_SR',
+ 'desc': 'Delete 1 character with combining diaeresis above and below, selection oblique on diaeresis and following text',
+ 'pad': 'foo&#x0308;[&#x0324;bar]baz',
+ 'expected': 'fo^baz' },
+
+ { 'id': 'CHAR-6_SC',
+ 'desc': 'Delete 1 character with enclosing square',
+ 'pad': 'foo&#x20DE;^barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-7_SC',
+ 'desc': 'Delete 1 character with combining long solidus overlay',
+ 'pad': 'foo&#x0338;^barbaz',
+ 'expected': 'fo^barbaz' }
+ ]
+ },
+
+ { 'desc': 'delete text selection',
+ 'tests': [
+ { 'id': 'TEXT-1_SI',
+ 'desc': 'Delete text selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo^baz' },
+
+ { 'id': 'B-1_SS',
+ 'desc': 'Delete at start of span',
+ 'pad': 'foo<b>^bar</b>baz',
+ 'expected': 'fo^<b>bar</b>baz' },
+
+ { 'id': 'B-1_SA',
+ 'desc': 'Delete from position after span',
+ 'pad': 'foo<b>bar</b>^baz',
+ 'expected': 'foo<b>ba^</b>baz' },
+
+ { 'id': 'B-1_SW',
+ 'desc': 'Delete selection that wraps the whole span content',
+ 'pad': 'foo<b>[bar]</b>baz',
+ 'expected': 'foo^baz' },
+
+ { 'id': 'B-1_SO',
+ 'desc': 'Delete selection that wraps the whole span',
+ 'pad': 'foo[<b>bar</b>]baz',
+ 'expected': 'foo^baz' },
+
+ { 'id': 'B-1_SL',
+ 'desc': 'Delete oblique selection that starts before span',
+ 'pad': 'foo[bar<b>baz]quoz</b>quuz',
+ 'expected': 'foo^<b>quoz</b>quuz' },
+
+ { 'id': 'B-1_SR',
+ 'desc': 'Delete oblique selection that ends after span',
+ 'pad': 'foo<b>bar[baz</b>quoz]quuz',
+ 'expected': 'foo<b>bar^</b>quuz' },
+
+ { 'id': 'B.I-1_SM',
+ 'desc': 'Delete oblique selection that starts and ends in different spans',
+ 'pad': 'foo<b>bar[baz</b><i>qoz]quuz</i>quuuz',
+ 'expected': 'foo<b>bar^</b><i>quuz</i>quuuz' },
+
+ { 'id': 'GEN-1_SS',
+ 'desc': 'Delete at start of span with generated content',
+ 'pad': 'foo<gen>^bar</gen>baz',
+ 'expected': 'fo^<gen>bar</gen>baz' },
+
+ { 'id': 'GEN-1_SA',
+ 'desc': 'Delete from position after span with generated content',
+ 'pad': 'foo<gen>bar</gen>^baz',
+ 'expected': 'foo<gen>ba^</gen>baz' }
+ ]
+ },
+
+ { 'desc': 'delete paragraphs',
+ 'tests': [
+ { 'id': 'P2-1_SS2',
+ 'desc': 'Delete from collapsed selection at start of paragraph - should merge with previous',
+ 'pad': '<p>foobar</p><p>^bazqoz</p>',
+ 'expected': '<p>foobar^bazqoz</p>' },
+
+ { 'id': 'P2-1_SI2',
+ 'desc': 'Delete non-collapsed selection at start of paragraph - should not merge with previous',
+ 'pad': '<p>foobar</p><p>[baz]qoz</p>',
+ 'expected': '<p>foobar</p><p>^qoz</p>' },
+
+ { 'id': 'P2-1_SM',
+ 'desc': 'Delete non-collapsed selection spanning 2 paragraphs - should merge them',
+ 'pad': '<p>foo[bar</p><p>baz]qoz</p>',
+ 'expected': '<p>foo^qoz</p>' }
+ ]
+ },
+
+ { 'desc': 'delete lists and list items',
+ 'tests': [
+ { 'id': 'OL-LI2-1_SO1',
+ 'desc': 'Delete fully wrapped list item',
+ 'pad': 'foo<ol>{<li>bar</li>}<li>baz</li></ol>qoz',
+ 'expected': ['foo<ol>|<li>baz</li></ol>qoz',
+ 'foo<ol><li>^baz</li></ol>qoz'] },
+
+ { 'id': 'OL-LI2-1_SM',
+ 'desc': 'Delete oblique range between list items within same list',
+ 'pad': 'foo<ol><li>ba[r</li><li>b]az</li></ol>qoz',
+ 'expected': 'foo<ol><li>ba^az</li></ol>qoz' },
+
+ { 'id': 'OL-LI-1_SW',
+ 'desc': 'Delete contents of last list item (list should remain)',
+ 'pad': 'foo<ol><li>[foo]</li></ol>qoz',
+ 'expected': ['foo<ol><li>|</li></ol>qoz',
+ 'foo<ol><li>^</li></ol>qoz'] },
+
+ { 'id': 'OL-LI-1_SO',
+ 'desc': 'Delete last list item of list (should remove entire list)',
+ 'pad': 'foo<ol>{<li>foo</li>}</ol>qoz',
+ 'expected': 'foo^qoz' }
+ ]
+ },
+
+ { 'desc': 'delete with strange selections',
+ 'tests': [
+ { 'id': 'HR.BR-1_SM',
+ 'desc': 'Delete selection that starts and ends within nodes that don\'t have children',
+ 'pad': 'foo<hr {>bar<br }>baz',
+ 'expected': 'foo<hr>|<br>baz' }
+ ]
+ },
+
+ { 'desc': 'delete after table',
+ 'tests': [
+ { 'id': 'TABLE-1_SA',
+ 'desc': 'Delete from position immediately after table (should have no effect)',
+ 'pad': 'foo<table><tbody><tr><td>bar</td></tr></tbody></table>^baz',
+ 'expected': 'foo<table><tbody><tr><td>bar</td></tr></tbody></table>^baz' }
+ ]
+ },
+
+ { 'desc': 'delete within table cells',
+ 'tests': [
+ { 'id': 'TD-1_SS',
+ 'desc': 'Delete from start of first cell (should have no effect)',
+ 'pad': 'foo<table><tbody><tr><td>^bar</td></tr></tbody></table>baz',
+ 'expected': 'foo<table><tbody><tr><td>^bar</td></tr></tbody></table>baz' },
+
+ { 'id': 'TD2-1_SS2',
+ 'desc': 'Delete from start of inner cell (should have no effect)',
+ 'pad': 'foo<table><tbody><tr><td>bar</td><td>^baz</td></tr></tbody></table>quoz',
+ 'expected': 'foo<table><tbody><tr><td>bar</td><td>^baz</td></tr></tbody></table>quoz' },
+
+ { 'id': 'TD2-1_SM',
+ 'desc': 'Delete with selection spanning 2 cells',
+ 'pad': 'foo<table><tbody><tr><td>ba[r</td><td>b]az</td></tr></tbody></table>quoz',
+ 'expected': 'foo<table><tbody><tr><td>ba^</td><td>az</td></tr></tbody></table>quoz' }
+ ]
+ },
+
+ { 'desc': 'delete table rows',
+ 'tests': [
+ { 'id': 'TR3-1_SO1',
+ 'desc': 'Delete first table row',
+ 'pad': '<table><tbody>{<tr><td>A</td></tr>}<tr><td>B</td></tr><tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody>|<tr><td>B</td></tr><tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>^B</td></tr><tr><td>C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3-1_SO2',
+ 'desc': 'Delete middle table row',
+ 'pad': '<table><tbody><tr><td>A</td></tr>{<tr><td>B</td></tr>}<tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td></tr>|<tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>A</td></tr><tr><td>^C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3-1_SO3',
+ 'desc': 'Delete last table row',
+ 'pad': '<table><tbody><tr><td>A</td></tr><tr><td>B</td></tr>{<tr><td>C</td></tr>}</tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td></tr><tr><td>B</td></tr>|</tbody></table>',
+ '<table><tbody><tr><td>A</td></tr><tr><td>B^</td></tr></tbody></table>'] },
+
+ { 'id': 'TR2rs:2-1_SO1',
+ 'desc': 'Delete first table row where a cell has rowspan 2',
+ 'pad': '<table><tbody>{<tr><td>A</td><td rowspan=2>R</td></tr>}<tr><td>B</td></tr></tbody></table>',
+ 'expected': ['<table><tbody>|<tr><td>B</td><td>R</td></tr></tbody></table>',
+ '<table><tbody><tr><td>^B</td><td>R</td></tr></tbody></table>'] },
+
+ { 'id': 'TR2rs:2-1_SO2',
+ 'desc': 'Delete second table row where a cell has rowspan 2',
+ 'pad': '<table><tbody><tr><td>A</td><td rowspan=2>R</td></tr>{<tr><td>B</td></tr>}</tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td><td>R</td></tr>|</tbody></table>',
+ '<table><tbody><tr><td>A</td><td>R^</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3rs:3-1_SO1',
+ 'desc': 'Delete first table row where a cell has rowspan 3',
+ 'pad': '<table><tbody>{<tr><td>A</td><td rowspan=3>R</td></tr>}<tr><td>B</td></tr><tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody>|<tr><td>A</td><td rowspan="2">R</td></tr><tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>^A</td><td rowspan="2">R</td></tr><tr><td>C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3rs:3-1_SO2',
+ 'desc': 'Delete middle table row where a cell has rowspan 3',
+ 'pad': '<table><tbody><tr><td>A</td><td rowspan=3>R</td></tr>{<tr><td>B</td></tr>}<tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody><tr><td>B</td><td rowspan="2">R</td></tr>|<tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>B</td><td rowspan="2">R</td></tr><tr><td>^C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3rs:3-1_SO3',
+ 'desc': 'Delete last table row where a cell has rowspan 3',
+ 'pad': '<table><tbody><tr><td>A</td><td rowspan=3>R</td></tr><tr><td>B</td></tr>{<tr><td>C</td></tr>}</tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td><td rowspan="2">R</td></tr><tr><td>B</td></tr>|</tbody></table>',
+ '<table><tbody><tr><td>A</td><td rowspan="2">R</td></tr><tr><td>B^</td></tr></tbody></table>'] }
+ ]
+ },
+
+ { 'desc': 'delete with non-editable nested content',
+ 'tests': [
+ { 'id': 'DIV:ce:false-1_SO',
+ 'desc': 'Delete nested non-editable <div>',
+ 'pad': 'foo[bar<div contenteditable="false">NESTED</div>baz]qoz',
+ 'expected': 'foo^qoz' },
+
+ { 'id': 'DIV:ce:false-1_SB',
+ 'desc': 'Delete from immediately after a nested non-editable <div> (should be no-op)',
+ 'pad': 'foobar<div contenteditable="false">NESTED</div>^bazqoz',
+ 'expected': 'foobar<div contenteditable="false">NESTED</div>^bazqoz' },
+
+ { 'id': 'DIV:ce:false-1_SL',
+ 'desc': 'Delete nested non-editable <div> with oblique selection',
+ 'pad': 'foo[bar<div contenteditable="false">NES]TED</div>bazqoz',
+ 'expected': [ 'foo^<div contenteditable="false">NESTED</div>bazqoz',
+ 'foo<div contenteditable="false">[NES]TED</div>bazqoz' ] },
+
+ { 'id': 'DIV:ce:false-1_SR',
+ 'desc': 'Delete nested non-editable <div> with oblique selection',
+ 'pad': 'foobar<div contenteditable="false">NES[TED</div>baz]qoz',
+ 'expected': [ 'foobar<div contenteditable="false">NESTED</div>^qoz',
+ 'foobar<div contenteditable="false">NES[TED]</div>qoz' ] },
+
+ { 'id': 'DIV:ce:false-1_SI',
+ 'desc': 'Delete inside nested non-editable <div> (should be no-op)',
+ 'pad': 'foobar<div contenteditable="false">NE[ST]ED</div>bazqoz',
+ 'expected': 'foobar<div contenteditable="false">NE[ST]ED</div>bazqoz' }
+ ]
+ },
+
+ { 'desc': 'Delete with display:inline-block',
+ 'checkStyle': True,
+ 'tests': [
+ { 'id': 'SPAN:d:ib-1_SC',
+ 'desc': 'Delete inside an inline-block <span>',
+ 'pad': 'foo<span style="display: inline-block">bar^baz</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">ba^baz</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-1_SA',
+ 'desc': 'Delete from immediately after an inline-block <span>',
+ 'pad': 'foo<span style="display: inline-block">barbaz</span>^qoz',
+ 'expected': 'foo<span style="display: inline-block">barba^</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-2_SL',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo[DEL<span style="display: inline-block">ETE]bar</span>baz',
+ 'expected': 'foo^<span style="display: inline-block">bar</span>baz' },
+
+ { 'id': 'SPAN:d:ib-3_SR',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">bar[DEL</span>ETE]baz',
+ 'expected': 'foo<span style="display: inline-block">bar^</span>baz' },
+
+ { 'id': 'SPAN:d:ib-4i_SI',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">bar[DELETE]baz</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">bar^baz</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-4l_SI',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">[DELETE]barbaz</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">^barbaz</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-4r_SI',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">barbaz[DELETE]</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">barbaz^</span>qoz' }
+ ]
+ }
+ ]
+}
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/forwarddelete.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/forwarddelete.py
new file mode 100644
index 000000000..d625a2a7d
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/forwarddelete.py
@@ -0,0 +1,315 @@
+
+FORWARDDELETE_TESTS = {
+ 'id': 'FD',
+ 'caption': 'Forward-Delete Tests',
+ 'command': 'forwardDelete',
+ 'checkAttrs': True,
+ 'checkStyle': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'forward-delete single characters',
+ 'tests': [
+ { 'id': 'CHAR-1_SC',
+ 'desc': 'Delete 1 character',
+ 'pad': 'foo^barbaz',
+ 'expected': 'foo^arbaz' },
+
+ { 'id': 'CHAR-2_SC',
+ 'desc': 'Delete 1 pre-composed character o with diaeresis',
+ 'pad': 'fo^&#xF6;barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-3_SC',
+ 'desc': 'Delete 1 character with combining diaeresis above',
+ 'pad': 'fo^o&#x0308;barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-4_SC',
+ 'desc': 'Delete 1 character with combining diaeresis below',
+ 'pad': 'fo^o&#x0324;barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-5_SC',
+ 'desc': 'Delete 1 character with combining diaeresis above and below',
+ 'pad': 'fo^o&#x0308;&#x0324;barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-6_SC',
+ 'desc': 'Delete 1 character with enclosing square',
+ 'pad': 'fo^o&#x20DE;barbaz',
+ 'expected': 'fo^barbaz' },
+
+ { 'id': 'CHAR-7_SC',
+ 'desc': 'Delete 1 character with combining long solidus overlay',
+ 'pad': 'fo^o&#x0338;barbaz',
+ 'expected': 'fo^barbaz' }
+ ]
+ },
+
+ { 'desc': 'forward-delete text selections',
+ 'tests': [
+ { 'id': 'TEXT-1_SI',
+ 'desc': 'Delete text selection',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo^baz' },
+
+ { 'id': 'B-1_SE',
+ 'desc': 'Forward-delete at end of span',
+ 'pad': 'foo<b>bar^</b>baz',
+ 'expected': 'foo<b>bar^</b>az' },
+
+ { 'id': 'B-1_SB',
+ 'desc': 'Forward-delete from position before span',
+ 'pad': 'foo^<b>bar</b>baz',
+ 'expected': 'foo^<b>ar</b>baz' },
+
+ { 'id': 'B-1_SW',
+ 'desc': 'Delete selection that wraps the whole span content',
+ 'pad': 'foo<b>[bar]</b>baz',
+ 'expected': 'foo^baz' },
+
+ { 'id': 'B-1_SO',
+ 'desc': 'Delete selection that wraps the whole span',
+ 'pad': 'foo[<b>bar</b>]baz',
+ 'expected': 'foo^baz' },
+
+ { 'id': 'B-1_SL',
+ 'desc': 'Delete oblique selection that starts before span',
+ 'pad': 'foo[bar<b>baz]quoz</b>quuz',
+ 'expected': 'foo^<b>quoz</b>quuz' },
+
+ { 'id': 'B-1_SR',
+ 'desc': 'Delete oblique selection that ends after span',
+ 'pad': 'foo<b>bar[baz</b>quoz]quuz',
+ 'expected': 'foo<b>bar^</b>quuz' },
+
+ { 'id': 'B.I-1_SM',
+ 'desc': 'Delete oblique selection that starts and ends in different spans',
+ 'pad': 'foo<b>bar[baz</b><i>qoz]quuz</i>quuuz',
+ 'expected': 'foo<b>bar^</b><i>quuz</i>quuuz' },
+
+ { 'id': 'GEN-1_SE',
+ 'desc': 'Delete at end of span with generated content',
+ 'pad': 'foo<gen>bar^</gen>baz',
+ 'expected': 'foo<gen>bar^</gen>az' },
+
+ { 'id': 'GEN-1_SB',
+ 'desc': 'Delete from position before span with generated content',
+ 'pad': 'foo^<gen>bar</gen>baz',
+ 'expected': 'foo^<gen>ar</gen>baz' }
+ ]
+ },
+
+ { 'desc': 'forward-delete paragraphs',
+ 'tests': [
+ { 'id': 'P2-1_SE1',
+ 'desc': 'Delete from collapsed selection at end of paragraph - should merge with next',
+ 'pad': '<p>foobar^</p><p>bazqoz</p>',
+ 'expected': '<p>foobar^bazqoz</p>' },
+
+ { 'id': 'P2-1_SI1',
+ 'desc': 'Delete non-collapsed selection at end of paragraph - should not merge with next',
+ 'pad': '<p>foo[bar]</p><p>bazqoz</p>',
+ 'expected': '<p>foo^</p><p>bazqoz</p>' },
+
+ { 'id': 'P2-1_SM',
+ 'desc': 'Delete non-collapsed selection spanning 2 paragraphs - should merge them',
+ 'pad': '<p>foo[bar</p><p>baz]qoz</p>',
+ 'expected': '<p>foo^qoz</p>' }
+ ]
+ },
+
+ { 'desc': 'forward-delete lists and list items',
+ 'tests': [
+ { 'id': 'OL-LI2-1_SO1',
+ 'desc': 'Delete fully wrapped list item',
+ 'pad': 'foo<ol>{<li>bar</li>}<li>baz</li></ol>qoz',
+ 'expected': ['foo<ol>|<li>baz</li></ol>qoz',
+ 'foo<ol><li>^baz</li></ol>qoz'] },
+
+ { 'id': 'OL-LI2-1_SM',
+ 'desc': 'Delete oblique range between list items within same list',
+ 'pad': 'foo<ol><li>ba[r</li><li>b]az</li></ol>qoz',
+ 'expected': 'foo<ol><li>ba^az</li></ol>qoz' },
+
+ { 'id': 'OL-LI-1_SW',
+ 'desc': 'Delete contents of last list item (list should remain)',
+ 'pad': 'foo<ol><li>[foo]</li></ol>qoz',
+ 'expected': ['foo<ol><li>|</li></ol>qoz',
+ 'foo<ol><li>^</li></ol>qoz'] },
+
+ { 'id': 'OL-LI-1_SO',
+ 'desc': 'Delete last list item of list (should remove entire list)',
+ 'pad': 'foo<ol>{<li>foo</li>}</ol>qoz',
+ 'expected': 'foo^qoz' }
+ ]
+ },
+
+ { 'desc': 'forward-delete with strange selections',
+ 'tests': [
+ { 'id': 'HR.BR-1_SM',
+ 'desc': 'Delete selection that starts and ends within nodes that don\'t have children',
+ 'pad': 'foo<hr {>bar<br }>baz',
+ 'expected': 'foo<hr>|<br>baz' }
+ ]
+ },
+
+ { 'desc': 'forward-delete from immediately before a table',
+ 'tests': [
+ { 'id': 'TABLE-1_SB',
+ 'desc': 'Delete from position immediately before table (should have no effect)',
+ 'pad': 'foo^<table><tbody><tr><td>bar</td></tr></tbody></table>baz',
+ 'expected': 'foo^<table><tbody><tr><td>bar</td></tr></tbody></table>baz' }
+ ]
+ },
+
+ { 'desc': 'forward-delete within table cells',
+ 'tests': [
+ { 'id': 'TD-1_SE',
+ 'desc': 'Delete from end of last cell (should have no effect)',
+ 'pad': 'foo<table><tbody><tr><td>bar^</td></tr></tbody></table>baz',
+ 'expected': 'foo<table><tbody><tr><td>bar^</td></tr></tbody></table>baz' },
+
+ { 'id': 'TD2-1_SE1',
+ 'desc': 'Delete from end of inner cell (should have no effect)',
+ 'pad': 'foo<table><tbody><tr><td>bar^</td><td>baz</td></tr></tbody></table>quoz',
+ 'expected': 'foo<table><tbody><tr><td>bar^</td><td>baz</td></tr></tbody></table>quoz' },
+
+ { 'id': 'TD2-1_SM',
+ 'desc': 'Delete with selection spanning 2 cells',
+ 'pad': 'foo<table><tbody><tr><td>ba[r</td><td>b]az</td></tr></tbody></table>quoz',
+ 'expected': 'foo<table><tbody><tr><td>ba^</td><td>az</td></tr></tbody></table>quoz' }
+ ]
+ },
+
+ { 'desc': 'forward-delete table rows',
+ 'tests': [
+ { 'id': 'TR3-1_SO1',
+ 'desc': 'Delete first table row',
+ 'pad': '<table><tbody>{<tr><td>A</td></tr>}<tr><td>B</td></tr><tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody>|<tr><td>B</td></tr><tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>^B</td></tr><tr><td>C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3-1_SO2',
+ 'desc': 'Delete middle table row',
+ 'pad': '<table><tbody><tr><td>A</td></tr>{<tr><td>B</td></tr>}<tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td></tr>|<tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>A</td></tr><tr><td>^C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3-1_SO3',
+ 'desc': 'Delete last table row',
+ 'pad': '<table><tbody><tr><td>A</td></tr><tr><td>B</td></tr>{<tr><td>C</td></tr>}</tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td></tr><tr><td>B</td></tr>|</tbody></table>',
+ '<table><tbody><tr><td>A</td></tr><tr><td>B^</td></tr></tbody></table>'] },
+
+ { 'id': 'TR2rs:2-1_SO1',
+ 'desc': 'Delete first table row where a cell has rowspan 2',
+ 'pad': '<table><tbody>{<tr><td>A</td><td rowspan=2>R</td></tr>}<tr><td>B</td></tr></tbody></table>',
+ 'expected': ['<table><tbody>|<tr><td>B</td><td>R</td></tr></tbody></table>',
+ '<table><tbody><tr><td>^B</td><td>R</td></tr></tbody></table>'] },
+
+ { 'id': 'TR2rs:2-1_SO2',
+ 'desc': 'Delete second table row where a cell has rowspan 2',
+ 'pad': '<table><tbody><tr><td>A</td><td rowspan=2>R</td></tr>{<tr><td>B</td></tr>}</tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td><td>R</td></tr>|</tbody></table>',
+ '<table><tbody><tr><td>A</td><td>R^</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3rs:3-1_SO1',
+ 'desc': 'Delete first table row where a cell has rowspan 3',
+ 'pad': '<table><tbody>{<tr><td>A</td><td rowspan=3>R</td></tr>}<tr><td>B</td></tr><tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody>|<tr><td>A</td><td rowspan="2">R</td></tr><tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>^A</td><td rowspan="2">R</td></tr><tr><td>C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3rs:3-1_SO2',
+ 'desc': 'Delete middle table row where a cell has rowspan 3',
+ 'pad': '<table><tbody><tr><td>A</td><td rowspan=3>R</td></tr>{<tr><td>B</td></tr>}<tr><td>C</td></tr></tbody></table>',
+ 'expected': ['<table><tbody><tr><td>B</td><td rowspan="2">R</td></tr>|<tr><td>C</td></tr></tbody></table>',
+ '<table><tbody><tr><td>B</td><td rowspan="2">R</td></tr><tr><td>^C</td></tr></tbody></table>'] },
+
+ { 'id': 'TR3rs:3-1_SO3',
+ 'desc': 'Delete last table row where a cell has rowspan 3',
+ 'pad': '<table><tbody><tr><td>A</td><td rowspan=3>R</td></tr><tr><td>B</td></tr>{<tr><td>C</td></tr>}</tbody></table>',
+ 'expected': ['<table><tbody><tr><td>A</td><td rowspan="2">R</td></tr><tr><td>B</td></tr>|</tbody></table>',
+ '<table><tbody><tr><td>A</td><td rowspan="2">R</td></tr><tr><td>B^</td></tr></tbody></table>'] }
+ ]
+ },
+
+ { 'desc': 'delete with non-editable nested content',
+ 'tests': [
+ { 'id': 'DIV:ce:false-1_SO',
+ 'desc': 'Delete nested non-editable <div>',
+ 'pad': 'foo[bar<div contenteditable="false">NESTED</div>baz]qoz',
+ 'expected': 'foo^qoz' },
+
+ { 'id': 'DIV:ce:false-1_SB',
+ 'desc': 'Delete from immediately before a nested non-editable <div> (should be no-op)',
+ 'pad': 'foobar^<div contenteditable="false">NESTED</div>bazqoz',
+ 'expected': 'foobar^<div contenteditable="false">NESTED</div>bazqoz' },
+
+ { 'id': 'DIV:ce:false-1_SL',
+ 'desc': 'Delete nested non-editable <div> with oblique selection',
+ 'pad': 'foo[bar<div contenteditable="false">NES]TED</div>bazqoz',
+ 'expected': [ 'foo^<div contenteditable="false">NESTED</div>bazqoz',
+ 'foo<div contenteditable="false">[NES]TED</div>bazqoz' ] },
+
+ { 'id': 'DIV:ce:false-1_SR',
+ 'desc': 'Delete nested non-editable <div> with oblique selection',
+ 'pad': 'foobar<div contenteditable="false">NES[TED</div>baz]qoz',
+ 'expected': [ 'foobar<div contenteditable="false">NESTED</div>^qoz',
+ 'foobar<div contenteditable="false">NES[TED]</div>qoz' ] },
+
+ { 'id': 'DIV:ce:false-1_SI',
+ 'desc': 'Delete inside nested non-editable <div> (should be no-op)',
+ 'pad': 'foobar<div contenteditable="false">NE[ST]ED</div>bazqoz',
+ 'expected': 'foobar<div contenteditable="false">NE[ST]ED</div>bazqoz' }
+ ]
+ },
+
+ { 'desc': 'Delete with display:inline-block',
+ 'checkStyle': True,
+ 'tests': [
+ { 'id': 'SPAN:d:ib-1_SC',
+ 'desc': 'Delete inside an inline-block <span>',
+ 'pad': 'foo<span style="display: inline-block">bar^baz</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">bar^az</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-1_SA',
+ 'desc': 'Delete from immediately before an inline-block <span>',
+ 'pad': 'foo^<span style="display: inline-block">barbaz</span>qoz',
+ 'expected': 'foo^<span style="display: inline-block">arbaz</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-2_SL',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo[DEL<span style="display: inline-block">ETE]bar</span>baz',
+ 'expected': 'foo^<span style="display: inline-block">bar</span>baz' },
+
+ { 'id': 'SPAN:d:ib-3_SR',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">bar[DEL</span>ETE]baz',
+ 'expected': 'foo<span style="display: inline-block">bar^</span>baz' },
+
+ { 'id': 'SPAN:d:ib-4i_SI',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">bar[DELETE]baz</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">bar^baz</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-4l_SI',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">[DELETE]barbaz</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">^barbaz</span>qoz' },
+
+ { 'id': 'SPAN:d:ib-4r_SI',
+ 'desc': 'Delete with nested inline-block <span>, oblique selection',
+ 'pad': 'foo<span style="display: inline-block">barbaz[DELETE]</span>qoz',
+ 'expected': 'foo<span style="display: inline-block">barbaz^</span>qoz' }
+ ]
+ }
+ ]
+}
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/insert.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/insert.py
new file mode 100644
index 000000000..a2e79c27c
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/insert.py
@@ -0,0 +1,285 @@
+
+INSERT_TESTS = {
+ 'id': 'I',
+ 'caption': 'Insert Tests',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'insert <hr>',
+ 'command': 'inserthorizontalrule',
+ 'tests': [
+ { 'id': 'IHR_TEXT-1_SC',
+ 'rte1-id': 'a-inserthorizontalrule-0',
+ 'desc': 'Insert <hr> into text',
+ 'pad': 'foo^bar',
+ 'expected': 'foo<hr>^bar',
+ 'accept': 'foo<hr>|bar' },
+
+ { 'id': 'IHR_TEXT-1_SI',
+ 'desc': 'Insert <hr>, replacing selected text',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<hr>^baz',
+ 'accept': 'foo<hr>|baz' },
+
+ { 'id': 'IHR_DIV-B-1_SX',
+ 'desc': 'Insert <hr> between elements',
+ 'pad': '<div><b>foo</b>|<b>bar</b></div>',
+ 'expected': '<div><b>foo</b><hr>|<b>bar</b></div>' },
+
+ { 'id': 'IHR_DIV-B-2_SO',
+ 'desc': 'Insert <hr>, replacing a fully wrapped element',
+ 'pad': '<div><b>foo</b>{<b>bar</b>}<b>baz</b></div>',
+ 'expected': '<div><b>foo</b><hr>|<b>baz</b></div>' },
+
+ { 'id': 'IHR_B-1_SC',
+ 'desc': 'Insert <hr> into a span, splitting it',
+ 'pad': '<b>foo^bar</b>',
+ 'expected': '<b>foo</b><hr><b>^bar</b>' },
+
+ { 'id': 'IHR_B-1_SS',
+ 'desc': 'Insert <hr> into a span at the start (should not create an empty span)',
+ 'pad': '<b>^foobar</b>',
+ 'expected': '<hr><b>^foobar</b>' },
+
+ { 'id': 'IHR_B-1_SE',
+ 'desc': 'Insert <hr> into a span at the end',
+ 'pad': '<b>foobar^</b>',
+ 'expected': [ '<b>foobar</b><hr>|',
+ '<b>foobar</b><hr><b>^</b>' ] },
+
+ { 'id': 'IHR_B-2_SL',
+ 'desc': 'Insert <hr> with oblique selection starting outside of span',
+ 'pad': 'foo[bar<b>baz]qoz</b>',
+ 'expected': 'foo<hr>|<b>qoz</b>' },
+
+ { 'id': 'IHR_B-2_SLR',
+ 'desc': 'Insert <hr> with oblique reversed selection starting outside of span',
+ 'pad': 'foo]bar<b>baz[qoz</b>',
+ 'expected': [ 'foo<hr>|<b>qoz</b>',
+ 'foo<hr><b>^qoz</b>' ] },
+
+ { 'id': 'IHR_B-3_SR',
+ 'desc': 'Insert <hr> with oblique selection ending outside of span',
+ 'pad': '<b>foo[bar</b>baz]quoz',
+ 'expected': [ '<b>foo</b><hr>|quoz',
+ '<b>foo</b><hr><b>^</b>quoz' ] },
+
+ { 'id': 'IHR_B-3_SRR',
+ 'desc': 'Insert <hr> with oblique reversed selection starting outside of span',
+ 'pad': '<b>foo]bar</b>baz[quoz',
+ 'expected': '<b>foo</b><hr>|quoz' },
+
+ { 'id': 'IHR_B-I-1_SM',
+ 'desc': 'Insert <hr> with oblique selection between different spans',
+ 'pad': '<b>foo[bar</b><i>baz]quoz</i>',
+ 'expected': [ '<b>foo</b><hr>|<i>quoz</i>',
+ '<b>foo</b><hr><b>^</b><i>quoz</i>' ] },
+
+ { 'id': 'IHR_B-I-1_SMR',
+ 'desc': 'Insert <hr> with reversed oblique selection between different spans',
+ 'pad': '<b>foo]bar</b><i>baz[quoz</i>',
+ 'expected': '<b>foo</b><hr><i>^quoz</i>' },
+
+ { 'id': 'IHR_P-1_SC',
+ 'desc': 'Insert <hr> into a paragraph, splitting it',
+ 'pad': '<p>foo^bar</p>',
+ 'expected': [ '<p>foo</p><hr>|<p>bar</p>',
+ '<p>foo</p><hr><p>^bar</p>' ] },
+
+ { 'id': 'IHR_P-1_SS',
+ 'desc': 'Insert <hr> into a paragraph at the start (should not create an empty span)',
+ 'pad': '<p>^foobar</p>',
+ 'expected': [ '<hr>|<p>foobar</p>',
+ '<hr><p>^foobar</p>' ] },
+
+ { 'id': 'IHR_P-1_SE',
+ 'desc': 'Insert <hr> into a paragraph at the end (should not create an empty span)',
+ 'pad': '<p>foobar^</p>',
+ 'expected': '<p>foobar</p><hr>|' }
+ ]
+ },
+
+ { 'desc': 'insert <p>',
+ 'command': 'insertparagraph',
+ 'tests': [
+ { 'id': 'IP_P-1_SC',
+ 'desc': 'Split paragraph',
+ 'pad': '<p>foo^bar</p>',
+ 'expected': '<p>foo</p><p>^bar</p>' },
+
+ { 'id': 'IP_UL-LI-1_SC',
+ 'desc': 'Split list item',
+ 'pad': '<ul><li>foo^bar</li></ul>',
+ 'expected': '<ul><li>foo</li><li>^bar</li></ul>' }
+ ]
+ },
+
+ { 'desc': 'insert text',
+ 'command': 'inserttext',
+ 'tests': [
+ { 'id': 'ITEXT:text_TEXT-1_SC',
+ 'desc': 'Insert text',
+ 'value': 'text',
+ 'pad': 'foo^bar',
+ 'expected': 'footext^bar' },
+
+ { 'id': 'ITEXT:text_TEXT-1_SI',
+ 'desc': 'Insert text, replacing selected text',
+ 'value': 'text',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'footext^baz' }
+ ]
+ },
+
+ { 'desc': 'insert <br>',
+ 'command': 'insertlinebreak',
+ 'tests': [
+ { 'id': 'IBR_TEXT-1_SC',
+ 'desc': 'Insert <br> into text',
+ 'pad': 'foo^bar',
+ 'expected': [ 'foo<br>|bar',
+ 'foo<br>^bar' ] },
+
+ { 'id': 'IBR_TEXT-1_SI',
+ 'desc': 'Insert <br>, replacing selected text',
+ 'pad': 'foo[bar]baz',
+ 'expected': [ 'foo<br>|baz',
+ 'foo<br>^baz' ] },
+
+ { 'id': 'IBR_LI-1_SC',
+ 'desc': 'Insert <br> within list item',
+ 'pad': '<ul><li>foo^bar</li></ul>',
+ 'expected': '<ul><li>foo<br>^bar</li></ul>' }
+ ]
+ },
+
+ { 'desc': 'insert <img>',
+ 'command': 'insertimage',
+ 'tests': [
+ { 'id': 'IIMG:url_TEXT-1_SC',
+ 'rte1-id': 'a-insertimage-0',
+ 'desc': 'Insert image with URL "bar.png"',
+ 'value': 'bar.png',
+ 'checkAttrs': True,
+ 'pad': 'foo^bar',
+ 'expected': [ 'foo<img src="bar.png">|bar',
+ 'foo<img src="bar.png">^bar' ] },
+
+ { 'id': 'IIMG:url_IMG-1_SO',
+ 'desc': 'Change existing image to new URL, selection on <img>',
+ 'value': 'quz.png',
+ 'checkAttrs': True,
+ 'pad': '<span>foo{<img src="bar.png">}bar</span>',
+ 'expected': [ '<span>foo<img src="quz.png"/>|bar</span>',
+ '<span>foo<img src="quz.png"/>^bar</span>' ] },
+
+ { 'id': 'IIMG:url_SPAN-IMG-1_SO',
+ 'desc': 'Change existing image to new URL, selection in text surrounding <img>',
+ 'value': 'quz.png',
+ 'checkAttrs': True,
+ 'pad': 'foo[<img src="bar.png">]bar',
+ 'expected': [ 'foo<img src="quz.png"/>|bar',
+ 'foo<img src="quz.png"/>^bar' ] },
+
+ { 'id': 'IIMG:._SPAN-IMG-1_SO',
+ 'desc': 'Remove existing image or URL, selection on <img>',
+ 'value': '',
+ 'checkAttrs': True,
+ 'pad': '<span>foo{<img src="bar.png">}bar</span>',
+ 'expected': [ '<span>foo^bar</span>',
+ '<span>foo<img>|bar</span>',
+ '<span>foo<img>^bar</span>',
+ '<span>foo<img src="">|bar</span>',
+ '<span>foo<img src="">^bar</span>' ] },
+
+ { 'id': 'IIMG:._IMG-1_SO',
+ 'desc': 'Remove existing image or URL, selection in text surrounding <img>',
+ 'value': '',
+ 'checkAttrs': True,
+ 'pad': 'foo[<img src="bar.png">]bar',
+ 'expected': [ 'foo^bar',
+ 'foo<img>|bar',
+ 'foo<img>^bar',
+ 'foo<img src="">|bar',
+ 'foo<img src="">^bar' ] }
+ ]
+ },
+
+ { 'desc': 'insert <ol>',
+ 'command': 'insertorderedlist',
+ 'tests': [
+ { 'id': 'IOL_TEXT-1_SC',
+ 'rte1-id': 'a-insertorderedlist-0',
+ 'desc': 'Insert ordered list on collapsed selection',
+ 'pad': 'foo^bar',
+ 'expected': '<ol><li>foo^bar</li></ol>' },
+
+ { 'id': 'IOL_TEXT-1_SI',
+ 'desc': 'Insert ordered list on selected text',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<ol><li>foo[bar]baz</li></ol>' }
+ ]
+ },
+
+ { 'desc': 'insert <ul>',
+ 'command': 'insertunorderedlist',
+ 'tests': [
+ { 'id': 'IUL_TEXT-1_SC',
+ 'desc': 'Insert unordered list on collapsed selection',
+ 'pad': 'foo^bar',
+ 'expected': '<ul><li>foo^bar</li></ul>' },
+
+ { 'id': 'IUL_TEXT-1_SI',
+ 'rte1-id': 'a-insertunorderedlist-0',
+ 'desc': 'Insert unordered list on selected text',
+ 'pad': 'foo[bar]baz',
+ 'expected': '<ul><li>foo[bar]baz</li></ul>' }
+ ]
+ },
+
+ { 'desc': 'insert arbitrary HTML',
+ 'command': 'inserthtml',
+ 'tests': [
+ { 'id': 'IHTML:BR_TEXT-1_SC',
+ 'rte1-id': 'a-inserthtml-0',
+ 'desc': 'InsertHTML: <br>',
+ 'value': '<br>',
+ 'pad': 'foo^barbaz',
+ 'expected': 'foo<br>^barbaz' },
+
+ { 'id': 'IHTML:text_TEXT-1_SI',
+ 'desc': 'InsertHTML: "NEW"',
+ 'value': 'NEW',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'fooNEW^baz' },
+
+ { 'id': 'IHTML:S_TEXT-1_SI',
+ 'desc': 'InsertHTML: "<span>NEW<span>"',
+ 'value': '<span>NEW</span>',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<span>NEW</span>^baz' },
+
+ { 'id': 'IHTML:H1.H2_TEXT-1_SI',
+ 'desc': 'InsertHTML: "<h1>NEW</h1><h2>HTML</h2>"',
+ 'value': '<h1>NEW</h1><h2>HTML</h2>',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<h1>NEW</h1><h2>HTML</h2>^baz' },
+
+ { 'id': 'IHTML:P-B_TEXT-1_SI',
+ 'desc': 'InsertHTML: "<p>NEW<b>HTML</b>!</p>"',
+ 'value': '<p>NEW<b>HTML</b>!</p>',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'foo<p>NEW<b>HTML</b>!</p>^baz' }
+ ]
+ }
+ ]
+}
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryEnabled.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryEnabled.py
new file mode 100644
index 000000000..eb721923b
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryEnabled.py
@@ -0,0 +1,215 @@
+
+QUERYENABLED_TESTS = {
+ 'id': 'QE',
+ 'caption': 'queryCommandEnabled Tests',
+ 'pad': 'foo[bar]baz',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': False,
+ 'expected': True,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'HTML5 commands',
+ 'tests': [
+ { 'id': 'SELECTALL_TEXT-1',
+ 'desc': 'check whether the "selectall" command is enabled',
+ 'qcenabled': 'selectall' },
+
+ { 'id': 'UNSELECT_TEXT-1',
+ 'desc': 'check whether the "unselect" command is enabled',
+ 'qcenabled': 'unselect' },
+
+ { 'id': 'UNDO_TEXT-1',
+ 'desc': 'check whether the "undo" command is enabled',
+ 'qcenabled': 'undo' },
+
+ { 'id': 'REDO_TEXT-1',
+ 'desc': 'check whether the "redo" command is enabled',
+ 'qcenabled': 'redo' },
+
+ { 'id': 'BOLD_TEXT-1',
+ 'desc': 'check whether the "bold" command is enabled',
+ 'qcenabled': 'bold' },
+
+ { 'id': 'ITALIC_TEXT-1',
+ 'desc': 'check whether the "italic" command is enabled',
+ 'qcenabled': 'italic' },
+
+ { 'id': 'UNDERLINE_TEXT-1',
+ 'desc': 'check whether the "underline" command is enabled',
+ 'qcenabled': 'underline' },
+
+ { 'id': 'STRIKETHROUGH_TEXT-1',
+ 'desc': 'check whether the "strikethrough" command is enabled',
+ 'qcenabled': 'strikethrough' },
+
+ { 'id': 'SUBSCRIPT_TEXT-1',
+ 'desc': 'check whether the "subscript" command is enabled',
+ 'qcenabled': 'subscript' },
+
+ { 'id': 'SUPERSCRIPT_TEXT-1',
+ 'desc': 'check whether the "superscript" command is enabled',
+ 'qcenabled': 'superscript' },
+
+ { 'id': 'FORMATBLOCK_TEXT-1',
+ 'desc': 'check whether the "formatblock" command is enabled',
+ 'qcenabled': 'formatblock' },
+
+ { 'id': 'CREATELINK_TEXT-1',
+ 'desc': 'check whether the "createlink" command is enabled',
+ 'qcenabled': 'createlink' },
+
+ { 'id': 'UNLINK_TEXT-1',
+ 'desc': 'check whether the "unlink" command is enabled',
+ 'qcenabled': 'unlink' },
+
+ { 'id': 'INSERTHTML_TEXT-1',
+ 'desc': 'check whether the "inserthtml" command is enabled',
+ 'qcenabled': 'inserthtml' },
+
+ { 'id': 'INSERTHORIZONTALRULE_TEXT-1',
+ 'desc': 'check whether the "inserthorizontalrule" command is enabled',
+ 'qcenabled': 'inserthorizontalrule' },
+
+ { 'id': 'INSERTIMAGE_TEXT-1',
+ 'desc': 'check whether the "insertimage" command is enabled',
+ 'qcenabled': 'insertimage' },
+
+ { 'id': 'INSERTLINEBREAK_TEXT-1',
+ 'desc': 'check whether the "insertlinebreak" command is enabled',
+ 'qcenabled': 'insertlinebreak' },
+
+ { 'id': 'INSERTPARAGRAPH_TEXT-1',
+ 'desc': 'check whether the "insertparagraph" command is enabled',
+ 'qcenabled': 'insertparagraph' },
+
+ { 'id': 'INSERTORDEREDLIST_TEXT-1',
+ 'desc': 'check whether the "insertorderedlist" command is enabled',
+ 'qcenabled': 'insertorderedlist' },
+
+ { 'id': 'INSERTUNORDEREDLIST_TEXT-1',
+ 'desc': 'check whether the "insertunorderedlist" command is enabled',
+ 'qcenabled': 'insertunorderedlist' },
+
+ { 'id': 'INSERTTEXT_TEXT-1',
+ 'desc': 'check whether the "inserttext" command is enabled',
+ 'qcenabled': 'inserttext' },
+
+ { 'id': 'DELETE_TEXT-1',
+ 'desc': 'check whether the "delete" command is enabled',
+ 'qcenabled': 'delete' },
+
+ { 'id': 'FORWARDDELETE_TEXT-1',
+ 'desc': 'check whether the "forwarddelete" command is enabled',
+ 'qcenabled': 'forwarddelete' }
+ ]
+ },
+
+ { 'desc': 'MIDAS commands',
+ 'tests': [
+ { 'id': 'STYLEWITHCSS_TEXT-1',
+ 'desc': 'check whether the "styleWithCSS" command is enabled',
+ 'qcenabled': 'styleWithCSS' },
+
+ { 'id': 'CONTENTREADONLY_TEXT-1',
+ 'desc': 'check whether the "contentreadonly" command is enabled',
+ 'qcenabled': 'contentreadonly' },
+
+ { 'id': 'BACKCOLOR_TEXT-1',
+ 'desc': 'check whether the "backcolor" command is enabled',
+ 'qcenabled': 'backcolor' },
+
+ { 'id': 'FORECOLOR_TEXT-1',
+ 'desc': 'check whether the "forecolor" command is enabled',
+ 'qcenabled': 'forecolor' },
+
+ { 'id': 'HILITECOLOR_TEXT-1',
+ 'desc': 'check whether the "hilitecolor" command is enabled',
+ 'qcenabled': 'hilitecolor' },
+
+ { 'id': 'FONTNAME_TEXT-1',
+ 'desc': 'check whether the "fontname" command is enabled',
+ 'qcenabled': 'fontname' },
+
+ { 'id': 'FONTSIZE_TEXT-1',
+ 'desc': 'check whether the "fontsize" command is enabled',
+ 'qcenabled': 'fontsize' },
+
+ { 'id': 'INCREASEFONTSIZE_TEXT-1',
+ 'desc': 'check whether the "increasefontsize" command is enabled',
+ 'qcenabled': 'increasefontsize' },
+
+ { 'id': 'DECREASEFONTSIZE_TEXT-1',
+ 'desc': 'check whether the "decreasefontsize" command is enabled',
+ 'qcenabled': 'decreasefontsize' },
+
+ { 'id': 'HEADING_TEXT-1',
+ 'desc': 'check whether the "heading" command is enabled',
+ 'qcenabled': 'heading' },
+
+ { 'id': 'INDENT_TEXT-1',
+ 'desc': 'check whether the "indent" command is enabled',
+ 'qcenabled': 'indent' },
+
+ { 'id': 'OUTDENT_TEXT-1',
+ 'desc': 'check whether the "outdent" command is enabled',
+ 'qcenabled': 'outdent' },
+
+ { 'id': 'CREATEBOOKMARK_TEXT-1',
+ 'desc': 'check whether the "createbookmark" command is enabled',
+ 'qcenabled': 'createbookmark' },
+
+ { 'id': 'UNBOOKMARK_TEXT-1',
+ 'desc': 'check whether the "unbookmark" command is enabled',
+ 'qcenabled': 'unbookmark' },
+
+ { 'id': 'JUSTIFYCENTER_TEXT-1',
+ 'desc': 'check whether the "justifycenter" command is enabled',
+ 'qcenabled': 'justifycenter' },
+
+ { 'id': 'JUSTIFYFULL_TEXT-1',
+ 'desc': 'check whether the "justifyfull" command is enabled',
+ 'qcenabled': 'justifyfull' },
+
+ { 'id': 'JUSTIFYLEFT_TEXT-1',
+ 'desc': 'check whether the "justifyleft" command is enabled',
+ 'qcenabled': 'justifyleft' },
+
+ { 'id': 'JUSTIFYRIGHT_TEXT-1',
+ 'desc': 'check whether the "justifyright" command is enabled',
+ 'qcenabled': 'justifyright' },
+
+ { 'id': 'REMOVEFORMAT_TEXT-1',
+ 'desc': 'check whether the "removeformat" command is enabled',
+ 'qcenabled': 'removeformat' },
+
+ { 'id': 'COPY_TEXT-1',
+ 'desc': 'check whether the "copy" command is enabled',
+ 'qcenabled': 'copy' },
+
+ { 'id': 'CUT_TEXT-1',
+ 'desc': 'check whether the "cut" command is enabled',
+ 'qcenabled': 'cut' },
+
+ { 'id': 'PASTE_TEXT-1',
+ 'desc': 'check whether the "paste" command is enabled',
+ 'qcenabled': 'paste' }
+ ]
+ },
+
+ { 'desc': 'Other tests',
+ 'tests': [
+ { 'id': 'garbage-1_TEXT-1',
+ 'desc': 'check correct return value with garbage input',
+ 'qcenabled': '#!#@7',
+ 'expected': False }
+ ]
+ }
+ ]
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryIndeterm.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryIndeterm.py
new file mode 100644
index 000000000..d1ad8debd
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryIndeterm.py
@@ -0,0 +1,214 @@
+
+QUERYINDETERM_TESTS = {
+ 'id': 'QI',
+ 'caption': 'queryCommandIndeterm Tests',
+ 'pad': 'foo[bar]baz',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': False,
+ 'expected': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'HTML5 commands',
+ 'tests': [
+ { 'id': 'SELECTALL_TEXT-1',
+ 'desc': 'check whether the "selectall" command is indeterminate',
+ 'qcindeterm': 'selectall' },
+
+ { 'id': 'UNSELECT_TEXT-1',
+ 'desc': 'check whether the "unselect" command is indeterminate',
+ 'qcindeterm': 'unselect' },
+
+ { 'id': 'UNDO_TEXT-1',
+ 'desc': 'check whether the "undo" command is indeterminate',
+ 'qcindeterm': 'undo' },
+
+ { 'id': 'REDO_TEXT-1',
+ 'desc': 'check whether the "redo" command is indeterminate',
+ 'qcindeterm': 'redo' },
+
+ { 'id': 'BOLD_TEXT-1',
+ 'desc': 'check whether the "bold" command is indeterminate',
+ 'qcindeterm': 'bold' },
+
+ { 'id': 'ITALIC_TEXT-1',
+ 'desc': 'check whether the "italic" command is indeterminate',
+ 'qcindeterm': 'italic' },
+
+ { 'id': 'UNDERLINE_TEXT-1',
+ 'desc': 'check whether the "underline" command is indeterminate',
+ 'qcindeterm': 'underline' },
+
+ { 'id': 'STRIKETHROUGH_TEXT-1',
+ 'desc': 'check whether the "strikethrough" command is indeterminate',
+ 'qcindeterm': 'strikethrough' },
+
+ { 'id': 'SUBSCRIPT_TEXT-1',
+ 'desc': 'check whether the "subscript" command is indeterminate',
+ 'qcindeterm': 'subscript' },
+
+ { 'id': 'SUPERSCRIPT_TEXT-1',
+ 'desc': 'check whether the "superscript" command is indeterminate',
+ 'qcindeterm': 'superscript' },
+
+ { 'id': 'FORMATBLOCK_TEXT-1',
+ 'desc': 'check whether the "formatblock" command is indeterminate',
+ 'qcindeterm': 'formatblock' },
+
+ { 'id': 'CREATELINK_TEXT-1',
+ 'desc': 'check whether the "createlink" command is indeterminate',
+ 'qcindeterm': 'createlink' },
+
+ { 'id': 'UNLINK_TEXT-1',
+ 'desc': 'check whether the "unlink" command is indeterminate',
+ 'qcindeterm': 'unlink' },
+
+ { 'id': 'INSERTHTML_TEXT-1',
+ 'desc': 'check whether the "inserthtml" command is indeterminate',
+ 'qcindeterm': 'inserthtml' },
+
+ { 'id': 'INSERTHORIZONTALRULE_TEXT-1',
+ 'desc': 'check whether the "inserthorizontalrule" command is indeterminate',
+ 'qcindeterm': 'inserthorizontalrule' },
+
+ { 'id': 'INSERTIMAGE_TEXT-1',
+ 'desc': 'check whether the "insertimage" command is indeterminate',
+ 'qcindeterm': 'insertimage' },
+
+ { 'id': 'INSERTLINEBREAK_TEXT-1',
+ 'desc': 'check whether the "insertlinebreak" command is indeterminate',
+ 'qcindeterm': 'insertlinebreak' },
+
+ { 'id': 'INSERTPARAGRAPH_TEXT-1',
+ 'desc': 'check whether the "insertparagraph" command is indeterminate',
+ 'qcindeterm': 'insertparagraph' },
+
+ { 'id': 'INSERTORDEREDLIST_TEXT-1',
+ 'desc': 'check whether the "insertorderedlist" command is indeterminate',
+ 'qcindeterm': 'insertorderedlist' },
+
+ { 'id': 'INSERTUNORDEREDLIST_TEXT-1',
+ 'desc': 'check whether the "insertunorderedlist" command is indeterminate',
+ 'qcindeterm': 'insertunorderedlist' },
+
+ { 'id': 'INSERTTEXT_TEXT-1',
+ 'desc': 'check whether the "inserttext" command is indeterminate',
+ 'qcindeterm': 'inserttext' },
+
+ { 'id': 'DELETE_TEXT-1',
+ 'desc': 'check whether the "delete" command is indeterminate',
+ 'qcindeterm': 'delete' },
+
+ { 'id': 'FORWARDDELETE_TEXT-1',
+ 'desc': 'check whether the "forwarddelete" command is indeterminate',
+ 'qcindeterm': 'forwarddelete' }
+ ]
+ },
+
+ { 'desc': 'MIDAS commands',
+ 'tests': [
+ { 'id': 'STYLEWITHCSS_TEXT-1',
+ 'desc': 'check whether the "styleWithCSS" command is indeterminate',
+ 'qcindeterm': 'styleWithCSS' },
+
+ { 'id': 'CONTENTREADONLY_TEXT-1',
+ 'desc': 'check whether the "contentreadonly" command is indeterminate',
+ 'qcindeterm': 'contentreadonly' },
+
+ { 'id': 'BACKCOLOR_TEXT-1',
+ 'desc': 'check whether the "backcolor" command is indeterminate',
+ 'qcindeterm': 'backcolor' },
+
+ { 'id': 'FORECOLOR_TEXT-1',
+ 'desc': 'check whether the "forecolor" command is indeterminate',
+ 'qcindeterm': 'forecolor' },
+
+ { 'id': 'HILITECOLOR_TEXT-1',
+ 'desc': 'check whether the "hilitecolor" command is indeterminate',
+ 'qcindeterm': 'hilitecolor' },
+
+ { 'id': 'FONTNAME_TEXT-1',
+ 'desc': 'check whether the "fontname" command is indeterminate',
+ 'qcindeterm': 'fontname' },
+
+ { 'id': 'FONTSIZE_TEXT-1',
+ 'desc': 'check whether the "fontsize" command is indeterminate',
+ 'qcindeterm': 'fontsize' },
+
+ { 'id': 'INCREASEFONTSIZE_TEXT-1',
+ 'desc': 'check whether the "increasefontsize" command is indeterminate',
+ 'qcindeterm': 'increasefontsize' },
+
+ { 'id': 'DECREASEFONTSIZE_TEXT-1',
+ 'desc': 'check whether the "decreasefontsize" command is indeterminate',
+ 'qcindeterm': 'decreasefontsize' },
+
+ { 'id': 'HEADING_TEXT-1',
+ 'desc': 'check whether the "heading" command is indeterminate',
+ 'qcindeterm': 'heading' },
+
+ { 'id': 'INDENT_TEXT-1',
+ 'desc': 'check whether the "indent" command is indeterminate',
+ 'qcindeterm': 'indent' },
+
+ { 'id': 'OUTDENT_TEXT-1',
+ 'desc': 'check whether the "outdent" command is indeterminate',
+ 'qcindeterm': 'outdent' },
+
+ { 'id': 'CREATEBOOKMARK_TEXT-1',
+ 'desc': 'check whether the "createbookmark" command is indeterminate',
+ 'qcindeterm': 'createbookmark' },
+
+ { 'id': 'UNBOOKMARK_TEXT-1',
+ 'desc': 'check whether the "unbookmark" command is indeterminate',
+ 'qcindeterm': 'unbookmark' },
+
+ { 'id': 'JUSTIFYCENTER_TEXT-1',
+ 'desc': 'check whether the "justifycenter" command is indeterminate',
+ 'qcindeterm': 'justifycenter' },
+
+ { 'id': 'JUSTIFYFULL_TEXT-1',
+ 'desc': 'check whether the "justifyfull" command is indeterminate',
+ 'qcindeterm': 'justifyfull' },
+
+ { 'id': 'JUSTIFYLEFT_TEXT-1',
+ 'desc': 'check whether the "justifyleft" command is indeterminate',
+ 'qcindeterm': 'justifyleft' },
+
+ { 'id': 'JUSTIFYRIGHT_TEXT-1',
+ 'desc': 'check whether the "justifyright" command is indeterminate',
+ 'qcindeterm': 'justifyright' },
+
+ { 'id': 'REMOVEFORMAT_TEXT-1',
+ 'desc': 'check whether the "removeformat" command is indeterminate',
+ 'qcindeterm': 'removeformat' },
+
+ { 'id': 'COPY_TEXT-1',
+ 'desc': 'check whether the "copy" command is indeterminate',
+ 'qcindeterm': 'copy' },
+
+ { 'id': 'CUT_TEXT-1',
+ 'desc': 'check whether the "cut" command is indeterminate',
+ 'qcindeterm': 'cut' },
+
+ { 'id': 'PASTE_TEXT-1',
+ 'desc': 'check whether the "paste" command is indeterminate',
+ 'qcindeterm': 'paste' }
+ ]
+ },
+
+ { 'desc': 'Other tests',
+ 'tests': [
+ { 'id': 'garbage-1_TEXT-1',
+ 'desc': 'check correct return value with garbage input',
+ 'qcindeterm': '#!#@7' }
+ ]
+ }
+ ]
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryState.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryState.py
new file mode 100644
index 000000000..297559d62
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryState.py
@@ -0,0 +1,575 @@
+
+QUERYSTATE_TESTS = {
+ 'id': 'QS',
+ 'caption': 'queryCommandState Tests',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'qcstate': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'query bold state',
+ 'qcstate': 'bold',
+ 'tests': [
+ { 'id': 'B_TEXT_SI',
+ 'rte1-id': 'q-bold-0',
+ 'desc': 'query the "bold" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'B_B-1_SI',
+ 'rte1-id': 'q-bold-1',
+ 'desc': 'query the "bold" state',
+ 'pad': '<b>foo[bar]baz</b>',
+ 'expected': True },
+
+ { 'id': 'B_STRONG-1_SI',
+ 'rte1-id': 'q-bold-2',
+ 'desc': 'query the "bold" state',
+ 'pad': '<strong>foo[bar]baz</strong>',
+ 'expected': True },
+
+ { 'id': 'B_SPANs:fw:b-1_SI',
+ 'rte1-id': 'q-bold-3',
+ 'desc': 'query the "bold" state',
+ 'pad': '<span style="font-weight: bold">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'B_SPANs:fw:n-1_SI',
+ 'desc': 'query the "bold" state',
+ 'pad': '<span style="font-weight: normal">foo[bar]baz</span>',
+ 'expected': False },
+
+ { 'id': 'B_Bs:fw:n-1_SI',
+ 'rte1-id': 'q-bold-4',
+ 'desc': 'query the "bold" state',
+ 'pad': '<span style="font-weight: normal">foo[bar]baz</span>',
+ 'expected': False },
+
+ { 'id': 'B_B-SPANs:fw:n-1_SI',
+ 'rte1-id': 'q-bold-5',
+ 'desc': 'query the "bold" state',
+ 'pad': '<b><span style="font-weight: normal">foo[bar]baz</span></b>',
+ 'expected': False },
+
+ { 'id': 'B_SPAN.b-1-SI',
+ 'desc': 'query the "bold" state',
+ 'pad': '<span class="b">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'B_MYB-1-SI',
+ 'desc': 'query the "bold" state',
+ 'pad': '<myb>foo[bar]baz</myb>',
+ 'expected': True },
+
+ { 'id': 'B_B-I-1_SC',
+ 'desc': 'query the "bold" state, bold tag not immediate parent',
+ 'pad': '<b>foo<i>ba^r</i>baz</b>',
+ 'expected': True },
+
+ { 'id': 'B_B-I-1_SL',
+ 'desc': 'query the "bold" state, selection partially in child element',
+ 'pad': '<b>fo[o<i>b]ar</i>baz</b>',
+ 'expected': True },
+
+ { 'id': 'B_B-I-1_SR',
+ 'desc': 'query the "bold" state, selection partially in child element',
+ 'pad': '<b>foo<i>ba[r</i>b]az</b>',
+ 'expected': True },
+
+ { 'id': 'B_STRONG-I-1_SC',
+ 'desc': 'query the "bold" state, bold tag not immediate parent',
+ 'pad': '<strong>foo<i>ba^r</i>baz</strong>',
+ 'expected': True },
+
+ { 'id': 'B_B-I-U-1_SC',
+ 'desc': 'query the "bold" state, bold tag not immediate parent',
+ 'pad': '<b>foo<i>bar<u>b^az</u></i></strong>',
+ 'expected': True },
+
+ { 'id': 'B_B-I-U-1_SM',
+ 'desc': 'query the "bold" state, bold tag not immediate parent',
+ 'pad': '<b>foo<i>ba[r<u>b]az</u></i></strong>',
+ 'expected': True },
+
+ { 'id': 'B_TEXT-B-1_SO-1',
+ 'desc': 'query the "bold" state, selection wrapping the bold tag',
+ 'pad': 'foo[<b>bar</b>]baz',
+ 'expected': True },
+
+ { 'id': 'B_TEXT-B-1_SO-2',
+ 'desc': 'query the "bold" state, selection wrapping the bold tag',
+ 'pad': 'foo{<b>bar</b>}baz',
+ 'expected': True },
+
+ { 'id': 'B_TEXT-B-1_SL',
+ 'desc': 'query the "bold" state, selection containing non-bold text',
+ 'pad': 'fo[o<b>ba]r</b>baz',
+ 'expected': False },
+
+ { 'id': 'B_TEXT-B-1_SR',
+ 'desc': 'query the "bold" state, selection containing non-bold text',
+ 'pad': 'foo<b>b[ar</b>b]az',
+ 'expected': False },
+
+ { 'id': 'B_TEXT-B-1_SO-3',
+ 'desc': 'query the "bold" state, selection containing non-bold text',
+ 'pad': 'fo[o<b>bar</b>b]az',
+ 'expected': False },
+
+ { 'id': 'B_B.TEXT.B-1_SM',
+ 'desc': 'query the "bold" state, selection including non-bold text',
+ 'pad': '<b>fo[o</b>bar<b>b]az</b>',
+ 'expected': False },
+
+ { 'id': 'B_B.B.B-1_SM',
+ 'desc': 'query the "bold" state, selection mixed, but all bold',
+ 'pad': '<b>fo[o</b><b>bar</b><b>b]az</b>',
+ 'expected': True },
+
+ { 'id': 'B_B.STRONG.B-1_SM',
+ 'desc': 'query the "bold" state, selection mixed, but all bold',
+ 'pad': '<b>fo[o</b><strong>bar</strong><b>b]az</b>',
+ 'expected': True },
+
+ { 'id': 'B_SPAN.b.MYB.SPANs:fw:b-1_SM',
+ 'desc': 'query the "bold" state, selection mixed, but all bold',
+ 'pad': '<span class="b">fo[o</span><myb>bar</myb><span style="font-weight: bold">b]az</span>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query italic state',
+ 'qcstate': 'italic',
+ 'tests': [
+ { 'id': 'I_TEXT_SI',
+ 'rte1-id': 'q-italic-0',
+ 'desc': 'query the "italic" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'I_I-1_SI',
+ 'rte1-id': 'q-italic-1',
+ 'desc': 'query the "italic" state',
+ 'pad': '<i>foo[bar]baz</i>',
+ 'expected': True },
+
+ { 'id': 'I_EM-1_SI',
+ 'rte1-id': 'q-italic-2',
+ 'desc': 'query the "italic" state',
+ 'pad': '<em>foo[bar]baz</em>',
+ 'expected': True },
+
+ { 'id': 'I_SPANs:fs:i-1_SI',
+ 'rte1-id': 'q-italic-3',
+ 'desc': 'query the "italic" state',
+ 'pad': '<span style="font-style: italic">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'I_SPANs:fs:n-1_SI',
+ 'desc': 'query the "italic" state',
+ 'pad': '<span style="font-style: normal">foo[bar]baz</span>',
+ 'expected': False },
+
+ { 'id': 'I_I-SPANs:fs:n-1_SI',
+ 'rte1-id': 'q-italic-4',
+ 'desc': 'query the "italic" state',
+ 'pad': '<i><span style="font-style: normal">foo[bar]baz</span></i>',
+ 'expected': False },
+
+ { 'id': 'I_SPAN.i-1-SI',
+ 'desc': 'query the "italic" state',
+ 'pad': '<span class="i">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'I_MYI-1-SI',
+ 'desc': 'query the "italic" state',
+ 'pad': '<myi>foo[bar]baz</myi>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query underline state',
+ 'qcstate': 'underline',
+ 'tests': [
+ { 'id': 'U_TEXT_SI',
+ 'rte1-id': 'q-underline-0',
+ 'desc': 'query the "underline" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'U_U-1_SI',
+ 'rte1-id': 'q-underline-1',
+ 'desc': 'query the "underline" state',
+ 'pad': '<u>foo[bar]baz</u>',
+ 'expected': True },
+
+ { 'id': 'U_Us:td:n-1_SI',
+ 'rte1-id': 'q-underline-4',
+ 'desc': 'query the "underline" state',
+ 'pad': '<u style="text-decoration: none">foo[bar]baz</u>',
+ 'expected': False },
+
+ { 'id': 'U_Ah:url-1_SI',
+ 'rte1-id': 'q-underline-2',
+ 'desc': 'query the "underline" state',
+ 'pad': '<a href="http://www.goo.gl">foo[bar]baz</a>',
+ 'expected': True },
+
+ { 'id': 'U_Ah:url.s:td:n-1_SI',
+ 'rte1-id': 'q-underline-5',
+ 'desc': 'query the "underline" state',
+ 'pad': '<a href="http://www.goo.gl" style="text-decoration: none">foo[bar]baz</a>',
+ 'expected': False },
+
+ { 'id': 'U_SPANs:td:u-1_SI',
+ 'rte1-id': 'q-underline-3',
+ 'desc': 'query the "underline" state',
+ 'pad': '<span style="text-decoration: underline">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'U_SPAN.u-1-SI',
+ 'desc': 'query the "underline" state',
+ 'pad': '<span class="u">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'U_MYU-1-SI',
+ 'desc': 'query the "underline" state',
+ 'pad': '<myu>foo[bar]baz</myu>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query strike-through state',
+ 'qcstate': 'strikethrough',
+ 'tests': [
+ { 'id': 'S_TEXT_SI',
+ 'rte1-id': 'q-strikethrough-0',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'S_S-1_SI',
+ 'rte1-id': 'q-strikethrough-3',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<s>foo[bar]baz</s>',
+ 'expected': True },
+
+ { 'id': 'S_STRIKE-1_SI',
+ 'rte1-id': 'q-strikethrough-1',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<strike>foo[bar]baz</strike>',
+ 'expected': True },
+
+ { 'id': 'S_STRIKEs:td:n-1_SI',
+ 'rte1-id': 'q-strikethrough-2',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<strike style="text-decoration: none">foo[bar]baz</strike>',
+ 'expected': False },
+
+ { 'id': 'S_DEL-1_SI',
+ 'rte1-id': 'q-strikethrough-4',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<del>foo[bar]baz</del>',
+ 'expected': True },
+
+ { 'id': 'S_SPANs:td:lt-1_SI',
+ 'rte1-id': 'q-strikethrough-5',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<span style="text-decoration: line-through">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'S_SPAN.s-1-SI',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<span class="s">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'S_MYS-1-SI',
+ 'desc': 'query the "strikethrough" state',
+ 'pad': '<mys>foo[bar]baz</mys>',
+ 'expected': True },
+
+ { 'id': 'S_S.STRIKE.DEL-1_SM',
+ 'desc': 'query the "strikethrough" state, selection mixed, but all struck',
+ 'pad': '<s>fo[o</s><strike>bar</strike><del>b]az</del>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query subscript state',
+ 'qcstate': 'subscript',
+ 'tests': [
+ { 'id': 'SUB_TEXT_SI',
+ 'rte1-id': 'q-subscript-0',
+ 'desc': 'query the "subscript" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'SUB_SUB-1_SI',
+ 'rte1-id': 'q-subscript-1',
+ 'desc': 'query the "subscript" state',
+ 'pad': '<sub>foo[bar]baz</sub>',
+ 'expected': True },
+
+ { 'id': 'SUB_SPAN.sub-1-SI',
+ 'desc': 'query the "subscript" state',
+ 'pad': '<span class="sub">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'SUB_MYSUB-1-SI',
+ 'desc': 'query the "subscript" state',
+ 'pad': '<mysub>foo[bar]baz</mysub>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query superscript state',
+ 'qcstate': 'superscript',
+ 'tests': [
+ { 'id': 'SUP_TEXT_SI',
+ 'rte1-id': 'q-superscript-0',
+ 'desc': 'query the "superscript" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'SUP_SUP-1_SI',
+ 'rte1-id': 'q-superscript-1',
+ 'desc': 'query the "superscript" state',
+ 'pad': '<sup>foo[bar]baz</sup>',
+ 'expected': True },
+
+ { 'id': 'IOL_TEXT_SI',
+ 'desc': 'query the "insertorderedlist" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'SUP_SPAN.sup-1-SI',
+ 'desc': 'query the "superscript" state',
+ 'pad': '<span class="sup">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'SUP_MYSUP-1-SI',
+ 'desc': 'query the "superscript" state',
+ 'pad': '<mysup>foo[bar]baz</mysup>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query whether the selection is in an ordered list',
+ 'qcstate': 'insertorderedlist',
+ 'tests': [
+ { 'id': 'IOL_TEXT-1_SI',
+ 'rte1-id': 'q-insertorderedlist-0',
+ 'desc': 'query the "insertorderedlist" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'IOL_OL-LI-1_SI',
+ 'rte1-id': 'q-insertorderedlist-1',
+ 'desc': 'query the "insertorderedlist" state',
+ 'pad': '<ol><li>foo[bar]baz</li></ol>',
+ 'expected': True },
+
+ { 'id': 'IOL_UL_LI-1_SI',
+ 'rte1-id': 'q-insertorderedlist-2',
+ 'desc': 'query the "insertorderedlist" state',
+ 'pad': '<ul><li>foo[bar]baz</li></ul>',
+ 'expected': False }
+ ]
+ },
+
+ { 'desc': 'query whether the selection is in an unordered list',
+ 'qcstate': 'insertunorderedlist',
+ 'tests': [
+ { 'id': 'IUL_TEXT_SI',
+ 'rte1-id': 'q-insertunorderedlist-0',
+ 'desc': 'query the "insertunorderedlist" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'IUL_OL-LI-1_SI',
+ 'rte1-id': 'q-insertunorderedlist-1',
+ 'desc': 'query the "insertunorderedlist" state',
+ 'pad': '<ol><li>foo[bar]baz</li></ol>',
+ 'expected': False },
+
+ { 'id': 'IUL_UL-LI-1_SI',
+ 'rte1-id': 'q-insertunorderedlist-2',
+ 'desc': 'query the "insertunorderedlist" state',
+ 'pad': '<ul><li>foo[bar]baz</li></ul>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query whether the paragraph is centered',
+ 'qcstate': 'justifycenter',
+ 'tests': [
+ { 'id': 'JC_TEXT_SI',
+ 'rte1-id': 'q-justifycenter-0',
+ 'desc': 'query the "justifycenter" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'JC_DIVa:c-1_SI',
+ 'rte1-id': 'q-justifycenter-1',
+ 'desc': 'query the "justifycenter" state',
+ 'pad': '<div align="center">foo[bar]baz</div>',
+ 'expected': True },
+
+ { 'id': 'JC_Pa:c-1_SI',
+ 'rte1-id': 'q-justifycenter-2',
+ 'desc': 'query the "justifycenter" state',
+ 'pad': '<p align="center">foo[bar]baz</p>',
+ 'expected': True },
+
+ { 'id': 'JC_SPANs:ta:c-1_SI',
+ 'rte1-id': 'q-justifycenter-3',
+ 'desc': 'query the "justifycenter" state',
+ 'pad': '<div style="text-align: center">foo[bar]baz</div>',
+ 'expected': True },
+
+ { 'id': 'JC_SPAN.jc-1-SI',
+ 'desc': 'query the "justifycenter" state',
+ 'pad': '<span class="jc">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JC_MYJC-1-SI',
+ 'desc': 'query the "justifycenter" state',
+ 'pad': '<myjc>foo[bar]baz</myjc>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query whether the paragraph is justified',
+ 'qcstate': 'justifyfull',
+ 'tests': [
+ { 'id': 'JF_TEXT_SI',
+ 'rte1-id': 'q-justifyfull-0',
+ 'desc': 'query the "justifyfull" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'JF_DIVa:j-1_SI',
+ 'rte1-id': 'q-justifyfull-1',
+ 'desc': 'query the "justifyfull" state',
+ 'pad': '<div align="justify">foo[bar]baz</div>',
+ 'expected': True },
+
+ { 'id': 'JF_Pa:j-1_SI',
+ 'rte1-id': 'q-justifyfull-2',
+ 'desc': 'query the "justifyfull" state',
+ 'pad': '<p align="justify">foo[bar]baz</p>',
+ 'expected': True },
+
+ { 'id': 'JF_SPANs:ta:j-1_SI',
+ 'rte1-id': 'q-justifyfull-3',
+ 'desc': 'query the "justifyfull" state',
+ 'pad': '<span style="text-align: justify">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JF_SPAN.jf-1-SI',
+ 'desc': 'query the "justifyfull" state',
+ 'pad': '<span class="jf">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JF_MYJF-1-SI',
+ 'desc': 'query the "justifyfull" state',
+ 'pad': '<myjf>foo[bar]baz</myjf>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query whether the paragraph is aligned left',
+ 'qcstate': 'justifyleft',
+ 'tests': [
+ { 'id': 'JL_TEXT_SI',
+ 'desc': 'query the "justifyleft" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'JL_DIVa:l-1_SI',
+ 'rte1-id': 'q-justifyleft-0',
+ 'desc': 'query the "justifyleft" state',
+ 'pad': '<div align="left">foo[bar]baz</div>',
+ 'expected': True },
+
+ { 'id': 'JL_Pa:l-1_SI',
+ 'rte1-id': 'q-justifyleft-1',
+ 'desc': 'query the "justifyleft" state',
+ 'pad': '<p align="left">foo[bar]baz</p>',
+ 'expected': True },
+
+ { 'id': 'JL_SPANs:ta:l-1_SI',
+ 'rte1-id': 'q-justifyleft-2',
+ 'desc': 'query the "justifyleft" state',
+ 'pad': '<span style="text-align: left">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JL_SPAN.jl-1-SI',
+ 'desc': 'query the "justifyleft" state',
+ 'pad': '<span class="jl">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JL_MYJL-1-SI',
+ 'desc': 'query the "justifyleft" state',
+ 'pad': '<myjl>foo[bar]baz</myjl>',
+ 'expected': True }
+ ]
+ },
+
+ { 'desc': 'query whether the paragraph is aligned right',
+ 'qcstate': 'justifyright',
+ 'tests': [
+ { 'id': 'JR_TEXT_SI',
+ 'rte1-id': 'q-justifyright-0',
+ 'desc': 'query the "justifyright" state',
+ 'pad': 'foo[bar]baz',
+ 'expected': False },
+
+ { 'id': 'JR_DIVa:r-1_SI',
+ 'rte1-id': 'q-justifyright-1',
+ 'desc': 'query the "justifyright" state',
+ 'pad': '<div align="right">foo[bar]baz</div>',
+ 'expected': True },
+
+ { 'id': 'JR_Pa:r-1_SI',
+ 'rte1-id': 'q-justifyright-2',
+ 'desc': 'query the "justifyright" state',
+ 'pad': '<p align="right">foo[bar]baz</p>',
+ 'expected': True },
+
+ { 'id': 'JR_SPANs:ta:r-1_SI',
+ 'rte1-id': 'q-justifyright-3',
+ 'desc': 'query the "justifyright" state',
+ 'pad': '<span style="text-align: right">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JR_SPAN.jr-1-SI',
+ 'desc': 'query the "justifyright" state',
+ 'pad': '<span class="jr">foo[bar]baz</span>',
+ 'expected': True },
+
+ { 'id': 'JR_MYJR-1-SI',
+ 'desc': 'query the "justifyright" state',
+ 'pad': '<myjr>foo[bar]baz</myjr>',
+ 'expected': True }
+ ]
+ }
+ ]
+}
+
+QUERYSTATE_TESTS_CSS = {
+ 'id': 'QSC',
+ 'caption': 'queryCommandState Tests, using styleWithCSS',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': True,
+
+ 'Proposed': QUERYSTATE_TESTS['Proposed']
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/querySupported.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/querySupported.py
new file mode 100644
index 000000000..af23a428c
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/querySupported.py
@@ -0,0 +1,226 @@
+
+QUERYSUPPORTED_TESTS = {
+ 'id': 'Q',
+ 'caption': 'queryCommandSupported Tests',
+ 'pad': 'foo[bar]baz',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': False,
+ 'expected': True,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'HTML5 commands',
+ 'tests': [
+ { 'id': 'SELECTALL_TEXT-1',
+ 'desc': 'check whether the "selectall" command is supported',
+ 'qcsupported': 'selectall' },
+
+ { 'id': 'UNSELECT_TEXT-1',
+ 'desc': 'check whether the "unselect" command is supported',
+ 'qcsupported': 'unselect' },
+
+ { 'id': 'UNDO_TEXT-1',
+ 'desc': 'check whether the "undo" command is supported',
+ 'qcsupported': 'undo' },
+
+ { 'id': 'REDO_TEXT-1',
+ 'desc': 'check whether the "redo" command is supported',
+ 'qcsupported': 'redo' },
+
+ { 'id': 'BOLD_TEXT-1',
+ 'desc': 'check whether the "bold" command is supported',
+ 'qcsupported': 'bold' },
+
+ { 'id': 'BOLD_B',
+ 'desc': 'check whether the "bold" command is supported',
+ 'qcsupported': 'bold',
+ 'pad': '<b>foo[bar]baz</b>' },
+
+ { 'id': 'ITALIC_TEXT-1',
+ 'desc': 'check whether the "italic" command is supported',
+ 'qcsupported': 'italic' },
+
+ { 'id': 'ITALIC_I',
+ 'desc': 'check whether the "italic" command is supported',
+ 'qcsupported': 'italic',
+ 'pad': '<i>foo[bar]baz</i>' },
+
+ { 'id': 'UNDERLINE_TEXT-1',
+ 'desc': 'check whether the "underline" command is supported',
+ 'qcsupported': 'underline' },
+
+ { 'id': 'STRIKETHROUGH_TEXT-1',
+ 'desc': 'check whether the "strikethrough" command is supported',
+ 'qcsupported': 'strikethrough' },
+
+ { 'id': 'SUBSCRIPT_TEXT-1',
+ 'desc': 'check whether the "subscript" command is supported',
+ 'qcsupported': 'subscript' },
+
+ { 'id': 'SUPERSCRIPT_TEXT-1',
+ 'desc': 'check whether the "superscript" command is supported',
+ 'qcsupported': 'superscript' },
+
+ { 'id': 'FORMATBLOCK_TEXT-1',
+ 'desc': 'check whether the "formatblock" command is supported',
+ 'qcsupported': 'formatblock' },
+
+ { 'id': 'CREATELINK_TEXT-1',
+ 'desc': 'check whether the "createlink" command is supported',
+ 'qcsupported': 'createlink' },
+
+ { 'id': 'UNLINK_TEXT-1',
+ 'desc': 'check whether the "unlink" command is supported',
+ 'qcsupported': 'unlink' },
+
+ { 'id': 'INSERTHTML_TEXT-1',
+ 'desc': 'check whether the "inserthtml" command is supported',
+ 'qcsupported': 'inserthtml' },
+
+ { 'id': 'INSERTHORIZONTALRULE_TEXT-1',
+ 'desc': 'check whether the "inserthorizontalrule" command is supported',
+ 'qcsupported': 'inserthorizontalrule' },
+
+ { 'id': 'INSERTIMAGE_TEXT-1',
+ 'desc': 'check whether the "insertimage" command is supported',
+ 'qcsupported': 'insertimage' },
+
+ { 'id': 'INSERTLINEBREAK_TEXT-1',
+ 'desc': 'check whether the "insertlinebreak" command is supported',
+ 'qcsupported': 'insertlinebreak' },
+
+ { 'id': 'INSERTPARAGRAPH_TEXT-1',
+ 'desc': 'check whether the "insertparagraph" command is supported',
+ 'qcsupported': 'insertparagraph' },
+
+ { 'id': 'INSERTORDEREDLIST_TEXT-1',
+ 'desc': 'check whether the "insertorderedlist" command is supported',
+ 'qcsupported': 'insertorderedlist' },
+
+ { 'id': 'INSERTUNORDEREDLIST_TEXT-1',
+ 'desc': 'check whether the "insertunorderedlist" command is supported',
+ 'qcsupported': 'insertunorderedlist' },
+
+ { 'id': 'INSERTTEXT_TEXT-1',
+ 'desc': 'check whether the "inserttext" command is supported',
+ 'qcsupported': 'inserttext' },
+
+ { 'id': 'DELETE_TEXT-1',
+ 'desc': 'check whether the "delete" command is supported',
+ 'qcsupported': 'delete' },
+
+ { 'id': 'FORWARDDELETE_TEXT-1',
+ 'desc': 'check whether the "forwarddelete" command is supported',
+ 'qcsupported': 'forwarddelete' }
+ ]
+ },
+
+ { 'desc': 'MIDAS commands',
+ 'tests': [
+ { 'id': 'STYLEWITHCSS_TEXT-1',
+ 'desc': 'check whether the "styleWithCSS" command is supported',
+ 'qcsupported': 'styleWithCSS' },
+
+ { 'id': 'CONTENTREADONLY_TEXT-1',
+ 'desc': 'check whether the "contentreadonly" command is supported',
+ 'qcsupported': 'contentreadonly' },
+
+ { 'id': 'BACKCOLOR_TEXT-1',
+ 'desc': 'check whether the "backcolor" command is supported',
+ 'qcsupported': 'backcolor' },
+
+ { 'id': 'FORECOLOR_TEXT-1',
+ 'desc': 'check whether the "forecolor" command is supported',
+ 'qcsupported': 'forecolor' },
+
+ { 'id': 'HILITECOLOR_TEXT-1',
+ 'desc': 'check whether the "hilitecolor" command is supported',
+ 'qcsupported': 'hilitecolor' },
+
+ { 'id': 'FONTNAME_TEXT-1',
+ 'desc': 'check whether the "fontname" command is supported',
+ 'qcsupported': 'fontname' },
+
+ { 'id': 'FONTSIZE_TEXT-1',
+ 'desc': 'check whether the "fontsize" command is supported',
+ 'qcsupported': 'fontsize' },
+
+ { 'id': 'INCREASEFONTSIZE_TEXT-1',
+ 'desc': 'check whether the "increasefontsize" command is supported',
+ 'qcsupported': 'increasefontsize' },
+
+ { 'id': 'DECREASEFONTSIZE_TEXT-1',
+ 'desc': 'check whether the "decreasefontsize" command is supported',
+ 'qcsupported': 'decreasefontsize' },
+
+ { 'id': 'HEADING_TEXT-1',
+ 'desc': 'check whether the "heading" command is supported',
+ 'qcsupported': 'heading' },
+
+ { 'id': 'INDENT_TEXT-1',
+ 'desc': 'check whether the "indent" command is supported',
+ 'qcsupported': 'indent' },
+
+ { 'id': 'OUTDENT_TEXT-1',
+ 'desc': 'check whether the "outdent" command is supported',
+ 'qcsupported': 'outdent' },
+
+ { 'id': 'CREATEBOOKMARK_TEXT-1',
+ 'desc': 'check whether the "createbookmark" command is supported',
+ 'qcsupported': 'createbookmark' },
+
+ { 'id': 'UNBOOKMARK_TEXT-1',
+ 'desc': 'check whether the "unbookmark" command is supported',
+ 'qcsupported': 'unbookmark' },
+
+ { 'id': 'JUSTIFYCENTER_TEXT-1',
+ 'desc': 'check whether the "justifycenter" command is supported',
+ 'qcsupported': 'justifycenter' },
+
+ { 'id': 'JUSTIFYFULL_TEXT-1',
+ 'desc': 'check whether the "justifyfull" command is supported',
+ 'qcsupported': 'justifyfull' },
+
+ { 'id': 'JUSTIFYLEFT_TEXT-1',
+ 'desc': 'check whether the "justifyleft" command is supported',
+ 'qcsupported': 'justifyleft' },
+
+ { 'id': 'JUSTIFYRIGHT_TEXT-1',
+ 'desc': 'check whether the "justifyright" command is supported',
+ 'qcsupported': 'justifyright' },
+
+ { 'id': 'REMOVEFORMAT_TEXT-1',
+ 'desc': 'check whether the "removeformat" command is supported',
+ 'qcsupported': 'removeformat' },
+
+ { 'id': 'COPY_TEXT-1',
+ 'desc': 'check whether the "copy" command is supported',
+ 'qcsupported': 'copy' },
+
+ { 'id': 'CUT_TEXT-1',
+ 'desc': 'check whether the "cut" command is supported',
+ 'qcsupported': 'cut' },
+
+ { 'id': 'PASTE_TEXT-1',
+ 'desc': 'check whether the "paste" command is supported',
+ 'qcsupported': 'paste' }
+ ]
+ },
+
+ { 'desc': 'Other tests',
+ 'tests': [
+ { 'id': 'garbage-1_TEXT-1',
+ 'desc': 'check correct return value with garbage input',
+ 'qcsupported': '#!#@7',
+ 'expected': False }
+ ]
+ }
+ ]
+}
+
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryValue.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryValue.py
new file mode 100644
index 000000000..793b7cb6c
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/queryValue.py
@@ -0,0 +1,429 @@
+
+QUERYVALUE_TESTS = {
+ 'id': 'QV',
+ 'caption': 'queryCommandValue Tests',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': '[HTML5] query bold value',
+ 'qcvalue': 'bold',
+ 'tests': [
+ { 'id': 'B_TEXT_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'false' },
+
+ { 'id': 'B_B-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<b>foo[bar]baz</b>',
+ 'expected': 'true' },
+
+ { 'id': 'B_STRONG-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<strong>foo[bar]baz</strong>',
+ 'expected': 'true' },
+
+ { 'id': 'B_SPANs:fw:b-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<span style="font-weight: bold">foo[bar]baz</span>',
+ 'expected': 'true' },
+
+ { 'id': 'B_SPANs:fw:n-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<span style="font-weight: normal">foo[bar]baz</span>',
+ 'expected': 'false' },
+
+ { 'id': 'B_Bs:fw:n-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<b><span style="font-weight: normal">foo[bar]baz</span></b>',
+ 'expected': 'false' },
+
+ { 'id': 'B_SPAN.b-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<span class="b">foo[bar]baz</span>',
+ 'expected': 'true' },
+
+ { 'id': 'B_MYB-1-SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<myb>foo[bar]baz</myb>',
+ 'expected': 'true' }
+ ]
+ },
+
+ { 'desc': '[HTML5] query italic value',
+ 'qcvalue': 'italic',
+ 'tests': [
+ { 'id': 'I_TEXT_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': 'foo[bar]baz',
+ 'expected': 'false' },
+
+ { 'id': 'I_I-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<i>foo[bar]baz</i>',
+ 'expected': 'true' },
+
+ { 'id': 'I_EM-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<em>foo[bar]baz</em>',
+ 'expected': 'true' },
+
+ { 'id': 'I_SPANs:fs:i-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<span style="font-style: italic">foo[bar]baz</span>',
+ 'expected': 'true' },
+
+ { 'id': 'I_SPANs:fs:n-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<span style="font-style: normal">foo[bar]baz</span>',
+ 'expected': 'false' },
+
+ { 'id': 'I_I-SPANs:fs:n-1_SI',
+ 'desc': 'query the "bold" value',
+ 'pad': '<i><span style="font-style: normal">foo[bar]baz</span></i>',
+ 'expected': 'false' },
+
+ { 'id': 'I_SPAN.i-1_SI',
+ 'desc': 'query the "italic" value',
+ 'pad': '<span class="i">foo[bar]baz</span>',
+ 'expected': 'true' },
+
+ { 'id': 'I_MYI-1-SI',
+ 'desc': 'query the "italic" value',
+ 'pad': '<myi>foo[bar]baz</myi>',
+ 'expected': 'true' }
+ ]
+ },
+
+ { 'desc': '[HTML5] query block formatting value',
+ 'qcvalue': 'formatblock',
+ 'tests': [
+ { 'id': 'FB_TEXT-1_SC',
+ 'desc': 'query the "formatBlock" value',
+ 'pad': 'foobar^baz',
+ 'expected': '',
+ 'accept': 'normal' },
+
+ { 'id': 'FB_H1-1_SC',
+ 'desc': 'query the "formatBlock" value',
+ 'pad': '<h1>foobar^baz</h1>',
+ 'expected': 'h1' },
+
+ { 'id': 'FB_PRE-1_SC',
+ 'desc': 'query the "formatBlock" value',
+ 'pad': '<pre>foobar^baz</pre>',
+ 'expected': 'pre' },
+
+ { 'id': 'FB_BQ-1_SC',
+ 'desc': 'query the "formatBlock" value',
+ 'pad': '<blockquote>foobar^baz</blockquote>',
+ 'expected': 'blockquote' },
+
+ { 'id': 'FB_ADDRESS-1_SC',
+ 'desc': 'query the "formatBlock" value',
+ 'pad': '<address>foobar^baz</address>',
+ 'expected': 'address' },
+
+ { 'id': 'FB_H1-H2-1_SC',
+ 'desc': 'query the "formatBlock" value',
+ 'pad': '<h1>foo<h2>ba^r</h2>baz</h1>',
+ 'expected': 'h2' },
+
+ { 'id': 'FB_H1-H2-1_SL',
+ 'desc': 'query the "formatBlock" value on oblique selection (outermost formatting expected)',
+ 'pad': '<h1>fo[o<h2>ba]r</h2>baz</h1>',
+ 'expected': 'h1' },
+
+ { 'id': 'FB_H1-H2-1_SR',
+ 'desc': 'query the "formatBlock" value on oblique selection (outermost formatting expected)',
+ 'pad': '<h1>foo<h2>b[ar</h2>ba]z</h1>',
+ 'expected': 'h1' },
+
+ { 'id': 'FB_TEXT-ADDRESS-1_SL',
+ 'desc': 'query the "formatBlock" value on oblique selection (outermost formatting expected)',
+ 'pad': 'fo[o<ADDRESS>ba]r</ADDRESS>baz',
+ 'expected': '',
+ 'accept': 'normal' },
+
+ { 'id': 'FB_TEXT-ADDRESS-1_SR',
+ 'desc': 'query the "formatBlock" value on oblique selection (outermost formatting expected)',
+ 'pad': 'foo<ADDRESS>b[ar</ADDRESS>ba]z',
+ 'expected': '',
+ 'accept': 'normal' },
+
+ { 'id': 'FB_H1-H2.TEXT.H2-1_SM',
+ 'desc': 'query the "formatBlock" value on oblique selection (outermost formatting expected)',
+ 'pad': '<h1><h2>fo[o</h2>bar<h2>b]az</h2></h1>',
+ 'expected': 'h1' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] query heading type',
+ 'qcvalue': 'heading',
+ 'tests': [
+ { 'id': 'H_H1-1_SC',
+ 'desc': 'query the "heading" type',
+ 'pad': '<h1>foobar^baz</h1>',
+ 'expected': 'h1',
+ 'accept': '<h1>' },
+
+ { 'id': 'H_H3-1_SC',
+ 'desc': 'query the "heading" type',
+ 'pad': '<h3>foobar^baz</h3>',
+ 'expected': 'h3',
+ 'accept': '<h3>' },
+
+ { 'id': 'H_H1-H2-H3-H4-1_SC',
+ 'desc': 'query the "heading" type within nested heading tags',
+ 'pad': '<h1><h2><h3><h4>foobar^baz</h4></h3></h2></h1>',
+ 'expected': 'h4',
+ 'accept': '<h4>' },
+
+ { 'id': 'H_P-1_SC',
+ 'desc': 'query the "heading" type outside of a heading',
+ 'pad': '<p>foobar^baz</p>',
+ 'expected': '' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] query font name',
+ 'qcvalue': 'fontname',
+ 'tests': [
+ { 'id': 'FN_FONTf:a-1_SI',
+ 'rte1-id': 'q-fontname-0',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<font face="arial">foo[bar]baz</font>',
+ 'expected': 'arial' },
+
+ { 'id': 'FN_SPANs:ff:a-1_SI',
+ 'rte1-id': 'q-fontname-1',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<span style="font-family: arial">foo[bar]baz</span>',
+ 'expected': 'arial' },
+
+ { 'id': 'FN_FONTf:a.s:ff:c-1_SI',
+ 'rte1-id': 'q-fontname-2',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<font face="arial" style="font-family: courier">foo[bar]baz</font>',
+ 'expected': 'courier' },
+
+ { 'id': 'FN_FONTf:a-FONTf:c-1_SI',
+ 'rte1-id': 'q-fontname-3',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<font face="arial"><font face="courier">foo[bar]baz</font></font>',
+ 'expected': 'courier' },
+
+ { 'id': 'FN_SPANs:ff:c-FONTf:a-1_SI',
+ 'rte1-id': 'q-fontname-4',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<span style="font-family: courier"><font face="arial">foo[bar]baz</font></span>',
+ 'expected': 'arial' },
+
+ { 'id': 'FN_SPAN.fs18px-1_SI',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<span class="courier">foo[bar]baz</span>',
+ 'expected': 'courier' },
+
+ { 'id': 'FN_MYCOURIER-1-SI',
+ 'desc': 'query the "fontname" value',
+ 'pad': '<mycourier>foo[bar]baz</mycourier>',
+ 'expected': 'courier' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] query font size',
+ 'qcvalue': 'fontsize',
+ 'tests': [
+ { 'id': 'FS_FONTsz:4-1_SI',
+ 'rte1-id': 'q-fontsize-0',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<font size=4>foo[bar]baz</font>',
+ 'expected': '18px' },
+
+ { 'id': 'FS_FONTs:fs:l-1_SI',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<font style="font-size: large">foo[bar]baz</font>',
+ 'expected': '18px' },
+
+ { 'id': 'FS_FONT.ass.s:fs:l-1_SI',
+ 'rte1-id': 'q-fontsize-1',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<font class="Apple-style-span" style="font-size: large">foo[bar]baz</font>',
+ 'expected': '18px' },
+
+ { 'id': 'FS_FONTsz:1.s:fs:xl-1_SI',
+ 'rte1-id': 'q-fontsize-2',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<font size=1 style="font-size: x-large">foo[bar]baz</font>',
+ 'expected': '24px' },
+
+ { 'id': 'FS_SPAN.large-1_SI',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<span class="large">foo[bar]baz</span>',
+ 'expected': 'large' },
+
+ { 'id': 'FS_SPAN.fs18px-1_SI',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<span class="fs18px">foo[bar]baz</span>',
+ 'expected': '18px' },
+
+ { 'id': 'FA_MYLARGE-1-SI',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<mylarge>foo[bar]baz</mylarge>',
+ 'expected': 'large' },
+
+ { 'id': 'FA_MYFS18PX-1-SI',
+ 'desc': 'query the "fontsize" value',
+ 'pad': '<myfs18px>foo[bar]baz</myfs18px>',
+ 'expected': '18px' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] query background color',
+ 'qcvalue': 'backcolor',
+ 'tests': [
+ { 'id': 'BC_FONTs:bc:fca-1_SI',
+ 'rte1-id': 'q-backcolor-0',
+ 'desc': 'query the "backcolor" value',
+ 'pad': '<font style="background-color: #ffccaa">foo[bar]baz</font>',
+ 'expected': '#ffccaa' },
+
+ { 'id': 'BC_SPANs:bc:abc-1_SI',
+ 'rte1-id': 'q-backcolor-2',
+ 'desc': 'query the "backcolor" value',
+ 'pad': '<span style="background-color: #aabbcc">foo[bar]baz</span>',
+ 'expected': '#aabbcc' },
+
+ { 'id': 'BC_FONTs:bc:084-SPAN-1_SI',
+ 'desc': 'query the "backcolor" value, where the color was set on an ancestor',
+ 'pad': '<font style="background-color: #008844"><span>foo[bar]baz</span></font>',
+ 'expected': '#008844' },
+
+ { 'id': 'BC_SPANs:bc:cde-SPAN-1_SI',
+ 'desc': 'query the "backcolor" value, where the color was set on an ancestor',
+ 'pad': '<span style="background-color: #ccddee"><span>foo[bar]baz</span></span>',
+ 'expected': '#ccddee' },
+
+ { 'id': 'BC_SPAN.ass.s:bc:rgb-1_SI',
+ 'rte1-id': 'q-backcolor-1',
+ 'desc': 'query the "backcolor" value',
+ 'pad': '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0)">foo[bar]baz</span>',
+ 'expected': '#ff0000' },
+
+ { 'id': 'BC_SPAN.bcred-1_SI',
+ 'desc': 'query the "backcolor" value',
+ 'pad': '<span class="bcred">foo[bar]baz</span>',
+ 'expected': 'red' },
+
+ { 'id': 'BC_MYBCRED-1-SI',
+ 'desc': 'query the "backcolor" value',
+ 'pad': '<mybcred>foo[bar]baz</mybcred>',
+ 'expected': 'red' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] query text color',
+ 'qcvalue': 'forecolor',
+ 'tests': [
+ { 'id': 'FC_FONTc:f00-1_SI',
+ 'rte1-id': 'q-forecolor-0',
+ 'desc': 'query the "forecolor" value',
+ 'pad': '<font color="#ff0000">foo[bar]baz</font>',
+ 'expected': '#ff0000' },
+
+ { 'id': 'FC_SPANs:c:0f0-1_SI',
+ 'rte1-id': 'q-forecolor-1',
+ 'desc': 'query the "forecolor" value',
+ 'pad': '<span style="color: #00ff00">foo[bar]baz</span>',
+ 'expected': '#00ff00' },
+
+ { 'id': 'FC_FONTc:333.s:c:999-1_SI',
+ 'rte1-id': 'q-forecolor-2',
+ 'desc': 'query the "forecolor" value',
+ 'pad': '<font color="#333333" style="color: #999999">foo[bar]baz</font>',
+ 'expected': '#999999' },
+
+ { 'id': 'FC_FONTc:641-SPAN-1_SI',
+ 'desc': 'query the "forecolor" value, where the color was set on an ancestor',
+ 'pad': '<font color="#664411"><span>foo[bar]baz</span></font>',
+ 'expected': '#664411' },
+
+ { 'id': 'FC_SPANs:c:d95-SPAN-1_SI',
+ 'desc': 'query the "forecolor" value, where the color was set on an ancestor',
+ 'pad': '<span style="color: #dd9955"><span>foo[bar]baz</span></span>',
+ 'expected': '#dd9955' },
+
+ { 'id': 'FC_SPAN.red-1_SI',
+ 'desc': 'query the "forecolor" value',
+ 'pad': '<span class="red">foo[bar]baz</span>',
+ 'expected': 'red' },
+
+ { 'id': 'FC_MYRED-1-SI',
+ 'desc': 'query the "forecolor" value',
+ 'pad': '<myred>foo[bar]baz</myred>',
+ 'expected': 'red' }
+ ]
+ },
+
+ { 'desc': '[MIDAS] query hilight color (same as background color)',
+ 'qcvalue': 'hilitecolor',
+ 'tests': [
+ { 'id': 'HC_FONTs:bc:fc0-1_SI',
+ 'rte1-id': 'q-hilitecolor-0',
+ 'desc': 'query the "hilitecolor" value',
+ 'pad': '<font style="background-color: #ffcc00">foo[bar]baz</font>',
+ 'expected': '#ffcc00' },
+
+ { 'id': 'HC_SPANs:bc:a0c-1_SI',
+ 'rte1-id': 'q-hilitecolor-2',
+ 'desc': 'query the "hilitecolor" value',
+ 'pad': '<span style="background-color: #aa00cc">foo[bar]baz</span>',
+ 'expected': '#aa00cc' },
+
+ { 'id': 'HC_SPAN.ass.s:bc:rgb-1_SI',
+ 'rte1-id': 'q-hilitecolor-1',
+ 'desc': 'query the "hilitecolor" value',
+ 'pad': '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0)">foo[bar]baz</span>',
+ 'expected': '#ff0000' },
+
+ { 'id': 'HC_FONTs:bc:83e-SPAN-1_SI',
+ 'desc': 'query the "hilitecolor" value, where the color was set on an ancestor',
+ 'pad': '<font style="background-color: #8833ee"><span>foo[bar]baz</span></font>',
+ 'expected': '#8833ee' },
+
+ { 'id': 'HC_SPANs:bc:b12-SPAN-1_SI',
+ 'desc': 'query the "hilitecolor" value, where the color was set on an ancestor',
+ 'pad': '<span style="background-color: #bb1122"><span>foo[bar]baz</span></span>',
+ 'expected': '#bb1122' },
+
+ { 'id': 'HC_SPAN.bcred-1_SI',
+ 'desc': 'query the "hilitecolor" value',
+ 'pad': '<span class="bcred">foo[bar]baz</span>',
+ 'expected': 'red' },
+
+ { 'id': 'HC_MYBCRED-1-SI',
+ 'desc': 'query the "hilitecolor" value',
+ 'pad': '<mybcred>foo[bar]baz</mybcred>',
+ 'expected': 'red' }
+ ]
+ }
+ ]
+}
+
+QUERYVALUE_TESTS_CSS = {
+ 'id': 'QVC',
+ 'caption': 'queryCommandValue Tests, using styleWithCSS',
+ 'checkAttrs': False,
+ 'checkStyle': False,
+ 'styleWithCSS': True,
+
+ 'Proposed': QUERYVALUE_TESTS['Proposed']
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/selection.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/selection.py
new file mode 100644
index 000000000..35891386a
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/selection.py
@@ -0,0 +1,772 @@
+
+SELECTION_TESTS = {
+ 'id': 'S',
+ 'caption': 'Selection Tests',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': False,
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'selectall',
+ 'command': 'selectall',
+ 'tests': [
+ { 'id': 'SELALL_TEXT-1_SI',
+ 'desc': 'select all, text only',
+ 'pad': 'foo [bar] baz',
+ 'expected': [ '[foo bar baz]',
+ '{foo bar baz}' ] },
+
+ { 'id': 'SELALL_I-1_SI',
+ 'desc': 'select all, with outer tags',
+ 'pad': '<i>foo [bar] baz</i>',
+ 'expected': '{<i>foo bar baz</i>}' }
+ ]
+ },
+
+ { 'desc': 'unselect',
+ 'command': 'unselect',
+ 'tests': [
+ { 'id': 'UNSEL_TEXT-1_SI',
+ 'desc': 'unselect',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo bar baz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify (generic)',
+ 'tests': [
+ { 'id': 'SM:m.f.c_TEXT-1_SC-1',
+ 'desc': 'move caret 1 character forward',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'foo b^ar baz',
+ 'expected': 'foo ba^r baz' },
+
+ { 'id': 'SM:m.b.c_TEXT-1_SC-1',
+ 'desc': 'move caret 1 character backward',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo b^ar baz',
+ 'expected': 'foo ^bar baz' },
+
+ { 'id': 'SM:m.f.c_TEXT-1_SI-1',
+ 'desc': 'move caret forward (sollapse selection)',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo bar^ baz' },
+
+ { 'id': 'SM:m.b.c_TEXT-1_SI-1',
+ 'desc': 'move caret backward (collapse selection)',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo ^bar baz' },
+
+ { 'id': 'SM:m.f.w_TEXT-1_SC-1',
+ 'desc': 'move caret 1 word forward',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': 'foo b^ar baz',
+ 'expected': 'foo bar^ baz' },
+
+ { 'id': 'SM:m.f.w_TEXT-1_SC-2',
+ 'desc': 'move caret 1 word forward',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': 'foo^ bar baz',
+ 'expected': 'foo bar^ baz' },
+
+ { 'id': 'SM:m.f.w_TEXT-1_SI-1',
+ 'desc': 'move caret 1 word forward from non-collapsed selection',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo bar baz^' },
+
+ { 'id': 'SM:m.b.w_TEXT-1_SC-1',
+ 'desc': 'move caret 1 word backward',
+ 'function': 'sel.modify("move", "backward", "word");',
+ 'pad': 'foo b^ar baz',
+ 'expected': 'foo ^bar baz' },
+
+ { 'id': 'SM:m.b.w_TEXT-1_SC-3',
+ 'desc': 'move caret 1 word backward',
+ 'function': 'sel.modify("move", "backward", "word");',
+ 'pad': 'foo bar ^baz',
+ 'expected': 'foo ^bar baz' },
+
+ { 'id': 'SM:m.b.w_TEXT-1_SI-1',
+ 'desc': 'move caret 1 word backward from non-collapsed selection',
+ 'function': 'sel.modify("move", "backward", "word");',
+ 'pad': 'foo [bar] baz',
+ 'expected': '^foo bar baz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: move forward over combining diacritics, etc.',
+ 'tests': [
+ { 'id': 'SM:m.f.c_CHAR-2_SC-1',
+ 'desc': 'move 1 character forward over combined o with diaeresis',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo^&#xF6;barbaz',
+ 'expected': 'fo&#xF6;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-3_SC-1',
+ 'desc': 'move 1 character forward over character with combining diaeresis above',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo^o&#x0308;barbaz',
+ 'expected': 'foo&#x0308;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-4_SC-1',
+ 'desc': 'move 1 character forward over character with combining diaeresis below',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo^o&#x0324;barbaz',
+ 'expected': 'foo&#x0324;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-5_SC-1',
+ 'desc': 'move 1 character forward over character with combining diaeresis above and below',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo^o&#x0308;&#x0324;barbaz',
+ 'expected': 'foo&#x0308;&#x0324;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-5_SI-1',
+ 'desc': 'move 1 character forward over character with combining diaeresis above and below, selection on diaeresis above',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'foo[&#x0308;]&#x0324;barbaz',
+ 'expected': 'foo&#x0308;&#x0324;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-5_SI-2',
+ 'desc': 'move 1 character forward over character with combining diaeresis above and below, selection on diaeresis below',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'foo&#x0308;[&#x0324;]barbaz',
+ 'expected': 'foo&#x0308;&#x0324;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-5_SL',
+ 'desc': 'move 1 character forward over character with combining diaeresis above and below, selection oblique on diaeresis and preceding text',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo[o&#x0308;]&#x0324;barbaz',
+ 'expected': 'foo&#x0308;&#x0324;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-5_SR',
+ 'desc': 'move 1 character forward over character with combining diaeresis above and below, selection oblique on diaeresis and following text',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'foo&#x0308;[&#x0324;bar]baz',
+ 'expected': 'foo&#x0308;&#x0324;bar^baz' },
+
+ { 'id': 'SM:m.f.c_CHAR-6_SC-1',
+ 'desc': 'move 1 character forward over character with enclosing square',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo^o&#x20DE;barbaz',
+ 'expected': 'foo&#x20DE;^barbaz' },
+
+ { 'id': 'SM:m.f.c_CHAR-7_SC-1',
+ 'desc': 'move 1 character forward over character with combining long solidus overlay',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'fo^o&#x0338;barbaz',
+ 'expected': 'foo&#x0338;^barbaz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: move backward over combining diacritics, etc.',
+ 'tests': [
+ { 'id': 'SM:m.b.c_CHAR-2_SC-1',
+ 'desc': 'move 1 character backward over combined o with diaeresis',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'fo&#xF6;^barbaz',
+ 'expected': 'fo^&#xF6;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-3_SC-1',
+ 'desc': 'move 1 character backward over character with combining diaeresis above',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x0308;^barbaz',
+ 'expected': 'fo^o&#x0308;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-4_SC-1',
+ 'desc': 'move 1 character backward over character with combining diaeresis below',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x0324;^barbaz',
+ 'expected': 'fo^o&#x0324;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-5_SC-1',
+ 'desc': 'move 1 character backward over character with combining diaeresis above and below',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x0308;&#x0324;^barbaz',
+ 'expected': 'fo^o&#x0308;&#x0324;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-5_SI-1',
+ 'desc': 'move 1 character backward over character with combining diaeresis above and below, selection on diaeresis above',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo[&#x0308;]&#x0324;barbaz',
+ 'expected': 'fo^o&#x0308;&#x0324;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-5_SI-2',
+ 'desc': 'move 1 character backward over character with combining diaeresis above and below, selection on diaeresis below',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x0308;[&#x0324;]barbaz',
+ 'expected': 'fo^o&#x0308;&#x0324;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-5_SL',
+ 'desc': 'move 1 character backward over character with combining diaeresis above and below, selection oblique on diaeresis and preceding text',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'fo[o&#x0308;]&#x0324;barbaz',
+ 'expected': 'fo^o&#x0308;&#x0324;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-5_SR',
+ 'desc': 'move 1 character backward over character with combining diaeresis above and below, selection oblique on diaeresis and following text',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x0308;[&#x0324;bar]baz',
+ 'expected': 'fo^o&#x0308;&#x0324;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-6_SC-1',
+ 'desc': 'move 1 character backward over character with enclosing square',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x20DE;^barbaz',
+ 'expected': 'fo^o&#x20DE;barbaz' },
+
+ { 'id': 'SM:m.b.c_CHAR-7_SC-1',
+ 'desc': 'move 1 character backward over character with combining long solidus overlay',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo&#x0338;^barbaz',
+ 'expected': 'fo^o&#x0338;barbaz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: move forward/backward/left/right in RTL text',
+ 'tests': [
+ { 'id': 'SM:m.f.c_Pdir:rtl-1_SC-1',
+ 'desc': 'move caret forward 1 character in right-to-left text',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': '<p dir="rtl">foo b^ar baz</p>',
+ 'expected': '<p dir="rtl">foo ba^r baz</p>' },
+
+ { 'id': 'SM:m.b.c_Pdir:rtl-1_SC-1',
+ 'desc': 'move caret backward 1 character in right-to-left text',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': '<p dir="rtl">foo ba^r baz</p>',
+ 'expected': '<p dir="rtl">foo b^ar baz</p>' },
+
+ { 'id': 'SM:m.r.c_Pdir:rtl-1_SC-1',
+ 'desc': 'move caret 1 character to the right in LTR text within RTL context',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': '<p dir="rtl">foo b^ar baz</p>',
+ 'expected': '<p dir="rtl">foo ba^r baz</p>' },
+
+ { 'id': 'SM:m.l.c_Pdir:rtl-1_SC-1',
+ 'desc': 'move caret 1 character to the left in LTR text within RTL context',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': '<p dir="rtl">foo ba^r baz</p>',
+ 'expected': '<p dir="rtl">foo b^ar baz</p>' },
+
+
+ { 'id': 'SM:m.f.c_TEXT:ar-1_SC-1',
+ 'desc': 'move caret forward 1 character in Arabic text',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': '&#1605;&#x0631;&#1581;^&#1576;&#x0627; &#x0627;&#1604;&#x0639;&#x0627;&#1604;&#1605;',
+ 'expected': '&#1605;&#x0631;&#1581;&#1576;^&#x0627; &#x0627;&#1604;&#x0639;&#x0627;&#1604;&#1605;' },
+
+ { 'id': 'SM:m.b.c_TEXT:ar-1_SC-1',
+ 'desc': 'move caret backward 1 character in Arabic text',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': '&#1605;&#x0631;&#1581;^&#1576;&#x0627; &#x0627;&#1604;&#x0639;&#x0627;&#1604;&#1605;',
+ 'expected': '&#1605;&#x0631;^&#1581;&#1576;&#x0627; &#x0627;&#1604;&#x0639;&#x0627;&#1604;&#1605;' },
+
+ { 'id': 'SM:m.f.c_TEXT:he-1_SC-1',
+ 'desc': 'move caret forward 1 character in Hebrew text',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': '&#x05E9;&#x05DC;^&#x05D5;&#x05DD; &#x05E2;&#x05D5;&#x05DC;&#x05DD;',
+ 'expected': '&#x05E9;&#x05DC;&#x05D5;^&#x05DD; &#x05E2;&#x05D5;&#x05DC;&#x05DD;' },
+
+ { 'id': 'SM:m.b.c_TEXT:he-1_SC-1',
+ 'desc': 'move caret backward 1 character in Hebrew text',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': '&#x05E9;&#x05DC;^&#x05D5;&#x05DD; &#x05E2;&#x05D5;&#x05DC;&#x05DD;',
+ 'expected': '&#x05E9;^&#x05DC;&#x05D5;&#x05DD; &#x05E2;&#x05D5;&#x05DC;&#x05DD;' },
+
+
+ { 'id': 'SM:m.f.c_BDOdir:rtl-1_SC-1',
+ 'desc': 'move caret forward 1 character inside <bdo>',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'foo <bdo dir="rtl">b^ar</bdo> baz',
+ 'expected': 'foo <bdo dir="rtl">ba^r</bdo> baz' },
+
+ { 'id': 'SM:m.b.c_BDOdir:rtl-1_SC-1',
+ 'desc': 'move caret backward 1 character inside <bdo>',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'foo <bdo dir="rtl">ba^r</bdo> baz',
+ 'expected': 'foo <bdo dir="rtl">b^ar</bdo> baz' },
+
+ { 'id': 'SM:m.r.c_BDOdir:rtl-1_SC-1',
+ 'desc': 'move caret 1 character to the right inside <bdo>',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': 'foo <bdo dir="rtl">ba^r</bdo> baz',
+ 'expected': 'foo <bdo dir="rtl">b^ar</bdo> baz' },
+
+ { 'id': 'SM:m.l.c_BDOdir:rtl-1_SC-1',
+ 'desc': 'move caret 1 character to the left inside <bdo>',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': 'foo <bdo dir="rtl">b^ar</bdo> baz',
+ 'expected': 'foo <bdo dir="rtl">ba^r</bdo> baz' },
+
+
+ { 'id': 'SM:m.f.c_TEXTrle-1_SC-rtl-1',
+ 'desc': 'move caret forward in RTL text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;^&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.b.c_TEXTrle-1_SC-rtl-1',
+ 'desc': 'move caret backward in RTL text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;^&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.r.c_TEXTrle-1_SC-rtl-1',
+ 'desc': 'move caret 1 character to the right in RTL text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;^&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.l.c_TEXTrle-1_SC-rtl-1',
+ 'desc': 'move caret 1 character to the left in RTL text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;^&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.f.c_TEXTrle-1_SC-ltr-1',
+ 'desc': 'move caret forward in LTR text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.b.c_TEXTrle-1_SC-ltr-1',
+ 'desc': 'move caret backward in LTR text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.r.c_TEXTrle-1_SC-ltr-1',
+ 'desc': 'move caret 1 character to the right in LTR text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.l.c_TEXTrle-1_SC-ltr-1',
+ 'desc': 'move caret 1 character to the left in LTR text within RLE-PDF marks',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': 'I said, "(RLE)&#x202B;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLE)&#x202B;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+
+ { 'id': 'SM:m.f.c_TEXTrlo-1_SC-rtl-1',
+ 'desc': 'move caret forward in RTL text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;^&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.b.c_TEXTrlo-1_SC-rtl-1',
+ 'desc': 'move caret backward in RTL text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;^&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.r.c_TEXTrlo-1_SC-rtl-1',
+ 'desc': 'move caret 1 character to the right in RTL text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;^&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.l.c_TEXTrlo-1_SC-rtl-1',
+ 'desc': 'move caret 1 character to the left in RTL text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;^&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;car &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;^&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.f.c_TEXTrlo-1_SC-ltr-1',
+ 'desc': 'move caret forward in Latin text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.b.c_TEXTrlo-1_SC-ltr-1',
+ 'desc': 'move caret backward in Latin text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.r.c_TEXTrlo-1_SC-ltr-1',
+ 'desc': 'move caret 1 character to the right in Latin text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+ { 'id': 'SM:m.l.c_TEXTrlo-1_SC-ltr-1',
+ 'desc': 'move caret 1 character to the left in Latin text within RLO-PDF marks',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': 'I said, "(RLO)&#x202E;c^ar &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".',
+ 'expected': 'I said, "(RLO)&#x202E;ca^r &#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;&#x202C;(PDF)".' },
+
+
+ { 'id': 'SM:m.f.c_TEXTrlm-1_SC-1',
+ 'desc': 'move caret forward in RTL text within neutral characters followed by RLM',
+ 'function': 'sel.modify("move", "forward", "character");',
+ 'pad': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!^?!&#x200F;(RLM)".',
+ 'expected': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!?^!&#x200F;(RLM)".' },
+
+ { 'id': 'SM:m.b.c_TEXTrlm-1_SC-1',
+ 'desc': 'move caret backward in RTL text within neutral characters followed by RLM',
+ 'function': 'sel.modify("move", "backward", "character");',
+ 'pad': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!?^!&#x200F;(RLM)".',
+ 'expected': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!^?!&#x200F;(RLM)".' },
+
+ { 'id': 'SM:m.r.c_TEXTrlm-1_SC-1',
+ 'desc': 'move caret 1 character to the right in RTL text within neutral characters followed by RLM',
+ 'function': 'sel.modify("move", "right", "character");',
+ 'pad': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!?^!&#x200F;(RLM)".',
+ 'expected': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!^?!&#x200F;(RLM)".' },
+
+ { 'id': 'SM:m.l.c_TEXTrlm-1_SC-1',
+ 'desc': 'move caret 1 character to the left in RTL text within neutral characters followed by RLM',
+ 'function': 'sel.modify("move", "left", "character");',
+ 'pad': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!^?!&#x200F;(RLM)".',
+ 'expected': 'I said, "&#x064A;&#x0639;&#x0646;&#x064A; &#x0633;&#x064A;&#x0627;&#x0631;&#x0629;!?^!&#x200F;(RLM)".' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: move forward/backward over words in Japanese text',
+ 'tests': [
+ { 'id': 'SM:m.f.w_TEXT-jp_SC-1',
+ 'desc': 'move caret forward 1 word in Japanese text (adjective)',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': '^&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;',
+ 'expected': '&#x9762;&#x767D;&#x3044;^&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;' },
+
+ { 'id': 'SM:m.f.w_TEXT-jp_SC-2',
+ 'desc': 'move caret forward 1 word in Japanese text (in the middle of a word)',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': '&#x9762;^&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;',
+ 'expected': '&#x9762;&#x767D;&#x3044;^&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;' },
+
+ { 'id': 'SM:m.f.w_TEXT-jp_SC-3',
+ 'desc': 'move caret forward 1 word in Japanese text (noun)',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': '&#x9762;&#x767D;&#x3044;^&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;',
+ 'expected': [ '&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;^&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;',
+ '&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;^&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;' ] },
+
+ { 'id': 'SM:m.f.w_TEXT-jp_SC-4',
+ 'desc': 'move caret forward 1 word in Japanese text (Katakana)',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': '&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;^&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;',
+ 'expected': '&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;^&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;' },
+
+ { 'id': 'SM:m.f.w_TEXT-jp_SC-5',
+ 'desc': 'move caret forward 1 word in Japanese text (verb)',
+ 'function': 'sel.modify("move", "forward", "word");',
+ 'pad': '&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;^&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;&#x3002;',
+ 'expected': '&#x9762;&#x767D;&#x3044;&#x4F8B;&#x6587;&#x3092;&#x30C6;&#x30B9;&#x30C8;&#x3057;&#x307E;&#x3057;&#x3087;&#x3046;^&#x3002;' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection forward',
+ 'tests': [
+ { 'id': 'SM:e.f.c_TEXT-1_SC-1',
+ 'desc': 'extend selection 1 character forward',
+ 'function': 'sel.modify("extend", "forward", "character");',
+ 'pad': 'foo ^bar baz',
+ 'expected': 'foo [b]ar baz' },
+
+ { 'id': 'SM:e.f.c_TEXT-1_SI-1',
+ 'desc': 'extend selection 1 character forward',
+ 'function': 'sel.modify("extend", "forward", "character");',
+ 'pad': 'foo [b]ar baz',
+ 'expected': 'foo [ba]r baz' },
+
+ { 'id': 'SM:e.f.w_TEXT-1_SC-1',
+ 'desc': 'extend selection 1 word forward',
+ 'function': 'sel.modify("extend", "forward", "word");',
+ 'pad': 'foo ^bar baz',
+ 'expected': 'foo [bar] baz' },
+
+ { 'id': 'SM:e.f.w_TEXT-1_SI-1',
+ 'desc': 'extend selection 1 word forward',
+ 'function': 'sel.modify("extend", "forward", "word");',
+ 'pad': 'foo [b]ar baz',
+ 'expected': 'foo [bar] baz' },
+
+ { 'id': 'SM:e.f.w_TEXT-1_SI-2',
+ 'desc': 'extend selection 1 word forward',
+ 'function': 'sel.modify("extend", "forward", "word");',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo [bar baz]' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection backward, shrinking it',
+ 'tests': [
+ { 'id': 'SM:e.b.c_TEXT-1_SI-2',
+ 'desc': 'extend selection 1 character backward',
+ 'function': 'sel.modify("extend", "backward", "character");',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo [ba]r baz' },
+
+ { 'id': 'SM:e.b.c_TEXT-1_SI-1',
+ 'desc': 'extend selection 1 character backward',
+ 'function': 'sel.modify("extend", "backward", "character");',
+ 'pad': 'foo [b]ar baz',
+ 'expected': 'foo ^bar baz' },
+
+ { 'id': 'SM:e.b.w_TEXT-1_SI-3',
+ 'desc': 'extend selection 1 word backward',
+ 'function': 'sel.modify("extend", "backward", "word");',
+ 'pad': 'foo [bar baz]',
+ 'expected': 'foo [bar] baz' },
+
+ { 'id': 'SM:e.b.w_TEXT-1_SI-2',
+ 'desc': 'extend selection 1 word backward',
+ 'function': 'sel.modify("extend", "backward", "word");',
+ 'pad': 'foo [bar] baz',
+ 'expected': 'foo ^bar baz' },
+
+ { 'id': 'SM:e.b.w_TEXT-1_SI-4',
+ 'desc': 'extend selection 1 word backward',
+ 'function': 'sel.modify("extend", "backward", "word");',
+ 'pad': 'foo b[ar baz]',
+ 'expected': 'foo b[ar] baz' },
+
+ { 'id': 'SM:e.b.w_TEXT-1_SI-5',
+ 'desc': 'extend selection 1 word backward',
+ 'function': 'sel.modify("extend", "backward", "word");',
+ 'pad': 'foo b[ar] baz',
+ 'expected': 'foo b^ar baz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection backward, creating or extending a reverse selections',
+ 'tests': [
+ { 'id': 'SM:e.b.c_TEXT-1_SC-1',
+ 'desc': 'extend selection 1 character backward',
+ 'function': 'sel.modify("extend", "backward", "character");',
+ 'pad': 'foo b^ar baz',
+ 'expected': 'foo ]b[ar baz' },
+
+ { 'id': 'SM:e.b.c_TEXT-1_SIR-1',
+ 'desc': 'extend selection 1 character backward',
+ 'function': 'sel.modify("extend", "backward", "character");',
+ 'pad': 'foo b]a[r baz',
+ 'expected': 'foo ]ba[r baz' },
+
+ { 'id': 'SM:e.b.w_TEXT-1_SIR-1',
+ 'desc': 'extend selection 1 word backward',
+ 'function': 'sel.modify("extend", "backward", "word");',
+ 'pad': 'foo b]a[r baz',
+ 'expected': 'foo ]ba[r baz' },
+
+ { 'id': 'SM:e.b.w_TEXT-1_SIR-2',
+ 'desc': 'extend selection 1 word backward',
+ 'function': 'sel.modify("extend", "backward", "word");',
+ 'pad': 'foo ]ba[r baz',
+ 'expected': ']foo ba[r baz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection forward, shrinking a reverse selections',
+ 'tests': [
+ { 'id': 'SM:e.f.c_TEXT-1_SIR-1',
+ 'desc': 'extend selection 1 character forward',
+ 'function': 'sel.modify("extend", "forward", "character");',
+ 'pad': 'foo b]a[r baz',
+ 'expected': 'foo ba^r baz' },
+
+ { 'id': 'SM:e.f.c_TEXT-1_SIR-2',
+ 'desc': 'extend selection 1 character forward',
+ 'function': 'sel.modify("extend", "forward", "character");',
+ 'pad': 'foo ]ba[r baz',
+ 'expected': 'foo b]a[r baz' },
+
+ { 'id': 'SM:e.f.w_TEXT-1_SIR-1',
+ 'desc': 'extend selection 1 word forward',
+ 'function': 'sel.modify("extend", "forward", "word");',
+ 'pad': 'foo ]ba[r baz',
+ 'expected': 'foo ba^r baz' },
+
+ { 'id': 'SM:e.f.w_TEXT-1_SIR-3',
+ 'desc': 'extend selection 1 word forward',
+ 'function': 'sel.modify("extend", "forward", "word");',
+ 'pad': ']foo ba[r baz',
+ 'expected': 'foo ]ba[r baz' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection forward to line boundary',
+ 'tests': [
+ { 'id': 'SM:e.f.lb_BR.BR-1_SC-1',
+ 'desc': 'extend selection forward to line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': 'fo^o<br>bar<br>baz',
+ 'expected': 'fo[o]<br>bar<br>baz' },
+
+ { 'id': 'SM:e.f.lb_BR.BR-1_SI-1',
+ 'desc': 'extend selection forward to next line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': 'fo[o]<br>bar<br>baz',
+ 'expected': 'fo[o<br>bar]<br>baz' },
+
+ { 'id': 'SM:e.f.lb_BR.BR-1_SM-1',
+ 'desc': 'extend selection forward to line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': 'fo[o<br>b]ar<br>baz',
+ 'expected': 'fo[o<br>bar]<br>baz' },
+
+ { 'id': 'SM:e.f.lb_P.P.P-1_SC-1',
+ 'desc': 'extend selection forward to line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': '<p>fo^o</p><p>bar</p><p>baz</p>',
+ 'expected': '<p>fo[o]</p><p>bar</p><p>baz</p>' },
+
+ { 'id': 'SM:e.f.lb_P.P.P-1_SI-1',
+ 'desc': 'extend selection forward to next line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': '<p>fo[o]</p><p>bar</p><p>baz</p>',
+ 'expected': '<p>fo[o</p><p>bar]</p><p>baz</p>' },
+
+ { 'id': 'SM:e.f.lb_P.P.P-1_SM-1',
+ 'desc': 'extend selection forward to line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': '<p>fo[o</p><p>b]ar</p><p>baz</p>',
+ 'expected': '<p>fo[o</p><p>bar]</p><p>baz</p>' },
+
+ { 'id': 'SM:e.f.lb_P.P.P-1_SMR-1',
+ 'desc': 'extend selection forward to line boundary',
+ 'function': 'sel.modify("extend", "forward", "lineboundary");',
+ 'pad': '<p>foo</p><p>b]a[r</p><p>baz</p>',
+ 'expected': '<p>foo</p><p>ba[r]</p><p>baz</p>' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection backward to line boundary',
+ 'tests': [
+ { 'id': 'SM:e.b.lb_BR.BR-1_SC-2',
+ 'desc': 'extend selection backward to line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': 'foo<br>bar<br>b^az',
+ 'expected': 'foo<br>bar<br>]b[az' },
+
+ { 'id': 'SM:e.b.lb_BR.BR-1_SIR-2',
+ 'desc': 'extend selection backward to previous line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': 'foo<br>bar<br>]b[az',
+ 'expected': 'foo<br>]bar<br>b[az' },
+
+ { 'id': 'SM:e.b.lb_BR.BR-1_SMR-2',
+ 'desc': 'extend selection backward to line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': 'foo<br>ba]r<br>b[az',
+ 'expected': 'foo<br>]bar<br>b[az' },
+
+ { 'id': 'SM:e.b.lb_P.P.P-1_SC-2',
+ 'desc': 'extend selection backward to line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': '<p>foo</p><p>bar</p><p>b^az</p>',
+ 'expected': '<p>foo</p><p>bar</p><p>]b[az</p>' },
+
+ { 'id': 'SM:e.b.lb_P.P.P-1_SIR-2',
+ 'desc': 'extend selection backward to previous line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': '<p>foo</p><p>bar</p><p>]b[az</p>',
+ 'expected': '<p>foo</p><p>]bar</p><p>b[az</p>' },
+
+ { 'id': 'SM:e.b.lb_P.P.P-1_SMR-2',
+ 'desc': 'extend selection backward to line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': '<p>foo</p><p>ba]r</p><p>b[az</p>',
+ 'expected': '<p>foo</p><p>]bar</p><p>b[az</p>' },
+
+ { 'id': 'SM:e.b.lb_P.P.P-1_SM-2',
+ 'desc': 'extend selection backward to line boundary',
+ 'function': 'sel.modify("extend", "backward", "lineboundary");',
+ 'pad': '<p>foo</p><p>b[a]r</p><p>baz</p>',
+ 'expected': '<p>foo</p><p>]b[ar</p><p>baz</p>' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection forward to next line (NOTE: use identical text in every line!)',
+ 'tests': [
+ { 'id': 'SM:e.f.l_BR.BR-2_SC-1',
+ 'desc': 'extend selection forward to next line',
+ 'function': 'sel.modify("extend", "forward", "line");',
+ 'pad': 'fo^o<br>foo<br>foo',
+ 'expected': 'fo[o<br>fo]o<br>foo' },
+
+ { 'id': 'SM:e.f.l_BR.BR-2_SI-1',
+ 'desc': 'extend selection forward to next line',
+ 'function': 'sel.modify("extend", "forward", "line");',
+ 'pad': 'fo[o]<br>foo<br>foo',
+ 'expected': 'fo[o<br>foo]<br>foo' },
+
+ { 'id': 'SM:e.f.l_BR.BR-2_SM-1',
+ 'desc': 'extend selection forward to next line',
+ 'function': 'sel.modify("extend", "forward", "line");',
+ 'pad': 'fo[o<br>f]oo<br>foo',
+ 'expected': 'fo[o<br>foo<br>f]oo' },
+
+ { 'id': 'SM:e.f.l_P.P-1_SC-1',
+ 'desc': 'extend selection forward to next line over paragraph boundaries',
+ 'function': 'sel.modify("extend", "forward", "line");',
+ 'pad': '<p>foo^bar</p><p>foobar</p>',
+ 'expected': '<p>foo[bar</p><p>foo]bar</p>' },
+
+ { 'id': 'SM:e.f.l_P.P-1_SMR-1',
+ 'desc': 'extend selection forward to next line over paragraph boundaries',
+ 'function': 'sel.modify("extend", "forward", "line");',
+ 'pad': '<p>fo]obar</p><p>foob[ar</p>',
+ 'expected': '<p>foobar</p><p>fo]ob[ar</p>' }
+ ]
+ },
+
+ { 'desc': 'sel.modify: extend selection backward to previous line (NOTE: use identical text in every line!)',
+ 'tests': [
+ { 'id': 'SM:e.b.l_BR.BR-2_SC-2',
+ 'desc': 'extend selection backward to previous line',
+ 'function': 'sel.modify("extend", "backward", "line");',
+ 'pad': 'foo<br>foo<br>f^oo',
+ 'expected': 'foo<br>f]oo<br>f[oo' },
+
+ { 'id': 'SM:e.b.l_BR.BR-2_SIR-2',
+ 'desc': 'extend selection backward to previous line',
+ 'function': 'sel.modify("extend", "backward", "line");',
+ 'pad': 'foo<br>foo<br>]f[oo',
+ 'expected': 'foo<br>]foo<br>f[oo' },
+
+ { 'id': 'SM:e.b.l_BR.BR-2_SMR-2',
+ 'desc': 'extend selection backward to previous line',
+ 'function': 'sel.modify("extend", "backward", "line");',
+ 'pad': 'foo<br>fo]o<br>f[oo',
+ 'expected': 'fo]o<br>foo<br>f[oo' },
+
+ { 'id': 'SM:e.b.l_P.P-1_SC-2',
+ 'desc': 'extend selection backward to next line over paragraph boundaries',
+ 'function': 'sel.modify("extend", "backward", "line");',
+ 'pad': '<p>foobar</p><p>foo^bar</p>',
+ 'expected': '<p>foo]bar</p><p>foo[bar</p>' },
+
+ { 'id': 'SM:e.b.l_P.P-1_SM-1',
+ 'desc': 'extend selection backward to next line over paragraph boundaries',
+ 'function': 'sel.modify("extend", "backward", "line");',
+ 'pad': '<p>fo[obar</p><p>foob]ar</p>',
+ 'expected': '<p>fo[ob]ar</p><p>foobar</p>' }
+ ]
+ },
+
+ { 'desc': 'sel.selectAllChildren(<element>)',
+ 'function': 'sel.selectAllChildren(doc.getElementById("div"));',
+ 'tests': [
+ { 'id': 'SAC:div_DIV-1_SC-1',
+ 'desc': 'selectAllChildren(<div>)',
+ 'pad': 'foo<div id="div">bar <span>ba^z</span></div>qoz',
+ 'expected': [ 'foo<div id="div">[bar <span>baz</span>}</div>qoz',
+ 'foo<div id="div">{bar <span>baz</span>}</div>qoz' ] },
+ ]
+ }
+ ]
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapply.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapply.py
new file mode 100644
index 000000000..adad65617
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapply.py
@@ -0,0 +1,462 @@
+
+UNAPPLY_TESTS = {
+ 'id': 'U',
+ 'caption': 'Unapply Existing Formatting Tests',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': False,
+ 'expected': 'foo[bar]baz',
+
+ 'RFC': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'remove link',
+ 'command': 'unlink',
+ 'tests': [
+ { 'id': 'UNLINK_A-1_SO',
+ 'desc': 'unlink wrapped <a> element',
+ 'pad': 'foo[<a>bar</a>]baz' },
+
+ { 'id': 'UNLINK_A-1_SW',
+ 'desc': 'unlink <a> element where the selection wraps the full content',
+ 'pad': 'foo<a>[bar]</a>baz' },
+
+ { 'id': 'UNLINK_An:a.h:id-1_SO',
+ 'desc': 'unlink wrapped <a> element that has a name and href attribute',
+ 'pad': 'foo[<a name="A" href="#UNLINK:An:a.h:id-1_SO">bar</a>]baz' },
+
+ { 'id': 'UNLINK_A-2_SO',
+ 'desc': 'unlink contained <a> element',
+ 'pad': 'foo[b<a>a</a>r]baz' },
+
+ { 'id': 'UNLINK_A2-1_SO',
+ 'desc': 'unlink 2 contained <a> elements',
+ 'pad': 'foo[<a>b</a>a<a>r</a>]baz' }
+ ]
+ }
+ ],
+
+ 'Proposed': [
+ { 'desc': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'remove bold',
+ 'command': 'bold',
+ 'tests': [
+ { 'id': 'B_B-1_SW',
+ 'rte1-id': 'u-bold-0',
+ 'desc': 'Selection within tags; remove <b> tags',
+ 'pad': 'foo<b>[bar]</b>baz' },
+
+ { 'id': 'B_B-1_SO',
+ 'desc': 'Selection outside of tags; remove <b> tags',
+ 'pad': 'foo[<b>bar</b>]baz' },
+
+ { 'id': 'B_B-1_SL',
+ 'desc': 'Selection oblique left; remove <b> tags',
+ 'pad': 'foo[<b>bar]</b>baz' },
+
+ { 'id': 'B_B-1_SR',
+ 'desc': 'Selection oblique right; remove <b> tags',
+ 'pad': 'foo<b>[bar</b>]baz' },
+
+ { 'id': 'B_STRONG-1_SW',
+ 'rte1-id': 'u-bold-1',
+ 'desc': 'Selection within tags; remove <strong> tags',
+ 'pad': 'foo<strong>[bar]</strong>baz' },
+
+ { 'id': 'B_STRONG-1_SO',
+ 'desc': 'Selection outside of tags; remove <strong> tags',
+ 'pad': 'foo[<strong>bar</strong>]baz' },
+
+ { 'id': 'B_STRONG-1_SL',
+ 'desc': 'Selection oblique left; remove <strong> tags',
+ 'pad': 'foo[<strong>bar]</strong>baz' },
+
+ { 'id': 'B_STRONG-1_SR',
+ 'desc': 'Selection oblique right; remove <strong> tags',
+ 'pad': 'foo<strong>[bar</strong>]baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SW',
+ 'rte1-id': 'u-bold-2',
+ 'desc': 'Selection within tags; remove "font-weight: bold"',
+ 'pad': 'foo<span style="font-weight: bold">[bar]</span>baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SO',
+ 'desc': 'Selection outside of tags; remove "font-weight: bold"',
+ 'pad': 'foo[<span style="font-weight: bold">bar</span>]baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SL',
+ 'desc': 'Selection oblique left; remove "font-weight: bold"',
+ 'pad': 'foo[<span style="font-weight: bold">bar]</span>baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SR',
+ 'desc': 'Selection oblique right; remove "font-weight: bold"',
+ 'pad': 'foo<span style="font-weight: bold">[bar</span>]baz' },
+
+ { 'id': 'B_B-P3-1_SO12',
+ 'desc': 'Unbolding multiple paragraphs in inside bolded content with content-model violation',
+ 'pad': '<b>{<p>foo</p><p>bar</p>}<p>baz</p></b>',
+ 'expected': [ '<p>[foo</p><p>bar]</p><p><b>baz</b></p>',
+ '<p>[foo</p><p>bar]</p><b><p>baz</p></b>' ] },
+
+ { 'id': 'B_B-P-I..P-1_SO-I',
+ 'desc': 'Unbolding italicized content inside bolded content with content-model violation',
+ 'pad': '<b><p>foo[<i>bar</i>]</p><p>baz</p></b>',
+ 'expected': [ '<p><b>foo</b><i>[bar]</i></p><p><b>baz</b></p>',
+ '<b><p>foo</p></b><p><i>[bar]</i></p><b><p>baz</p></b>' ] },
+
+ { 'id': 'B_B-2_SL',
+ 'desc': 'Remove partially covered bold, selection extends left',
+ 'pad': 'foo [bar <b>baz] qoz</b> quz sic',
+ 'expected': 'foo [bar baz]<b> qoz</b> quz sic' },
+
+ { 'id': 'B_B-2_SR',
+ 'desc': 'Remove partially covered bold, selection extends right',
+ 'pad': 'foo bar <b>baz [qoz</b> quz] sic',
+ 'expected': 'foo bar <b>baz </b>[qoz quz] sic' }
+ ]
+ },
+
+ { 'desc': 'remove italic',
+ 'command': 'italic',
+ 'tests': [
+ { 'id': 'I_I-1_SW',
+ 'rte1-id': 'u-italic-0',
+ 'desc': 'Selection within tags; remove <i> tags',
+ 'pad': 'foo<i>[bar]</i>baz' },
+
+ { 'id': 'I_I-1_SO',
+ 'desc': 'Selection outside of tags; remove <i> tags',
+ 'pad': 'foo[<i>bar</i>]baz' },
+
+ { 'id': 'I_I-1_SL',
+ 'desc': 'Selection oblique left; remove <i> tags',
+ 'pad': 'foo[<i>bar]</i>baz' },
+
+ { 'id': 'I_I-1_SR',
+ 'desc': 'Selection oblique right; remove <i> tags',
+ 'pad': 'foo<i>[bar</i>]baz' },
+
+ { 'id': 'I_EM-1_SW',
+ 'rte1-id': 'u-italic-1',
+ 'desc': 'Selection within tags; remove <em> tags',
+ 'pad': 'foo<em>[bar]</em>baz' },
+
+ { 'id': 'I_EM-1_SO',
+ 'desc': 'Selection outside of tags; remove <em> tags',
+ 'pad': 'foo[<em>bar</em>]baz' },
+
+ { 'id': 'I_EM-1_SL',
+ 'desc': 'Selection oblique left; remove <em> tags',
+ 'pad': 'foo[<em>bar]</em>baz' },
+
+ { 'id': 'I_EM-1_SR',
+ 'desc': 'Selection oblique right; remove <em> tags',
+ 'pad': 'foo<em>[bar</em>]baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SW',
+ 'rte1-id': 'u-italic-2',
+ 'desc': 'Selection within tags; remove "font-style: italic"',
+ 'pad': 'foo<span style="font-style: italic">[bar]</span>baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SO',
+ 'desc': 'Selection outside of tags; Italicize "font-style: italic"',
+ 'pad': 'foo[<span style="font-style: italic">bar</span>]baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SL',
+ 'desc': 'Selection oblique left; Italicize "font-style: italic"',
+ 'pad': 'foo[<span style="font-style: italic">bar]</span>baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SR',
+ 'desc': 'Selection oblique right; Italicize "font-style: italic"',
+ 'pad': 'foo<span style="font-style: italic">[bar</span>]baz' },
+
+ { 'id': 'I_I-P3-1_SO2',
+ 'desc': 'Unitalicize content with content-model violation',
+ 'pad': '<i><p>foo</p>{<p>bar</p>}<p>baz</p></i>',
+ 'expected': [ '<p><i>foo</i></p><p>[bar]</p><p><i>baz</i></p>',
+ '<i><p>foo</p></i><p>[bar]</p><i><p>baz</p></i>' ] }
+ ]
+ },
+
+ { 'desc': 'remove underline',
+ 'command': 'underline',
+ 'tests': [
+ { 'id': 'U_U-1_SW',
+ 'rte1-id': 'u-underline-0',
+ 'desc': 'Selection within tags; remove <u> tags',
+ 'pad': 'foo<u>[bar]</u>baz' },
+
+ { 'id': 'U_U-1_SO',
+ 'desc': 'Selection outside of tags; remove <u> tags',
+ 'pad': 'foo[<u>bar</u>]baz' },
+
+ { 'id': 'U_U-1_SL',
+ 'desc': 'Selection oblique left; remove <u> tags',
+ 'pad': 'foo[<u>bar]</u>baz' },
+
+ { 'id': 'U_U-1_SR',
+ 'desc': 'Selection oblique right; remove <u> tags',
+ 'pad': 'foo<u>[bar</u>]baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SW',
+ 'rte1-id': 'u-underline-1',
+ 'desc': 'Selection within tags; remove "text-decoration: underline"',
+ 'pad': 'foo<span style="text-decoration: underline">[bar]</span>baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SO',
+ 'desc': 'Selection outside of tags; remove "text-decoration: underline"',
+ 'pad': 'foo[<span style="text-decoration: underline">bar</span>]baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SL',
+ 'desc': 'Selection oblique left; remove "text-decoration: underline"',
+ 'pad': 'foo[<span style="text-decoration: underline">bar]</span>baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SR',
+ 'desc': 'Selection oblique right; remove "text-decoration: underline"',
+ 'pad': 'foo<span style="text-decoration: underline">[bar</span>]baz' },
+
+ { 'id': 'U_U-S-1_SO',
+ 'desc': 'Removing underline from underlined content with striked content',
+ 'pad': '<u>foo[bar<s>baz</s>quoz]</u>',
+ 'expected': '<u>foo</u>[bar<s>baz</s>quoz]' },
+
+ { 'id': 'U_U-S-2_SI',
+ 'desc': 'Removing underline from striked content inside underlined content',
+ 'pad': '<u><s>foo[bar]baz</s>quoz</u>',
+ 'expected': '<s><u>foo</u>[bar]<u>baz</u>quoz</s>' },
+
+ { 'id': 'U_U-P3-1_SO',
+ 'desc': 'Removing underline from underlined content with content-model violation',
+ 'pad': '<u><p>foo</p>{<p>bar</p>}<p>baz</p></u>',
+ 'expected': [ '<p><u>foo</u></p><p>[bar]</p><p><u>baz</u></p>',
+ '<u><p>foo</p></u><p>[bar]</p><u><p>baz</p></u>' ] }
+ ]
+ },
+
+ { 'desc': 'remove strike through',
+ 'command': 'strikethrough',
+ 'tests': [
+ { 'id': 'S_S-1_SW',
+ 'rte1-id': 'u-strikethrough-1',
+ 'desc': 'Selection within tags; remove <s> tags',
+ 'pad': 'foo<s>[bar]</s>baz' },
+
+ { 'id': 'S_S-1_SO',
+ 'desc': 'Selection outside of tags; remove <s> tags',
+ 'pad': 'foo[<s>bar</s>]baz' },
+
+ { 'id': 'S_S-1_SL',
+ 'desc': 'Selection oblique left; remove <s> tags',
+ 'pad': 'foo[<s>bar]</s>baz' },
+
+ { 'id': 'S_S-1_SR',
+ 'desc': 'Selection oblique right; remove <s> tags',
+ 'pad': 'foo<s>[bar</s>]baz' },
+
+ { 'id': 'S_STRIKE-1_SW',
+ 'rte1-id': 'u-strikethrough-0',
+ 'desc': 'Selection within tags; remove <strike> tags',
+ 'pad': 'foo<strike>[bar]</strike>baz' },
+
+ { 'id': 'S_STRIKE-1_SO',
+ 'desc': 'Selection outside of tags; remove <strike> tags',
+ 'pad': 'foo[<strike>bar</strike>]baz' },
+
+ { 'id': 'S_STRIKE-1_SL',
+ 'desc': 'Selection oblique left; remove <strike> tags',
+ 'pad': 'foo[<strike>bar]</strike>baz' },
+
+ { 'id': 'S_STRIKE-2_SR',
+ 'desc': 'Selection oblique right; remove <strike> tags',
+ 'pad': 'foo<strike>[bar</strike>]baz' },
+
+ { 'id': 'S_DEL-1_SW',
+ 'rte1-id': 'u-strikethrough-2',
+ 'desc': 'Selection within tags; remove <del> tags',
+ 'pad': 'foo<del>[bar]</del>baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SW',
+ 'rte1-id': 'u-strikethrough-3',
+ 'desc': 'Selection within tags; remove "text-decoration:line-through"',
+ 'pad': 'foo<span style="text-decoration:line-through">[bar]</span>baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SO',
+ 'desc': 'Selection outside of tags; Italicize "text-decoration:line-through"',
+ 'pad': 'foo[<span style="text-decoration:line-through">bar</span>]baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SL',
+ 'desc': 'Selection oblique left; Italicize "text-decoration:line-through"',
+ 'pad': 'foo[<span style="text-decoration:line-through">bar]</span>baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SR',
+ 'desc': 'Selection oblique right; Italicize "text-decoration:line-through"',
+ 'pad': 'foo<span style="text-decoration:line-through">[bar</span>]baz' },
+
+ { 'id': 'S_S-U-1_SI',
+ 'desc': 'Removing underline from underlined content inside striked content',
+ 'pad': '<s><u>foo[bar]baz</u>quoz</s>',
+ 'expected': '<s><u>foo</u></s><u>[bar]</u><s><u>baz</u>quoz</s>' },
+
+ { 'id': 'S_U-S-1_SI',
+ 'desc': 'Removing underline from striked content inside underlined content',
+ 'pad': '<u><s>foo[bar]baz</s>quoz</u>',
+ 'expected': '<u><s>foo</s>[bar]<s>baz</s>quoz</u>' }
+ ]
+ },
+
+ { 'desc': 'remove subscript',
+ 'command': 'subscript',
+ 'tests': [
+ { 'id': 'SUB_SUB-1_SW',
+ 'rte1-id': 'u-subscript-0',
+ 'desc': 'remove subscript',
+ 'pad': 'foo<sub>[bar]</sub>baz' },
+
+ { 'id': 'SUB_SPANs:va:sub-1_SW',
+ 'rte1-id': 'u-subscript-1',
+ 'desc': 'remove subscript',
+ 'pad': 'foo<span style="vertical-align: sub">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': 'remove superscript',
+ 'command': 'superscript',
+ 'tests': [
+ { 'id': 'SUP_SUP-1_SW',
+ 'rte1-id': 'u-superscript-0',
+ 'desc': 'remove superscript',
+ 'pad': 'foo<sup>[bar]</sup>baz' },
+
+ { 'id': 'SUP_SPANs:va:super-1_SW',
+ 'rte1-id': 'u-superscript-1',
+ 'desc': 'remove superscript',
+ 'pad': 'foo<span style="vertical-align: super">[bar]</span>baz' }
+ ]
+ },
+
+ { 'desc': 'remove links',
+ 'command': 'unlink',
+ 'tests': [
+ { 'id': 'UNLINK_Ahref:url-1_SW',
+ 'rte1-id': 'u-unlink-0',
+ 'desc': 'unlink an <a> element with href attribute where all children are selected',
+ 'pad': 'foo<a href="http://www.goo.gl">[bar]</a>baz' },
+
+ { 'id': 'UNLINK_A-1_SC',
+ 'desc': 'unlink an <a> element that contains the collapsed selection',
+ 'pad': 'foo<a>ba^r</a>baz',
+ 'expected': 'fooba^rbaz' },
+
+ { 'id': 'UNLINK_A-1_SI',
+ 'desc': 'unlink an <a> element that contains the whole selection',
+ 'pad': 'foo<a>b[a]r</a>baz',
+ 'expected': 'foob[a]rbaz' },
+
+ { 'id': 'UNLINK_A-2_SL',
+ 'desc': 'unlink a partially contained <a> element',
+ 'pad': 'foo[ba<a>r]ba</a>z' },
+
+ { 'id': 'UNLINK_A-3_SR',
+ 'desc': 'unlink a partially contained <a> element',
+ 'pad': 'fo<a>o[ba</a>r]baz' },
+
+ { 'id': 'UNLINK_As:d:b.fw:b-1_SW',
+ 'desc': 'unlink, preserving styles',
+ 'pad': 'foo<a href="#" style="display: block; font-weight: bold">[bar]</a>baz',
+ 'expected': 'foo<span style="display: block; font-weight: bold">[bar]</span>baz' },
+
+ { 'id': 'UNLINK_A-IMG-1_SO',
+ 'desc': 'unlink a linked image at the start of the content',
+ 'pad': '{<a href="#"><img src="pic.jpg" align="right" height="140" width="200"></a>abc]',
+ 'expected': '{<img src="pic.jpg" align="right" height="140" width="200">abc]' }
+ ]
+ },
+
+ { 'desc': 'outdent',
+ 'command': 'outdent',
+ 'tests': [
+ { 'id': 'OUTDENT_BQ-1_SW',
+ 'rte1-id': 'u-outdent-0',
+ 'desc': 'outdent (remove) a <blockquote>',
+ 'pad': 'foo<blockquote>[bar]</blockquote>baz',
+ 'expected': [ 'foo<p>[bar]</p>baz',
+ 'foo<div>[bar]</div>baz' ],
+ 'accept': 'foo<br>[bar]<br>baz' },
+
+ { 'id': 'OUTDENT_BQ.wibq.s:m:00040.b:n.p:0-1_SW',
+ 'rte1-id': 'u-outdent-1',
+ 'desc': 'outdent (remove) a styled <blockquote>',
+ 'pad': 'foo<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px">[bar]</blockquote>baz',
+ 'expected': [ 'foo<p>[bar]</p>baz',
+ 'foo<div>[bar]</div>baz' ],
+ 'accept': 'foo<br>[bar]<br>baz' },
+
+ { 'id': 'OUTDENT_OL-LI-1_SW',
+ 'rte1-id': 'u-outdent-3',
+ 'desc': 'outdent (remove) an ordered list',
+ 'pad': 'foo<ol><li>[bar]</li></ol>baz',
+ 'expected': [ 'foo<p>[bar]</p>baz',
+ 'foo<div>[bar]</div>baz' ],
+ 'accept': 'foo<br>[bar]<br>baz' },
+
+ { 'id': 'OUTDENT_UL-LI-1_SW',
+ 'rte1-id': 'u-outdent-2',
+ 'desc': 'outdent (remove) an unordered list',
+ 'pad': 'foo<ul><li>[bar]</li></ul>baz',
+ 'expected': [ 'foo<p>[bar]</p>baz',
+ 'foo<div>[bar]</div>baz' ],
+ 'accept': 'foo<br>[bar]<br>baz' },
+
+ { 'id': 'OUTDENT_DIV-1_SW',
+ 'rte1-id': 'u-outdent-4',
+ 'desc': 'outdent (remove) a styled <div> with margin',
+ 'pad': 'foo<div style="margin-left: 40px;">[bar]</div>baz',
+ 'expected': [ 'foo<p>[bar]</p>baz',
+ 'foo<div>[bar]</div>baz' ],
+ 'accept': 'foo<br>[bar]<br>baz' }
+ ]
+ },
+
+ { 'desc': 'remove all formatting',
+ 'command': 'removeformat',
+ 'tests': [
+ { 'id': 'REMOVEFORMAT_B-1_SW',
+ 'rte1-id': 'u-removeformat-0',
+ 'desc': 'remove a <b> tag using "removeformat"',
+ 'pad': 'foo<b>[bar]</b>baz' },
+
+ { 'id': 'REMOVEFORMAT_Ahref:url-1_SW',
+ 'rte1-id': 'u-removeformat-0',
+ 'desc': 'remove a link using "removeformat"',
+ 'pad': 'foo<a href="http://www.goo.gl">[bar]</a>baz' },
+
+ { 'id': 'REMOVEFORMAT_TABLE-TBODY-TR-TD-1_SW',
+ 'rte1-id': 'u-removeformat-2',
+ 'desc': 'remove a table using "removeformat"',
+ 'pad': 'foo<table><tbody><tr><td>[bar]</td></tr></tbody></table>baz',
+ 'expected': [ 'foo<p>[bar]</p>baz',
+ 'foo<div>[bar]</div>baz' ],
+ 'accept': 'foo<br>[bar]<br>baz' }
+ ]
+ },
+
+ { 'desc': 'remove bookmark',
+ 'command': 'unbookmark',
+ 'tests': [
+ { 'id': 'UNBOOKMARK_An:name-1_SW',
+ 'rte1-id': 'u-unbookmark-0',
+ 'desc': 'unlink a bookmark (a named <a> element) where all children are selected',
+ 'pad': 'foo<a name="bookmark">[bar]</a>baz' }
+ ]
+ }
+ ]
+}
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapplyCSS.py b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapplyCSS.py
new file mode 100644
index 000000000..6f934a0f0
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/tests/unapplyCSS.py
@@ -0,0 +1,226 @@
+
+UNAPPLY_TESTS_CSS = {
+ 'id': 'UC',
+ 'caption': 'Unapply Existing Formatting Tests, using styleWithCSS',
+ 'checkAttrs': True,
+ 'checkStyle': True,
+ 'styleWithCSS': True,
+ 'expected': 'foo[bar]baz',
+
+ 'Proposed': [
+ { 'desc': '',
+ 'id': '',
+ 'command': '',
+ 'tests': [
+ ]
+ },
+
+ { 'desc': 'remove bold',
+ 'command': 'bold',
+ 'tests': [
+ { 'id': 'B_B-1_SW',
+ 'desc': 'Selection within tags; remove <b> tags',
+ 'pad': 'foo<b>[bar]</b>baz' },
+
+ { 'id': 'B_B-1_SO',
+ 'desc': 'Selection outside of tags; remove <b> tags',
+ 'pad': 'foo[<b>bar</b>]baz' },
+
+ { 'id': 'B_B-1_SL',
+ 'desc': 'Selection oblique left; remove <b> tags',
+ 'pad': 'foo[<b>bar]</b>baz' },
+
+ { 'id': 'B_B-1_SR',
+ 'desc': 'Selection oblique right; remove <b> tags',
+ 'pad': 'foo<b>[bar</b>]baz' },
+
+ { 'id': 'B_STRONG-1_SW',
+ 'desc': 'Selection within tags; remove <strong> tags',
+ 'pad': 'foo<strong>[bar]</strong>baz' },
+
+ { 'id': 'B_STRONG-1_SO',
+ 'desc': 'Selection outside of tags; remove <strong> tags',
+ 'pad': 'foo[<strong>bar</strong>]baz' },
+
+ { 'id': 'B_STRONG-1_SL',
+ 'desc': 'Selection oblique left; remove <strong> tags',
+ 'pad': 'foo[<strong>bar]</strong>baz' },
+
+ { 'id': 'B_STRONG-1_SR',
+ 'desc': 'Selection oblique right; remove <strong> tags',
+ 'pad': 'foo<strong>[bar</strong>]baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SW',
+ 'desc': 'Selection within tags; remove "font-weight: bold"',
+ 'pad': 'foo<span style="font-weight: bold">[bar]</span>baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SO',
+ 'desc': 'Selection outside of tags; remove "font-weight: bold"',
+ 'pad': 'foo[<span style="font-weight: bold">bar</span>]baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SL',
+ 'desc': 'Selection oblique left; remove "font-weight: bold"',
+ 'pad': 'foo[<span style="font-weight: bold">bar]</span>baz' },
+
+ { 'id': 'B_SPANs:fw:b-1_SR',
+ 'desc': 'Selection oblique right; remove "font-weight: bold"',
+ 'pad': 'foo<span style="font-weight: bold">[bar</span>]baz' }
+ ]
+ },
+
+ { 'desc': 'remove italic',
+ 'command': 'italic',
+ 'tests': [
+ { 'id': 'I_I-1_SW',
+ 'desc': 'Selection within tags; remove <i> tags',
+ 'pad': 'foo<i>[bar]</i>baz' },
+
+ { 'id': 'I_I-1_SO',
+ 'desc': 'Selection outside of tags; remove <i> tags',
+ 'pad': 'foo[<i>bar</i>]baz' },
+
+ { 'id': 'I_I-1_SL',
+ 'desc': 'Selection oblique left; remove <i> tags',
+ 'pad': 'foo[<i>bar]</i>baz' },
+
+ { 'id': 'I_I-1_SR',
+ 'desc': 'Selection oblique right; remove <i> tags',
+ 'pad': 'foo<i>[bar</i>]baz' },
+
+ { 'id': 'I_EM-1_SW',
+ 'desc': 'Selection within tags; remove <em> tags',
+ 'pad': 'foo<em>[bar]</em>baz' },
+
+ { 'id': 'I_EM-1_SO',
+ 'desc': 'Selection outside of tags; remove <em> tags',
+ 'pad': 'foo[<em>bar</em>]baz' },
+
+ { 'id': 'I_EM-1_SL',
+ 'desc': 'Selection oblique left; remove <em> tags',
+ 'pad': 'foo[<em>bar]</em>baz' },
+
+ { 'id': 'I_EM-1_SR',
+ 'desc': 'Selection oblique right; remove <em> tags',
+ 'pad': 'foo<em>[bar</em>]baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SW',
+ 'desc': 'Selection within tags; remove "font-style: italic"',
+ 'pad': 'foo<span style="font-style: italic">[bar]</span>baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SO',
+ 'desc': 'Selection outside of tags; Italicize "font-style: italic"',
+ 'pad': 'foo[<span style="font-style: italic">bar</span>]baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SL',
+ 'desc': 'Selection oblique left; Italicize "font-style: italic"',
+ 'pad': 'foo[<span style="font-style: italic">bar]</span>baz' },
+
+ { 'id': 'I_SPANs:fs:i-1_SR',
+ 'desc': 'Selection oblique right; Italicize "font-style: italic"',
+ 'pad': 'foo<span style="font-style: italic">[bar</span>]baz' }
+ ]
+ },
+
+ { 'desc': 'remove underline',
+ 'command': 'underline',
+ 'tests': [
+ { 'id': 'U_U-1_SW',
+ 'desc': 'Selection within tags; remove <u> tags',
+ 'pad': 'foo<u>[bar]</u>baz' },
+
+ { 'id': 'U_U-1_SO',
+ 'desc': 'Selection outside of tags; remove <u> tags',
+ 'pad': 'foo[<u>bar</u>]baz' },
+
+ { 'id': 'U_U-1_SL',
+ 'desc': 'Selection oblique left; remove <u> tags',
+ 'pad': 'foo[<u>bar]</u>baz' },
+
+ { 'id': 'U_U-1_SR',
+ 'desc': 'Selection oblique right; remove <u> tags',
+ 'pad': 'foo<u>[bar</u>]baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SW',
+ 'desc': 'Selection within tags; remove "text-decoration: underline"',
+ 'pad': 'foo<span style="text-decoration: underline">[bar]</span>baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SO',
+ 'desc': 'Selection outside of tags; remove "text-decoration: underline"',
+ 'pad': 'foo[<span style="text-decoration: underline">bar</span>]baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SL',
+ 'desc': 'Selection oblique left; remove "text-decoration: underline"',
+ 'pad': 'foo[<span style="text-decoration: underline">bar]</span>baz' },
+
+ { 'id': 'U_SPANs:td:u-1_SR',
+ 'desc': 'Selection oblique right; remove "text-decoration: underline"',
+ 'pad': 'foo<span style="text-decoration: underline">[bar</span>]baz' }
+ ]
+ },
+
+ { 'desc': 'remove strike-through',
+ 'command': 'strikethrough',
+ 'tests': [
+ { 'id': 'S_S-1_SW',
+ 'desc': 'Selection within tags; remove <s> tags',
+ 'pad': 'foo<s>[bar]</s>baz' },
+
+ { 'id': 'S_S-1_SO',
+ 'desc': 'Selection outside of tags; remove <s> tags',
+ 'pad': 'foo[<s>bar</s>]baz' },
+
+ { 'id': 'S_S-1_SL',
+ 'desc': 'Selection oblique left; remove <s> tags',
+ 'pad': 'foo[<s>bar]</s>baz' },
+
+ { 'id': 'S_S-1_SR',
+ 'desc': 'Selection oblique right; remove <s> tags',
+ 'pad': 'foo<s>[bar</s>]baz' },
+
+ { 'id': 'S_STRIKE-1_SW',
+ 'desc': 'Selection within tags; remove <strike> tags',
+ 'pad': 'foo<strike>[bar]</strike>baz' },
+
+ { 'id': 'S_STRIKE-1_SO',
+ 'desc': 'Selection outside of tags; remove <strike> tags',
+ 'pad': 'foo[<strike>bar</strike>]baz' },
+
+ { 'id': 'S_STRIKE-1_SL',
+ 'desc': 'Selection oblique left; remove <strike> tags',
+ 'pad': 'foo[<strike>bar]</strike>baz' },
+
+ { 'id': 'S_STRIKE-1_SR',
+ 'desc': 'Selection oblique right; remove <strike> tags',
+ 'pad': 'foo<strike>[bar</strike>]baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SW',
+ 'desc': 'Selection within tags; remove "text-decoration:line-through"',
+ 'pad': 'foo<span style="text-decoration:line-through">[bar]</span>baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SO',
+ 'desc': 'Selection outside of tags; Italicize "text-decoration:line-through"',
+ 'pad': 'foo[<span style="text-decoration:line-through">bar</span>]baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SL',
+ 'desc': 'Selection oblique left; Italicize "text-decoration:line-through"',
+ 'pad': 'foo[<span style="text-decoration:line-through">bar]</span>baz' },
+
+ { 'id': 'S_SPANs:td:lt-1_SR',
+ 'desc': 'Selection oblique right; Italicize "text-decoration:line-through"',
+ 'pad': 'foo<span style="text-decoration:line-through">[bar</span>]baz' },
+
+ { 'id': 'S_SPANc:s-1_SW',
+ 'desc': 'Unapply "strike-through" on interited CSS style',
+ 'checkClass': True,
+ 'pad': 'foo<span class="s">[bar]</span>baz' },
+
+ { 'id': 'S_SPANc:s-2_SI',
+ 'desc': 'Unapply "strike-through" on interited CSS style',
+ 'pad': '<span class="s">foo[bar]baz</span>',
+ 'checkClass': True,
+ 'expected': '<span class="s">foo</span>[bar]<span class="s">baz</span>' }
+ ]
+ }
+ ]
+}
+
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/unittestexample.html b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/unittestexample.html
new file mode 100644
index 000000000..4e27b0554
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/unittestexample.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+
+ <title>Rich Text 2 Unit Test Example</title>
+
+ <!-- utility scripts -->
+ <script type="text/javascript" src="static/js/variables.js"></script>
+ <script type="text/javascript" src="static/js/canonicalize.js"></script>
+ <script type="text/javascript" src="static/js/compare.js"></script>
+ <script type="text/javascript" src="static/js/pad.js"></script>
+ <script type="text/javascript" src="static/js/range.js"></script>
+ <script type="text/javascript" src="static/js/units.js"></script>
+ <script type="text/javascript" src="static/js/run.js"></script>
+ <!-- you do not need static/js/output.js -->
+
+ <!--
+ Tests - note that those have the extensions .py,
+ but can be used as JS files directly.
+ -->
+ <script type="text/javascript" src="tests/selection.py"></script>
+ <script type="text/javascript" src="tests/apply.py"></script>
+ <script type="text/javascript" src="tests/applyCSS.py"></script>
+ <script type="text/javascript" src="tests/change.py"></script>
+ <script type="text/javascript" src="tests/changeCSS.py"></script>
+ <script type="text/javascript" src="tests/unapply.py"></script>
+ <script type="text/javascript" src="tests/unapplyCSS.py"></script>
+ <script type="text/javascript" src="tests/delete.py"></script>
+ <script type="text/javascript" src="tests/forwarddelete.py"></script>
+ <script type="text/javascript" src="tests/insert.py"></script>
+ <script type="text/javascript" src="tests/querySupported.py"></script>
+ <script type="text/javascript" src="tests/queryEnabled.py"></script>
+ <script type="text/javascript" src="tests/queryIndeterm.py"></script>
+ <script type="text/javascript" src="tests/queryState.py"></script>
+ <script type="text/javascript" src="tests/queryValue.py"></script>
+
+ <!-- Do something -->
+ <script type="text/javascript">
+ function runTest() {
+ initVariables();
+ initEditorDocs();
+
+ runTestSuite(UNAPPLY_TESTS);
+
+ // Below alert is just a simple demonstration on how to access the test results.
+ // Note that we only ran UNAPPLY tests above, so we have only results from that test set.
+ //
+ // The 'results' structure is as follows:
+ //
+ // results structure containing all results
+ // [<suite ID>] structure containing the results for the given suite *)
+ // .count number of tests in the given suite
+ // .valscore sum of all test value results (HTML or query value)
+ // .selscore sum of all selection results (HTML tests only)
+ // [<class ID>] structure containing the results for the given class **)
+ // .count number of tests in the given suite
+ // .valscore sum of all test value results (HTML or query value)
+ // .selscore sum of all selection results (HTML tests only)
+ // [<test ID>] structure containing the reults for a given test ***)
+ // .valscore value score (0 or 1), minimum over all containers
+ // .selscore selection score (0 or 1), minimum over all containers (HTML tests only)
+ // .valresult worst test value result (integer, see variables.js)
+ // .selresult worst selection result (integer, see variables.js)
+ // [<cont. ID>] structure containing the results of the test for a given container ****)
+ // .valscore value score (0 or 1)
+ // .selscore selection score (0 or 1)
+ // .valresult value result (integer, see variables.js)
+ // .selresult selection result (integer, see variables.js)
+ // .output output string (mainly for use by the online version)
+ // .innerHTML inner HTML of the testing container (<div> or <body>) after the test
+ // .outerHTML outer HTML of the testing container (<div> or <body>) after the test
+ // .bodyInnerHTML inner HTML of the <body> after the test
+ // .bodyOuterHTML outer HTML of the <body> after the test
+ //
+ // *) <suite ID>: a 1-3 character ID, e.g. UNAPPLY_TESTS.id, or 'U' (both referring the same suite)
+ // **) <class ID>: one of 'Proposed', 'RFC' or 'Finalized'
+ // ***) <test ID>: the ID of the test, without the leading 'RTE2-<suite ID>_' part
+ // ****) <container ID>: one of 'div' (test within a <div contenteditable="true">)
+ // 'dM' (test with designMode = 'on')
+ // 'body' (test within a <body contenteditable="true">)
+
+ alert("Result of 'Apply' tests:\nOut of " +
+ results[UNAPPLY_TESTS.id].count + " tests\n" +
+ results[UNAPPLY_TESTS.id].valscore + " had correct HTML, and\n" +
+ results[UNAPPLY_TESTS.id].selscore + " had a correct result selection\n(in all testing containers)." +
+ "\n\n" +
+ "Test RTE2-U_B_B-1_SW results with a contenteditable <body>:\n" +
+ results['U']['Proposed']['B_B-1_SW']['body'].valscore + " points for the value result, and\n" +
+ results['U']['Proposed']['B_B-1_SW']['body'].selscore + " points for the selection" +
+ ""
+ );
+ }
+ </script>
+</head>
+
+<body onload="runTest()">
+ <iframe name="iframe-dM" id="iframe-dM" src="static/editable-dM.html"></iframe>
+ <iframe name="iframe-body" id="iframe-body" src="static/editable-body.html"></iframe>
+ <iframe name="iframe-div" id="iframe-div" src="static/editable-div.html"></iframe>
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/update_from_upstream b/editor/libeditor/tests/browserscope/lib/richtext2/update_from_upstream
new file mode 100644
index 000000000..baeb76745
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/lib/richtext2/update_from_upstream
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -x
+
+if test -d richtext2; then
+ rm -drf richtext2;
+fi
+
+svn checkout http://browserscope.googlecode.com/svn/trunk/categories/richtext2 richtext2 | tail -1 | sed 's/[^0-9]//g' > current_revision
+
+find richtext2 -type d -name .svn -exec rm -drf \{\} \; 2> /dev/null
+
+# Remove test_set.py and other similarly named files because they confuse our mochitest runner
+find richtext2 =type f -name test_\* -exec rm -rf \{\} \; 2> /dev/null
+
+hg add current_revision richtext2
+
+hg stat .
+
diff --git a/editor/libeditor/tests/browserscope/mochitest.ini b/editor/libeditor/tests/browserscope/mochitest.ini
new file mode 100644
index 000000000..e6e2db413
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/mochitest.ini
@@ -0,0 +1,59 @@
+[default]
+support-files =
+ lib/richtext2/current_revision
+ lib/richtext2/richtext2/common.py
+ lib/richtext2/richtext2/unittestexample.html
+ lib/richtext2/richtext2/static/editable-dM.html
+ lib/richtext2/richtext2/static/editable.css
+ lib/richtext2/richtext2/static/editable-body.html
+ lib/richtext2/richtext2/static/editable-div.html
+ lib/richtext2/richtext2/static/js/variables.js
+ lib/richtext2/richtext2/static/js/range-bootstrap.js
+ lib/richtext2/richtext2/static/js/range.js
+ lib/richtext2/richtext2/static/js/output.js
+ lib/richtext2/richtext2/static/js/compare.js
+ lib/richtext2/richtext2/static/js/canonicalize.js
+ lib/richtext2/richtext2/static/js/pad.js
+ lib/richtext2/richtext2/static/js/run.js
+ lib/richtext2/richtext2/static/js/units.js
+ lib/richtext2/richtext2/static/common.css
+ lib/richtext2/richtext2/__init__.py
+ lib/richtext2/richtext2/handlers.py
+ lib/richtext2/richtext2/templates/output.html
+ lib/richtext2/richtext2/templates/richtext2.html
+ lib/richtext2/richtext2/tests/forwarddelete.py
+ lib/richtext2/richtext2/tests/selection.py
+ lib/richtext2/richtext2/tests/queryIndeterm.py
+ lib/richtext2/richtext2/tests/unapplyCSS.py
+ lib/richtext2/richtext2/tests/apply.py
+ lib/richtext2/richtext2/tests/unapply.py
+ lib/richtext2/richtext2/tests/change.py
+ lib/richtext2/richtext2/tests/queryState.py
+ lib/richtext2/richtext2/tests/queryValue.py
+ lib/richtext2/richtext2/tests/__init__.py
+ lib/richtext2/richtext2/tests/insert.py
+ lib/richtext2/richtext2/tests/queryEnabled.py
+ lib/richtext2/richtext2/tests/applyCSS.py
+ lib/richtext2/richtext2/tests/changeCSS.py
+ lib/richtext2/richtext2/tests/delete.py
+ lib/richtext2/richtext2/tests/querySupported.py
+ lib/richtext2/README
+ lib/richtext2/update_from_upstream
+ lib/richtext2/LICENSE
+ lib/richtext2/README.Mozilla
+ lib/richtext2/currentStatus.js
+ lib/richtext/current_revision
+ lib/richtext/README
+ lib/richtext/update_from_upstream
+ lib/richtext/LICENSE
+ lib/richtext/README.Mozilla
+ lib/richtext/richtext/editable.html
+ lib/richtext/richtext/richtext.html
+ lib/richtext/richtext/js/range.js
+ lib/richtext/currentStatus.js
+
+[test_richtext2.html]
+subsuite = clipboard
+skip-if = os == 'android' && debug # Bug 1202045
+[test_richtext.html]
+
diff --git a/editor/libeditor/tests/browserscope/test_richtext.html b/editor/libeditor/tests/browserscope/test_richtext.html
new file mode 100644
index 000000000..45f8bef38
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/test_richtext.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+BrowserScope richtext category tests
+-->
+<head>
+ <title>BrowserScope Richtext Tests</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="lib/richtext/currentStatus.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=550569">Mozilla Bug 550569</a>
+<p id="display"></p>
+<div id="content">
+ <iframe src="lib/richtext/richtext/richtext.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+// Running all of the tests can take a long time, try to account for it
+SimpleTest.requestLongerTimeout(5);
+
+function sendScore(results, continueParams) {
+ ok(results.length > 1, "At least one test should have been run");
+ for (var i = 1; i < results.length; ++i) {
+ var result = results[i];
+ [type, command, param, success] = result.split(/[\-=]/);
+ var comp = is;
+ if (isKnownFailure(type, command, param)) {
+ comp = todo_is;
+ }
+ comp(success, "1", "Browserscope richtext category=" + type +
+ " test=" + command +
+ " param=" + param);
+ }
+}
+
+document.getElementsByTagName("iframe")[0].addEventListener("load", function() {
+ SimpleTest.finish();
+}, false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/browserscope/test_richtext2.html b/editor/libeditor/tests/browserscope/test_richtext2.html
new file mode 100644
index 000000000..c0ce07a8f
--- /dev/null
+++ b/editor/libeditor/tests/browserscope/test_richtext2.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<html lang="en">
+<!--
+BrowserScope richtext2 category tests
+
+This test is originally based on the unit test example available as part of the
+RichText2 suite:
+http://code.google.com/p/browserscope/source/browse/trunk/categories/richtext2/unittestexample.html
+-->
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+
+ <title>BrowserScope Richtext2 Tests</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+ <!-- utility scripts -->
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/variables.js"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/canonicalize.js"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/compare.js"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/pad.js"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/range.js"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/units.js"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/static/js/run.js"></script>
+ <!-- you do not need static/js/output.js -->
+
+ <!--
+ Tests - note that those have the extensions .py,
+ but can be used as JS files directly.
+ -->
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/selection.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/apply.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/applyCSS.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/change.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/changeCSS.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/unapply.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/unapplyCSS.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/delete.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/forwarddelete.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/insert.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/querySupported.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/queryEnabled.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/queryIndeterm.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/queryState.py"></script>
+ <script type="text/javascript" src="lib/richtext2/richtext2/tests/queryValue.py"></script>
+
+ <script type="text/javascript" src="lib/richtext2/currentStatus.js"></script>
+
+ <!-- Do something -->
+ <script type="text/javascript">
+ // Set this constant to true in order to get the current status of the test suite.
+ // This is useful for updating the currentStatus.js file when an editor bug is fixed.
+ const UPDATE_TEST_RESULTS = false;
+
+ // some tests (at least RTE2-QE_PASTE_TEXT-1) require clipboard data
+ function startTest() {
+ SimpleTest.waitForClipboard("foo",
+ function() {
+ SpecialPowers.clipboardCopyString("foo");
+ },
+ runTest,
+ function() {
+ ok(false, "Failed to copy a string to the clipboard");
+ SimpleTest.finish();
+ }
+ );
+ }
+
+ function runTest() {
+ initVariables();
+ initEditorDocs();
+
+ const tests = [
+ SELECTION_TESTS,
+ APPLY_TESTS,
+ APPLY_TESTS_CSS,
+ CHANGE_TESTS,
+ CHANGE_TESTS_CSS,
+ UNAPPLY_TESTS,
+ UNAPPLY_TESTS_CSS,
+ DELETE_TESTS,
+ FORWARDDELETE_TESTS,
+ INSERT_TESTS,
+ QUERYSUPPORTED_TESTS,
+ QUERYENABLED_TESTS,
+ QUERYINDETERM_TESTS,
+ QUERYSTATE_TESTS,
+ QUERYVALUE_TESTS,
+ ];
+
+ for (var i = 0; i < tests.length; ++i) {
+ runTestSuite(tests[i]);
+ }
+
+ // Below alert is just a simple demonstration on how to access the test results.
+ // Note that we only ran UNAPPLY tests above, so we have only results from that test set.
+ //
+ // The 'results' structure is as follows:
+ //
+ // results structure containing all results
+ // [<suite ID>] structure containing the results for the given suite *)
+ // .count number of tests in the given suite
+ // .valscore sum of all test value results (HTML or query value)
+ // .selscore sum of all selection results (HTML tests only)
+ // [<class ID>] structure containing the results for the given class **)
+ // .count number of tests in the given suite
+ // .valscore sum of all test value results (HTML or query value)
+ // .selscore sum of all selection results (HTML tests only)
+ // [<test ID>] structure containing the reults for a given test ***)
+ // .valscore value score (0 or 1), minimum over all containers
+ // .selscore selection score (0 or 1), minimum over all containers (HTML tests only)
+ // .valresult worst test value result (integer, see variables.js)
+ // .selresult worst selection result (integer, see variables.js)
+ // [<cont. ID>] structure containing the results of the test for a given container ****)
+ // .valscore value score (0 or 1)
+ // .selscore selection score (0 or 1)
+ // .valresult value result (integer, see variables.js)
+ // .selresult selection result (integer, see variables.js)
+ // .output output string (mainly for use by the online version)
+ // .innerHTML inner HTML of the testing container (<div> or <body>) after the test
+ // .outerHTML outer HTML of the testing container (<div> or <body>) after the test
+ // .bodyInnerHTML inner HTML of the <body> after the test
+ // .bodyOuterHTML outer HTML of the <body> after the test
+ //
+ // *) <suite ID>: a 1-3 character ID, e.g. UNAPPLY_TESTS.id, or 'U' (both referring the same suite)
+ // **) <class ID>: one of 'Proposed', 'RFC' or 'Finalized'
+ // ***) <test ID>: the ID of the test, without the leading 'RTE2-<suite ID>_' part
+ // ****) <container ID>: one of 'div' (test within a <div contenteditable="true">)
+ // 'dM' (test with designMode = 'on')
+ // 'body' (test within a <body contenteditable="true">)
+
+ if (UPDATE_TEST_RESULTS) {
+ var newKnownFailures = {value: {}, select: {}};
+ for (var i = 0; i < tests.length; ++i) {
+ var category = tests[i];
+ for (var group in results[category.id]) {
+ switch (group) {
+ // Skip the known properties
+ case "count":
+ case "valscore":
+ case "selscore":
+ case "time":
+ break;
+ default:
+ for (var test_id in results[category.id][group]) {
+ switch (test_id) {
+ // Skip the known properties
+ case "count":
+ case "valscore":
+ case "selscore":
+ break;
+ default:
+ for (var structure in results[category.id][group][test_id]) {
+ switch (structure) {
+ // Only look at each test structure
+ case "dM":
+ case "body":
+ case "div":
+ if (!results[category.id][group][test_id][structure].valscore) {
+ newKnownFailures.value[category.id + "-" + group + "-" + test_id + "-" + structure] = true;
+ }
+ if (!results[category.id][group][test_id][structure].selscore) {
+ newKnownFailures.select[category.id + "-" + group + "-" + test_id + "-" + structure] = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ var resultContainer = document.getElementById("results");
+ resultContainer.style.display = "";
+ resultContainer.textContent = JSON.stringify(newKnownFailures);
+ } else {
+ for (var i = 0; i < tests.length; ++i) {
+ var category = tests[i];
+ for (var group in results[category.id]) {
+ switch (group) {
+ // Skip the known properties
+ case "count":
+ case "valscore":
+ case "selscore":
+ case "time":
+ break;
+ default:
+ for (var test_id in results[category.id][group]) {
+ switch (test_id) {
+ // Skip the known properties
+ case "count":
+ case "valscore":
+ case "selscore":
+ break;
+ default:
+ for (var structure in results[category.id][group][test_id]) {
+ switch (structure) {
+ // Only look at each test structure
+ case "dM":
+ case "body":
+ case "div":
+ var row = results[category.id][group][test_id][structure];
+ var testName = [category.id, group, test_id, structure].join("-");
+ (testName in knownFailures.value ? todo_is : is)(
+ row.valscore, 1, "Browserscope richtext2 value: " + testName);
+ (testName in knownFailures.select ? todo_is : is)(
+ row.selscore, 1, "Browserscope richtext2 selection: " + testName);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // Running all of the tests can take a long time, try to account for it
+ SimpleTest.requestLongerTimeout(5);
+ </script>
+</head>
+
+<body onload="startTest()">
+ <iframe name="iframe-dM" id="iframe-dM" src="lib/richtext2/richtext2/static/editable-dM.html"></iframe>
+ <iframe name="iframe-body" id="iframe-body" src="lib/richtext2/richtext2/static/editable-body.html"></iframe>
+ <iframe name="iframe-div" id="iframe-div" src="lib/richtext2/richtext2/static/editable-div.html"></iframe>
+ <pre id="results" style="display: none"></pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/bug527935.html b/editor/libeditor/tests/bug527935.html
new file mode 100644
index 000000000..4bfa1bac2
--- /dev/null
+++ b/editor/libeditor/tests/bug527935.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div id="content">
+ <iframe id="formTarget" name="formTarget"></iframe>
+ <form action="data:text/html," target="formTarget">
+ <input name="test" id="initValue"><input type="submit">
+ </form>
+</div>
+</body>
+</html
diff --git a/editor/libeditor/tests/bug629172.html b/editor/libeditor/tests/bug629172.html
new file mode 100644
index 000000000..e583b2d44
--- /dev/null
+++ b/editor/libeditor/tests/bug629172.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<style>
+textarea { resize: none }
+</style>
+</head>
+<body>
+<div id="content">
+<textarea id="ltr-ref" style="display: none">test.</textarea>
+<textarea id="rtl-ref" style="display: none; direction: rtl">test.</textarea>
+</div
+</body>
+</html>
diff --git a/editor/libeditor/tests/chrome.ini b/editor/libeditor/tests/chrome.ini
new file mode 100644
index 000000000..98db30001
--- /dev/null
+++ b/editor/libeditor/tests/chrome.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files = green.png
+
+[test_bug489202.xul]
+[test_bug599983.xul]
+[test_bug607584.xul]
+[test_bug616590.xul]
+[test_bug780908.xul]
+[test_contenteditable_text_input_handling.html]
+[test_htmleditor_keyevent_handling.html]
+[test_set_document_title_transaction.html]
+[test_texteditor_keyevent_handling.html]
+skip-if = (debug && os=='win') || (os == 'linux') # Bug 1116205, leaks on windows debug, fails delete key on linux
diff --git a/editor/libeditor/tests/data/cfhtml-chromium.txt b/editor/libeditor/tests/data/cfhtml-chromium.txt
new file mode 100644
index 000000000..7e0253715
--- /dev/null
+++ b/editor/libeditor/tests/data/cfhtml-chromium.txt
Binary files differ
diff --git a/editor/libeditor/tests/data/cfhtml-firefox.txt b/editor/libeditor/tests/data/cfhtml-firefox.txt
new file mode 100644
index 000000000..cc686d856
--- /dev/null
+++ b/editor/libeditor/tests/data/cfhtml-firefox.txt
Binary files differ
diff --git a/editor/libeditor/tests/data/cfhtml-ie.txt b/editor/libeditor/tests/data/cfhtml-ie.txt
new file mode 100644
index 000000000..a30bc5295
--- /dev/null
+++ b/editor/libeditor/tests/data/cfhtml-ie.txt
Binary files differ
diff --git a/editor/libeditor/tests/data/cfhtml-nocontext.txt b/editor/libeditor/tests/data/cfhtml-nocontext.txt
new file mode 100644
index 000000000..aa4882227
--- /dev/null
+++ b/editor/libeditor/tests/data/cfhtml-nocontext.txt
@@ -0,0 +1,18 @@
+Version:0.9
+StartHTML:-1
+EndHTML:-1
+StartFragment:0000000111
+EndFragment:0000000246
+<!--StartFragment-->
+<html>
+ <head>
+ <title>Test</title>
+
+ </head>
+ <body>
+ <p>
+ 3.<b>1415926535897932</b>
+ </p>
+ </body>
+</html>
+<!--EndFragment-->
diff --git a/editor/libeditor/tests/data/cfhtml-ooo.txt b/editor/libeditor/tests/data/cfhtml-ooo.txt
new file mode 100644
index 000000000..0bcf7616e
--- /dev/null
+++ b/editor/libeditor/tests/data/cfhtml-ooo.txt
Binary files differ
diff --git a/editor/libeditor/tests/file_bug549262.html b/editor/libeditor/tests/file_bug549262.html
new file mode 100644
index 000000000..92a0c76f3
--- /dev/null
+++ b/editor/libeditor/tests/file_bug549262.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <a href="">test</a>
+ <div id="editor" contenteditable="true">abc</div>
+ <div style="height: 20000px;"></div>
+ </body>
+</html>
diff --git a/editor/libeditor/tests/file_bug586662.html b/editor/libeditor/tests/file_bug586662.html
new file mode 100644
index 000000000..298953197
--- /dev/null
+++ b/editor/libeditor/tests/file_bug586662.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <div style="height: 20000px;"></div>
+ <textarea id="editor"></textarea>
+ </body>
+</html>
diff --git a/editor/libeditor/tests/file_bug674770-1.html b/editor/libeditor/tests/file_bug674770-1.html
new file mode 100644
index 000000000..6750bb878
--- /dev/null
+++ b/editor/libeditor/tests/file_bug674770-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE>
+<script>
+ localStorage["clicked"] = "true";
+ close();
+</script>
diff --git a/editor/libeditor/tests/file_bug915962.html b/editor/libeditor/tests/file_bug915962.html
new file mode 100644
index 000000000..85c5139d3
--- /dev/null
+++ b/editor/libeditor/tests/file_bug915962.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <button>Button</button>
+ <img src="green.png" usemap="#map">
+ <map name="map">
+ <!-- This URL ensures that the link doesn't get clicked, since
+ mochitests cannot access the outside network. -->
+ <area shape="rect" coords="0,0,10,10" href="https://youtube.com/">
+ </map>
+ <div style="height: 20000px;" tabindex="-1"><hr></div>
+ </body>
+</html>
diff --git a/editor/libeditor/tests/file_select_all_without_body.html b/editor/libeditor/tests/file_select_all_without_body.html
new file mode 100644
index 000000000..70050a847
--- /dev/null
+++ b/editor/libeditor/tests/file_select_all_without_body.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function unload()
+{
+ window.opener.SimpleTest.finish();
+}
+
+function boom()
+{
+ var root = document.documentElement;
+ while(root.firstChild) {
+ root.removeChild(root.firstChild);
+ }
+ root.appendChild(document.createTextNode("Mozilla"));
+ root.focus();
+ cespan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ cespan.setAttributeNS(null, "contenteditable", "true");
+ root.appendChild(cespan);
+ try {
+ document.execCommand("selectAll", false, null);
+ } catch(e) { }
+
+ is(window.getSelection().toString(), "Mozilla",
+ "The nodes are not selected");
+
+ window.close();
+}
+
+window.opener.SimpleTest.waitForFocus(boom, window);
+
+</script></head>
+
+<body onunload="unload();"></body>
+</html>
diff --git a/editor/libeditor/tests/green.png b/editor/libeditor/tests/green.png
new file mode 100644
index 000000000..0aaec2093
--- /dev/null
+++ b/editor/libeditor/tests/green.png
Binary files differ
diff --git a/editor/libeditor/tests/mochitest.ini b/editor/libeditor/tests/mochitest.ini
new file mode 100644
index 000000000..447fb8b65
--- /dev/null
+++ b/editor/libeditor/tests/mochitest.ini
@@ -0,0 +1,245 @@
+[DEFAULT]
+support-files =
+ data/cfhtml-chromium.txt
+ data/cfhtml-firefox.txt
+ data/cfhtml-ie.txt
+ data/cfhtml-ooo.txt
+ data/cfhtml-nocontext.txt
+ file_bug549262.html
+ file_bug586662.html
+ file_bug674770-1.html
+ file_bug915962.html
+ file_select_all_without_body.html
+ green.png
+ spellcheck.js
+
+[test_bug46555.html]
+[test_bug200416.html]
+[test_bug289384.html]
+skip-if = os != "mac"
+[test_bug290026.html]
+[test_bug291780.html]
+[test_bug309731.html]
+[test_bug316447.html]
+[test_bug318065.html]
+[test_bug332636.html]
+support-files = test_bug332636.html^headers^
+[test_bug366682.html]
+skip-if = os == 'android'
+[test_bug372345.html]
+skip-if = toolkit == 'android'
+[test_bug404320.html]
+[test_bug408231.html]
+skip-if = toolkit == 'android'
+[test_bug410986.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug414526.html]
+[test_bug417418.html]
+skip-if = android_version == '18' # bug 1147989
+[test_bug432225.html]
+skip-if = toolkit == 'android'
+[test_bug439808.html]
+[test_bug442186.html]
+[test_bug449243.html]
+[test_bug455992.html]
+[test_bug456244.html]
+[test_bug460740.html]
+[test_bug468353.html]
+[test_bug471319.html]
+[test_bug471722.html]
+[test_bug478725.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug480647.html]
+[test_bug480972.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug483651.html]
+[test_bug484181.html]
+skip-if = toolkit == 'android'
+[test_bug487524.html]
+[test_bug490879.html]
+subsuite = clipboard
+skip-if = toolkit == 'android' # bug 1299578
+[test_bug502673.html]
+[test_bug514156.html]
+[test_bug520189.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug525389.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug537046.html]
+[test_bug549262.html]
+skip-if = toolkit == 'android'
+[test_bug550434.html]
+skip-if = android_version == '18' # bug 1147989
+[test_bug551704.html]
+subsuite = clipboard
+[test_bug552782.html]
+[test_bug567213.html]
+[test_bug569988.html]
+skip-if = os == 'android'
+[test_bug570144.html]
+[test_bug578771.html]
+skip-if = android_version == '18' # bug 1147989
+[test_bug586662.html]
+skip-if = toolkit == 'android'
+[test_bug587461.html]
+[test_bug590554.html]
+[test_bug592592.html]
+[test_bug596001.html]
+subsuite = clipboard
+[test_bug596333.html]
+skip-if = toolkit == 'android'
+[test_bug596506.html]
+[test_bug597331.html]
+skip-if = toolkit == 'android' || asan || (os == "win" && os_version != "5.1") # Bug 718316, Bug 1211213
+[test_bug597784.html]
+[test_bug599322.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug599983.html]
+[test_bug600570.html]
+subsuite = clipboard
+skip-if = toolkit == 'android' || (os == "win" && os_version != "5.1") # Bug 718316
+[test_bug602130.html]
+[test_bug603556.html]
+subsuite = clipboard
+[test_bug604532.html]
+skip-if = toolkit == 'android'
+[test_bug607584.html]
+[test_bug611182.html]
+skip-if = toolkit == 'android'
+[test_bug612128.html]
+[test_bug612447.html]
+[test_bug620906.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug622371.html]
+skip-if = toolkit == 'android' #bug 957797
+[test_bug625452.html]
+[test_bug629845.html]
+[test_bug635636.html]
+skip-if = e10s || os == 'android'
+[test_bug636465.html]
+skip-if = os == 'android'
+[test_bug638596.html]
+[test_bug640321.html]
+skip-if = android_version == '18' # bug 1147989
+[test_bug641466.html]
+[test_bug645914.html]
+[test_bug646194.html]
+[test_bug668599.html]
+[test_bug674770-1.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug674770-2.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug674861.html]
+[test_bug676401.html]
+[test_bug677752.html]
+[test_bug681229.html]
+subsuite = clipboard
+[test_bug686203.html]
+[test_bug692520.html]
+[test_bug697842.html]
+[test_bug725069.html]
+[test_bug735059.html]
+[test_bug738366.html]
+[test_bug740784.html]
+[test_bug742261.html]
+[test_bug757371.html]
+[test_bug757771.html]
+[test_bug767684.html]
+[test_bug772796.html]
+skip-if = toolkit == 'android' # bug 1309431
+[test_bug773262.html]
+[test_bug780035.html]
+[test_bug787432.html]
+[test_bug790475.html]
+[test_bug795418.html]
+[test_bug795418-2.html]
+[test_bug795418-3.html]
+[test_bug795418-4.html]
+[test_bug795418-5.html]
+[test_bug795418-6.html]
+[test_bug795785.html]
+[test_bug796839.html]
+[test_bug830600.html]
+subsuite = clipboard
+skip-if = e10s
+[test_bug832025.html]
+[test_bug850043.html]
+[test_bug857487.html]
+[test_bug858918.html]
+[test_bug915962.html]
+[test_bug974309.html]
+skip-if = toolkit == 'android'
+[test_bug966155.html]
+skip-if = os != "win"
+[test_bug966552.html]
+skip-if = os != "win"
+[test_bug998188.html]
+[test_bug1026397.html]
+[test_bug1053048.html]
+[test_bug1067255.html]
+[test_bug1068979.html]
+subsuite = clipboard
+[test_bug1094000.html]
+[test_bug1100966.html]
+skip-if = os == 'android'
+[test_bug1102906.html]
+skip-if = os == 'android'
+[test_bug1101392.html]
+subsuite = clipboard
+[test_bug1109465.html]
+[test_bug1140105.html]
+[test_bug1140617.html]
+subsuite = clipboard
+skip-if = toolkit == 'android' # bug 1299578
+[test_bug1153237.html]
+[test_bug1154791.html]
+skip-if = os == 'android'
+[test_bug1162952.html]
+[test_bug1181130-1.html]
+[test_bug1181130-2.html]
+[test_bug1186799.html]
+[test_bug1230473.html]
+[test_bug1247483.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[test_bug1248128.html]
+[test_bug1250010.html]
+[test_bug1257363.html]
+[test_bug1248185.html]
+[test_bug1258085.html]
+[test_bug1268736.html]
+[test_bug1270235.html]
+[test_bug1310912.html]
+skip-if = toolkit == 'android' # bug 1315898
+[test_bug1314790.html]
+[test_bug1315065.html]
+[test_bug1328023.html]
+[test_bug1330796.html]
+[test_bug1332876.html]
+
+[test_CF_HTML_clipboard.html]
+subsuite = clipboard
+[test_composition_event_created_in_chrome.html]
+[test_contenteditable_focus.html]
+[test_dom_input_event_on_htmleditor.html]
+skip-if = toolkit == 'android' # bug 1054087
+[test_dom_input_event_on_texteditor.html]
+[test_dragdrop.html]
+skip-if = os == 'android'
+[test_keypress_untrusted_event.html]
+[test_root_element_replacement.html]
+[test_select_all_without_body.html]
+[test_spellcheck_pref.html]
+skip-if = toolkit == 'android'
+[test_backspace_vs.html]
+[test_css_chrome_load_access.html]
+skip-if = toolkit == 'android' # chrome urls not available due to packaging
+[test_selection_move_commands.html]
diff --git a/editor/libeditor/tests/spellcheck.js b/editor/libeditor/tests/spellcheck.js
new file mode 100644
index 000000000..9d36c3254
--- /dev/null
+++ b/editor/libeditor/tests/spellcheck.js
@@ -0,0 +1,20 @@
+function isSpellingCheckOk(aEditor, aMisspelledWords) {
+ var selcon = aEditor.selectionController;
+ var sel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
+ var numWords = sel.rangeCount;
+
+ is(numWords, aMisspelledWords.length, "Correct number of misspellings and words.");
+
+ if (numWords !== aMisspelledWords.length) {
+ return false;
+ }
+
+ for (var i = 0; i < numWords; ++i) {
+ var word = String(sel.getRangeAt(i));
+ is(word, aMisspelledWords[i], "Misspelling is what we think it is.");
+ if (word !== aMisspelledWords[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/editor/libeditor/tests/test_CF_HTML_clipboard.html b/editor/libeditor/tests/test_CF_HTML_clipboard.html
new file mode 100644
index 000000000..4949f40b3
--- /dev/null
+++ b/editor/libeditor/tests/test_CF_HTML_clipboard.html
@@ -0,0 +1,159 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=572642
+-->
+<head>
+ <title>Test for Bug 572642</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=572642">Mozilla Bug 572642</a>
+<p id="display"></p>
+<div id="content">
+ <div id="editor1" contenteditable="true"></div>
+ <iframe id="editor2"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 572642 **/
+
+function copyCF_HTML(cfhtml, success, failure) {
+ const Cc = SpecialPowers.Cc;
+ const Ci = SpecialPowers.Ci;
+ const CF_HTML = "application/x-moz-nativehtml";
+
+ function getLoadContext() {
+ return SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+ }
+
+ var cb = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+
+ var counter = 0;
+ function copyCF_HTML_worker(success, failure) {
+ if (++counter > 50) {
+ ok(false, "Timed out while polling clipboard for pasted data");
+ failure();
+ return;
+ }
+
+ var flavors = [CF_HTML];
+ if (!cb.hasDataMatchingFlavors(flavors, flavors.length, cb.kGlobalClipboard)) {
+ setTimeout(function() { copyCF_HTML_worker(success, failure); }, 100);
+ return;
+ }
+
+ var trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor(CF_HTML);
+ cb.getData(trans, cb.kGlobalClipboard);
+ var data = SpecialPowers.createBlankObject();
+ try {
+ trans.getTransferData(CF_HTML, data, {});
+ data = SpecialPowers.wrap(data).value.QueryInterface(Ci.nsISupportsCString).data;
+ } catch (e) {
+ setTimeout(function() { copyCF_HTML_worker(success, failure); }, 100);
+ return;
+ }
+ success();
+ }
+
+ var trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor(CF_HTML);
+ var data = Cc["@mozilla.org/supports-cstring;1"].
+ createInstance(Ci.nsISupportsCString);
+ data.data = cfhtml;
+ trans.setTransferData(CF_HTML, data, cfhtml.length);
+ cb.setData(trans, null, cb.kGlobalClipboard);
+ copyCF_HTML_worker(success, failure);
+}
+
+function loadCF_HTMLdata(filename) {
+ var req = new XMLHttpRequest();
+ req.open("GET", filename, false);
+ req.overrideMimeType("text/plain; charset=x-user-defined");
+ req.send(null);
+ ok(req.status, 200, "Could not read the binary file " + filename);
+ return req.responseText;
+}
+
+var gTests = [
+ // Copied from Firefox
+ {fileName: "cfhtml-firefox.txt", expected: "Firefox"},
+ // Copied from OpenOffice.org
+ {fileName: "cfhtml-ooo.txt", expected: "hello"},
+ // Copied from IE
+ {fileName: "cfhtml-ie.txt", expected: "browser"},
+ // Copied from Chromium
+ {fileName: "cfhtml-chromium.txt", expected: "Pacific"},
+ // CF_HTML with no context specified (StartHTML and EndHTML set to -1)
+ {fileName: "cfhtml-nocontext.txt", expected: "3.1415926535897932"},
+];
+var gTestIndex = 0;
+
+SimpleTest.waitForExplicitFinish();
+
+for (var i = 0; i < gTests.length; ++i) {
+ gTests[i].data = loadCF_HTMLdata("data/" + gTests[i].fileName);
+}
+
+function runTest() {
+ var test = gTests[gTestIndex++];
+
+ copyCF_HTML(test.data, function() {
+ // contenteditable
+ var contentEditable = document.getElementById("editor1");
+ contentEditable.innerHTML = "";
+ contentEditable.focus();
+ synthesizeKey("v", {accelKey: true});
+ isnot(contentEditable.textContent.indexOf(test.expected), -1,
+ "Paste operation for " + test.fileName + " should be successful in contenteditable");
+
+ // designMode
+ var iframe = document.getElementById("editor2");
+ iframe.addEventListener("load", function() {
+ iframe.removeEventListener("load", arguments.callee, false);
+ var doc = iframe.contentDocument;
+ var win = doc.defaultView;
+ setTimeout(function() {
+ win.addEventListener("focus", function() {
+ win.removeEventListener("focus", arguments.callee, false);
+ doc.designMode = "on";
+ synthesizeKey("v", {accelKey: true}, win);
+ isnot(doc.body.textContent.indexOf(test.expected), -1,
+ "Paste operation for " + test.fileName + " should be successful in designMode");
+
+ if (gTestIndex == gTests.length)
+ SimpleTest.finish();
+ else
+ runTest();
+ }, false);
+ win.focus();
+ }, 0);
+ }, false);
+ iframe.src = "data:text/html,";
+ }, SimpleTest.finish);
+}
+
+var isMac = ("nsILocalFileMac" in SpecialPowers.Ci);
+if (isMac)
+ SimpleTest.waitForFocus(runTest);
+else {
+ // This test is not yet supported on non-Mac platforms, see bug 574005.
+ todo(false, "Test not supported on this platform");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_backspace_vs.html b/editor/libeditor/tests/test_backspace_vs.html
new file mode 100644
index 000000000..1ee754c95
--- /dev/null
+++ b/editor/libeditor/tests/test_backspace_vs.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1216427
+-->
+<head>
+ <title>Test for Bug 1216427</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1216427">Mozilla Bug 1216427</a>
+<p id="display"></p>
+<div id="content">
+ <div id="edit1" contenteditable="true">a&#x263a;&#xfe0f;b</div><!-- BMP symbol with VS16 -->
+ <div id="edit2" contenteditable="true">a&#x1f310;&#xfe0e;b</div><!-- plane 1 symbol with VS15 -->
+ <div id="edit3" contenteditable="true">a&#x3402;&#xE0100;b</div><!-- BMP ideograph with VS17 -->
+ <div id="edit4" contenteditable="true">a&#x20000;&#xE0101;b</div><!-- SMP ideograph with VS18 -->
+ <div id="edit5" contenteditable="true">a&#x263a;&#xfe01;&#xfe02;&#xfe03;b</div><!-- BMP symbol with extra VSes -->
+ <div id="edit6" contenteditable="true">a&#x20000;&#xE0100;&#xE0101;&#xE0102;b</div><!-- SMP symbol with extra VSes -->
+ <!-- The Regional Indicator combinations here were supported by Apple Color Emoji
+ even prior to the major extension of coverage in the 10.10.5 timeframe. -->
+ <div id="edit7" contenteditable="true">a&#x1F1E8;&#x1F1F3;b</div><!-- Regional Indicator flag: CN -->
+ <div id="edit8" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;b</div><!-- two RI flags: CN, DE -->
+ <div id="edit9" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;b</div><!-- three RI flags: CN, DE, ES -->
+ <div id="edit10" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;b</div><!-- four RI flags: CN, DE, ES, FR -->
+ <div id="edit11" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;&#x1F1EC;&#x1F1E7;b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
+
+ <div id="edit1b" contenteditable="true">a&#x263a;&#xfe0f;b</div><!-- BMP symbol with VS16 -->
+ <div id="edit2b" contenteditable="true">a&#x1f310;&#xfe0e;b</div><!-- plane 1 symbol with VS15 -->
+ <div id="edit3b" contenteditable="true">a&#x3402;&#xE0100;b</div><!-- BMP ideograph with VS17 -->
+ <div id="edit4b" contenteditable="true">a&#x20000;&#xE0101;b</div><!-- SMP ideograph with VS18 -->
+ <div id="edit5b" contenteditable="true">a&#x263a;&#xfe01;&#xfe02;&#xfe03;b</div><!-- BMP symbol with extra VSes -->
+ <div id="edit6b" contenteditable="true">a&#x20000;&#xE0100;&#xE0101;&#xE0102;b</div><!-- SMP symbol with extra VSes -->
+ <div id="edit7b" contenteditable="true">a&#x1F1E8;&#x1F1F3;b</div><!-- Regional Indicator flag: CN -->
+ <div id="edit8b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;b</div><!-- two RI flags: CN, DE -->
+ <div id="edit9b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;b</div><!-- three RI flags: CN, DE, ES -->
+ <div id="edit10b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;b</div><!-- four RI flags: CN, DE, ES, FR -->
+ <div id="edit11b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;&#x1F1EC;&#x1F1E7;b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1216427 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+function test(edit, bsCount) {
+ edit.focus();
+ var sel = window.getSelection();
+ sel.collapse(edit.childNodes[0], edit.textContent.length - 1);
+ for (i = 0; i < bsCount; ++i) {
+ synthesizeKey("VK_BACK_SPACE", {});
+ }
+ is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
+}
+
+function testWithMove(edit, offset, bsCount) {
+ edit.focus();
+ var sel = window.getSelection();
+ sel.collapse(edit.childNodes[0], 0);
+ var i;
+ for (i = 0; i < offset; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_RIGHT", {});
+ }
+ for (i = 0; i < bsCount; ++i) {
+ synthesizeKey("VK_BACK_SPACE", {});
+ }
+ is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
+}
+
+function runTest() {
+ /* test backspace-deletion of the middle character(s) */
+ test(document.getElementById("edit1"), 1);
+ test(document.getElementById("edit2"), 1);
+ test(document.getElementById("edit3"), 1);
+ test(document.getElementById("edit4"), 1);
+ test(document.getElementById("edit5"), 1);
+ test(document.getElementById("edit6"), 1);
+
+ /*
+ * Tests with Regional Indicator flags: these behave differently depending
+ * whether an emoji font is present, as ligated flags are edited as single
+ * characters whereas non-ligated RI characters act individually.
+ *
+ * For now, only rely on such an emoji font on OS X 10.7+. (Note that the
+ * Segoe UI Emoji font on Win8.1 and Win10 does not implement Regional
+ * Indicator flags.)
+ *
+ * Once the Firefox Emoji font is ready, we can load that via @font-face
+ * and expect these tests to work across all platforms.
+ */
+ hasEmojiFont =
+ (navigator.platform.indexOf("Mac") == 0 &&
+ /10\.([7-9]|[1-9][0-9])/.test(navigator.oscpu));
+
+ if (hasEmojiFont) {
+ test(document.getElementById("edit7"), 1);
+ test(document.getElementById("edit8"), 2);
+ test(document.getElementById("edit9"), 3);
+ test(document.getElementById("edit10"), 4);
+ test(document.getElementById("edit11"), 5);
+ }
+
+ /* extra tests with the use of RIGHT and LEFT to get to the right place */
+ testWithMove(document.getElementById("edit1b"), 2, 1);
+ testWithMove(document.getElementById("edit2b"), 2, 1);
+ testWithMove(document.getElementById("edit3b"), 2, 1);
+ testWithMove(document.getElementById("edit4b"), 2, 1);
+ testWithMove(document.getElementById("edit5b"), 2, 1);
+ testWithMove(document.getElementById("edit6b"), 2, 1);
+ if (hasEmojiFont) {
+ testWithMove(document.getElementById("edit7b"), 2, 1);
+ testWithMove(document.getElementById("edit8b"), 3, 2);
+ testWithMove(document.getElementById("edit9b"), 4, 3);
+ testWithMove(document.getElementById("edit10b"), 5, 4);
+ testWithMove(document.getElementById("edit11b"), 6, 5);
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1026397.html b/editor/libeditor/tests/test_bug1026397.html
new file mode 100644
index 000000000..487f3e87f
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1026397.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1026397
+-->
+<head>
+ <title>Test for Bug 1026397</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1026397">Mozilla Bug 1026397</a>
+<p id="display"></p>
+<div id="content">
+<input id="input">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1026397 **/
+SimpleTest.waitForExplicitFinish();
+
+function runTests()
+{
+ var input = document.getElementById("input");
+ input.focus();
+
+ function doTest(aMaxLength, aInitialValue, aCaretOffset,
+ aInsertString, aExpectedValueDuringComposition,
+ aExpectedValueAfterCompositionEnd, aAdditionalExplanation)
+ {
+ input.value = aInitialValue;
+ var maxLengthStr = "";
+ if (aMaxLength >= 0) {
+ input.maxLength = aMaxLength;
+ maxLengthStr = aMaxLength.toString();
+ } else {
+ input.removeAttribute("maxlength");
+ maxLengthStr = "not specified";
+ }
+ input.selectionStart = input.selectionEnd = aCaretOffset;
+ if (aAdditionalExplanation) {
+ aAdditionalExplanation = " " + aAdditionalExplanation;
+ } else {
+ aAdditionalExplanation = "";
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": aInsertString,
+ "clauses":
+ [
+ { "length": aInsertString.length, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": aInsertString.length, "length": 0 }
+ });
+ is(input.value, aExpectedValueDuringComposition,
+ "The value of input whose maxlength is " + maxLengthStr + " should be "
+ + aExpectedValueDuringComposition + " during composition" + aAdditionalExplanation);
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(input.value, aExpectedValueAfterCompositionEnd,
+ "The value of input whose maxlength is " + maxLengthStr + " should be "
+ + aExpectedValueAfterCompositionEnd + " after compositionend" + aAdditionalExplanation);
+ }
+
+ // maxlength hasn't been specified yet.
+ doTest(-1, "", 0, "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6");
+
+ // maxlength="1"
+ doTest(1, "", 0, "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6", "");
+
+ // maxlength="2"
+ doTest(2, "", 0, "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7");
+ doTest(2, "X", 1, "\uD842\uDFB7\u91CE\u5BB6", "X\uD842\uDFB7\u91CE\u5BB6", "X");
+ doTest(2, "Y", 0, "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6Y", "Y");
+
+ // maxlength="3"
+ doTest(3, "", 0, "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE");
+ doTest(3, "A", 1, "\uD842\uDFB7\u91CE\u5BB6", "A\uD842\uDFB7\u91CE\u5BB6", "A\uD842\uDFB7");
+ doTest(3, "B", 0, "\uD842\uDFB7\u91CE\u5BB6", "\uD842\uDFB7\u91CE\u5BB6B", "\uD842\uDFB7B");
+ doTest(3, "CD", 1, "\uD842\uDFB7\u91CE\u5BB6", "C\uD842\uDFB7\u91CE\u5BB6D", "CD");
+
+ // maxlength="4"
+ doTest(4, "EF", 1, "\uD842\uDFB7\u91CE\u5BB6", "E\uD842\uDFB7\u91CE\u5BB6F", "E\uD842\uDFB7F");
+ doTest(4, "GHI", 1, "\uD842\uDFB7\u91CE\u5BB6", "G\uD842\uDFB7\u91CE\u5BB6HI", "GHI");
+
+ // maxlength="1", inputting only high surrogate
+ doTest(1, "", 0, "\uD842", "\uD842", "\uD842", "even if input string is only a high surrogate");
+
+ // maxlength="1", inputting only low surrogate
+ doTest(1, "", 0, "\uDFB7", "\uDFB7", "\uDFB7", "even if input string is only a low surrogate");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1053048.html b/editor/libeditor/tests/test_bug1053048.html
new file mode 100644
index 000000000..4032d32c2
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1053048.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1053048
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1053048</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script type="application/javascript">
+
+ /** Test for Bug 1053048 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(runTests);
+
+ const nsISelectionPrivate = SpecialPowers.Ci.nsISelectionPrivate;
+ const nsISelectionListener = SpecialPowers.Ci.nsISelectionListener;
+ const nsIDOMNSEditableElement = SpecialPowers.Ci.nsIDOMNSEditableElement;
+
+ function runTests()
+ {
+ var textarea = SpecialPowers.wrap(document.getElementById("textarea"));
+ textarea.focus();
+
+ var editor = textarea.QueryInterface(nsIDOMNSEditableElement).editor;
+ var selectionPrivate = editor.selection.QueryInterface(nsISelectionPrivate);
+
+ var selectionListener = {
+ count: 0,
+ notifySelectionChanged: function (aDocument, aSelection, aReason)
+ {
+ ok(true, "selectionStart: " + textarea.selectionStart);
+ ok(true, "selectionEnd: " + textarea.selectionEnd);
+ this.count++;
+ }
+ };
+
+ // Move caret to the end of the textarea
+ synthesizeMouse(textarea, 290, 10, {});
+ is(textarea.selectionStart, 3, "selectionStart should be 3 (after \"foo\")");
+ is(textarea.selectionEnd, 3, "selectionEnd should be 3 (after \"foo\")");
+
+ selectionPrivate.addSelectionListener(selectionListener);
+
+ sendKey("RETURN");
+ is(selectionListener.count, 1, "nsISelectionListener.notifySelectionChanged() should be called");
+ is(textarea.selectionStart, 4, "selectionStart should be 4");
+ is(textarea.selectionEnd, 4, "selectionEnd should be 4");
+ is(textarea.value, "foo\n", "The line break should be appended");
+
+ selectionPrivate.removeSelectionListener(selectionListener);
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1053048">Mozilla Bug 1053048</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<textarea id="textarea"
+ style="height: 100px; width: 300px; -moz-appearance: none"
+ spellcheck="false"
+ onkeydown="this.style.display='block'; this.style.height='200px';">foo</textarea>
+
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1067255.html b/editor/libeditor/tests/test_bug1067255.html
new file mode 100644
index 000000000..be2d703c5
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1067255.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1067255
+-->
+
+<head>
+ <title>Test for Bug 1067255</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body onload="doTest();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1067255">Mozilla Bug 1067255</a>
+
+ <pre id="test">
+ <script type="application/javascript">
+ /** Test for Bug 1067255 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var text = $("text-field");
+ var password = $("password-field");
+
+ var editor1 = SpecialPowers.wrap(text).editor;
+ var editor2 = SpecialPowers.wrap(password).editor;
+
+ text.focus();
+ text.select();
+
+ ok(editor1.canCopy(), "can copy, text");
+ ok(editor1.canCut(), "can cut, text");
+ ok(editor1.canDelete(), "can delete, text");
+
+ password.focus();
+ password.select();
+
+ // Copy and cut commands don't do anything on passoword fields by default,
+ // but webpages can hook up event handlers to the event, and thus, we have to
+ // always keep the cut and copy event enabled in HTML/XHTML documents.
+ ok(editor2.canCopy(), "can copy, password");
+ ok(editor2.canCut(), "can cut, password");
+ ok(editor2.canDelete(), "can delete, password");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <input type="text" value="Gonzo says hi" id="text-field" />
+ <input type="password" value="Jan also" id="password-field" />
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1068979.html b/editor/libeditor/tests/test_bug1068979.html
new file mode 100644
index 000000000..126c39d27
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1068979.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1068979
+-->
+<head>
+ <title>Test for Bug 1068979</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1068979">Mozilla Bug 1068979</a>
+<p id="display"></p>
+<div id="content">
+ <div id="editor1" contenteditable="true">&#x1d400;</div>
+ <div id="editor2" contenteditable="true">a<u>&#x1d401;</u>b</div>
+ <div id="editor3" contenteditable="true">a&#x1d402;<u>b</u></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1068979 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ // Test backspacing over SMP characters pasted-in to a contentEditable
+ getSelection().selectAllChildren(document.getElementById("editor1"));
+ var ed1 = document.getElementById("editor1");
+ var ch1 = ed1.textContent;
+ ed1.focus();
+ synthesizeKey("C", {accelKey: true});
+ synthesizeKey("V", {accelKey: true});
+ synthesizeKey("V", {accelKey: true});
+ synthesizeKey("V", {accelKey: true});
+ synthesizeKey("V", {accelKey: true});
+ is(ed1.textContent, ch1 + ch1 + ch1 + ch1, "Should have four SMP characters");
+ sendKey("back_space");
+ is(ed1.textContent, ch1 + ch1 + ch1, "Three complete characters should remain");
+ sendKey("back_space");
+ is(ed1.textContent, ch1 + ch1, "Two complete characters should remain");
+ sendKey("back_space");
+ is(ed1.textContent, ch1, "Only one complete SMP character should remain");
+ ed1.blur();
+
+ // Test backspacing across an SMP character in a sub-element
+ getSelection().selectAllChildren(document.getElementById("editor2"));
+ var ed2 = document.getElementById("editor2");
+ ed2.focus();
+ sendKey("right");
+ sendKey("back_space");
+ sendKey("back_space");
+ is(ed2.textContent, "a", "Only the 'a' should remain");
+ ed2.blur();
+
+ // Test backspacing across an SMP character from a following sub-element
+ getSelection().selectAllChildren(document.getElementById("editor3"));
+ var ed3 = document.getElementById("editor3");
+ ed3.focus();
+ sendKey("right");
+ sendKey("left");
+ sendKey("back_space");
+ is(ed3.textContent, "ab", "The letters 'ab' should remain");
+ ed3.blur();
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1094000.html b/editor/libeditor/tests/test_bug1094000.html
new file mode 100644
index 000000000..cc27cc675
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1094000.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1094000
+-->
+<head>
+ <title>Test for Bug 1094000</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094000">Mozilla Bug 1094000</a>
+<p id="display"></p>
+<div id="content">
+ <div id="editor0" contenteditable></div>
+ <div id="editor1" contenteditable><br></div>
+ <div id="editor2" contenteditable>a</div>
+ <div id="editor3" contenteditable>b</div>
+ <div id="editor4" contenteditable>c</div>
+ <div id="editor5" contenteditable>d</div>
+ <div id="editor6" contenteditable>e</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1094000 **/
+
+SimpleTest.waitForExplicitFinish();
+
+const kIsLinux = navigator.platform.indexOf("Linux") == 0;
+
+function runTests()
+{
+ var editor0 = document.getElementById("editor0");
+ var editor1 = document.getElementById("editor1");
+ var editor2 = document.getElementById("editor2");
+ var editor3 = document.getElementById("editor3");
+ var editor4 = document.getElementById("editor4");
+ var editor5 = document.getElementById("editor5");
+ var editor6 = document.getElementById("editor6");
+
+ ok(editor1.getBoundingClientRect().height - editor0.getBoundingClientRect().height > 1,
+ "an editor having a <br> element and an empty editor shouldn't be same height");
+ ok(Math.abs(editor1.getBoundingClientRect().height - editor2.getBoundingClientRect().height) <= 1,
+ "an editor having only a <br> element and an editor having \"a\" should be same height");
+
+ editor2.focus();
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(editor2.innerHTML, "<br>",
+ "an editor which had \"a\" should have only <br> element after Backspace keypress");
+ ok(Math.abs(editor2.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
+ "an editor whose content was removed by Backspace key should have a place to put a caret");
+
+ editor3.focus();
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_DELETE", {});
+ is(editor3.innerHTML, "<br>",
+ "an editor which had \"b\" should have only <br> element after Delete keypress");
+ ok(Math.abs(editor3.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
+ "an editor whose content was removed by Delete key should have a place to put a caret");
+
+ editor4.focus();
+ window.getSelection().selectAllChildren(editor4);
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(editor4.innerHTML, "<br>",
+ "an editor which had \"c\" should have only <br> element after removing selected text with Backspace key");
+ ok(Math.abs(editor4.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
+ "an editor whose content was selected and removed by Backspace key should have a place to put a caret");
+
+ editor5.focus();
+ window.getSelection().selectAllChildren(editor5);
+ synthesizeKey("VK_DELETE", {});
+ is(editor5.innerHTML, "<br>",
+ "an editor which had \"d\" should have only <br> element after removing selected text with Delete key");
+ ok(Math.abs(editor5.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
+ "an editor whose content was selected and removed by Delete key should have a place to put a caret");
+
+ editor6.focus();
+ window.getSelection().selectAllChildren(editor6);
+ synthesizeKey("x", { accelKey: true });
+ is(editor6.innerHTML, "<br>",
+ "an editor which had \"e\" should have only <br> element after removing selected text by \"Cut\"");
+ ok(Math.abs(editor6.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
+ "an editor whose content was selected and removed by \"Cut\" should have a place to put a caret");
+
+ editor0.focus();
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(editor0.innerHTML, "",
+ "an empty editor should keep being empty even if Backspace key is pressed");
+ synthesizeKey("VK_DELETE", {});
+ is(editor0.innerHTML, "",
+ "an empty editor should keep being empty even if Delete key is pressed");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1100966.html b/editor/libeditor/tests/test_bug1100966.html
new file mode 100644
index 000000000..28c30c849
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1100966.html
@@ -0,0 +1,65 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1100966
+-->
+<head>
+ <title>Test for Bug 1100966</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable>
+=====<br>
+correct<br>
+fivee sixx<br>
+====
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1100966 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("content");
+ div.focus();
+ synthesizeMouseAtCenter(div, {});
+
+ synthesizeKey(" ", {});
+ setTimeout(function() {
+ synthesizeKey("a", {});
+ setTimeout(function() {
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ var sel = getSpellCheckSelection();
+ is(sel.rangeCount, 2, "We should have two misspelled words");
+ is(String(sel.getRangeAt(0)), "fivee", "Correct misspelled word");
+ is(String(sel.getRangeAt(1)), "sixx", "Correct misspelled word");
+
+ SimpleTest.finish();
+ },0);
+ },0);
+
+});
+
+function getSpellCheckSelection() {
+ var Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ var editor = editingSession.getEditorForWindow(window);
+ var selcon = editor.selectionController;
+ return selcon.getSelection(selcon.SELECTION_SPELLCHECK);
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug1101392.html b/editor/libeditor/tests/test_bug1101392.html
new file mode 100644
index 000000000..76917203b
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1101392.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1101392
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1101392</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1101392 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(runTests);
+
+ function runCopyCommand(element, compareText, nextTest)
+ {
+ element.focus();
+ var expectedEndpoint, sel;
+ if (element.localName == "textarea") {
+ element.select();
+ expectedEndpoint = element.selectionEnd;
+ } else {
+ sel = getSelection();
+ sel.selectAllChildren(element.parentNode);
+ expectedEndpoint = [sel.getRangeAt(0).endContainer,
+ sel.getRangeAt(0).endOffset];
+ }
+
+ function checkCollapse() {
+ var desc = " after cmd_copyAndCollapseToEnd for " +
+ element.localName;
+ if (element.localName == "textarea") {
+ is(element.selectionStart, expectedEndpoint, "start offset" + desc);
+ is(element.selectionEnd, expectedEndpoint, "end offset" + desc);
+ } else {
+ is(sel.isCollapsed, true, "collapsed" + desc);
+ is(sel.anchorNode, expectedEndpoint[0], "node" + desc);
+ is(sel.anchorOffset, expectedEndpoint[1], "offset" + desc);
+ }
+
+ nextTest();
+ }
+
+ SimpleTest.waitForClipboard(compareText,
+ () => SpecialPowers.doCommand(window, "cmd_copyAndCollapseToEnd"),
+ checkCollapse, checkCollapse);
+ }
+
+ function testDiv()
+ {
+ var content = document.getElementById("content");
+ runCopyCommand(content, 'abc', testTextarea);
+ }
+
+ function testTextarea()
+ {
+ var textarea = document.getElementById("textarea");
+ runCopyCommand(textarea, 'def', SimpleTest.finish);
+ }
+
+ function runTests()
+ {
+ testDiv();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1101392">Mozilla Bug 1101392</a>
+<div><div id="content">abc</div></div>
+
+<textarea id="textarea">def</textarea>
+
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1102906.html b/editor/libeditor/tests/test_bug1102906.html
new file mode 100644
index 000000000..36bfc8600
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1102906.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1102906
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1102906</title>
+
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+
+ <script>
+ "use strict";
+
+ /* Test for Bug 1102906 */
+ /* The caret should be movable by using keyboard after drag-and-drop. */
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus( () => {
+ let content = document.getElementById("content");
+ let drag = document.getElementById("drag")
+ let selection = window.getSelection();
+
+ /* Perform drag-and-drop for an arbitrary content. The caret should be at
+ the end of the contenteditable. */
+ selection.selectAllChildren(drag);
+ synthesizeDrop(drag, content, {}, "copy");
+
+ let textContentAfterDrop = content.textContent;
+
+ /* Move the caret to the front of the contenteditable by using keyboard. */
+ for (let i = 0; i < content.textContent.length; ++i) {
+ sendKey("LEFT");
+ }
+ sendChar("!");
+
+ is(content.textContent, "!" + textContentAfterDrop,
+ "The exclamation mark should be inserted at the front.");
+
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1102906">Mozilla Bug 1102906</a>
+<div id="content" contenteditable="true"><span id="drag">Drag</span></div>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1109465.html b/editor/libeditor/tests/test_bug1109465.html
new file mode 100644
index 000000000..dc31e9bf0
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1109465.html
@@ -0,0 +1,69 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1109465
+-->
+<head>
+ <title>Test for Bug 1109465</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <textarea></textarea>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1109465 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var t = document.querySelector("textarea");
+ t.focus();
+
+ // Type foo\nbar and place the caret at the end of the last line
+ synthesizeKey("f", {});
+ synthesizeKey("o", {});
+ synthesizeKey("o", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("b", {});
+ synthesizeKey("a", {});
+ synthesizeKey("r", {});
+ synthesizeKey("VK_UP", {});
+ is(t.selectionStart, 3, "Correct start of selection");
+ is(t.selectionEnd, 3, "Correct end of selection");
+
+ // Compose an IME string
+ synthesizeComposition({ type: "compositionstart" });
+ var composingString = "\u306B";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": composingString,
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ synthesizeComposition({ type: "compositioncommitasis" });
+ is(t.value, "foo\u306B\nbar", "Correct value after composition");
+
+ // Now undo to test that the transaction merger has correctly detected the
+ // IMETextTxn.
+ synthesizeKey("Z", {accelKey: true});
+ is(t.value, "foo\nbar", "Correct value after undo");
+
+ SimpleTest.finish();
+});
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug1140105.html b/editor/libeditor/tests/test_bug1140105.html
new file mode 100644
index 000000000..21d003054
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1140105.html
@@ -0,0 +1,71 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1140105
+-->
+<head>
+ <title>Test for Bug 1140105</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="display">
+</div>
+
+<div id="content" contenteditable><font face="Arial">1234567890</font></div>
+
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1140105 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("content");
+ div.focus();
+ synthesizeMouseAtCenter(div, {});
+ synthesizeKey("VK_LEFT", {});
+
+ var sel = window.getSelection();
+ var selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be in text node");
+ is(selRange.endOffset, 9, "offset should be 9");
+
+ var firstHas = {};
+ var anyHas = {};
+ var allHas = {};
+ var editor = getEditor();
+
+ var atomService = SpecialPowers.Cc["@mozilla.org/atom-service;1"].getService(SpecialPowers.Ci.nsIAtomService);
+ var propAtom = atomService.getAtom("font");
+ editor.getInlineProperty(propAtom, "face", "Arial", firstHas, anyHas, allHas);
+ is(firstHas.value, true, "Test for Arial: firstHas: true expected");
+ is(anyHas.value, true, "Test for Arial: anyHas: true expected");
+ is(allHas.value, true, "Test for Arial: allHas: true expected");
+ editor.getInlineProperty(propAtom, "face", "Courier", firstHas, anyHas, allHas);
+ is(firstHas.value, false, "Test for Courier: firstHas: false expected");
+ is(anyHas.value, false, "Test for Courier: anyHas: false expected");
+ is(allHas.value, false, "Test for Courier: allHas: false expected");
+
+ SimpleTest.finish();
+
+});
+
+function getEditor() {
+ var Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ var editor = editingSession.getEditorForWindow(window);
+ editor.QueryInterface(Ci.nsIHTMLEditor);
+ return editor;
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug1140617.html b/editor/libeditor/tests/test_bug1140617.html
new file mode 100644
index 000000000..078458a3a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1140617.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>Mozilla Bug 1140617</title>
+<link rel=stylesheet href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1140617"
+ target="_blank">Mozilla Bug 1140617</a>
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe>
+<img id="i" src="green.png">
+<script>
+function runTest() {
+ SpecialPowers.setCommandNode(window, document.getElementById("i"));
+ SpecialPowers.doCommand(window, "cmd_copyImageContents");
+
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ doc.designMode = "on";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(doc.body);
+ selection.collapseToEnd();
+
+ doc.execCommand("fontname", false, "Arial");
+ doc.execCommand("bold", false, null);
+ doc.execCommand("insertText", false, "12345");
+ doc.execCommand("paste", false, null);
+ doc.execCommand("insertText", false, "a");
+
+ is(doc.queryCommandValue("fontname"), "Arial", "Arial expected");
+ is(doc.queryCommandState("bold"), true, "Bold expected");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
diff --git a/editor/libeditor/tests/test_bug1153237.html b/editor/libeditor/tests/test_bug1153237.html
new file mode 100644
index 000000000..38d631326
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1153237.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1153237
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1153237</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Avoid platform selection differences
+ SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["layout.word_select.eat_space_to_next_word", true]]
+ }, runTests);
+ });
+
+ function runTests()
+ {
+ var element = document.getElementById("editor");
+ var sel = window.getSelection();
+
+ element.focus();
+ is(sel.getRangeAt(0).startOffset, 0, "offset is zero");
+
+ SpecialPowers.doCommand(window, "cmd_selectRight2");
+ is(sel.toString(), "Some ",
+ "first word + space is selected: got '" + sel.toString() + "'");
+
+ SpecialPowers.doCommand(window, "cmd_selectRight2");
+ is(sel.toString(), "Some text",
+ "both words are selected: got '" + sel.toString() + "'");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1153237">Mozilla Bug 1153237</a>
+<div id="editor" contenteditable>Some text</div><span></span>
+
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1154791.html b/editor/libeditor/tests/test_bug1154791.html
new file mode 100644
index 000000000..03b605e20
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1154791.html
@@ -0,0 +1,67 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1154791
+-->
+<head>
+ <title>Test for Bug 1154791</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="display">
+</div>
+
+<div id="content" contenteditable>
+<tt>thiss onee is stilll a</tt>
+</div>
+
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1154791 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("content");
+ div.focus();
+ synthesizeMouseAtCenter(div, {});
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_LEFT", {});
+
+ setTimeout(function() {
+ synthesizeKey("VK_BACK_SPACE", {});
+ setTimeout(function() {
+ synthesizeKey(" ", {});
+
+ setTimeout(function() {
+ var sel = getSpellCheckSelection();
+ is(sel.rangeCount, 2, "We should have two misspelled words");
+ is(String(sel.getRangeAt(0)), "thiss", "Correct misspelled word");
+ is(String(sel.getRangeAt(1)), "onee", "Correct misspelled word");
+
+ SimpleTest.finish();
+ },0);
+ },0);
+ },0);
+
+});
+
+function getSpellCheckSelection() {
+ var Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ var editor = editingSession.getEditorForWindow(window);
+ var selcon = editor.selectionController;
+ return selcon.getSelection(selcon.SELECTION_SPELLCHECK);
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug1162952.html b/editor/libeditor/tests/test_bug1162952.html
new file mode 100644
index 000000000..ad119de87
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1162952.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162952
+-->
+<head>
+ <title>Test for Bug 1162952</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1162952">Mozilla Bug 1162952</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1162952 **/
+var userCallbackRun = false;
+
+document.addEventListener('keydown', function() {
+ // During a user callback, the commands should be enabled
+ userCallbackRun = true;
+ is(true, document.queryCommandEnabled('cut'));
+ is(true, document.queryCommandEnabled('copy'));
+});
+
+// Otherwise, they should be disabled
+is(false, document.queryCommandEnabled('cut'));
+is(false, document.queryCommandEnabled('copy'));
+
+// Fire a user callback
+synthesizeKey('A', {});
+
+ok(userCallbackRun, "User callback should've been run");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1181130-1.html b/editor/libeditor/tests/test_bug1181130-1.html
new file mode 100644
index 000000000..eb27526a3
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1181130-1.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1181130
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1181130</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a>
+<p id="display"></p>
+<div id="container" contenteditable="true">
+ editable div
+ <div id="noneditable" contenteditable="false">
+ non-editable div
+ <div id="editable" contenteditable="true">nested editable div</div>
+ </div>
+</div>
+<script type="application/javascript">
+/** Test for Bug 1181130 **/
+var container = document.getElementById("container");
+var noneditable = document.getElementById("noneditable");
+var editable = document.getElementById("editable");
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ synthesizeMouseAtCenter(noneditable, {});
+ ok(!document.getSelection().toString().includes("nested editable div"),
+ "Selection should not include non-editable content");
+
+ synthesizeMouseAtCenter(container, {});
+ ok(!document.getSelection().toString().includes("nested editable div"),
+ "Selection should not include non-editable content");
+
+ synthesizeMouseAtCenter(editable, {});
+ ok(!document.getSelection().toString().includes("nested editable div"),
+ "Selection should not include non-editable content");
+
+ SimpleTest.finish();
+});
+</script>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1181130-2.html b/editor/libeditor/tests/test_bug1181130-2.html
new file mode 100644
index 000000000..edb380e98
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1181130-2.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1181130
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1181130</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a>
+<p id="display"></p>
+<div id="container" contenteditable="true">
+ editable div
+ <div id="noneditable" contenteditable="false">
+ non-editable div
+ </div>
+</div>
+<script type="application/javascript">
+/** Test for Bug 1181130 **/
+var container = document.getElementById("container");
+var noneditable = document.getElementById("noneditable");
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ var nonHTMLElement = document.createElementNS("http://www.example.com", "element");
+ nonHTMLElement.innerHTML = '<div contenteditable="true">nested editable div</div>';
+ noneditable.appendChild(nonHTMLElement);
+
+ synthesizeMouseAtCenter(noneditable, {});
+ ok(!document.getSelection().toString().includes("nested editable div"),
+ "Selection should not include non-editable content");
+
+ SimpleTest.finish();
+});
+</script>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1186799.html b/editor/libeditor/tests/test_bug1186799.html
new file mode 100644
index 000000000..b0b583a7e
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1186799.html
@@ -0,0 +1,81 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1186799
+-->
+<head>
+ <title>Test for Bug 1186799</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1186799">Mozilla Bug 1186799</a>
+<p id="display"></p>
+<div id="content">
+ <span id="span">span</span>
+ <div id="editor" contenteditable></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1186799 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var span = document.getElementById("span");
+ var editor = document.getElementById("editor");
+ editor.focus();
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3042",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ ok(isThereIMESelection(), "There should be IME selection");
+
+ var compositionEnd = false;
+ editor.addEventListener("compositionend", function () { compositionEnd = true; }, true);
+
+ synthesizeMouseAtCenter(span, {});
+
+ ok(compositionEnd, "composition end should be fired at clicking outside of the editor");
+ ok(!isThereIMESelection(), "There should be no IME selection");
+
+ SimpleTest.finish();
+});
+
+function isThereIMESelection()
+{
+ var selCon = SpecialPowers.wrap(window).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIEditingSession).
+ getEditorForWindow(window).
+ selectionController;
+ const kIMESelections = [
+ SpecialPowers.Ci.nsISelectionController.SELECTION_IME_RAWINPUT,
+ SpecialPowers.Ci.nsISelectionController.SELECTION_IME_SELECTEDRAWTEXT,
+ SpecialPowers.Ci.nsISelectionController.SELECTION_IME_CONVERTEDTEXT,
+ SpecialPowers.Ci.nsISelectionController.SELECTION_IME_SELECTEDCONVERTEDTEXT
+ ];
+ for (var i = 0; i < kIMESelections.length; i++) {
+ var sel = selCon.getSelection(kIMESelections[i]);
+ if (sel && sel.rangeCount) {
+ return true;
+ }
+ }
+ return false;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1230473.html b/editor/libeditor/tests/test_bug1230473.html
new file mode 100644
index 000000000..bff7826d1
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1230473.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1230473
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1230473</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1230473">Mozilla Bug 1230473</a>
+<input id="input">
+<textarea id="textarea"></textarea>
+<div id="div" contenteditable></div>
+<script type="application/javascript">
+/** Test for Bug 1230473 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(()=>{
+ function runTest(aEditor) {
+ function committer() {
+ aEditor.blur();
+ aEditor.focus();
+ }
+ function isNSEditableElement() {
+ return aEditor.tagName.toLowerCase() == "input" || aEditor.tagName.toLowerCase() == "textarea";
+ }
+ function value() {
+ return isNSEditableElement() ? aEditor.value : aEditor.textContent;
+ }
+ function isComposing() {
+ return isNSEditableElement() ? SpecialPowers.wrap(aEditor)
+ .QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement)
+ .editor
+ .QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport)
+ .composing :
+ SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .editor
+ .QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport)
+ .composing;
+ }
+ function clear() {
+ if (isNSEditableElement()) {
+ aEditor.value = "";
+ } else {
+ aEditor.textContent = "";
+ }
+ }
+
+ clear();
+
+ // Committing at compositionstart
+ aEditor.focus();
+ aEditor.addEventListener("compositionstart", committer, true);
+ synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 1, length: 0 }});
+ aEditor.removeEventListener("compositionstart", committer, true);
+ ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionstart event handler");
+ is(value(), "", "composition in " + aEditor.id + " shouldn't insert any text since it's committed at compositionstart");
+ clear();
+
+ // Committing at first compositionupdate
+ aEditor.focus();
+ aEditor.addEventListener("compositionupdate", committer, true);
+ synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 1, length: 0 }});
+ aEditor.removeEventListener("compositionupdate", committer, true);
+ ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
+ is(value(), "", "composition in " + aEditor.id + " shouldn't have inserted any text since it's committed at first compositionupdate");
+ clear();
+
+ // Committing at first text (eCompositionChange)
+ aEditor.focus();
+ aEditor.addEventListener("text", committer, true);
+ synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 1, length: 0 }});
+ aEditor.removeEventListener("text", committer, true);
+ ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
+ is(value(), "", "composition in " + aEditor.id + " should have inserted any text since it's committed at first text");
+ clear();
+
+ // Committing at second compositionupdate
+ aEditor.focus();
+ synthesizeComposition({ type: "compositionstart" });
+ synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 1, length: 0 }});
+ ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second compositionupdate");
+ is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second compositionupdate");
+ aEditor.addEventListener("compositionupdate", committer, true);
+ synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 2, length: 0 }});
+ aEditor.removeEventListener("compositionupdate", committer, true);
+ ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
+ todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second compositionupdate");
+ clear();
+
+ // Committing at second text (eCompositionChange)
+ aEditor.focus();
+ synthesizeComposition({ type: "compositionstart" });
+ synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 1, length: 0 }});
+ ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second text");
+ is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second text");
+ aEditor.addEventListener("text", committer, true);
+ synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+ caret: { start: 2, length: 0 }});
+ aEditor.removeEventListener("text", committer, true);
+ ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
+ todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second text");
+ clear();
+ }
+ runTest(document.getElementById("input"));
+ runTest(document.getElementById("textarea"));
+ runTest(document.getElementById("div"));
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1247483.html b/editor/libeditor/tests/test_bug1247483.html
new file mode 100644
index 000000000..40dbc36ce
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1247483.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 1247483</title>
+<style src="/tests/SimpleTest/test.css" type="text/css"></style>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+
+function runTest() {
+ // Copy content from table.
+ var selection = getSelection();
+ var startRange = document.createRange();
+ startRange.setStart(document.getElementById("start"), 0);
+ startRange.setEnd(document.getElementById("end"), 2);
+ selection.removeAllRanges();
+ selection.addRange(startRange);
+ SpecialPowers.wrap(document).execCommand("copy", false, null);
+
+ // Paste content into "pastecontainer"
+ var pasteContainer = document.getElementById("pastecontainer");
+ var pasteRange = document.createRange();
+ pasteRange.selectNodeContents(pasteContainer);
+ pasteRange.collapse(false);
+ selection.removeAllRanges();
+ selection.addRange(pasteRange);
+ SpecialPowers.wrap(document).execCommand("paste", false, null);
+
+ is(pasteContainer.querySelectorAll("td").length, 4, "4 <td> should be pasted.");
+
+ document.execCommand("undo", false, null);
+
+ is(pasteContainer.querySelectorAll("td").length, 0, "Undo should have remove the 4 pasted <td>.");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247483">Mozilla Bug 1247483</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<div id="container" contenteditable="true">
+<table>
+ <tr id="start"><td>1 1</td><td>1 2</td></tr>
+ <tr id="end"><td>2 1</td><td>2 2</td></tr>
+</table>
+</div>
+
+<div id="pastecontainer" contenteditable="true">
+</div>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1248128.html b/editor/libeditor/tests/test_bug1248128.html
new file mode 100644
index 000000000..08b0139c9
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1248128.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1248128
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1248128</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(function() {
+ var outer = document.querySelector("html");
+ ok(outer.scrollTop == 0, "scrollTop is zero: got " + outer.scrollTop);
+
+ var input = document.getElementById("testInput");
+ input.focus();
+
+ var scroll = outer.scrollTop;
+ ok(scroll > 0, "element has scrolled: new value " + scroll);
+
+ try {
+ SpecialPowers.doCommand(window, "cmd_moveLeft");
+ ok(false, "should not be able to do kMoveLeft");
+ } catch (e) {
+ ok(true, "unable to perform kMoveLeft");
+ }
+
+ ok(outer.scrollTop == scroll,
+ "scroll is unchanged: got " + outer.scrollTop + ", expected " + scroll);
+
+ // Make sure cmd_moveLeft isn't failing for some unrelated reason
+ synthesizeKey("a", {});
+ is(input.selectionStart, 1, "selectionStart after typing");
+ SpecialPowers.doCommand(window, "cmd_moveLeft");
+ is(input.selectionStart, 0, "selectionStart after move left");
+
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1248128">Mozilla Bug 1248128</a>
+<div style="height: 2000px;"></div>
+<input type="text" id="testInput"></input>
+<div style="height: 200px;"></div>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1248185.html b/editor/libeditor/tests/test_bug1248185.html
new file mode 100644
index 000000000..d545cfc53
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1248185.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1248185
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1248185</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Avoid platform selection differences
+ SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["layout.word_select.eat_space_to_next_word", true]]
+ }, runTests);
+ });
+
+ function runTests()
+ {
+ var editor = document.querySelector("#test");
+ editor.focus();
+
+ var sel = window.getSelection();
+
+ SpecialPowers.doCommand(window, "cmd_moveRight2");
+ SpecialPowers.doCommand(window, "cmd_moveRight2");
+ SpecialPowers.doCommand(window, "cmd_moveRight2");
+ SpecialPowers.doCommand(window, "cmd_selectRight2");
+ ok(sel.toString() == "three ", "expected 'three ' to be selected");
+
+ SpecialPowers.doCommand(window, "cmd_moveRight2");
+ SpecialPowers.doCommand(window, "cmd_moveRight2");
+ SpecialPowers.doCommand(window, "cmd_moveRight2");
+ ok(sel.toString() == "", "expected empty selection");
+
+ SpecialPowers.doCommand(window, "cmd_selectLeft2");
+ ok(sel.toString() == "five", "expected 'five' to be selected");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1248185">Mozilla Bug 1248185</a>
+<body>
+<div style="font: 12px monospace; width: 45ch;">
+<span contenteditable="" id="test">blablablablablablablablablablablablablabla one two three four five</span>
+<div>
+<span>foo</span>
+</div>
+</div>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1250010.html b/editor/libeditor/tests/test_bug1250010.html
new file mode 100644
index 000000000..d1e0154dc
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1250010.html
@@ -0,0 +1,93 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1250010
+-->
+<head>
+ <title>Test for Bug 1250010</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="display">
+</div>
+
+<div id="test1" contenteditable><p><b><font color="red">1234567890</font></b></p></div>
+<div id="test2" contenteditable><p><tt>xyz</tt></p><p><tt><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAIAAABvrngfAAAAFklEQVQImWMwjWhCQwxECoW3oCHihAB0LyYv5/oAHwAAAABJRU5ErkJggg=="></tt></p></div>
+
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+function getImageDataURI()
+{
+ return document.getElementsByTagName("img")[0].getAttribute("src");
+}
+
+/** Test for Bug 1250010 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+
+ // First test: Empty paragraph is split correctly.
+ var div = document.getElementById("test1");
+ div.focus();
+ synthesizeMouseAtCenter(div, {});
+
+ var sel = window.getSelection();
+ var selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 10, "offset should be 10");
+
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("b", {});
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("a", {});
+
+ is(div.innerHTML, "<p><b><font color=\"red\">1234567890</font></b></p>" +
+ "<p><b><font color=\"red\">a<br></font></b></p>" +
+ "<p><b><font color=\"red\">b<br></font></b></p>",
+ "unexpected HTML");
+
+ // Second test: Since we modified the code path that splits non-text nodes,
+ // test that this works, if the split node is not empty.
+ div = document.getElementById("test2");
+ div.focus();
+ synthesizeMouseAtCenter(div, {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 3, "offset should be 3");
+
+ // Move behind the image and press enter, insert an "A".
+ // That should insert a new empty paragraph with the "A" after what we have.
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("A", {});
+
+ // The resulting HTML is sadly less than optimal:
+ // A <br> gets inserted after the image and the "A" is followed by an empty <tt></tt>.
+ var newHTML = div.innerHTML;
+ var expectedHTML;
+ // Existing part with additional <br> inserted.
+ expectedHTML = "<p><tt>xyz</tt></p><p><tt><img src=\"" + getImageDataURI() + "\"><br></tt></p>" +
+ // New part caused by pressing enter after the image and typing an "A".
+ "<p><tt>A</tt><br><tt></tt></p>";
+ is(newHTML, expectedHTML, "unexpected HTML");
+
+ // In case the empty tag gets deleted some day, let them know that something improved.
+ expectedHTML = "<p><tt>xyz</tt></p><p><tt><img src=\"" + getImageDataURI() + "\"><br></tt></p>" +
+ "<p><tt>A</tt><br></p>";
+ todo_is(newHTML, expectedHTML, "unexpected HTML");
+
+ SimpleTest.finish();
+
+});
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug1257363.html b/editor/libeditor/tests/test_bug1257363.html
new file mode 100644
index 000000000..c1610494f
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1257363.html
@@ -0,0 +1,182 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1257363
+-->
+<head>
+ <title>Test for Bug 1257363</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="display">
+</div>
+
+<div id="backspaceCSS" contenteditable><p style="color:red;">12345</p>67</div>
+<div id="backspace" contenteditable><p><font color="red">12345</font></p>67</div>
+<div id="deleteCSS" contenteditable><p style="color:red;">x</p></div>
+<div id="delete" contenteditable><p><font color="red">y</font></p></div>
+
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 1257363 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+
+ // ***** Backspace test *****
+ var div = document.getElementById("backspaceCSS");
+ div.focus();
+ synthesizeMouse(div, 100, 2, {}); /* click behind and down */
+
+ var sel = window.getSelection();
+ var selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 5, "offset should be 5");
+
+ // Return and backspace should take us to where we started.
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 5, "offset should be 5");
+
+ // Add an "a" to the end of the paragraph.
+ synthesizeKey("a", {});
+
+ // Return and forward delete should take us to the following line.
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_DELETE", {});
+
+ // Add a "b" to the start.
+ synthesizeKey("b", {});
+
+ is(div.innerHTML, "<p style=\"color:red;\">12345a</p>b67",
+ "unexpected HTML");
+
+ // Let's repeat the whole thing, but a font tag instead of CSS.
+ // The behaviour is different since the font is carried over.
+ div = document.getElementById("backspace");
+ div.focus();
+ synthesizeMouse(div, 100, 2, {}); /* click behind and down */
+
+ sel = window.getSelection();
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 5, "offset should be 5");
+
+ // Return and backspace should take us to where we started.
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 5, "offset should be 5");
+
+ // Add an "a" to the end of the paragraph.
+ synthesizeKey("a", {});
+
+ // Return and forward delete should take us to the following line.
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_DELETE", {});
+
+ // Add a "b" to the start.
+ synthesizeKey("b", {});
+
+ // Here we get a somewhat ugly result since the red sticks.
+ is(div.innerHTML, "<p><font color=\"red\">12345a</font></p><font color=\"red\">b</font>67",
+ "unexpected HTML");
+
+ // ***** Delete test *****
+ div = document.getElementById("deleteCSS");
+ div.focus();
+ synthesizeMouse(div, 100, 2, {}); /* click behind and down */
+
+ var sel = window.getSelection();
+ var selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 1, "offset should be 1");
+
+ // left, enter should create a new empty paragraph before
+ // but leave the selection at the start of the existing paragraph.
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_RETURN", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the start of text node");
+ is(selRange.endOffset, 0, "offset should be 0");
+ is(selRange.endContainer.nodeValue, "x", "we should be in the text node with the x");
+
+ // Now moving up into the new empty paragraph.
+ synthesizeKey("VK_UP", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "P", "selection should be the new empty paragraph");
+ is(selRange.endOffset, 0, "offset should be 0");
+
+ // Forward delete should now take us to where we started.
+ synthesizeKey("VK_DELETE", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the start of text node");
+ is(selRange.endOffset, 0, "offset should be 0");
+
+ // Add an "a" to the start of the paragraph.
+ synthesizeKey("a", {});
+
+ is(div.innerHTML, "<p style=\"color:red;\">ax</p>",
+ "unexpected HTML");
+
+ // Let's repeat the whole thing, but a font tag instead of CSS.
+ div = document.getElementById("delete");
+ div.focus();
+ synthesizeMouse(div, 100, 2, {}); /* click behind and down */
+
+ var sel = window.getSelection();
+ var selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the end of text node");
+ is(selRange.endOffset, 1, "offset should be 1");
+
+ // left, enter should create a new empty paragraph before
+ // but leave the selection at the start of the existing paragraph.
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_RETURN", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the start of text node");
+ is(selRange.endOffset, 0, "offset should be 0");
+ is(selRange.endContainer.nodeValue, "y", "we should be in the text node with the y");
+
+ // Now moving up into the new empty paragraph.
+ synthesizeKey("VK_UP", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "FONT", "selection should be the font tag");
+ is(selRange.endOffset, 0, "offset should be 0");
+ is(selRange.endContainer.parentNode.nodeName, "P", "the parent of the font should be a paragraph");
+
+ // Forward delete should now take us to where we started.
+ synthesizeKey("VK_DELETE", {});
+
+ selRange = sel.getRangeAt(0);
+ is(selRange.endContainer.nodeName, "#text", "selection should be at the start of text node");
+ is(selRange.endOffset, 0, "offset should be 0");
+
+ // Add an "a" to the start of the paragraph.
+ synthesizeKey("a", {});
+
+ is(div.innerHTML, "<p><font color=\"red\">ay</font></p>",
+ "unexpected HTML");
+
+ SimpleTest.finish();
+
+});
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug1258085.html b/editor/libeditor/tests/test_bug1258085.html
new file mode 100644
index 000000000..342f068ee
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1258085.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>Test for Bug 1186799</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<div contenteditable></div>
+<script>
+var div = document.querySelector("div");
+
+function reset() {
+ div.innerHTML = "x<br> y";
+ div.focus();
+ synthesizeKey("VK_DOWN", {});
+}
+
+function checks(msg) {
+ is(div.innerHTML, "x<br><br>",
+ msg + ": Should add a second <br> to prevent collapse of first");
+ is(div.childNodes.length, 3, msg + ": No empty text nodes allowed");
+ ok(getSelection().isCollapsed, msg + ": Selection must be collapsed");
+ is(getSelection().focusNode, div, msg + ": Focus must be in div");
+ is(getSelection().focusOffset, 2,
+ msg + ": Focus must be between the two <br>s");
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ // Put selection after the "y" and backspace
+ reset();
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ checks("Collapsed backspace");
+
+ // Now do the same with delete
+ reset();
+ synthesizeKey("VK_DELETE", {});
+ checks("Collapsed delete");
+
+ // Forward selection
+ reset();
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+ synthesizeKey("VK_BACK_SPACE", {});
+ checks("Forward-selected backspace");
+
+ // Backward selection
+ reset();
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_LEFT", {shiftKey: true});
+ synthesizeKey("VK_BACK_SPACE", {});
+ checks("Backward-selected backspace");
+
+ // Make sure we're not deleting if the whitespace isn't actually collapsed
+ div.style.whiteSpace = "pre-wrap";
+ reset();
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ if (div.innerHTML, "x<br> ", "pre-wrap: Don't delete uncollapsed space");
+ ok(getSelection().isCollapsed, "pre-wrap: Selection must be collapsed");
+ is(getSelection().focusNode, div.lastChild,
+ "pre-wrap: Focus must be in final text node");
+ is(getSelection().focusOffset, 1, "pre-wrap: Focus must be at end of node");
+
+ SimpleTest.finish();
+});
+</script>
diff --git a/editor/libeditor/tests/test_bug1268736.html b/editor/libeditor/tests/test_bug1268736.html
new file mode 100644
index 000000000..fb1f341b5
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1268736.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1268736
+-->
+<head>
+ <title>Test for Bug 1268736</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268736">Mozilla Bug 1268736</a>
+<table id="table" border="1" width="100%">
+ <tbody>
+ <tr>
+ <td>a</td>
+ <td>b</td>
+ <td>c</td>
+ </tr>
+ <tr>
+ <td>d</td>
+ <td id="cell_readonly">e</td>
+ <td contenteditable="true" id="cell_writable">f</td>
+ </tr>
+ </tbody>
+</table>
+
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1268736
+ *
+ * Tests for editing a table cell's contents when the table cell is or isn't a child of a contenteditable node.
+ *
+ */
+
+function getEditor() {
+ const Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
+}
+
+var table = document.getElementById("table");
+var tableHTML = table.innerHTML;
+var editor = getEditor();
+
+var cell = document.getElementById("cell_readonly");
+cell.focus();
+editor.deleteTableCellContents();
+is(table.innerHTML == tableHTML, true, "editor should not modify non-editable table cell" );
+
+cell = document.getElementById("cell_writable");
+cell.focus();
+editor.deleteTableCellContents();
+is(cell.innerHTML == "<br>", true, "editor can modify editable table cells" );
+
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1270235.html b/editor/libeditor/tests/test_bug1270235.html
new file mode 100644
index 000000000..da7fd4e7a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1270235.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1270235
+-->
+<head>
+ <title>Test for Bug 1270235</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1270235">Mozilla Bug 1270235</a>
+<p id="display"></p>
+<div id="content" style="display: none;"></div>
+
+<div id="edit1" contenteditable="true"><p>AB</p></div>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(()=>{
+ let element = document.getElementById('edit1');
+ element.focus();
+ let textNode = element.firstChild.firstChild;
+ let node = textNode.splitText(0);
+ node.parentNode.removeChild(node);
+
+ ok(!node.parentNode, 'parent must be null');
+
+ let newRange = document.createRange();
+ newRange.setStart(node, 0);
+ newRange.setEnd(node, 0);
+ let selection = document.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(newRange);
+
+ ok(selection.isCollapsed, 'isCollapsed must be true');
+
+ // Don't crash by user input
+ synthesizeKey("X", {});
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1310912.html b/editor/libeditor/tests/test_bug1310912.html
new file mode 100644
index 000000000..d73366a63
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1310912.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1310912
+-->
+<html>
+<head>
+ <title>Test for Bug 1310912</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1310912">Mozilla Bug 1310912</a>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<div id="editable1" contenteditable="true">ABC</div>
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ let elm = document.getElementById("editable1");
+
+ elm.focus();
+ let sel = window.getSelection();
+ sel.collapse(elm.childNodes[0], elm.textContent.length);
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "DEF",
+ clauses: [
+ { length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 3, length: 0 }
+ });
+ ok(elm.textContent == "ABCDEF", "composing text should be set");
+
+ window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
+ synthesizeCompositionChange({
+ composition: {
+ string: "GHI",
+ clauses: [
+ { length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ caret: { start: 0, length: 0 }
+ });
+ ok(elm.textContent == "ABCGHI", "composing text should be replaced");
+
+ window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
+ synthesizeCompositionChange({
+ composition: {
+ string: "JKL",
+ clauses: [
+ { length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ caret: { start: 0, length: 0 }
+ });
+ ok(elm.textContent == "ABCJKL", "composing text should be replaced");
+
+ window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
+ synthesizeCompositionChange({
+ composition: {
+ string: "MNO",
+ clauses: [
+ { length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ });
+ ok(elm.textContent == "ABCMNO", "composing text should be replaced");
+
+ window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
+ synthesizeComposition({ type: "compositioncommitasis" });
+ ok(elm.textContent == "ABCMNO", "composing text should be committed");
+
+ synthesizeKey("Z", { accelKey: true });
+ ok(elm.textContent == "ABC", "text should be undoed");
+
+ synthesizeKey("Z", { accelKey: true, shiftKey: true });
+ ok(elm.textContent == "ABCMNO", "text should be redoed");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1314790.html b/editor/libeditor/tests/test_bug1314790.html
new file mode 100644
index 000000000..ff1487244
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1314790.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1314790
+-->
+<html>
+<head>
+ <title>Test for Bug 1314790</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1314790">Mozilla Bug 1314790</a>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<div contenteditable="true" id="contenteditable1"><p>pen pineapple</p></div>
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ let elm = document.getElementById("contenteditable1");
+ elm.focus();
+ window.getSelection().collapse(elm.childNodes[0], 0);
+
+ SpecialPowers.doCommand(window, "cmd_wordNext");
+ SpecialPowers.doCommand(window, "cmd_wordNext");
+
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("a", {});
+ synthesizeKey("p", {});
+ synthesizeKey("p", {});
+ synthesizeKey("l", {});
+ synthesizeKey("e", {});
+ synthesizeKey(" ", {});
+ synthesizeKey("p", {});
+ synthesizeKey("e", {});
+ synthesizeKey("n", {});
+
+ is(elm.childNodes[0].textContent, "pen pineapple",
+ "'pen pineapple' is first elment");
+ is(elm.childNodes[1].textContent, "apple pen",
+ "'apple pen' is second elment");
+
+ SpecialPowers.doCommand(window, "cmd_deleteWordBackward");
+ SpecialPowers.doCommand(window, "cmd_deleteWordBackward");
+ is(elm.childNodes[0].textContent, "pen pineapple",
+ "'pen pineapple' is first elment");
+
+ SpecialPowers.doCommand(window, "cmd_deleteWordBackward");
+ is(elm.childNodes[0].textContent, "pen pineapple",
+ "'pen pineapple' is first elment");
+
+ SpecialPowers.doCommand(window, "cmd_deleteWordBackward");
+ is(elm.childNodes[0].textContent, "pen ", "'pen ' is first elment");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1315065.html b/editor/libeditor/tests/test_bug1315065.html
new file mode 100644
index 000000000..16f0de4e3
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1315065.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1315065
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1315065</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1315065">Mozilla Bug 1315065</a>
+<div contenteditable><p>abc<br></p></div>
+<script type="application/javascript">
+/** Test for Bug 1315065 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(()=>{
+ var editor = document.getElementsByTagName("div")[0];
+ function initForBackspace(aSelectionCollapsedTo /* = 0 ~ 3 */) {
+ editor.innerHTML = "<p id='p'>abc<br></p>";
+ var p = document.getElementById("p");
+ // FYI: We cannot inserting empty text nodes as expected with
+ // Node.appendChild() nor Node.insertBefore(). Therefore, let's use
+ // Range.insertNode() like actual web apps.
+ var selection = window.getSelection();
+ selection.collapse(p, 1);
+ var range = selection.getRangeAt(0);
+ var emptyTextNode3 = document.createTextNode("");
+ range.insertNode(emptyTextNode3);
+ var emptyTextNode2 = document.createTextNode("");
+ range.insertNode(emptyTextNode2);
+ var emptyTextNode1 = document.createTextNode("");
+ range.insertNode(emptyTextNode1);
+ is(p.childNodes.length, 5, "Failed to initialize the editor");
+ is(p.childNodes.item(1), emptyTextNode1, "1st text node should be emptyTextNode1");
+ is(p.childNodes.item(2), emptyTextNode2, "2nd text node should be emptyTextNode2");
+ is(p.childNodes.item(3), emptyTextNode3, "3rd text node should be emptyTextNode3");
+ switch (aSelectionCollapsedTo) {
+ case 0:
+ selection.collapse(p.firstChild, 3); // next to 'c'
+ break;
+ case 1:
+ selection.collapse(emptyTextNode1, 0);
+ break;
+ case 2:
+ selection.collapse(emptyTextNode2, 0);
+ break;
+ case 3:
+ selection.collapse(emptyTextNode3, 0);
+ break;
+ default:
+ ok(false, "aSelectionCollapsedTo is illegal value");
+ }
+ }
+
+ for (var i = 0; i < 4; i++) {
+ const kDescription = i == 0 ? "Backspace from immediately after the last character" :
+ "Backspace from " + i + "th empty text node";
+ editor.focus();
+ initForBackspace(i);
+ synthesizeKey("KEY_Backspace", { code: "Backspace" });
+ var p = document.getElementById("p");
+ ok(p, kDescription + ": <p> element shouldn't be removed by Backspace key press");
+ is(p.tagName.toLowerCase(), "p", kDescription + ": <p> element shouldn't be removed by Backspace key press");
+ // When Backspace key is pressed even in empty text nodes, Gecko should not remove empty text nodes for now
+ // because we should keep our traditional behavior (same as Edge) for backward compatibility as far as possible.
+ // In this case, Chromium removes all empty text nodes, but Edge doesn't remove any empty text nodes.
+ is(p.childNodes.length, 5, kDescription + ": <p> should have 5 children after pressing Backspace key");
+ is(p.childNodes.item(0).textContent, "ab", kDescription + ": 'c' should be removed by pressing Backspace key");
+ is(p.childNodes.item(1).textContent, "", kDescription + ": 1st empty text node should not be removed by pressing Backspace key");
+ is(p.childNodes.item(2).textContent, "", kDescription + ": 2nd empty text node should not be removed by pressing Backspace key");
+ is(p.childNodes.item(3).textContent, "", kDescription + ": 3rd empty text node should not be removed by pressing Backspace key");
+ editor.blur();
+ }
+
+ function initForDelete(aSelectionCollapsedTo /* = 0 ~ 3 */) {
+ editor.innerHTML = "<p id='p'>abc<br></p>";
+ var p = document.getElementById("p");
+ // FYI: We cannot inserting empty text nodes as expected with
+ // Node.appendChild() nor Node.insertBefore(). Therefore, let's use
+ // Range.insertNode() like actual web apps.
+ var selection = window.getSelection();
+ selection.collapse(p, 0);
+ var range = selection.getRangeAt(0);
+ var emptyTextNode1 = document.createTextNode("");
+ range.insertNode(emptyTextNode1);
+ var emptyTextNode2 = document.createTextNode("");
+ range.insertNode(emptyTextNode2);
+ var emptyTextNode3 = document.createTextNode("");
+ range.insertNode(emptyTextNode3);
+ is(p.childNodes.length, 5, "Failed to initialize the editor");
+ is(p.childNodes.item(0), emptyTextNode3, "1st text node should be emptyTextNode3");
+ is(p.childNodes.item(1), emptyTextNode2, "2nd text node should be emptyTextNode2");
+ is(p.childNodes.item(2), emptyTextNode1, "3rd text node should be emptyTextNode1");
+ switch (aSelectionCollapsedTo) {
+ case 0:
+ selection.collapse(p.childNodes.item(3), 0); // next to 'a'
+ break;
+ case 1:
+ selection.collapse(emptyTextNode1, 0);
+ break;
+ case 2:
+ selection.collapse(emptyTextNode2, 0);
+ break;
+ case 3:
+ selection.collapse(emptyTextNode3, 0);
+ break;
+ default:
+ ok(false, "aSelectionCollapsedTo is illegal value");
+ }
+ }
+
+ for (var i = 0; i < 4; i++) {
+ const kDescription = i == 0 ? "Delete from immediately before the first character" :
+ "Delete from " + i + "th empty text node";
+ editor.focus();
+ initForDelete(i);
+ synthesizeKey("KEY_Delete", { code: "Delete" });
+ var p = document.getElementById("p");
+ ok(p, kDescription + ": <p> element shouldn't be removed by Delete key press");
+ is(p.tagName.toLowerCase(), "p", kDescription + ": <p> element shouldn't be removed by Delete key press");
+ if (i == 0) {
+ // If Delete key is pressed in non-empty text node, only the text node should be modified.
+ // This is same behavior as Chromium, but different from Edge. Edge removes all empty text nodes in this case.
+ is(p.childNodes.length, 5, kDescription + ": <p> should have only 2 children after pressing Delete key (empty text nodes should be removed");
+ is(p.childNodes.item(0).textContent, "", kDescription + ": 1st empty text node should not be removed by pressing Delete key");
+ is(p.childNodes.item(1).textContent, "", kDescription + ": 2nd empty text node should not be removed by pressing Delete key");
+ is(p.childNodes.item(2).textContent, "", kDescription + ": 3rd empty text node should not be removed by pressing Delete key");
+ is(p.childNodes.item(3).textContent, "bc", kDescription + ": 'a' should be removed by pressing Delete key");
+ } else {
+ // If Delete key is pressed in an empty text node, it and following empty text nodes should be removed and the non-empty text node should be modified.
+ // This is same behavior as Chromium, but different from Edge. Edge removes all empty text nodes in this case.
+ var expectedEmptyTextNodes = 3 - i;
+ is(p.childNodes.length, expectedEmptyTextNodes + 2, kDescription + ": <p> should have only " + i + " children after pressing Delete key (" + i + " empty text nodes should be removed");
+ is(p.childNodes.item(expectedEmptyTextNodes).textContent, "bc", kDescription + ": empty text nodes and 'a' should be removed by pressing Delete key");
+ }
+ editor.blur();
+ }
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1328023.html b/editor/libeditor/tests/test_bug1328023.html
new file mode 100644
index 000000000..1b7fb7bf5
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1328023.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1328023
+-->
+<html>
+<head>
+ <title>Test for Bug 1328023</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1328023">Mozilla Bug 1328023</a>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<input type="text" id="input1"/>
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ let elm = document.getElementById("input1");
+
+ elm.focus();
+ synthesizeKey("A", {});
+ synthesizeKey("B", {});
+ is(elm.value, "AB", "AB is input.value now");
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(elm.value, "A", "A is input.value now");
+
+ synthesizeKey("Z", { accelKey: true });
+ is(elm.value, "AB", "AB is input.value now");
+
+ synthesizeKey("C", {});
+ is(elm.value, "ABC", "ABC is input.value now");
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ synthesizeKey("A", {});
+ synthesizeKey("B", {});
+ synthesizeKey("C", {});
+ is(elm.value, "ABC", "ABC is input.value now");
+
+ synthesizeKey("Z", { accelKey: true });
+ is(elm.value, "", "'' is input.value now");
+
+ synthesizeKey("Z", { accelKey: true, shiftKey: true });
+ is(elm.value, "ABC", "ABC is input.value now");
+
+ synthesizeKey("D", {});
+ is(elm.value, "ABCD", "ABCD is input.value now");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1330796.html b/editor/libeditor/tests/test_bug1330796.html
new file mode 100644
index 000000000..f8af02087
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1330796.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1330796
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 772796</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style> .pre { white-space: pre } </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 1330796</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="editable" contenteditable></div>
+
+<pre id="test">
+
+<script type="application/javascript">
+// We want to test what happens when the user splits a mail cite by clicking
+// at the start, the middle and the end of the cite and hitting the enter key.
+// Mail cites are spans, and since bug 1288911 they are displayed as blocks.
+// The _moz_quote attribute is used to give the cite a blue color via CSS.
+// As an internal attribute, it's not returned from the innerHTML.
+// To the user the tests look like:
+// > mailcite
+// This text is 10 characters long, so we position at 0, 5 and 10.
+// Althought since bug 1288911 those cites are displayed as block,
+// the tests are repeated also for inline display.
+// Each entry of the 'tests' array has the original HTML, the offset to click
+// at and the expected result HTML.
+var tests = [
+ // With style="display: block;".
+ [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite<br></span>", 0,
+ "x<br><span style=\"display: block;\">&gt; mailcite<br></span>" ],
+ [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite<br></span>", 5,
+ "<span style=\"display: block;\">&gt; mai<br></span>x<br><span style=\"display: block;\">lcite<br></span>"],
+ [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite<br></span>", 10,
+ "<span style=\"display: block;\">&gt; mailcite<br></span>x<br>" ],
+ // No <br> at the end to simulate prior deletion to the end of the quote.
+ [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite</span>", 10,
+ "<span style=\"display: block;\">&gt; mailcite<br></span>x<br>" ],
+
+ // Without style="display: block;".
+ [ "<span _moz_quote=true>&gt; mailcite<br></span>", 0,
+ "x<br><span>&gt; mailcite<br></span>" ],
+ [ "<span _moz_quote=true>&gt; mailcite<br></span>", 5,
+ "<span>&gt; mai</span><br>x<br><span>lcite<br></span>" ],
+ [ "<span _moz_quote=true>&gt; mailcite<br></span>", 10,
+ "<span>&gt; mailcite<br></span>x<br>" ],
+ // No <br> at the end to simulate prior deletion to the end of the quote.
+ [ "<span _moz_quote=true>&gt; mailcite</span>", 10,
+ "<span>&gt; mailcite</span><br>x<br>" ]
+];
+
+/** Test for Bug 1330796 **/
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+
+ var sel = window.getSelection();
+ var theEdit = document.getElementById("editable");
+ makeMailEditor();
+
+ for (i = 0; i < tests.length; i++) {
+ theEdit.innerHTML = tests[i][0];
+ theEdit.focus();
+ var theText = theEdit.firstChild.firstChild;
+ // Position set at the beginning , middle and end of the text.
+ sel.collapse(theText, tests[i][1]);
+
+ synthesizeKey("KEY_Enter", { code: "Enter" });
+ synthesizeKey("x", { code: "KeyX" });
+ is(theEdit.innerHTML, tests[i][2], "unexpected HTML for test " + i.toString());
+ }
+
+ SimpleTest.finish();
+
+});
+
+function makeMailEditor() {
+ var Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ var editor = editingSession.getEditorForWindow(window);
+ editor.flags |= Ci.nsIPlaintextEditor.eEditorMailMask;
+}
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug1332876.html b/editor/libeditor/tests/test_bug1332876.html
new file mode 100644
index 000000000..76dfa0fc7
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1332876.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1332876</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1332876">Mozilla Bug 1332876</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe src="data:text/html,<html><body><span>Edit me!</span>"></iframe>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 1332876 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var iframe = document.querySelector("iframe");
+ iframe.contentDocument.designMode='on';
+
+ iframe.contentWindow.addEventListener('keypress', function() {
+ iframe.style.display='none';
+ document.body.offsetHeight;
+ ok(true, "did not crash");
+ SimpleTest.finish();
+ });
+
+ iframe.contentWindow.addEventListener('click', function() {
+ synthesizeKey('a', {}, iframe.contentWindow);
+ });
+
+ synthesizeMouse(iframe,20,20,{})
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug200416.html b/editor/libeditor/tests/test_bug200416.html
new file mode 100644
index 000000000..9fb656425
--- /dev/null
+++ b/editor/libeditor/tests/test_bug200416.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=200416
+-->
+<title>Test for Bug 200416</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=200416">Mozilla Bug 200416</a>
+<div contenteditable><span>foo<p>bar</p></span></div>
+<script>
+getSelection().collapse(document.querySelector("p").firstChild, 0);
+document.execCommand("delete");
+var innerHTML = document.querySelector("div").innerHTML;
+ok(/foo.*bar/.test(innerHTML), "foo needs to still come before bar");
+</script>
diff --git a/editor/libeditor/tests/test_bug289384.html b/editor/libeditor/tests/test_bug289384.html
new file mode 100644
index 000000000..1d55e0c3f
--- /dev/null
+++ b/editor/libeditor/tests/test_bug289384.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=289384
+-->
+<head>
+ <title>Test for Bug 289384</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=289384">Mozilla Bug 289384</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var win = window.open("data:text/html,<a href=\"data:text/html,<body contenteditable onload='opener.continueTest(window);'>foo bar</body>\">link</a>", "", "test-289384");
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ win.document.querySelector("a").click();
+ }, false);
+});
+
+function continueTest(win) {
+ SimpleTest.waitForFocus(function() {
+ var doc = win.document;
+ var sel = win.getSelection();
+ doc.body.focus();
+ sel.collapse(doc.body.firstChild, 3);
+ SimpleTest.executeSoon(function() {
+ synthesizeKey("VK_LEFT", {accelKey: true}, win);
+ ok(sel.isCollapsed, "The selection must be collapsed");
+ is(sel.anchorNode, doc.body.firstChild, "The anchor node should be the body element's text node");
+ is(sel.anchorOffset, 0, "The anchor offset should be 0");
+ win.close();
+ SimpleTest.finish();
+ });
+ }, win);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug290026.html b/editor/libeditor/tests/test_bug290026.html
new file mode 100644
index 000000000..9e7686e72
--- /dev/null
+++ b/editor/libeditor/tests/test_bug290026.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=290026
+-->
+<head>
+ <title>Test for Bug 290026</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=290026">Mozilla Bug 290026</a>
+<p id="display"></p>
+<div id="editor" contenteditable></div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 290026 **/
+SimpleTest.waitForExplicitFinish();
+
+var editor = document.getElementById("editor");
+editor.innerHTML = '<p></p><ul><li>Item 1</li><li>Item 2</li></ul><p></p>';
+editor.focus();
+
+addLoadEvent(function() {
+ document.execCommand("stylewithcss", false, "true");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var lis = document.getElementsByTagName("li");
+ var range = document.createRange();
+ range.setStart(lis[0], 0);
+ range.setEnd(lis[1], lis[1].childNodes.length);
+ sel.addRange(range);
+ document.execCommand("indent", false, false);
+ var oneindent = '<p></p><ul style="margin-left: 40px;"><li>Item 1</li><li>Item 2</li></ul><p></p>';
+ is(editor.innerHTML, oneindent, "a once indented bulleted list");
+ document.execCommand("indent", false, false);
+ var twoindent = '<p></p><ul style="margin-left: 80px;"><li>Item 1</li><li>Item 2</li></ul><p></p>';
+ is(editor.innerHTML, twoindent, "a twice indented bulleted list");
+ document.execCommand("outdent", false, false);
+ is(editor.innerHTML, oneindent, "outdenting a twice indented bulleted list");
+
+ // done
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug291780.html b/editor/libeditor/tests/test_bug291780.html
new file mode 100644
index 000000000..93f63af61
--- /dev/null
+++ b/editor/libeditor/tests/test_bug291780.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=291780
+-->
+<head>
+ <title>Test for Bug 291780</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=291780">Mozilla Bug 291780</a>
+<p id="display"></p>
+<div id="editor" contenteditable></div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 291780 **/
+SimpleTest.waitForExplicitFinish();
+
+var original = '<ul style="margin-left: 40px;"><li>Item 1</li><li>Item 2</li><li>Item 3</li><li>Item 4</li></ul>';
+var editor = document.getElementById("editor");
+editor.innerHTML = original;
+editor.focus();
+
+addLoadEvent(function() {
+
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var lis = document.getElementsByTagName("li");
+ var range = document.createRange();
+ range.setStart(lis[1], 0);
+ range.setEnd(lis[2], lis[2].childNodes.length);
+ sel.addRange(range);
+ document.execCommand("indent", false, false);
+ var expected = '<ul style="margin-left: 40px;"><li>Item 1</li><ul><li>Item 2</li><li>Item 3</li></ul><li>Item 4</li></ul>';
+ is(editor.innerHTML, expected, "indenting part of an already indented bulleted list");
+ document.execCommand("outdent", false, false);
+ is(editor.innerHTML, original, "outdenting the partially indented part of an already indented bulleted list");
+
+ // done
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug309731.html b/editor/libeditor/tests/test_bug309731.html
new file mode 100644
index 000000000..85406905c
--- /dev/null
+++ b/editor/libeditor/tests/test_bug309731.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=309731
+-->
+<head>
+ <title>Test for Bug 309731</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=309731">Mozilla Bug 309731</a>
+<p id="display"></p>
+
+<div id="content">
+ <div id="input" contentEditable="true"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 309731 **/
+
+function selectNode(node) {
+ getSelection().selectAllChildren(node);
+}
+
+function selectInNode(node) {
+ getSelection().collapse(node, 0);
+}
+
+function doTest() {
+ var input = document.getElementById("input");
+
+ is(input.textContent, "", "Input node starts empty");
+
+ selectInNode(input);
+ ok(document.execCommand("inserthtml", false, ""), "execCommand should return true");
+ is(input.textContent, "", "empty inserthtml with empty selection shouldn't change contents");
+
+ selectInNode(input);
+ ok(document.execCommand("inserthtml", false, "foo"), "execCommand should return true");
+ is(input.textContent, "foo", "'foo'inserthtml with empty selection should add foo to contents");
+
+ selectNode(input);
+ ok(document.execCommand("inserthtml", false, "bar"), "execCommand should return true");
+ is(input.textContent, "bar", "'bar' inserthtml with complete selection should replace contents with bar");
+
+ selectNode(input);
+ ok(document.execCommand("inserthtml", false, ""), "execCommand should return true");
+ is(input.textContent, "", "empty inserthtml with complete selection should delete everything");
+}
+
+doTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug316447.html b/editor/libeditor/tests/test_bug316447.html
new file mode 100644
index 000000000..76d123815
--- /dev/null
+++ b/editor/libeditor/tests/test_bug316447.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=316447
+-->
+<title>Test for Bug 316447</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=316447">Mozilla Bug 316447</a>
+<div contenteditable><br></div>
+<script>
+/** Test for Bug 316447 **/
+
+getSelection().selectAllChildren(document.querySelector("div"));
+document.execCommand("inserthorizontalrule");
+is(document.querySelector("div").innerHTML, "<hr>", "Wrong innerHTML");
+</script>
diff --git a/editor/libeditor/tests/test_bug318065.html b/editor/libeditor/tests/test_bug318065.html
new file mode 100644
index 000000000..541653ab1
--- /dev/null
+++ b/editor/libeditor/tests/test_bug318065.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=318065
+-->
+
+<head>
+ <title>Test for Bug 318065</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=318065">Mozilla Bug 318065</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 318065 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ var expectedValues = ["A", "", "A", "", "A", "", "A"];
+ var messages = ["Initial text inserted",
+ "Initial text deleted",
+ "Undo of deletion",
+ "Redo of deletion",
+ "Initial text typed",
+ "Undo of typing",
+ "Redo of typing"];
+ var step = 0;
+
+ function onInput() {
+ is(this.value, expectedValues[step], messages[step]);
+ step++;
+ if (step == expectedValues.length) {
+ this.removeEventListener("input", onInput, false);
+ SimpleTest.finish();
+ }
+ }
+
+ var input = document.getElementById("t1");
+ input.addEventListener("input", onInput, false);
+ var input2 = document.getElementById("t2");
+ input2.addEventListener("input", onInput, false);
+
+ input.focus();
+
+ // Tests 0 + 1: Input letter and delete it again
+ synthesizeKey("A", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ // Test 2: Undo deletion. Value of input should be "A"
+ synthesizeKey("Z", {accelKey: true});
+
+ // Test 3: Redo deletion. Value of input should be ""
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+
+ input2.focus();
+
+ // Test 4: Input letter
+ synthesizeKey("A", {});
+
+ // Test 5: Undo typing. Value of input should be ""
+ synthesizeKey("Z", {accelKey: true});
+
+ // Test 6: Redo typing. Value of input should be "A"
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+ });
+ </script>
+ </pre>
+
+ <input type="text" value="" id="t1" />
+ <input type="text" value="" id="t2" />
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug332636.html b/editor/libeditor/tests/test_bug332636.html
new file mode 100644
index 000000000..5df386ac4
--- /dev/null
+++ b/editor/libeditor/tests/test_bug332636.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332636
+-->
+<head>
+ <title>Test for Bug 332636</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332636">Mozilla Bug 332636</a>
+<p id="display"></p>
+<div id="content">
+ <div id="edit0" contenteditable="true">axb</div><!-- reference: plane 0 base character -->
+ <div id="edit1" contenteditable="true">a&#x0308;b</div><!-- reference: plane 0 diacritic -->
+ <div id="edit2" contenteditable="true">a&#x10400;b</div><!-- plane 1 base character -->
+ <div id="edit3" contenteditable="true">a&#x10a0f;b</div><!-- plane 1 diacritic -->
+
+ <div id="edit0b" contenteditable="true">axb</div><!-- reference: plane 0 base character -->
+ <div id="edit1b" contenteditable="true">a&#x0308;b</div><!-- reference: plane 0 diacritic -->
+ <div id="edit2b" contenteditable="true">a&#x10400;b</div><!-- plane 1 base character -->
+ <div id="edit3b" contenteditable="true">a&#x10a0f;b</div><!-- plane 1 diacritic -->
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 332636 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+function test(edit) {
+ edit.focus();
+ var sel = window.getSelection();
+ sel.collapse(edit.childNodes[0], edit.textContent.length - 1);
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(edit.textContent, "ab", "The backspace key should delete the UTF-16 surrogate pair correctly");
+}
+
+function testWithMove(edit, offset) {
+ edit.focus();
+ var sel = window.getSelection();
+ sel.collapse(edit.childNodes[0], 0);
+ var i;
+ for (i = 0; i < offset; ++i) {
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_RIGHT", {});
+ }
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(edit.textContent, "ab", "The backspace key should delete the UTF-16 surrogate pair correctly");
+}
+
+function runTest() {
+ /* test backspace-deletion of the middle character */
+ test(document.getElementById("edit0"));
+ test(document.getElementById("edit1"));
+ test(document.getElementById("edit2"));
+ test(document.getElementById("edit3"));
+
+ /* extra tests with the use of RIGHT and LEFT to get to the right place */
+ testWithMove(document.getElementById("edit0b"), 2);
+ testWithMove(document.getElementById("edit1b"), 1);
+ testWithMove(document.getElementById("edit2b"), 2);
+ testWithMove(document.getElementById("edit3b"), 1);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug332636.html^headers^ b/editor/libeditor/tests/test_bug332636.html^headers^
new file mode 100644
index 000000000..e853d6cee
--- /dev/null
+++ b/editor/libeditor/tests/test_bug332636.html^headers^
@@ -0,0 +1 @@
+Content-Type: text/html; charset=UTF-8
diff --git a/editor/libeditor/tests/test_bug366682.html b/editor/libeditor/tests/test_bug366682.html
new file mode 100644
index 000000000..bac618941
--- /dev/null
+++ b/editor/libeditor/tests/test_bug366682.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=366682
+-->
+<head>
+ <title>Test for Bug 366682</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="spellcheck.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=366682">Mozilla Bug 366682</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 366682 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+var gMisspeltWords;
+
+function getEdit() {
+ return document.getElementById('edit');
+}
+
+function editDoc() {
+ return getEdit().contentDocument;
+}
+
+function getEditor() {
+ var Ci = SpecialPowers.Ci;
+ var win = editDoc().defaultView;
+ var editingSession = SpecialPowers.wrap(win)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(win);
+}
+
+function runTest() {
+ editDoc().body.innerHTML = "<div>errror and an other errror</div>";
+ gMisspeltWords = ["errror", "errror"];
+ editDoc().designMode = "on";
+
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
+ .onSpellCheck(editDoc().documentElement, evalTest);
+}
+
+function evalTest() {
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings accounted for.");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+
+<iframe id="edit" width="200" height="100" src="about:blank"></iframe>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug372345.html b/editor/libeditor/tests/test_bug372345.html
new file mode 100644
index 000000000..e9b1ac7a9
--- /dev/null
+++ b/editor/libeditor/tests/test_bug372345.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372345
+-->
+<head>
+ <title>Test for Bug 372345</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372345">Mozilla Bug 372345</a>
+<p id="display"></p>
+<div id="content">
+ <iframe src="data:text/html,<body>"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 372345 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var iframe = document.querySelector("iframe");
+ var doc = iframe.contentDocument;
+ var content = doc.body;
+ var link = content.querySelector("a");
+ function testCursor(post) {
+ setTimeout(function() {
+ var link = document.createElement("a");
+ link.href = "http://mozilla.org/";
+ link.textContent = "link";
+ link.style.cursor = "pointer";
+ content.appendChild(link);
+ is(iframe.contentWindow.getComputedStyle(link, null).cursor, "pointer", "Make sure that the cursor is set to pointer");
+ setTimeout(post, 0);
+ }, 0);
+ }
+ testCursor(function() {
+ doc.designMode = "on";
+ testCursor(function() {
+ doc.designMode = "off";
+ testCursor(function() {
+ content.setAttribute("contenteditable", "true");
+ testCursor(function() {
+ content.removeAttribute("contenteditable");
+ testCursor(function() {
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug404320.html b/editor/libeditor/tests/test_bug404320.html
new file mode 100644
index 000000000..b8249a557
--- /dev/null
+++ b/editor/libeditor/tests/test_bug404320.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=404320
+-->
+<head>
+ <title>Test for Bug 404320</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=404320">Mozilla Bug 404320</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="testIframe"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 404320 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+ var win = document.getElementById("testIframe").contentWindow;
+ var doc = document.getElementById("testIframe").contentDocument;
+
+ function testFormatBlock(tag, withAngleBrackets, shouldSucceed)
+ {
+ win.getSelection().selectAllChildren(doc.body.firstChild);
+ doc.execCommand("FormatBlock", false,
+ withAngleBrackets ? tag : "<" + tag + ">");
+ var resultNode;
+ if (shouldSucceed && (tag == "dd" || tag == "dt")) {
+ is(doc.body.firstChild.tagName, "DL", "tag was changed");
+ resultNode = doc.body.firstChild.firstChild;
+ }
+ else {
+ resultNode = doc.body.firstChild;
+ }
+
+ is(resultNode.tagName, shouldSucceed ? tag.toUpperCase() : "P", "tag was changed");
+ }
+
+ function formatBlockTests(tags, shouldSucceed)
+ {
+ var html = "<p>Content</p>";
+ for (var i = 0; i < tags.length; ++i) {
+ var tag = tags[i];
+ var resultTag = tag.toUpperCase();
+
+ doc.body.innerHTML = html;
+ testFormatBlock(tag, false, shouldSucceed);
+
+ doc.body.innerHTML = html;
+ testFormatBlock(tag, true, shouldSucceed);
+ }
+ }
+
+ doc.designMode = "on";
+
+ var goodTags = [ "address",
+ "blockquote",
+ "dd",
+ "div",
+ "dl",
+ "dt",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "p",
+ "pre" ];
+ var badTags = [ "b",
+ "i",
+ "span",
+ "foo" ];
+
+ formatBlockTests(goodTags, true);
+ formatBlockTests(badTags, false);
+ SimpleTest.finish();
+}
+
+addLoadEvent(runTests);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug408231.html b/editor/libeditor/tests/test_bug408231.html
new file mode 100644
index 000000000..d365bfa09
--- /dev/null
+++ b/editor/libeditor/tests/test_bug408231.html
@@ -0,0 +1,250 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=408231
+-->
+<head>
+ <title>Test for Bug 408231</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408231">Mozilla Bug 408231</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 408231 **/
+
+ var commandEnabledResults = [
+ ["contentReadOnly", "true"],
+ ["copy", "false"],
+ ["createlink", "true"],
+ ["cut", "false"],
+ ["decreasefontsize", "true"],
+ ["delete", "true"],
+ ["fontname", "true"],
+ ["fontsize", "true"],
+ ["formatblock", "true"],
+ ["heading", "true"],
+ ["hilitecolor", "true"],
+ ["increasefontsize", "true"],
+ ["indent", "true"],
+ ["inserthorizontalrule", "true"],
+ ["inserthtml", "true"],
+ ["insertimage", "true"],
+ ["insertorderedlist", "true"],
+ ["insertunorderedlist", "true"],
+ ["insertparagraph", "true"],
+ ["italic", "true"],
+ ["justifycenter", "true"],
+ ["justifyfull", "true"],
+ ["justifyleft", "true"],
+ ["justifyright", "true"],
+ ["outdent", "true"],
+ ["paste", "false"],
+ ["redo", "false"],
+ ["removeformat", "true"],
+ ["selectall", "true"],
+ ["strikethrough", "true"],
+ ["styleWithCSS", "true"],
+ ["subscript", "true"],
+ ["superscript", "true"],
+ ["underline", "true"],
+ ["undo", "false"],
+ ["unlink", "true"],
+ ["not-a-command", "false"]
+ ];
+
+ var commandIndetermResults = [
+ ["contentReadOnly", "false"],
+ ["copy", "false"],
+ ["createlink", "false"],
+ ["cut", "false"],
+ ["decreasefontsize", "false"],
+ ["delete", "false"],
+ ["fontname", "false"],
+ ["fontsize", "false"],
+ ["formatblock", "false"],
+ ["heading", "false"],
+ ["hilitecolor", "false"],
+ ["increasefontsize", "false"],
+ ["indent", "false"],
+ ["inserthorizontalrule", "false"],
+ ["inserthtml", "false"],
+ ["insertimage", "false"],
+ ["insertorderedlist", "false"],
+ ["insertunorderedlist", "false"],
+ ["insertparagraph", "false"],
+ ["italic", "false"],
+ ["justifycenter", "false"],
+ ["justifyfull", "false"],
+ ["justifyleft", "false"],
+ ["justifyright", "false"],
+ ["outdent", "false"],
+ //["paste", "false"],
+ ["redo", "false"],
+ ["removeformat", "false"],
+ ["selectall", "false"],
+ ["strikethrough", "false"],
+ ["styleWithCSS", "false"],
+ ["subscript", "false"],
+ ["superscript", "false"],
+ ["underline", "false"],
+ ["undo", "false"],
+ ["unlink", "false"],
+ ["not-a-command", "false"]
+ ];
+
+ var commandStateResults = [
+ ["contentReadOnly", "false"],
+ ["copy", "false"],
+ ["createlink", "false"],
+ ["cut", "false"],
+ ["decreasefontsize", "false"],
+ ["delete", "false"],
+ ["fontname", "false"],
+ ["fontsize", "false"],
+ ["formatblock", "false"],
+ ["heading", "false"],
+ ["hilitecolor", "false"],
+ ["increasefontsize", "false"],
+ ["indent", "false"],
+ ["inserthorizontalrule", "false"],
+ ["inserthtml", "false"],
+ ["insertimage", "false"],
+ ["insertorderedlist", "false"],
+ ["insertunorderedlist", "false"],
+ ["insertparagraph", "false"],
+ ["italic", "false"],
+ ["justifycenter", "false"],
+ ["justifyfull", "false"],
+ ["justifyleft", "true"],
+ ["justifyright", "false"],
+ ["outdent", "false"],
+ //["paste", "false"],
+ ["redo", "false"],
+ ["removeformat", "false"],
+ ["selectall", "false"],
+ ["strikethrough", "false"],
+ ["styleWithCSS", "false"],
+ ["subscript", "false"],
+ ["superscript", "false"],
+ ["underline", "false"],
+ ["undo", "false"],
+ ["unlink", "false"],
+ ["not-a-command", "false"]
+ ];
+
+ var commandValueResults = [
+ ["contentReadOnly", ""],
+ ["copy", ""],
+ ["createlink", ""],
+ ["cut", ""],
+ ["decreasefontsize", ""],
+ ["delete", ""],
+ ["fontname", "serif"],
+ ["fontsize", ""],
+ ["formatblock", ""],
+ ["heading", ""],
+ ["hilitecolor", "transparent"],
+ ["increasefontsize", ""],
+ ["indent", ""],
+ ["inserthorizontalrule", ""],
+ ["inserthtml", ""],
+ ["insertimage", ""],
+ ["insertorderedlist", ""],
+ ["insertunorderedlist", ""],
+ ["insertparagraph", ""],
+ ["italic", ""],
+ ["justifycenter", "left"],
+ ["justifyfull", "left"],
+ ["justifyleft", "left"],
+ ["justifyright", "left"],
+ ["outdent", ""],
+ //["paste", ""],
+ ["redo", ""],
+ ["removeformat", ""],
+ ["selectall", ""],
+ ["strikethrough", ""],
+ ["styleWithCSS", ""],
+ ["subscript", ""],
+ ["superscript", ""],
+ ["underline", ""],
+ ["undo", ""],
+ ["unlink", ""],
+ ["not-a-command", ""],
+ ];
+
+
+ function callQueryCommandEnabled(cmdName) {
+ var result;
+ try {
+ result = '' + document.queryCommandEnabled( cmdName );
+ } catch( error ) {
+ result = 'name' in error ? error.name : 'exception';
+ }
+ return result;
+ }
+
+ function callQueryCommandIndeterm(cmdName) {
+ var result;
+ try {
+ result = '' + document.queryCommandIndeterm( cmdName );
+ } catch( error ) {
+ result = 'name' in error ? error.name : 'exception';
+ }
+ return result;
+ }
+
+ function callQueryCommandState(cmdName) {
+ var result;
+ try {
+ result = '' + document.queryCommandState( cmdName );
+ } catch( error ) {
+ result = 'name' in error ? error.name : 'exception';
+ }
+ return result;
+ }
+
+ function callQueryCommandValue(cmdName) {
+ var result;
+ try {
+ result = '' + document.queryCommandValue( cmdName );
+ } catch( error ) {
+ result = 'name' in error ? error.name : 'exception';
+ }
+ return result;
+ }
+
+ function testQueryCommand(expectedResults, fun, funName) {
+ for (i=0; i<expectedResults.length; i++) {
+ var commandName = expectedResults[i][0];
+ var expectedResult = expectedResults[i][1];
+ var result = fun(commandName);
+ ok(result == expectedResult, funName + '('+commandName+') result=' +result+ ' expected=' + expectedResult);
+ }
+ }
+
+ function runTests() {
+ document.designMode='on';
+ window.getSelection().collapse(document.body, 0);
+ testQueryCommand(commandEnabledResults, callQueryCommandEnabled, "queryCommandEnabled");
+ testQueryCommand(commandIndetermResults, callQueryCommandIndeterm, "queryCommandIndeterm");
+ testQueryCommand(commandStateResults, callQueryCommandState, "queryCommandState");
+ testQueryCommand(commandValueResults, callQueryCommandValue, "queryCommandValue");
+ document.designMode='off';
+ SimpleTest.finish();
+ }
+
+ window.onload = runTests;
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/editor/libeditor/tests/test_bug410986.html b/editor/libeditor/tests/test_bug410986.html
new file mode 100644
index 000000000..a3f3a5602
--- /dev/null
+++ b/editor/libeditor/tests/test_bug410986.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=410986
+-->
+<head>
+ <title>Test for Bug 410986</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=410986">Mozilla Bug 410986</a>
+<p id="display"></p>
+<div id="content">
+ <div id="contents"><span style="color: green;">green text</span></div>
+ <div id="editor" contenteditable="true"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 410986 **/
+
+var gPasteEvents = 0;
+document.getElementById("editor").addEventListener("paste", function() {
+ ++gPasteEvents;
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ getSelection().selectAllChildren(document.getElementById("contents"));
+ SimpleTest.waitForClipboard("green text",
+ function() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function() {
+ var ed = document.getElementById("editor");
+ ed.focus();
+ if (navigator.platform.indexOf("Mac") >= 0) {
+ synthesizeKey("V", {accelKey: true, shiftKey: true, altKey: true});
+ } else {
+ synthesizeKey("V", {accelKey: true, shiftKey: true});
+ }
+ is(ed.innerHTML, "green text", "Content should be pasted in plaintext format");
+ is(gPasteEvents, 1, "One paste event must be fired");
+
+ ed.innerHTML = "";
+ ed.blur();
+ getSelection().selectAllChildren(document.getElementById("contents"));
+ SimpleTest.waitForClipboard("green text",
+ function() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function() {
+ var ed = document.getElementById("editor");
+ ed.focus();
+ synthesizeKey("V", {accelKey: true});
+ isnot(ed.innerHTML.indexOf("<span style=\"color: green;\">green text</span>"), -1,
+ "Content should be pasted in HTML format");
+ is(gPasteEvents, 2, "Two paste events must be fired");
+
+ SimpleTest.finish();
+ },
+ function() {
+ ok(false, "Failed to copy the second item to the clipboard");
+ SimpleTest.finish();
+ }
+ );
+ },
+ function() {
+ ok(false, "Failed to copy the first item to the clipboard");
+ SimpleTest.finish();
+ }
+ );
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug414526.html b/editor/libeditor/tests/test_bug414526.html
new file mode 100644
index 000000000..0975b6a5a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug414526.html
@@ -0,0 +1,247 @@
+<html>
+<head>
+ <title>Test for backspace key and delete key shouldn't remove another editing host's text</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="display"></div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests()
+{
+
+ var container = document.getElementById("display");
+
+ function reset()
+ {
+ document.execCommand("Undo", false, null);
+ }
+
+ var selection = window.getSelection();
+ function moveCaretToStartOf(aEditor)
+ {
+ selection.selectAllChildren(aEditor);
+ selection.collapseToStart();
+ }
+
+ function moveCaretToEndOf(aEditor)
+ {
+ selection.selectAllChildren(aEditor);
+ selection.collapseToEnd();
+ }
+
+ /* TestCase #1
+ */
+ const kTestCase1 =
+ "<p id=\"editor1\" contenteditable=\"true\">editor1</p>" +
+ "<p id=\"editor2\" contenteditable=\"true\">editor2</p>" +
+ "<div id=\"editor3\" contenteditable=\"true\"><div>editor3</div></div>" +
+ "<p id=\"editor4\" contenteditable=\"true\">editor4</p>" +
+ "non-editable text" +
+ "<p id=\"editor5\" contenteditable=\"true\">editor5</p>";
+
+ const kTestCase1_editor3_deleteAtStart =
+ "<p id=\"editor1\" contenteditable=\"true\">editor1</p>" +
+ "<p id=\"editor2\" contenteditable=\"true\">editor2</p>" +
+ "<div id=\"editor3\" contenteditable=\"true\"><div>ditor3</div></div>" +
+ "<p id=\"editor4\" contenteditable=\"true\">editor4</p>" +
+ "non-editable text" +
+ "<p id=\"editor5\" contenteditable=\"true\">editor5</p>";
+
+ const kTestCase1_editor3_backspaceAtEnd =
+ "<p id=\"editor1\" contenteditable=\"true\">editor1</p>" +
+ "<p id=\"editor2\" contenteditable=\"true\">editor2</p>" +
+ "<div id=\"editor3\" contenteditable=\"true\"><div>editor</div></div>" +
+ "<p id=\"editor4\" contenteditable=\"true\">editor4</p>" +
+ "non-editable text" +
+ "<p id=\"editor5\" contenteditable=\"true\">editor5</p>";
+
+ container.innerHTML = kTestCase1;
+
+ var editor1 = document.getElementById("editor1");
+ var editor2 = document.getElementById("editor2");
+ var editor3 = document.getElementById("editor3");
+ var editor4 = document.getElementById("editor4");
+ var editor5 = document.getElementById("editor5");
+
+ /* TestCase #1:
+ * pressing backspace key at start should not change the content.
+ */
+ editor2.focus();
+ moveCaretToStartOf(editor2);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing backspace key at start of editor2 changes the content");
+ reset();
+
+ editor3.focus();
+ moveCaretToStartOf(editor3);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing backspace key at start of editor3 changes the content");
+ reset();
+
+ editor4.focus();
+ moveCaretToStartOf(editor4);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing backspace key at start of editor4 changes the content");
+ reset();
+
+ editor5.focus();
+ moveCaretToStartOf(editor5);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing backspace key at start of editor5 changes the content");
+ reset();
+
+ /* TestCase #1:
+ * pressing delete key at end should not change the content.
+ */
+ editor1.focus();
+ moveCaretToEndOf(editor1);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing delete key at end of editor1 changes the content");
+ reset();
+
+ editor2.focus();
+ moveCaretToEndOf(editor2);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing delete key at end of editor2 changes the content");
+ reset();
+
+ editor3.focus();
+ moveCaretToEndOf(editor3);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing delete key at end of editor3 changes the content");
+ reset();
+
+ editor4.focus();
+ moveCaretToEndOf(editor4);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing delete key at end of editor4 changes the content");
+ reset();
+
+ /* TestCase #1: cases when the caret is not on text node.
+ * - pressing delete key at start should remove the first character
+ * - pressing backspace key at end should remove the first character
+ * and the adjacent blocks should not be changed.
+ */
+ editor3.focus();
+ moveCaretToStartOf(editor3);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase1_editor3_deleteAtStart,
+ "Pressing delete key at start of editor3 changes adjacent elements"
+ + " and/or does not remove the first character.");
+ reset();
+
+ // Backspace doesn't work here yet.
+ editor3.focus();
+ moveCaretToEndOf(editor3);
+ synthesizeKey("VK_BACK_SPACE", { });
+ todo_is(container.innerHTML, kTestCase1_editor3_backspaceAtEnd,
+ "Pressing backspace key at end of editor3 changes adjacent elements"
+ + " and/or does not remove the last character.");
+ reset();
+ // We can still check that adjacent elements are not affected.
+ editor3.focus();
+ moveCaretToEndOf(editor3);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase1,
+ "Pressing backspace key at end of editor3 changes the content");
+ reset();
+
+ /* TestCase #2:
+ * two adjacent editable <span> in a table cell.
+ */
+ const kTestCase2 = "<table><tbody><tr><td><span id=\"editor1\" contenteditable=\"true\">test</span>" +
+ "<span id=\"editor2\" contenteditable=\"true\">test</span></td></tr></tbody></table>";
+
+ container.innerHTML = kTestCase2;
+ editor1 = document.getElementById("editor1");
+ editor2 = document.getElementById("editor2");
+
+ editor2.focus();
+ moveCaretToStartOf(editor2);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase2,
+ "Pressing backspace key at the start of editor2 changes the content for kTestCase2");
+ reset();
+
+ editor1.focus();
+ moveCaretToEndOf(editor1);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase2,
+ "Pressing delete key at the end of editor1 changes the content for kTestCase2");
+ reset();
+
+ /* TestCase #3:
+ * editable <span> in two adjacent table cells.
+ */
+ const kTestCase3 = "<table><tbody><tr><td><span id=\"editor1\" contenteditable=\"true\">test</span></td>" +
+ "<td><span id=\"editor2\" contenteditable=\"true\">test</span></td></tr></tbody></table>";
+
+ container.innerHTML = kTestCase3;
+ editor1 = document.getElementById("editor1");
+ editor2 = document.getElementById("editor2");
+
+ editor2.focus();
+ moveCaretToStartOf(editor2);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase3,
+ "Pressing backspace key at the start of editor2 changes the content for kTestCase3");
+ reset();
+
+ editor1.focus();
+ moveCaretToEndOf(editor1);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase3,
+ "Pressing delete key at the end of editor1 changes the content for kTestCase3");
+ reset();
+
+ /* TestCase #4:
+ * editable <div> in two adjacent table cells.
+ */
+ const kTestCase4 = "<table><tbody><tr><td><div id=\"editor1\" contenteditable=\"true\">test</div></td>" +
+ "<td><div id=\"editor2\" contenteditable=\"true\">test</div></td></tr></tbody></table>";
+
+ container.innerHTML = kTestCase4;
+ editor1 = document.getElementById("editor1");
+ editor2 = document.getElementById("editor2");
+
+ editor2.focus();
+ moveCaretToStartOf(editor2);
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(container.innerHTML, kTestCase4,
+ "Pressing backspace key at the start of editor2 changes the content for kTestCase4");
+ reset();
+
+ editor1.focus();
+ moveCaretToEndOf(editor1);
+ synthesizeKey("VK_DELETE", { });
+ is(container.innerHTML, kTestCase4,
+ "Pressing delete key at the end of editor1 changes the content for kTestCase4");
+ reset();
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug417418.html b/editor/libeditor/tests/test_bug417418.html
new file mode 100644
index 000000000..146de0920
--- /dev/null
+++ b/editor/libeditor/tests/test_bug417418.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=417418
+-->
+<head>
+ <title>Test for Bug 417418</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=417418">Mozilla Bug 417418</a>
+<div id="display" contenteditable="true">
+<p id="coin">first paragraph</p>
+<p>second paragraph. <img id="img" src="green.png"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 417418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+function resetSelection() {
+ window.getSelection().collapse(document.getElementById("coin"), 0);
+}
+
+function runTest() {
+ var rightClickDown = {type: 'mousedown', button: 2},
+ rightClickUp = {type: 'mouseup', button: 2},
+ singleClickDown = {type: 'mousedown', button: 0},
+ singleClickUp = {type: 'mouseup', button: 0};
+ var selection = window.getSelection();
+
+ var div = document.getElementById('display');
+ var img = document.getElementById('img');
+ var divRect = div.getBoundingClientRect();
+ var imgselected;
+
+ resetSelection();
+ synthesizeMouse(div, divRect.width - 1, divRect.height - 1, rightClickDown);
+ synthesizeMouse(div, divRect.width - 1, divRect.height - 1, rightClickUp);
+ ok(selection.isCollapsed, "selection is not collapsed");
+
+ resetSelection();
+ synthesizeMouse(div, divRect.width - 1, divRect.height - 1, singleClickDown);
+ synthesizeMouse(div, divRect.width - 1, divRect.height - 1, singleClickUp);
+ ok(selection.isCollapsed, "selection is not collapsed");
+
+ resetSelection();
+ synthesizeMouseAtCenter(img, rightClickDown);
+ synthesizeMouseAtCenter(img, rightClickUp);
+ imgselected = selection.anchorNode == img.parentNode &&
+ selection.anchorOffset === 1 &&
+ selection.rangeCount === 1;
+ ok(imgselected, "image is not selected");
+
+ resetSelection();
+ synthesizeMouseAtCenter(img, singleClickDown);
+ synthesizeMouseAtCenter(img, singleClickUp);
+ imgselected = selection.anchorNode == img.parentNode &&
+ selection.anchorOffset === 1 &&
+ selection.rangeCount === 1;
+ ok(imgselected, "image is not selected");
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug432225.html b/editor/libeditor/tests/test_bug432225.html
new file mode 100644
index 000000000..58d158722
--- /dev/null
+++ b/editor/libeditor/tests/test_bug432225.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=432225
+-->
+<head>
+ <title>Test for Bug 432225</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="spellcheck.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=432225">Mozilla Bug 432225</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 432225 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+var gMisspeltWords = [];
+
+function getEdit() {
+ return document.getElementById('edit');
+}
+
+function editDoc() {
+ return getEdit().contentDocument;
+}
+
+function getEditor() {
+ var Ci = SpecialPowers.Ci;
+ var win = editDoc().defaultView;
+ var editingSession = SpecialPowers.wrap(win)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(win);
+}
+
+function runTest() {
+ editDoc().designMode = "on";
+ setTimeout(function() { addWords(100); }, 0);
+}
+
+function addWords(aLimit) {
+ if (aLimit == 0) {
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings accounted for.");
+ SimpleTest.finish();
+ return;
+ }
+ getEdit().focus();
+ sendString('aa OK ');
+ gMisspeltWords.push("aa");
+ setTimeout(function() { addWords(aLimit-1); }, 0);
+}
+</script>
+</pre>
+
+<iframe id="edit" width="200" height="100" src="about:blank"></iframe>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug439808.html b/editor/libeditor/tests/test_bug439808.html
new file mode 100644
index 000000000..a04d1d4d4
--- /dev/null
+++ b/editor/libeditor/tests/test_bug439808.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=439808
+-->
+<head>
+ <title>Test for Bug 439808</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=439808">Mozilla Bug 439808</a>
+<p id="display"></p>
+<div id="content">
+<span><span contenteditable id="e">twest</span></span>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 439808 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var e = document.getElementById("e");
+ e.focus();
+ getSelection().collapse(e.firstChild, 1);
+ synthesizeKey("VK_DELETE", {});
+ is(e.textContent, "test", "Delete key worked");
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(e.textContent, "est", "Backspace key worked");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug442186.html b/editor/libeditor/tests/test_bug442186.html
new file mode 100644
index 000000000..eab81e055
--- /dev/null
+++ b/editor/libeditor/tests/test_bug442186.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=442186
+-->
+<head>
+ <title>Test for Bug 442186</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=442186">Mozilla Bug 442186</a>
+<p id="display"></p>
+<div id="content">
+ <h2> two &lt;div&gt; containers </h2>
+ <section contenteditable id="test1">
+ <div> First paragraph with some text. </div>
+ <div> Second paragraph with some text. </div>
+ </section>
+
+ <h2> two paragraphs </h2>
+ <section contenteditable id="test2">
+ <p> First paragraph with some text. </p>
+ <p> Second paragraph with some text. </p>
+ </section>
+
+ <h2> one text node, one paragraph </h2>
+ <section contenteditable id="test3">
+ First paragraph with some text.
+ <p> Second paragraph with some text. </p>
+ </section>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 442186 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function justify(textNode, pos) {
+ if (!pos) pos = 10;
+
+ // put the caret on the requested character
+ var range = document.createRange();
+ var sel = window.getSelection();
+ range.setStart(textNode, pos);
+ range.setEnd(textNode, pos);
+ sel.addRange(range);
+
+ // align
+ document.execCommand("justifyright", false, null);
+}
+
+function runTests() {
+ document.execCommand("stylewithcss", false, "true");
+
+ const test1 = document.getElementById("test1");
+ const test2 = document.getElementById("test2");
+ const test3 = document.getElementById("test3");
+
+ // #test1: two <div> containers
+ const line1 = test1.querySelector("div").firstChild;
+ test1.focus();
+ justify(line1);
+ is(test1.querySelectorAll("*").length, 2,
+ "Aligning the first child should not create nor remove any element.");
+ is(line1.parentNode.nodeName.toLowerCase(), "div",
+ "Aligning the first <div> should not modify its node type.");
+ is(line1.parentNode.style.textAlign, "right",
+ "Aligning the first <div> should set a 'text-align: right' style rule.");
+
+ // #test2: two paragraphs
+ const line2 = test2.querySelector("p").firstChild;
+ test2.focus();
+ justify(line2);
+ is(test2.querySelectorAll("*").length, 2,
+ "Aligning the first child should not create nor remove any element.");
+ is(line2.parentNode.nodeName.toLowerCase(), "p",
+ "Aligning the first paragraph should not modify its node type.");
+ is(line2.parentNode.style.textAlign, "right",
+ "Aligning the first paragraph should set a 'text-align: right' style rule.");
+
+ // #test3: one text node, two paragraphs
+ const line3 = test3.firstChild;
+ test3.focus();
+ justify(line3);
+ is(test3.querySelectorAll("*").length, 2,
+ "Aligning the first child should create a block element.");
+ is(line3.parentNode.nodeName.toLowerCase(), "div",
+ "Aligning the first child should create a block element.");
+ is(line3.parentNode.style.textAlign, "right",
+ "Aligning the first line should set a 'text-align: right' style rule.");
+
+ // done
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug449243.html b/editor/libeditor/tests/test_bug449243.html
new file mode 100644
index 000000000..77a7c6a7d
--- /dev/null
+++ b/editor/libeditor/tests/test_bug449243.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449243
+-->
+<head>
+ <title>Test for Bug 449243</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=449243">Mozilla Bug 449243</a>
+<p id="display"></p>
+<div id="content" contenteditable>
+ <h2>This is a title</h2>
+ <ul>
+ <li>this is a</li>
+ <li>bullet list</li>
+ </ul>
+ <ol>
+ <li>this is a</li>
+ <li>numbered list</li>
+ </ol>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 449243 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+const CARET_BEGIN = 0;
+const CARET_MIDDLE = 1;
+const CARET_END = 2;
+
+function split(element, caretPos, nbKeyPresses) {
+ // put the caret on the requested position
+ var sel = window.getSelection();
+ var len = element.textContent.length;
+ var pos = -1;
+ switch (caretPos) {
+ case CARET_BEGIN:
+ pos = 0;
+ break;
+ case CARET_MIDDLE:
+ pos = Math.floor(len/2);
+ break;
+ case CARET_END:
+ pos = len;
+ break;
+ }
+ sel.collapse(element.firstChild, pos);
+
+ // simulates a [Return] keypress
+ for (var i = 0; i < nbKeyPresses; i++)
+ synthesizeKey("VK_RETURN", {});
+}
+
+function undo(nbKeyPresses) {
+ for (var i = 0; i < nbKeyPresses; i++)
+ document.execCommand("Undo", false, null);
+}
+
+function SameTypeAsPreviousSibling(element) {
+ var sibling = element.previousSibling;
+ while (sibling && sibling.nodeType != 1)
+ sibling = element.previousSibling;
+ return (element.nodeName == sibling.nodeName);
+}
+
+function isParagraph(element) {
+ return element.nodeName.toLowerCase() == "p";
+}
+
+function runTests() {
+ const content = document.querySelector("[contenteditable]");
+ const header = content.querySelector("h2");
+ const ulItem = content.querySelector("ul > li:last-child");
+ const olItem = content.querySelector("ol > li:last-child");
+ content.focus();
+
+ // beginning of selection: split current node
+ split(header, CARET_BEGIN, 1);
+ ok(SameTypeAsPreviousSibling(header),
+ "Pressing [Return] at the beginning of a header " +
+ "should create another header.");
+ split(ulItem, CARET_BEGIN, 2);
+ ok(SameTypeAsPreviousSibling(ulItem),
+ "Pressing [Return] at the beginning of an unordered list item " +
+ "should create another list item.");
+ split(olItem, CARET_BEGIN, 2);
+ ok(SameTypeAsPreviousSibling(olItem),
+ "Pressing [Return] at the beginning of an ordered list item " +
+ "should create another list item.");
+ undo(3);
+
+ // middle of selection: split current node
+ split(header, CARET_MIDDLE, 1);
+ ok(SameTypeAsPreviousSibling(header),
+ "Pressing [Return] at the middle of a header " +
+ "should create another header.");
+ split(ulItem, CARET_MIDDLE, 2);
+ ok(SameTypeAsPreviousSibling(ulItem),
+ "Pressing [Return] at the middle of an unordered list item " +
+ "should create another list item.");
+ split(olItem, CARET_MIDDLE, 2);
+ ok(SameTypeAsPreviousSibling(olItem),
+ "Pressing [Return] at the middle of an ordered list item " +
+ "should create another list item.");
+ undo(3);
+
+ // end of selection: create a new paragraph
+ split(header, CARET_END, 1);
+ ok(isParagraph(content.querySelector("h2+*")),
+ "Pressing [Return] at the end of a header " +
+ "should create a new paragraph.");
+ split(ulItem, CARET_END, 2);
+ ok(isParagraph(content.querySelector("ul+*")),
+ "Pressing [Return] twice at the end of an unordered list item " +
+ "should create a new paragraph.");
+ split(olItem, CARET_END, 2);
+ ok(isParagraph(content.querySelector("ol+*")),
+ "Pressing [Return] twice at the end of an ordered list item " +
+ "should create a new paragraph.");
+ undo(3);
+
+ // done
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug455992.html b/editor/libeditor/tests/test_bug455992.html
new file mode 100644
index 000000000..daf362acf
--- /dev/null
+++ b/editor/libeditor/tests/test_bug455992.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 455992</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+function runTest() {
+
+ function select(id) {
+ var e = document.getElementById(id);
+ e.focus();
+ return e;
+ }
+
+ function setupIframe(id) {
+ var e = document.getElementById(id);
+ var doc = e.contentDocument;
+ doc.body.innerHTML = String.fromCharCode(10)+'<span id="' + id + '_span" style="border:1px solid blue" contenteditable="true">X</span>'+String.fromCharCode(10);
+ e = doc.getElementById(id + "_span");
+ e.focus();
+ return e;
+ }
+
+ function test_begin_bs(e) {
+ const msg = "BACKSPACE at beginning of contenteditable inline element";
+ var before = e.parentNode.childNodes[0].nodeValue;
+ sendKey("back_space");
+ is(e.parentNode.childNodes[0].nodeValue, before, msg + " with id=" + e.id);
+ is(e.innerHTML, "X", msg + " with id=" + e.id);
+ }
+
+ function test_begin_space(e) {
+ const msg = "SPACE at beginning of contenteditable inline element";
+ var before = e.parentNode.childNodes[0].nodeValue;
+ sendChar(" ");
+ is(e.parentNode.childNodes[0].nodeValue, before, msg + " with id=" + e.id);
+ is(e.innerHTML, "&nbsp;X", msg + " with id=" + e.id);
+ }
+
+ function test_end_delete(e) {
+ const msg = "DEL at end of contenteditable inline element";
+ var before = e.parentNode.childNodes[2].nodeValue;
+ sendKey("right");
+ sendKey("delete");
+ is(e.parentNode.childNodes[2].nodeValue, before, msg + " with id=" + e.id);
+ is(e.innerHTML, "X", msg + " with id=" + e.id);
+ }
+
+ function test_end_space(e) {
+ const msg = "SPACE at end of contenteditable inline element";
+ var before = e.parentNode.childNodes[2].nodeValue;
+ sendKey("right");
+ sendChar(" ");
+ is(e.parentNode.childNodes[2].nodeValue, before, msg + " with id=" + e.id);
+ is(e.innerHTML, "X" + (e.tagName=="SPAN" ? "&nbsp;" : " <br>"), msg + " with id=" + e.id);
+ }
+
+ test_begin_bs(select("t1"));
+ test_begin_space(select("t2"));
+ test_end_delete(select("t3"));
+ test_end_space(select("t4"));
+ test_end_space(select("t5"));
+
+ test_begin_bs(setupIframe('i1'));
+ test_begin_space(setupIframe('i2'));
+ test_end_delete(setupIframe('i3'));
+ test_end_space(setupIframe('i4'));
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=455992">Mozilla Bug 455992</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<div> <span id="t1" style="border:1px solid blue" contenteditable="true">X</span> Y</div>
+<div> <span id="t2" style="border:1px solid blue" contenteditable="true">X</span> Y</div>
+<div> <span id="t3" style="border:1px solid blue" contenteditable="true">X</span> Y</div>
+<div> <span id="t4" style="border:1px solid blue" contenteditable="true">X</span> Y</div>
+<div> <div id="t5" style="border:1px solid blue" contenteditable="true">X</div> Y</div>
+
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i2" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i3" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i4" width="200" height="100" src="about:blank"></iframe><br>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug456244.html b/editor/libeditor/tests/test_bug456244.html
new file mode 100644
index 000000000..03cc2c9e3
--- /dev/null
+++ b/editor/libeditor/tests/test_bug456244.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 456244</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+function runTest() {
+
+ function select(id) {
+ var e = document.getElementById(id);
+ e.focus();
+ return e;
+ }
+
+ function setupIframe(id) {
+ var e = document.getElementById(id);
+ var doc = e.contentDocument;
+ doc.body.innerHTML = String.fromCharCode(10)+'<span id="' + id + '_span" style="border:1px solid blue" contenteditable="true">X</span>'+String.fromCharCode(10);
+ e = doc.getElementById(id + "_span");
+ e.focus();
+ return e;
+ }
+
+ function test_end_bs(e) {
+ const msg = "Deleting all text in contenteditable inline element";
+ var before = e.parentNode.childNodes[0].nodeValue;
+ sendKey("right");
+ sendKey("back_space");
+ sendKey("back_space");
+ is(e.parentNode.childNodes[0].nodeValue, before, msg + " with id=" + e.id);
+ is(e.innerHTML, "", msg + " with id=" + e.id);
+ }
+
+ test_end_bs(select("t1"));
+ test_end_bs(setupIframe('i1',0));
+
+ {
+ const msg = "Deleting all text in contenteditable body element";
+ var e = document.getElementById('i2');
+ var doc = e.contentDocument;
+ doc.body.setAttribute("contenteditable", "true");
+ doc.body.focus();
+ sendKey("right");
+ sendKey("back_space");
+ is(doc.body.innerHTML, "<br>", msg + " with id=" + e.id);
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=456244">Mozilla Bug 456244</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<div> <span id="t1" style="border:1px solid blue" contenteditable="true">X</span> Y</div>
+
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i2" width="200" height="100" src="about:blank">X</iframe><br>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug460740.html b/editor/libeditor/tests/test_bug460740.html
new file mode 100644
index 000000000..b9e79c1e0
--- /dev/null
+++ b/editor/libeditor/tests/test_bug460740.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=460740
+-->
+<head>
+ <title>Test for Bug 460740</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=460740">Mozilla Bug 460740</a>
+<p id="display"></p>
+<div id="content">
+ <ul>
+ <li contenteditable>
+ Editable LI
+ </li>
+ <li>
+ <div contenteditable>
+ Editable DIV inside LI
+ </div>
+ </li>
+ <li>
+ <div>
+ <div contenteditable>
+ Editable DIV inside DIV inside LI
+ </div>
+ </div>
+ </li>
+ <li>
+ <h3>
+ <div contenteditable>
+ Editable DIV inside H3 inside LI
+ </div>
+ </h3>
+ </li>
+ </ul>
+ <div contenteditable>
+ Editable DIV
+ </div>
+ <h3 contenteditable>
+ Editable H3
+ </h3>
+ <p contenteditable>
+ Editable P
+ </p>
+ <div>
+ <p contenteditable>
+ Editable P in a DIV
+ </p>
+ </div>
+ <p><span contenteditable>Editable SPAN in a P</span></p>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 460740 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+const CARET_BEGIN = 0;
+const CARET_MIDDLE = 1;
+const CARET_END = 2;
+
+function split(element, caretPos) {
+ // compute the requested position
+ var len = element.textContent.length;
+ var pos = -1;
+ switch (caretPos) {
+ case CARET_BEGIN:
+ pos = 0;
+ break;
+ case CARET_MIDDLE:
+ pos = Math.floor(len/2);
+ break;
+ case CARET_END:
+ pos = len;
+ break;
+ }
+
+ // put the caret on the requested position
+ var range = document.createRange();
+ var sel = window.getSelection();
+ range.setStart(element.firstChild, pos);
+ range.setEnd(element.firstChild, pos);
+ sel.addRange(range);
+
+ // simulates a [Return] keypress
+ synthesizeKey("VK_RETURN", {});
+}
+
+// count the number of non-BR elements in #content
+function getBlockCount() {
+ return document.querySelectorAll("#content *:not(br)").length;
+}
+
+// count the number of BRs in element
+function checkBR(element) {
+ return element.querySelectorAll("br").length;
+}
+
+function runTests() {
+ var count = getBlockCount();
+ var nodes = document.querySelectorAll("#content [contenteditable]");
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ node.focus();
+ is(checkBR(node), 0, node.textContent.trim() + ": This node should not have any <br> element yet.");
+ for (var j = 0; j < 3; j++) { // CARET_BEGIN|MIDDLE|END
+ split(node, j);
+ ok(checkBR(node) > 0, node.textContent.trim() + " " + j + ": Pressing [Return] should add (at least) one <br> element.");
+ is(getBlockCount(), count, node.textContent.trim() + " " + j + ": Pressing [Return] should not change the number of non-<br> elements.");
+ document.execCommand("Undo", false, null);
+ }
+ }
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug46555.html b/editor/libeditor/tests/test_bug46555.html
new file mode 100644
index 000000000..3838bdb3b
--- /dev/null
+++ b/editor/libeditor/tests/test_bug46555.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=46555
+-->
+
+<head>
+ <title>Test for Bug 46555</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=46555">Mozilla Bug 46555</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <input type="text" value="" id="t1" />
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 46555 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ const kCmd = "cmd_selectAll";
+
+ var input = document.getElementById("t1");
+ input.focus();
+ var controller =
+ SpecialPowers.wrap(input).controllers.getControllerForCommand(kCmd);
+
+ // Test 1: Select All should be disabled if editor is empty
+ is(controller.isCommandEnabled(kCmd), false,
+ "Select All command disabled when editor is empty");
+
+ SimpleTest.finish();
+ });
+ </script>
+ </pre>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug468353.html b/editor/libeditor/tests/test_bug468353.html
new file mode 100644
index 000000000..179c41cdc
--- /dev/null
+++ b/editor/libeditor/tests/test_bug468353.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=468353
+-->
+<head>
+ <title>Test for Bug 468353</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468353">Mozilla Bug 468353</a>
+<p id="display"></p>
+<div id="content">
+ <iframe></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var styleSheets = null;
+
+function checkStylesheets() {
+ // Evidently RemoveStyleSheet is the only method in nsIEditorStyleSheets
+ // that would throw. RemoveOverrideStyleSheet returns NS_OK even if the
+ // sheet is not there
+ var removed = 0;
+ try
+ {
+ styleSheets.removeStyleSheet("resource://gre/res/designmode.css");
+ removed++;
+ }
+ catch (ex) { }
+
+ try {
+ styleSheets.removeStyleSheet("resource://gre/res/contenteditable.css");
+ removed++;
+ }
+ catch (ex) { }
+
+ is(removed, 0, "Should have thrown if stylesheet was not there");
+}
+
+function runTest() {
+ const Ci = SpecialPowers.Ci;
+
+ /** Found while fixing bug 440614 **/
+ var editframe = window.frames[0];
+ var editdoc = editframe.document;
+ var editor = null;
+ editdoc.write('');
+ editdoc.close();
+
+ editdoc.designMode='on';
+
+ // Hold the reference to the editor
+ editor = SpecialPowers.wrap(editframe)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession)
+ .getEditorForWindow(editframe);
+
+ styleSheets = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+
+ editdoc.designMode='off';
+
+ checkStylesheets();
+
+ // Let go
+ editor = null;
+ styleSheets = null;
+
+ editdoc.body.contentEditable = true;
+
+ // Hold the reference to the editor
+ editor = SpecialPowers.wrap(editframe)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession)
+ .getEditorForWindow(editframe);
+
+ styleSheets = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+
+ editdoc.body.contentEditable = false;
+
+ checkStylesheets();
+
+ editdoc.designMode = "on";
+ editdoc.body.contentEditable = true;
+ editdoc.designMode = "off";
+
+ // Hold the reference to the editor
+ editor = SpecialPowers.wrap(editframe)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession)
+ .getEditorForWindow(editframe);
+
+ styleSheets = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+
+ editdoc.body.contentEditable = false;
+
+ checkStylesheets();
+
+ SimpleTest.finish();
+}
+
+//XXX I don't know if this is necessary, but we're dealing with iframes...
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug471319.html b/editor/libeditor/tests/test_bug471319.html
new file mode 100644
index 000000000..399ba4611
--- /dev/null
+++ b/editor/libeditor/tests/test_bug471319.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471319
+-->
+
+<head>
+ <title>Test for Bug 471319</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body onload="doTest();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471319">Mozilla Bug 471319</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript;version=1.7">
+
+ /** Test for Bug 471319 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ let t1 = SpecialPowers.wrap($("t1"));
+ let editor = null;
+
+ // Test 1: Undo on an empty editor - the editor should not forget about
+ // the bogus node
+ t1.QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement);
+ t1Editor = t1.editor;
+
+ // Did the editor recognize the new bogus node?
+ t1Editor.undo(1);
+ ok(!t1.value, "<br> still recognized as bogus node on undo");
+
+
+ // Test 2: Redo on an empty editor - the editor should not forget about
+ // the bogus node
+ let t2 = SpecialPowers.wrap($("t2"));
+ t2.QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement);
+ t2Editor = t2.editor;
+
+ // Did the editor recognize the new bogus node?
+ t2Editor.redo(1);
+ ok(!t2.value, "<br> still recognized as bogus node on redo");
+
+
+ // Test 3: Undoing a batched transaction where both end points of the
+ // transaction are the bogus node - the bogus node should still be
+ // recognized as bogus
+ t1Editor.transactionManager.beginBatch(null);
+ t1.value = "mozilla";
+ t1.value = "";
+ t1Editor.transactionManager.endBatch(false);
+ t1Editor.undo(1);
+ ok(!t1.value,
+ "recreated <br> from undo transaction recognized as bogus");
+
+
+ // Test 4: Redoing a batched transaction where both end points of the
+ // transaction are the bogus node - the bogus node should still be
+ // recognized as bogus
+ t1Editor.redo(1);
+ ok(!t1.value,
+ "recreated <br> from redo transaction recognized as bogus");
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <input type="text" id="t1" />
+ <input type="text" id="t2" />
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug471722.html b/editor/libeditor/tests/test_bug471722.html
new file mode 100644
index 000000000..74ff55307
--- /dev/null
+++ b/editor/libeditor/tests/test_bug471722.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471722
+-->
+
+<head>
+ <title>Test for Bug 471722</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body onload="doTest();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=471722">Mozilla Bug 471722</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 471722 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var t1 = $("t1");
+ var editor = null;
+
+ if (t1 instanceof SpecialPowers.Ci.nsIDOMNSEditableElement)
+ editor = SpecialPowers.wrap(t1).editor;
+ ok(editor, "able to get editor for the element");
+ t1.focus();
+ t1.select();
+
+ try {
+
+ // Cut the initial text in the textbox
+ ok(editor.canCut(), "can cut text");
+ editor.cut();
+ is(t1.value, "", "initial text was removed");
+
+ // So now we will have emptied the textfield
+ // and the editor will have created a bogus node
+ // Check the transaction is in the undo stack...
+ var t1Enabled = {};
+ var t1CanUndo = {};
+ editor.canUndo(t1Enabled, t1CanUndo);
+ ok(t1CanUndo.value, "undo is enabled");
+
+ // Undo the cut
+ editor.undo(1);
+ is(t1.value, "minefield", "text reinserted");
+
+ // So now, the cut should be in the redo stack,
+ // so executing the redo will clear the text once again
+ // and reinsert the bogus node that was removed after undo.
+ // This will require the editor to figure out that we have a
+ // bogus node again...
+ var t1CanRedo = {};
+ editor.canRedo(t1Enabled, t1CanRedo);
+ ok(t1CanRedo.value, "redo is enabled");
+ editor.redo(1);
+
+ // Did the editor notice a bogus node reappeared?
+ is(t1.value, "", "editor found bogus node");
+ } catch (e) {
+ ok(false, "test failed with error "+e);
+ }
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <input type="text" value="minefield" id="t1" />
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug478725.html b/editor/libeditor/tests/test_bug478725.html
new file mode 100644
index 000000000..8df85dfff
--- /dev/null
+++ b/editor/libeditor/tests/test_bug478725.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 478725</title>
+<style src="/tests/SimpleTest/test.css" type="text/css"></style>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+
+function runTest() {
+ function verifyContent(s) {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ is(doc.body.innerHTML, s, "");
+ }
+
+ function pasteInto(html,target_id) {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ doc.designMode = "on";
+ doc.body.innerHTML = html;
+ e = doc.getElementById(target_id);
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(e);
+ selection.collapseToEnd();
+ SpecialPowers.wrap(doc).execCommand("paste", false, null);
+ return e;
+ }
+
+ function copyToClipBoard(s,asHTML,target_id) {
+ var e = document.getElementById('i2');
+ var doc = e.contentDocument;
+ if (asHTML) {
+ doc.body.innerHTML = s;
+ } else {
+ var text = doc.createTextNode(s);
+ doc.body.appendChild(text);
+ }
+ doc.designMode = "on";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ if (!target_id) {
+ selection.selectAllChildren(doc.body);
+ } else {
+ var range = document.createRange();
+ range.selectNode(doc.getElementById(target_id));
+ selection.addRange(range);
+ }
+ SpecialPowers.wrap(doc).execCommand("copy", false, null);
+ return e;
+ }
+
+ copyToClipBoard("<dl><dd>Hello Kitty</dd></dl>", true);
+ pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X<dl><dd>Hello Kitty</dd></dl></li></ol>');
+
+ copyToClipBoard("<li>Hello Kitty</li>", true);
+ pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X</li><li>Hello Kitty</li></ol>');
+
+ copyToClipBoard("<ol><li>Hello Kitty</li></ol>", true);
+ pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X</li><li>Hello Kitty</li></ol>');
+
+ copyToClipBoard("<ul><li>Hello Kitty</li></ul>", true);
+ pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X</li><li>Hello Kitty</li></ol>');
+
+ copyToClipBoard("<ul><li>Hello</li><ul><li>Kitty</li></ul></ul>", true);
+ pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X</li><li>Hello</li><ul><li>Kitty</li></ul></ol>');
+
+ copyToClipBoard("<dl><dd>Hello</dd><dd>Kitty</dd></dl>", true);
+ pasteInto('<dl><dd id="paste_here">X</dd></dl>',"paste_here");
+ verifyContent('<dl><dd id="paste_here">X</dd><dd>Hello</dd><dd>Kitty</dd></dl>');
+
+ copyToClipBoard("<dl><dd>Hello</dd><dd>Kitty</dd></dl>", true);
+ pasteInto('<dl><dt id="paste_here">X</dt></dl>',"paste_here");
+ verifyContent('<dl><dt id="paste_here">X</dt><dd>Hello</dd><dd>Kitty</dd></dl>');
+
+ copyToClipBoard("<dl><dt>Hello</dt><dd>Kitty</dd></dl>", true);
+ pasteInto('<dl><dd id="paste_here">X</dd></dl>',"paste_here");
+ verifyContent('<dl><dd id="paste_here">X</dd><dt>Hello</dt><dd>Kitty</dd></dl>');
+
+ copyToClipBoard("<pre>Kitty</pre>", true);
+ pasteInto('<pre id="paste_here">Hello </pre>',"paste_here");
+ verifyContent('<pre id="paste_here">Hello Kitty</pre>');
+
+// I was expecting these to trigger the special TABLE/TR rules in nsHTMLEditor::InsertHTMLWithContext
+// but they don't for some reason...
+// copyToClipBoard('<table><tr id="copy_here"><td>Kitty</td></tr></table>', true, "copy_here");
+// pasteInto('<table><tr id="paste_here"><td>Hello</td></tr></table>',"paste_here");
+// verifyContent('');
+//
+// copyToClipBoard('<table id="copy_here"><tr><td>Kitty</td></tr></table>', true, "copy_here");
+// pasteInto('<table><tr id="paste_here"><td>Hello</td></tr></table>',"paste_here");
+// verifyContent('');
+//
+// copyToClipBoard('<table id="copy_here"><tr><td>Kitty</td></tr></table>', true, "copy_here");
+// pasteInto('<table id="paste_here"><tr><td>Hello</td></tr></table>',"paste_here");
+// verifyContent('');
+//
+// copyToClipBoard('<table><tr id="copy_here"><td>Kitty</td></tr></table>', true, "copy_here");
+// pasteInto('<table id="paste_here"><tr><td>Hello</td></tr></table>',"paste_here");
+// verifyContent('');
+
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478725">Mozilla Bug 478725</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i2" width="200" height="100" src="about:blank"></iframe><br>
+
+</body>
+</html>
+
diff --git a/editor/libeditor/tests/test_bug480647.html b/editor/libeditor/tests/test_bug480647.html
new file mode 100644
index 000000000..33f088a1b
--- /dev/null
+++ b/editor/libeditor/tests/test_bug480647.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=480647
+-->
+<title>Test for Bug 480647</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=480647">Mozilla Bug 480647</a>
+<div contenteditable></div>
+<script>
+/** Test for Bug 480647 **/
+
+var div = document.querySelector("div");
+
+function parseFontSize(input, expected) {
+ parseFontSizeInner(input, expected, is);
+}
+
+function parseFontSizeTodo(input, expected) {
+ parseFontSizeInner(input, expected, todo_is);
+}
+
+function parseFontSizeInner(input, expected, fn) {
+ div.innerHTML = "foo";
+ getSelection().selectAllChildren(div);
+ document.execCommand("fontSize", false, input);
+ if (expected === null) {
+ fn(div.innerHTML, "foo",
+ 'execCommand("fontSize", false, "' + input + '") should be no-op');
+ } else {
+ fn(div.innerHTML, '<font size="' + expected + '">foo</font>',
+ 'execCommand("fontSize", false, "' + input + '") should parse to ' +
+ expected);
+ }
+}
+
+// Parse errors
+parseFontSize("", null);
+parseFontSize("abc", null);
+parseFontSize("larger", null);
+parseFontSize("smaller", null);
+parseFontSize("xx-small", null);
+parseFontSize("x-small", null);
+parseFontSize("small", null);
+parseFontSize("medium", null);
+parseFontSize("large", null);
+parseFontSize("x-large", null);
+parseFontSize("xx-large", null);
+parseFontSize("xxx-large", null);
+// Bug 747879
+parseFontSizeTodo("1.2em", null);
+parseFontSizeTodo("8px", null);
+parseFontSizeTodo("-1.2em", null);
+parseFontSizeTodo("-8px", null);
+parseFontSizeTodo("+1.2em", null);
+parseFontSizeTodo("+8px", null);
+
+// Numbers
+parseFontSize("0", 1);
+parseFontSize("1", 1);
+parseFontSize("2", 2);
+parseFontSize("3", 3);
+parseFontSize("4", 4);
+parseFontSize("5", 5);
+parseFontSize("6", 6);
+parseFontSize("7", 7);
+parseFontSize("8", 7);
+parseFontSize("9", 7);
+parseFontSize("10", 7);
+parseFontSize("1000000000000000000000", 7);
+parseFontSize("2.72", 2);
+parseFontSize("2.72e9", 2);
+
+// Minus sign
+parseFontSize("-0", 3);
+parseFontSize("-1", 2);
+parseFontSize("-2", 1);
+parseFontSize("-3", 1);
+parseFontSize("-4", 1);
+parseFontSize("-5", 1);
+parseFontSize("-6", 1);
+parseFontSize("-7", 1);
+parseFontSize("-8", 1);
+parseFontSize("-9", 1);
+parseFontSize("-10", 1);
+parseFontSize("-1000000000000000000000", 1);
+parseFontSize("-1.72", 2);
+parseFontSize("-1.72e9", 2);
+
+// Plus sign
+parseFontSize("+0", 3);
+parseFontSize("+1", 4);
+parseFontSize("+2", 5);
+parseFontSize("+3", 6);
+parseFontSize("+4", 7);
+parseFontSize("+5", 7);
+parseFontSize("+6", 7);
+parseFontSize("+7", 7);
+parseFontSize("+8", 7);
+parseFontSize("+9", 7);
+parseFontSize("+10", 7);
+parseFontSize("+1000000000000000000000", 7);
+parseFontSize("+1.72", 4);
+parseFontSize("+1.72e9", 4);
+
+// Whitespace
+parseFontSize(" \t\n\r\f5 \t\n\r\f", 5);
+parseFontSize("\u00a05", null);
+parseFontSize("\b5", null);
+</script>
diff --git a/editor/libeditor/tests/test_bug480972.html b/editor/libeditor/tests/test_bug480972.html
new file mode 100644
index 000000000..3eed97100
--- /dev/null
+++ b/editor/libeditor/tests/test_bug480972.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 480972</title>
+<style src="/tests/SimpleTest/test.css" type="text/css"></style>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+
+function runTest() {
+ function verifyContent(s) {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ is(doc.body.innerHTML, s, "");
+ }
+
+ function pasteInto(html,target_id) {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ doc.designMode = "on";
+ doc.body.innerHTML = html;
+ doc.defaultView.focus();
+ if (target_id)
+ e = doc.getElementById(target_id);
+ else
+ e = doc.body;
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(e);
+ selection.collapseToEnd();
+ SpecialPowers.wrap(doc).execCommand("paste", false, null);
+ return e;
+ }
+
+ function copyToClipBoard(s,asHTML,target_id) {
+ var e = document.getElementById('i2');
+ var doc = e.contentDocument;
+ if (asHTML) {
+ doc.body.innerHTML = s;
+ } else {
+ var text = doc.createTextNode(s);
+ doc.body.appendChild(text);
+ }
+ doc.designMode = "on";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ if (!target_id) {
+ selection.selectAllChildren(doc.body);
+ } else {
+ var range = document.createRange();
+ range.selectNode(doc.getElementById(target_id));
+ selection.addRange(range);
+ }
+ SpecialPowers.wrap(doc).execCommand("copy", false, null);
+ return e;
+ }
+
+ copyToClipBoard('<span>Hello</span><span>Kitty</span>', true);
+ pasteInto('');
+ verifyContent('<span>Hello</span><span>Kitty</span>');
+
+ copyToClipBoard("<dl><dd>Hello Kitty</dd></dl><span>Hello</span><span>Kitty</span>", true);
+ pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X<dl><dd>Hello Kitty</dd></dl><span>Hello</span><span>Kitty</span></li></ol>');
+
+// The following test doesn't do what I expected, because the special handling
+// of IsList nodes in nsHTMLEditor::InsertHTMLWithContext simply removes
+// non-list/item children. See bug 481177.
+// copyToClipBoard("<ol><li>Hello Kitty</li><span>Hello</span></ol>", true);
+// pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+// verifyContent('<ol><li id="paste_here">X</li><li>Hello Kitty</li><span>Hello</span></ol>');
+
+ copyToClipBoard("<pre>Kitty</pre><span>Hello</span>", true);
+ pasteInto('<pre id="paste_here">Hello </pre>',"paste_here");
+ verifyContent('<pre id="paste_here">Hello Kitty<span>Hello</span></pre>');
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=480972">Mozilla Bug 480972</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i2" width="200" height="100" src="about:blank"></iframe><br>
+
+</body>
+</html>
+
diff --git a/editor/libeditor/tests/test_bug483651.html b/editor/libeditor/tests/test_bug483651.html
new file mode 100644
index 000000000..ee256b807
--- /dev/null
+++ b/editor/libeditor/tests/test_bug483651.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483651
+-->
+
+<head>
+ <title>Test for Bug 483651</title>
+ <script src="/MochiKit/packed.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body onload="doTest();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=483651">Mozilla Bug 483651</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 483651 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var t1 = $("t1");
+ var editor = SpecialPowers.wrap(t1).editor;
+
+ ok(editor, "able to get editor for the element");
+ t1.focus();
+ synthesizeKey("A", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ try {
+ // Was the trailing br removed?
+ is(editor.documentIsEmpty, true, "trailing <br> correctly removed");
+ } catch (e) {
+ ok(false, "test failed with error "+e);
+ }
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <textarea id="t1" rows="2" columns="80"></textarea>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug484181.html b/editor/libeditor/tests/test_bug484181.html
new file mode 100644
index 000000000..55cd8e806
--- /dev/null
+++ b/editor/libeditor/tests/test_bug484181.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=484181
+-->
+<head>
+ <title>Test for Bug 484181</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="spellcheck.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=484181">Mozilla Bug 484181</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 484181 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+var gMisspeltWords;
+
+function getEditor() {
+ var Ci = SpecialPowers.Ci;
+ var win = window;
+ var editingSession = SpecialPowers.wrap(win).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(win);
+}
+
+function append(str) {
+ var edit = document.getElementById("edit");
+ var editor = getEditor();
+ var sel = editor.selection;
+ sel.selectAllChildren(edit);
+ sel.collapseToEnd();
+
+ for (var i = 0; i < str.length; ++i) {
+ synthesizeKey(str[i], {});
+ }
+}
+
+function runTest() {
+ gMisspeltWords = ["haz", "cheezburger"];
+ var edit = document.getElementById("edit");
+ edit.focus();
+
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+ onSpellCheck(edit, function () {
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings before editing are accounted for.");
+
+ append(" becaz I'm a lulcat!");
+ onSpellCheck(edit, function () {
+ gMisspeltWords.push("becaz");
+ gMisspeltWords.push("lulcat");
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings after typing are accounted for.");
+
+ SimpleTest.finish();
+ });
+ });
+}
+</script>
+</pre>
+
+<div><div></div><div id="edit" contenteditable="true">I can haz cheezburger</div></div>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug487524.html b/editor/libeditor/tests/test_bug487524.html
new file mode 100644
index 000000000..d4972ba91
--- /dev/null
+++ b/editor/libeditor/tests/test_bug487524.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 487524</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+function runTest() {
+
+ function setupIframe(e,html,focus_id) {
+ var doc = e.contentDocument;
+ doc.body.innerHTML = html;
+ doc.designMode = "on";
+ e = doc.getElementById(focus_id);
+ doc.defaultView.focus();
+ if (e) e.focus();
+ return e;
+ }
+
+ var i1 = document.getElementById('i1')
+ var li1 = setupIframe(i1,'<ul><li id="li1">one</li><li>two</li><ul><li>a</li></ul></ul>','li1')
+ var doc = li1.ownerDocument;
+
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+
+ var range = doc.createRange();
+ range.setStart(li1,0);
+ range.setEnd(li1.nextSibling,0);
+ selection.addRange(range);
+
+ sendKey('delete');
+ is(doc.body.innerHTML,'<ul><li>two</li><ul><li>a</li></ul></ul>','delete 1st LI');
+
+ var li2 = setupIframe(i1,'<ul><li id="li2">two</li><ul><li>a</li></ul></ul>','li2')
+ selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+
+ range = doc.createRange();
+ range.setStart(li2,0);
+ range.setEnd(li2.nextSibling.firstChild,0);
+ selection.addRange(range);
+
+ sendKey('delete');
+ is(doc.body.innerHTML,'<ul><ul><li>a</li></ul></ul>','delete 2nd LI');
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=487524">Mozilla Bug 487524</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe><br>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug489202.xul b/editor/libeditor/tests/test_bug489202.xul
new file mode 100644
index 000000000..30e2a730d
--- /dev/null
+++ b/editor/libeditor/tests/test_bug489202.xul
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=489202
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 489202" onload="runTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=489202"
+ target="_blank">Mozilla Bug 489202</a>
+ <p/>
+ <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="i1"
+ type="content"
+ editortype="htmlmail"
+ style="width: 400px; height: 100px;"/>
+ <p/>
+ <pre id="test">
+ </pre>
+ </body>
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function runTest() {
+ var trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/html");
+ var test_data = '<meta/><a href="http://mozilla.org/">mozilla.org</a>';
+ var cstr = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ cstr.data = test_data;
+ trans.setTransferData("text/html", cstr, test_data.length*2);
+
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Components.interfaces.nsIDocShell)
+ .appType = Components.interfaces.nsIDocShell.APP_TYPE_EDITOR;
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ doc.designMode = "on";
+ doc.body.innerHTML = "";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(doc.body);
+ selection.collapseToEnd();
+
+ var point = doc.defaultView.getSelection().getRangeAt(0).startOffset;
+ ok(point==0, "Cursor should be at editor start before paste");
+
+ utils.sendContentCommandEvent("pasteTransferable", trans);
+
+ point = doc.defaultView.getSelection().getRangeAt(0).startOffset;
+ ok(point>0, "Cursor should not be at editor start after paste");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+]]>
+</script>
+</window>
diff --git a/editor/libeditor/tests/test_bug490879.html b/editor/libeditor/tests/test_bug490879.html
new file mode 100644
index 000000000..1e412a7d6
--- /dev/null
+++ b/editor/libeditor/tests/test_bug490879.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<title>Mozilla Bug 490879</title>
+<link rel=stylesheet href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=490879"
+ target="_blank">Mozilla Bug 490879</a>
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe>
+<img id="i" src="green.png">
+<script>
+function runTest() {
+ function verifyContent() {
+ const kExpectedImgSpec = "data:image/png;base64,";
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ is(doc.getElementsByTagName("img")[0].src.substring(0, kExpectedImgSpec.length),
+ kExpectedImgSpec, "The pasted image is a base64-encoded data: URI");
+ }
+
+ function pasteInto() {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ doc.designMode = "on";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(doc.body);
+ selection.collapseToEnd();
+ SpecialPowers.doCommand(window, "cmd_paste");
+ }
+
+ function copyToClipBoard() {
+ SpecialPowers.setCommandNode(window, document.getElementById("i"));
+ SpecialPowers.doCommand(window, "cmd_copyImageContents");
+ }
+
+ copyToClipBoard();
+ pasteInto();
+ verifyContent();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
diff --git a/editor/libeditor/tests/test_bug502673.html b/editor/libeditor/tests/test_bug502673.html
new file mode 100644
index 000000000..3bee4554a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug502673.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=502673
+-->
+
+<head>
+ <title>Test for Bug 502673</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body onload="doTest();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=502673">Mozilla Bug 502673</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 502673 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function listener() {
+ }
+
+ listener.prototype =
+ {
+ NotifyDocumentWillBeDestroyed: function () {
+ if (this.input instanceof SpecialPowers.Ci.nsIDOMNSEditableElement) {
+ var editor = SpecialPowers.wrap(this.input).editor;
+ editor.removeDocumentStateListener(this);
+ }
+ },
+
+ NotifyDocumentCreated: function () {
+ },
+
+ NotifyDocumentStateChanged: function (aNowDirty) {
+ if (this.input instanceof SpecialPowers.Ci.nsIDOMNSEditableElement) {
+ var editor = SpecialPowers.wrap(this.input).editor;
+ editor.removeDocumentStateListener(this);
+ }
+ },
+
+ QueryInterface: SpecialPowers.wrapCallback(function(iid) {
+ if (iid.equals(SpecialPowers.Ci.nsIDocumentStateListener) ||
+ iid.equals(SpecialPowers.Ci.nsISupports))
+ return this;
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ }),
+ };
+
+ function doTest() {
+ var input = document.getElementById("ip");
+ if (input instanceof SpecialPowers.Ci.nsIDOMNSEditableElement) {
+ // Add multiple listeners to the same editor
+ var editor = SpecialPowers.wrap(input).editor;
+ var listener1 = new listener();
+ listener1.input = input;
+ var listener2 = new listener();
+ listener2.input = input;
+ var listener3 = new listener();
+ listener3.input = input;
+ editor.addDocumentStateListener(listener1);
+ editor.addDocumentStateListener(listener2);
+ editor.addDocumentStateListener(listener3);
+
+ // Test 1. Fire NotifyDocumentStateChanged notifications where the
+ // listeners remove themselves
+ input.value = "mozilla";
+ editor.undo(1);
+
+ // Report success if we get here - clearly we didn't crash
+ ok(true, "Multiple listeners removed themselves after " +
+ "NotifyDocumentStateChanged notifications - didn't crash");
+
+ // Add the listeners again for the next test
+ editor.addDocumentStateListener(listener1);
+ editor.addDocumentStateListener(listener2);
+ editor.addDocumentStateListener(listener3);
+
+ }
+
+ // Test 2. Fire NotifyDocumentWillBeDestroyed notifications where the
+ // listeners remove themselves (though in the real world, listeners
+ // shouldn't do this as nsEditor::PreDestroy removes them as
+ // listeners anyway)
+ document.body.removeChild(input);
+ ok(true, "Multiple listeners removed themselves after " +
+ "NotifyDocumentWillBeDestroyed notifications - didn't crash");
+
+ // TODO: Test for NotifyDocumentCreated
+
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <input type="text" id="ip" />
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug514156.html b/editor/libeditor/tests/test_bug514156.html
new file mode 100644
index 000000000..3594d1c8d
--- /dev/null
+++ b/editor/libeditor/tests/test_bug514156.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514156
+-->
+<head>
+ <title>Test for Bug 514156</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=514156">Mozilla Bug 514156</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="input1">
+<input type="text" id="input2">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 514156 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var input1 = $("input1");
+ input1.focus();
+ synthesizeKey("\u200e", { });
+ synthesizeKey("\u05d0", { });
+ synthesizeKey("\u05d1", { });
+ is(escape(input1.value), escape("\u200e\u05d0\u05d1"), "non-spacing character and direction change shouldn't change content");
+
+ var input2 = $("input2");
+ input2.focus();
+ synthesizeKey("\u05b6", { });
+ synthesizeKey("a", { });
+ synthesizeKey("b", { });
+ synthesizeKey("c", { });
+ is(escape(input2.value), escape("\u05b6abc"), "non-spacing character and direction change shouldn't change content");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/editor/libeditor/tests/test_bug520189.html b/editor/libeditor/tests/test_bug520189.html
new file mode 100644
index 000000000..d1b429000
--- /dev/null
+++ b/editor/libeditor/tests/test_bug520189.html
@@ -0,0 +1,621 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=520182
+-->
+<head>
+ <title>Test for Bug 520182</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=520182">Mozilla Bug 520182</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="a" src="about:blank"></iframe>
+ <iframe id="b" src="about:blank"></iframe>
+ <iframe id="c" src="about:blank"></iframe>
+ <div id="d" contenteditable="true"></div>
+ <div id="e" contenteditable="true"></div>
+ <div id="f" contenteditable="true"></div>
+ <iframe id="g" src="about:blank"></iframe>
+ <iframe id="h" src="about:blank"></iframe>
+ <div id="i" contenteditable="true"></div>
+ <div id="j" contenteditable="true"></div>
+ <iframe id="k" src="about:blank"></iframe>
+ <div id="l" contenteditable="true"></div>
+ <iframe id="m" src="about:blank"></iframe>
+ <div id="n" contenteditable="true"></div>
+ <iframe id="o" src="about:blank"></iframe>
+ <div id="p" contenteditable="true"></div>
+ <iframe id="q" src="about:blank"></iframe>
+ <div id="r" contenteditable="true"></div>
+ <iframe id="s" src="about:blank"></iframe>
+ <div id="t" contenteditable="true"></div>
+ <iframe id="u" src="about:blank"></iframe>
+ <div id="v" contenteditable="true"></div>
+ <iframe id="w" src="about:blank"></iframe>
+ <div id="x" contenteditable="true"></div>
+ <iframe id="y" src="about:blank"></iframe>
+ <div id="z" contenteditable="true"></div>
+ <iframe id="aa" src="about:blank"></iframe>
+ <div id="bb" contenteditable="true"></div>
+ <iframe id="cc" src="about:blank"></iframe>
+ <div id="dd" contenteditable="true"></div>
+ <iframe id="ee" src="about:blank"></iframe>
+ <div id="ff" contenteditable="true"></div>
+ <iframe id="gg" src="about:blank"></iframe>
+ <div id="hh" contenteditable="true"></div>
+ <iframe id="ii" src="about:blank"></iframe>
+ <div id="jj" contenteditable="true"></div>
+ <iframe id="kk" src="about:blank"></iframe>
+ <div id="ll" contenteditable="true"></div>
+ <iframe id="mm" src="about:blank"></iframe>
+ <div id="nn" contenteditable="true"></div>
+ <iframe id="oo" src="about:blank"></iframe>
+ <div id="pp" contenteditable="true"></div>
+ <iframe id="qq" src="about:blank"></iframe>
+ <div id="rr" contenteditable="true"></div>
+ <iframe id="ss" src="about:blank"></iframe>
+ <div id="tt" contenteditable="true"></div>
+ <iframe id="uu" src="about:blank"></iframe>
+ <div id="vv" contenteditable="true"></div>
+ <div id="sss" contenteditable="true"></div>
+ <iframe id="ssss" src="about:blank"></iframe>
+ <div id="ttt" contenteditable="true"></div>
+ <iframe id="tttt" src="about:blank"></iframe>
+ <div id="uuu" contenteditable="true"></div>
+ <iframe id="uuuu" src="about:blank"></iframe>
+ <div id="vvv" contenteditable="true"></div>
+ <iframe id="vvvv" src="about:blank"></iframe>
+ <div id="www" contenteditable="true"></div>
+ <iframe id="wwww" src="about:blank"></iframe>
+ <div id="xxx" contenteditable="true"></div>
+ <iframe id="xxxx" src="about:blank"></iframe>
+ <div id="yyy" contenteditable="true"></div>
+ <iframe id="yyyy" src="about:blank"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 520182 **/
+
+const dataPayload = "foo<iframe src=\"data:text/html,bar\"></iframe>baz";
+const jsPayload = "foo<iframe src=\"javascript:void('bar');\"></iframe>baz";
+const httpPayload = "foo<iframe src=\"http://mochi.test:8888/\"></iframe>baz";
+const scriptPayload ="foo<script>document.write(\"<iframe></iframe>\");</sc" + "ript>baz";
+const scriptExternalPayload = "foo<script src=\"data:text/javascript,document.write('<iframe></iframe>');\"></sc" + "ript>baz";
+const validStyle1Payload = "foo<style>#bar{color:red;}</style>baz";
+const validStyle2Payload = "foo<span style=\"color:red\">bar</span>baz";
+const validStyle3Payload = "foo<style>@font-face{font-family:xxx;src:'xxx.ttf';}</style>baz";
+const validStyle4Payload = "foo<style>@namespace xxx url(http://example.com/);</style>baz";
+const invalidStyle1Payload = "foo<style>#bar{-moz-binding:url('data:text/xml,<?xml version=\"1.0\"><binding xmlns=\"http://www.mozilla.org/xbl\"/>');}</style>baz";
+const invalidStyle2Payload = "foo<span style=\"-moz-binding:url('data:text/xml,<?xml version=&quot;1.0&quot;><binding xmlns=&quot;http://www.mozilla.org/xbl&quot;/>');\">bar</span>baz";
+const invalidStyle3Payload = "foo<style>@import 'xxx.css';</style>baz";
+const invalidStyle4Payload = "foo<span style=\"@import 'xxx.css';\">bar</span>baz";
+const invalidStyle5Payload = "foo<span style=\"@font-face{font-family:xxx;src:'xxx.ttf';}\">bar</span>baz";
+const invalidStyle6Payload = "foo<span style=\"@namespace xxx url(http://example.com/);\">bar</span>baz";
+const invalidStyle7Payload = "<html><head><title>xxx</title></head><body>foo</body></html>";
+const invalidStyle8Payload = "foo<style>@-moz-document url(http://example.com/) {};</style>baz";
+const invalidStyle9Payload = "foo<style>@-moz-keyframes bar {};</style>baz";
+const nestedStylePayload = "foo<style>#bar1{-moz-binding:url('data:text/xml,<?xml version=&quot;1.0&quot;><binding xmlns=&quot;http://www.mozilla.org/xbl&quot; id=&quot;binding-1&quot;/>');<style></style>#bar2{-moz-binding:url('data:text/xml,<?xml version=&quot;1.0&quot;><binding xmlns=&quot;http://www.mozilla.org/xbl&quot; id=&quot;binding-2&quot;/>');</style>baz";
+const validImgSrc1Payload = "foo<img src=\"data:image/png,bar\">baz";
+const validImgSrc2Payload = "foo<img src=\"javascript:void('bar');\">baz";
+const validImgSrc3Payload = "foo<img src=\"file:///bar.png\">baz";
+const validDataFooPayload = "foo<span data-bar=\"value\">baz</span>";
+const validDataFoo2Payload = "foo<span _bar=\"value\">baz</span>";
+const svgPayload = "foo<svg><title>svgtitle</title></svg>bar";
+const svg2Payload = "foo<svg><bogussvg/></svg>bar";
+const mathPayload = "foo<math><bogusmath/></math>bar";
+const math2Payload = "foo<math><style>@import \"yyy.css\";</style</math>bar";
+const math3Payload = "foo<math><mi></mi></math>bar";
+const videoPayload = "foo<video></video>bar";
+const microdataPayload = "<head><meta name=foo content=bar><link rel=stylesheet href=url></head><body><meta itemprop=foo content=bar><link itemprop=bar href=url></body>";
+
+var tests = [
+ {
+ id: "a",
+ isIFrame: true,
+ payload: dataPayload,
+ iframeCount: 0,
+ rootElement() { return document.getElementById("a").contentDocument.documentElement; },
+ },
+ {
+ id: "b",
+ isIFrame: true,
+ payload: jsPayload,
+ iframeCount: 0,
+ rootElement() { return document.getElementById("b").contentDocument.documentElement; },
+ },
+ {
+ id: "c",
+ isIFrame: true,
+ payload: httpPayload,
+ iframeCount: 0,
+ rootElement() { return document.getElementById("c").contentDocument.documentElement; },
+ },
+ {
+ id: "g",
+ isIFrame: true,
+ payload: scriptPayload,
+ rootElement() { return document.getElementById("g").contentDocument.documentElement; },
+ iframeCount: 0
+ },
+ {
+ id: "h",
+ isIFrame: true,
+ payload: scriptExternalPayload,
+ rootElement() { return document.getElementById("h").contentDocument.documentElement; },
+ iframeCount: 0
+ },
+ {
+ id: "d",
+ payload: dataPayload,
+ iframeCount: 0,
+ rootElement() { return document.getElementById("d"); },
+ },
+ {
+ id: "e",
+ payload: jsPayload,
+ iframeCount: 0,
+ rootElement() { return document.getElementById("e"); },
+ },
+ {
+ id: "f",
+ payload: httpPayload,
+ iframeCount: 0,
+ rootElement() { return document.getElementById("f"); },
+ },
+ {
+ id: "i",
+ payload: scriptPayload,
+ rootElement() { return document.getElementById("i"); },
+ iframeCount: 0
+ },
+ {
+ id: "j",
+ payload: scriptExternalPayload,
+ rootElement() { return document.getElementById("j"); },
+ iframeCount: 0
+ },
+ {
+ id: "k",
+ isIFrame: true,
+ payload: validStyle1Payload,
+ rootElement() { return document.getElementById("k").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
+ },
+ {
+ id: "l",
+ payload: validStyle1Payload,
+ rootElement() { return document.getElementById("l"); },
+ checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
+ },
+ {
+ id: "m",
+ isIFrame: true,
+ payload: validStyle2Payload,
+ rootElement() { return document.getElementById("m").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
+ },
+ {
+ id: "n",
+ payload: validStyle2Payload,
+ rootElement() { return document.getElementById("n"); },
+ checkResult(html) { isnot(html.indexOf("style"), -1, "Should have retained style"); },
+ },
+ {
+ id: "o",
+ isIFrame: true,
+ payload: invalidStyle1Payload,
+ rootElement() { return document.getElementById("o").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
+ },
+ {
+ id: "p",
+ payload: invalidStyle1Payload,
+ rootElement() { return document.getElementById("p"); },
+ checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
+ },
+ {
+ id: "q",
+ isIFrame: true,
+ payload: invalidStyle2Payload,
+ rootElement() { return document.getElementById("q").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
+ },
+ {
+ id: "r",
+ payload: invalidStyle2Payload,
+ rootElement() { return document.getElementById("r"); },
+ checkResult(html) { is(html.indexOf("binding"), -1, "Should not have retained the binding style"); },
+ },
+ {
+ id: "s",
+ isIFrame: true,
+ payload: invalidStyle1Payload,
+ rootElement() { return document.getElementById("s").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
+ },
+ {
+ id: "t",
+ payload: invalidStyle1Payload,
+ rootElement() { return document.getElementById("t"); },
+ checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
+ },
+ {
+ id: "u",
+ isIFrame: true,
+ payload: invalidStyle2Payload,
+ rootElement() { return document.getElementById("u").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
+ },
+ {
+ id: "v",
+ payload: invalidStyle2Payload,
+ rootElement() { return document.getElementById("v"); },
+ checkResult(html) { is(html.indexOf("xxx"), -1, "Should not have retained the import style"); },
+ },
+ {
+ id: "w",
+ isIFrame: true,
+ payload: validStyle3Payload,
+ rootElement() { return document.getElementById("w").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style"); },
+ },
+ {
+ id: "x",
+ payload: validStyle3Payload,
+ rootElement() { return document.getElementById("x"); },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style"); },
+ },
+ {
+ id: "y",
+ isIFrame: true,
+ payload: invalidStyle5Payload,
+ rootElement() { return document.getElementById("y").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the font-face style"); },
+ },
+ {
+ id: "z",
+ payload: invalidStyle5Payload,
+ rootElement() { return document.getElementById("z"); },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the font-face style"); },
+ },
+ {
+ id: "aa",
+ isIFrame: true,
+ payload: nestedStylePayload,
+ rootElement() { return document.getElementById("aa").contentDocument.documentElement; },
+ checkResult: function(html, text) {
+ is(html.indexOf("binding-1"), -1, "Should not have retained the binding-1 style");
+ isnot(text.indexOf("#bar2"), -1, "Should have retained binding-2 as text content");
+ is(text.indexOf("binding-2"), -1, "Should not have retained binding-2 as a tag");
+ }
+ },
+ {
+ id: "bb",
+ payload: nestedStylePayload,
+ rootElement() { return document.getElementById("bb"); },
+ checkResult: function(html, text) {
+ is(html.indexOf("binding-1"), -1, "Should not have retained the binding-1 style");
+ isnot(text.indexOf("#bar2"), -1, "Should have retained binding-2 as text content");
+ is(text.indexOf("binding-2"), -1, "Should not have retained binding-2 as a tag");
+ }
+ },
+ {
+ id: "cc",
+ isIFrame: true,
+ payload: validStyle4Payload,
+ rootElement() { return document.getElementById("cc").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style"); },
+ },
+ {
+ id: "dd",
+ payload: validStyle4Payload,
+ rootElement() { return document.getElementById("dd"); },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style"); },
+ },
+ {
+ id: "ee",
+ isIFrame: true,
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("ee").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the namespace style"); },
+ },
+ {
+ id: "ff",
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("ff"); },
+ checkResult(html) { isnot(html.indexOf("xxx"), -1, "Should not have retained the namespace style"); },
+ },
+ {
+ id: "gg",
+ isIFrame: true,
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("gg").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
+ },
+ {
+ id: "hh",
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("hh"); },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
+ },
+ {
+ id: "ii",
+ isIFrame: true,
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("ii").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
+ },
+ {
+ id: "jj",
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("jj"); },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
+ },
+ {
+ id: "kk",
+ isIFrame: true,
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("kk").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
+ },
+ {
+ id: "ll",
+ payload: invalidStyle6Payload,
+ rootElement() { return document.getElementById("ll"); },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the src attribute for the image"); },
+ },
+ {
+ id: "mm",
+ isIFrame: true,
+ indirectPaste: true,
+ payload: invalidStyle7Payload,
+ rootElement() { return document.getElementById("mm").contentDocument.documentElement; },
+ checkResult: function(html) {
+ is(html.indexOf("xxx"), -1, "Should not have retained the title text");
+ isnot(html.indexOf("foo"), -1, "Should have retained the body text");
+ }
+ },
+ {
+ id: "nn",
+ indirectPaste: true,
+ payload: invalidStyle7Payload,
+ rootElement() { return document.getElementById("nn"); },
+ checkResult: function(html) {
+ is(html.indexOf("xxx"), -1, "Should not have retained the title text");
+ isnot(html.indexOf("foo"), -1, "Should have retained the body text");
+ }
+ },
+ {
+ id: "oo",
+ isIFrame: true,
+ payload: validDataFooPayload,
+ rootElement() { return document.getElementById("oo").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the data-bar attribute"); },
+ },
+ {
+ id: "pp",
+ payload: validDataFooPayload,
+ rootElement() { return document.getElementById("pp"); },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the data-bar attribute"); },
+ },
+ {
+ id: "qq",
+ isIFrame: true,
+ payload: validDataFoo2Payload,
+ rootElement() { return document.getElementById("qq").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the _bar attribute"); },
+ },
+ {
+ id: "rr",
+ payload: validDataFoo2Payload,
+ rootElement() { return document.getElementById("rr"); },
+ checkResult(html) { isnot(html.indexOf("bar"), -1, "Should have retained the _bar attribute"); },
+ },
+ {
+ id: "ss",
+ isIFrame: true,
+ payload: invalidStyle8Payload,
+ rootElement() { return document.getElementById("ss").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("@-moz-document"), -1, "Should not have retained the @-moz-document rule"); },
+ },
+ {
+ id: "tt",
+ payload: invalidStyle8Payload,
+ rootElement() { return document.getElementById("tt"); },
+ checkResult(html) { is(html.indexOf("@-moz-document"), -1, "Should not have retained the @-moz-document rule"); },
+ },
+ {
+ id: "uu",
+ isIFrame: true,
+ payload: invalidStyle9Payload,
+ rootElement() { return document.getElementById("uu").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("@-moz-keyframes"), -1, "Should not have retained the @-moz-keyframes rule"); },
+ },
+ {
+ id: "vv",
+ payload: invalidStyle9Payload,
+ rootElement() { return document.getElementById("vv"); },
+ checkResult(html) { is(html.indexOf("@-moz-keyframes"), -1, "Should not have retained the @-moz-keyframes rule"); },
+ },
+ {
+ id: "sss",
+ payload: svgPayload,
+ rootElement() { return document.getElementById("sss"); },
+ checkResult(html) { isnot(html.indexOf("svgtitle"), -1, "Should have retained SVG title"); },
+ },
+ {
+ id: "ssss",
+ isIFrame: true,
+ payload: svgPayload,
+ rootElement() { return document.getElementById("ssss").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("svgtitle"), -1, "Should have retained SVG title"); },
+ },
+ {
+ id: "ttt",
+ payload: svg2Payload,
+ rootElement() { return document.getElementById("ttt"); },
+ checkResult(html) { is(html.indexOf("bogussvg"), -1, "Should have dropped bogussvg element"); },
+ },
+ {
+ id: "tttt",
+ isIFrame: true,
+ payload: svg2Payload,
+ rootElement() { return document.getElementById("tttt").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("bogussvg"), -1, "Should have dropped bogussvg element"); },
+ },
+ {
+ id: "uuu",
+ payload: mathPayload,
+ rootElement() { return document.getElementById("uuu"); },
+ checkResult(html) { is(html.indexOf("bogusmath"), -1, "Should have dropped bogusmath element"); },
+ },
+ {
+ id: "uuuu",
+ isIFrame: true,
+ payload: mathPayload,
+ rootElement() { return document.getElementById("uuuu").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("bogusmath"), -1, "Should have dropped bogusmath element"); },
+ },
+ {
+ id: "vvv",
+ payload: math2Payload,
+ rootElement() { return document.getElementById("vvv"); },
+ checkResult(html) { is(html.indexOf("yyy.css"), -1, "Should have dropped MathML style element"); },
+ },
+ {
+ id: "vvvv",
+ isIFrame: true,
+ payload: math2Payload,
+ rootElement() { return document.getElementById("vvvv").contentDocument.documentElement; },
+ checkResult(html) { is(html.indexOf("yyy.css"), -1, "Should have dropped MathML style element"); },
+ },
+ {
+ id: "www",
+ payload: math3Payload,
+ rootElement() { return document.getElementById("www"); },
+ checkResult(html) { isnot(html.indexOf("<mi"), -1, "Should not have dropped MathML mi element"); },
+ },
+ {
+ id: "wwww",
+ isIFrame: true,
+ payload: math3Payload,
+ rootElement() { return document.getElementById("wwww").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("<mi"), -1, "Should not have dropped MathML mi element"); },
+ },
+ {
+ id: "xxx",
+ payload: videoPayload,
+ rootElement() { return document.getElementById("xxx"); },
+ checkResult(html) { isnot(html.indexOf("controls="), -1, "Should have added the controls attribute"); },
+ },
+ {
+ id: "xxxx",
+ isIFrame: true,
+ payload: videoPayload,
+ rootElement() { return document.getElementById("xxxx").contentDocument.documentElement; },
+ checkResult(html) { isnot(html.indexOf("controls="), -1, "Should have added the controls attribute"); },
+ },
+ {
+ id: "yyy",
+ payload: microdataPayload,
+ rootElement() { return document.getElementById("yyy"); },
+ checkResult: function(html) { is(html.indexOf("name"), -1, "Should have dropped name."); is(html.indexOf("rel"), -1, "Should have dropped rel."); isnot(html.indexOf("itemprop"), -1, "Should not have dropped itemprop."); }
+ },
+ {
+ id: "yyyy",
+ isIFrame: true,
+ payload: microdataPayload,
+ rootElement() { return document.getElementById("yyyy").contentDocument.documentElement; },
+ checkResult: function(html) { is(html.indexOf("name"), -1, "Should have dropped name."); is(html.indexOf("rel"), -1, "Should have dropped rel."); isnot(html.indexOf("itemprop"), -1, "Should not have dropped itemprop."); }
+ }
+];
+
+function doNextTest() {
+ if (typeof testCounter == "undefined")
+ testCounter = 0;
+ else if (++testCounter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runTest(tests[testCounter]);
+
+ doNextTest();
+}
+
+function getLoadContext() {
+ const Ci = SpecialPowers.Ci;
+ return SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function runTest(test) {
+ var elem = document.getElementById(test.id);
+ if ("isIFrame" in test) {
+ elem.contentDocument.designMode = "on";
+ elem.contentWindow.focus();
+ } else
+ elem.focus();
+
+ var trans = SpecialPowers.Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(SpecialPowers.Ci.nsITransferable);
+ trans.init(getLoadContext());
+ var data = SpecialPowers.Cc["@mozilla.org/supports-string;1"]
+ .createInstance(SpecialPowers.Ci.nsISupportsString);
+ data.data = test.payload;
+ trans.addDataFlavor("text/html");
+ trans.setTransferData("text/html", data, data.data.length * 2);
+
+ if ("indirectPaste" in test) {
+ var editor, win;
+ if ("isIFrame" in test) {
+ win = elem.contentDocument.defaultView;
+ } else {
+ getSelection().collapse(elem, 0);
+ win = window;
+ }
+ editor = SpecialPowers.wrap(win).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .editor;
+ editor.pasteTransferable(trans);
+ } else {
+ var clipboard = SpecialPowers.Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(SpecialPowers.Ci.nsIClipboard);
+
+ clipboard.setData(trans, null, SpecialPowers.Ci.nsIClipboard.kGlobalClipboard);
+
+ synthesizeKey("V", {accelKey: true});
+ }
+
+ if ("checkResult" in test) {
+ if ("isIFrame" in test) {
+ test.checkResult(elem.contentDocument.documentElement.innerHTML,
+ elem.contentDocument.documentElement.textContent);
+ } else {
+ test.checkResult(elem.innerHTML, elem.textContent);
+ }
+ } else {
+ var iframes = test.rootElement().querySelectorAll("iframe");
+ var expectedIFrameCount = ("iframeCount" in test) ? test.iframeCount : 1;
+ is(iframes.length, expectedIFrameCount, "Only " + expectedIFrameCount + " iframe should be pasted");
+ if (expectedIFrameCount > 0) {
+ ok(!iframes[0].hasAttribute("src"), "iframe should not have a src attrib");
+ }
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(doNextTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug525389.html b/editor/libeditor/tests/test_bug525389.html
new file mode 100644
index 000000000..43916eb51
--- /dev/null
+++ b/editor/libeditor/tests/test_bug525389.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 525389</title>
+<style src="/tests/SimpleTest/test.css" type="text/css"></style>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+
+ var utils = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ var Cc = SpecialPowers.Cc;
+ var Ci = SpecialPowers.Ci;
+
+function getLoadContext() {
+ return SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function runTest() {
+ var pasteCount = 0;
+ var pasteFunc = function (event) { pasteCount++; };
+
+ function verifyContent(s) {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ if (navigator.platform.indexOf("Win") >= 0) {
+ // On Windows ignore \n which got left over from the removal of the fragment tags
+ // <html><body>\n<!--StartFragment--> and <!--EndFragment-->\n</body>\n</html>.
+ is(doc.body.innerHTML.replace(/\n/g, ""), s, "");
+ } else {
+ is(doc.body.innerHTML, s, "");
+ }
+ }
+
+ function pasteInto(trans, html, target_id) {
+ var e = document.getElementById('i1');
+ var doc = e.contentDocument;
+ doc.designMode = "on";
+ doc.body.innerHTML = html;
+ doc.defaultView.focus();
+ if (target_id)
+ e = doc.getElementById(target_id);
+ else
+ e = doc.body;
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(e);
+ selection.collapseToEnd();
+
+ pasteCount = 0;
+ e.addEventListener("paste", pasteFunc, false);
+ utils.sendContentCommandEvent("pasteTransferable", trans);
+ e.removeEventListener("paste", pasteFunc, false);
+
+ return e;
+ }
+
+ function getTransferableFromClipboard(asHTML) {
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ if (asHTML) {
+ trans.addDataFlavor("text/html");
+ } else {
+ trans.addDataFlavor("text/unicode");
+ }
+ var clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+ clip.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
+ return trans;
+ }
+
+ function makeTransferable(s,asHTML,target_id) {
+ var e = document.getElementById('i2');
+ var doc = e.contentDocument;
+ if (asHTML) {
+ doc.body.innerHTML = s;
+ } else {
+ var text = doc.createTextNode(s);
+ doc.body.appendChild(text);
+ }
+ doc.designMode = "on";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ if (!target_id) {
+ selection.selectAllChildren(doc.body);
+ } else {
+ var range = document.createRange();
+ range.selectNode(doc.getElementById(target_id));
+ selection.addRange(range);
+ }
+
+ // We cannot use plain strings, we have to use nsSupportsString.
+ var supportsStringClass = SpecialPowers.Components.classes["@mozilla.org/supports-string;1"];
+ var ssData = supportsStringClass.createInstance(Ci.nsISupportsString);
+
+ // Create the transferable.
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+
+ // Add the data to the transferable.
+ if (asHTML) {
+ trans.addDataFlavor("text/html");
+ ssData.data = doc.body.innerHTML;
+ trans.setTransferData("text/html", ssData, ssData.length * 2);
+ } else {
+ trans.addDataFlavor("text/unicode");
+ ssData.data = doc.body.innerHTML;
+ trans.setTransferData("text/unicode", ssData, ssData.length * 2);
+ }
+
+ return trans;
+ }
+
+ function copyToClipBoard(s,asHTML,target_id) {
+ var e = document.getElementById('i2');
+ var doc = e.contentDocument;
+ if (asHTML) {
+ doc.body.innerHTML = s;
+ } else {
+ var text = doc.createTextNode(s);
+ doc.body.appendChild(text);
+ }
+ doc.designMode = "on";
+ doc.defaultView.focus();
+ var selection = doc.defaultView.getSelection();
+ selection.removeAllRanges();
+ if (!target_id) {
+ selection.selectAllChildren(doc.body);
+ } else {
+ var range = document.createRange();
+ range.selectNode(doc.getElementById(target_id));
+ selection.addRange(range);
+ }
+ SpecialPowers.wrap(doc).execCommand("copy", false, null);
+ return e;
+ }
+
+ copyToClipBoard('<span>Hello</span><span>Kitty</span>', true);
+ var trans = getTransferableFromClipboard(true);
+ pasteInto(trans, '');
+ verifyContent('<span>Hello</span><span>Kitty</span>');
+ is(pasteCount, 1, "paste event was not triggered");
+
+ // this test is not working out exactly like the clipboard test
+ // has to do with generating the nsITransferable above
+ //trans = makeTransferable('<span>Hello</span><span>Kitty</span>', true);
+ //pasteInto(trans, '');
+ //verifyContent('<span>Hello</span><span>Kitty</span>');
+
+ copyToClipBoard("<dl><dd>Hello Kitty</dd></dl><span>Hello</span><span>Kitty</span>", true);
+ trans = getTransferableFromClipboard(true);
+ pasteInto(trans, '<ol><li id="paste_here">X</li></ol>',"paste_here");
+ verifyContent('<ol><li id="paste_here">X<dl><dd>Hello Kitty</dd></dl><span>Hello</span><span>Kitty</span></li></ol>');
+ is(pasteCount, 1, "paste event was not triggered");
+
+// The following test doesn't do what I expected, because the special handling
+// of IsList nodes in nsHTMLEditor::InsertHTMLWithContext simply removes
+// non-list/item children. See bug 481177.
+// copyToClipBoard("<ol><li>Hello Kitty</li><span>Hello</span></ol>", true);
+// pasteInto('<ol><li id="paste_here">X</li></ol>',"paste_here");
+// verifyContent('<ol><li id="paste_here">X</li><li>Hello Kitty</li><span>Hello</span></ol>');
+
+ copyToClipBoard("<pre>Kitty</pre><span>Hello</span>", true);
+ trans = getTransferableFromClipboard(true);
+ pasteInto(trans, '<pre id="paste_here">Hello </pre>',"paste_here");
+ verifyContent('<pre id="paste_here">Hello Kitty<span>Hello</span></pre>');
+ is(pasteCount, 1, "paste event was not triggered");
+
+ // test that we can preventDefault pastes
+ pasteFunc = function (event) { event.preventDefault(); return false; };
+ copyToClipBoard("<pre>Kitty</pre><span>Hello</span>", true);
+ trans = getTransferableFromClipboard(true);
+ pasteInto(trans, '<pre id="paste_here">Hello </pre>',"paste_here");
+ verifyContent('<pre id="paste_here">Hello </pre>');
+ is(pasteCount, 0, "paste event was triggered");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525389">Mozilla Bug 525389</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<iframe id="i1" width="200" height="100" src="about:blank"></iframe><br>
+<iframe id="i2" width="200" height="100" src="about:blank"></iframe><br>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug537046.html b/editor/libeditor/tests/test_bug537046.html
new file mode 100644
index 000000000..6c3c07b29
--- /dev/null
+++ b/editor/libeditor/tests/test_bug537046.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=537046
+-->
+<head>
+ <title>Test for Bug 537046</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=537046">Mozilla Bug 537046</a>
+<p id="display"></p>
+<div id="content">
+ <div id="editor" contenteditable="true">
+ Some editable content
+ </div>
+ <div id="source" contenteditable="true">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 537046 **/
+
+SimpleTest.expectAssertions(1);
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ed = document.getElementById("editor");
+ var src = document.getElementById("source");
+ ed.addEventListener("DOMSubtreeModified", function() {
+ src.textContent = ed.innerHTML;
+ }, false);
+ src.addEventListener("DOMSubtreeModified", function() {
+ ed.innerHTML = ed.textContent;
+ }, false);
+
+ // Simulate pressing Enter twice
+ ed.focus();
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_RETURN", {});
+
+ ok(true, "Didn't crash!");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug549262.html b/editor/libeditor/tests/test_bug549262.html
new file mode 100644
index 000000000..fa1cbabc4
--- /dev/null
+++ b/editor/libeditor/tests/test_bug549262.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=549262
+-->
+<head>
+ <title>Test for Bug 549262</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=549262">Mozilla Bug 549262</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 549262 **/
+
+var smoothScrollPref = "general.smoothScroll";
+SimpleTest.waitForExplicitFinish();
+var win = window.open("file_bug549262.html", "_blank",
+ "width=600,height=600,scrollbars=yes");
+
+// grab the timer right at the start
+var cwu = SpecialPowers.getDOMWindowUtils(win);
+function step() {
+ cwu.advanceTimeAndRefresh(100);
+}
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, startTest);
+}, win);
+function startTest() {
+ // Make sure that pressing Space when a contenteditable element is not focused
+ // will scroll the page.
+ var ed = win.document.getElementById("editor");
+ var sc = win.document.querySelector("a");
+ sc.focus();
+ is(win.scrollY, 0, "Sanity check");
+ synthesizeKey(" ", {}, win);
+
+ step();
+
+ isnot(win.scrollY, 0, "Page is scrolled down");
+ is(ed.textContent, "abc", "The content of the editable element has not changed");
+ var oldY = win.scrollY;
+ synthesizeKey(" ", {shiftKey: true}, win);
+
+ step();
+
+ ok(win.scrollY < oldY, "Page is scrolled up");
+ is(ed.textContent, "abc", "The content of the editable element has not changed");
+
+ // Make sure that pressing Space when a contenteditable element is focused
+ // will not scroll the page, and will edit the element.
+ ed.focus();
+ win.getSelection().collapse(ed.firstChild, 1);
+ oldY = win.scrollY;
+ synthesizeKey(" ", {}, win);
+
+ step();
+
+ ok(win.scrollY <= oldY, "Page is not scrolled down");
+ is(ed.textContent, "a bc", "The content of the editable element has changed");
+ sc.focus();
+ synthesizeKey(" ", {}, win);
+
+ step();
+
+ isnot(win.scrollY, 0, "Page is scrolled down");
+ is(ed.textContent, "a bc", "The content of the editable element has not changed");
+ ed.focus();
+ win.getSelection().collapse(ed.firstChild, 3);
+ synthesizeKey(" ", {shiftKey: true}, win);
+
+ step();
+
+ isnot(win.scrollY, 0, "Page is not scrolled up");
+ is(ed.textContent, "a b c", "The content of the editable element has changed");
+
+ // Now let's test the down/up keys
+ sc = document.body;
+
+ step();
+
+ ed.blur();
+ sc.focus();
+ oldY = win.scrollY;
+ synthesizeKey("VK_UP", {}, win);
+
+ step();
+
+ ok(win.scrollY < oldY, "Page is scrolled up");
+ oldY = win.scrollY;
+ ed.focus();
+ win.getSelection().collapse(ed.firstChild, 3);
+ synthesizeKey("VK_UP", {}, win);
+
+ step();
+
+ is(win.scrollY, oldY, "Page is not scrolled up");
+ is(win.getSelection().focusNode, ed.firstChild, "Correct element selected");
+ is(win.getSelection().focusOffset, 0, "Selection should be moved to the beginning");
+ win.getSelection().removeAllRanges();
+ synthesizeMouse(sc, 300, 300, {}, win);
+ synthesizeKey("VK_DOWN", {}, win);
+
+ step();
+
+ ok(win.scrollY > oldY, "Page is scrolled down");
+ ed.focus();
+ win.getSelection().collapse(ed.firstChild, 3);
+ oldY = win.scrollY;
+ synthesizeKey("VK_DOWN", {}, win);
+
+ step();
+
+ is(win.scrollY, oldY, "Page is not scrolled down");
+ is(win.getSelection().focusNode, ed.firstChild, "Correct element selected");
+ is(win.getSelection().focusOffset, ed.textContent.length, "Selection should be moved to the end");
+
+ win.close();
+ cwu.restoreNormalRefresh();
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug550434.html b/editor/libeditor/tests/test_bug550434.html
new file mode 100644
index 000000000..0fa3ad159
--- /dev/null
+++ b/editor/libeditor/tests/test_bug550434.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=550434
+-->
+<head>
+ <title>Test for Bug 550434</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=550434">Mozilla Bug 550434</a>
+<p id="display"></p>
+<div id="content">
+ <div id="editor" contenteditable="true"
+ style="height: 250px; height: 200px; border: 4px solid red; outline: none;"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 550434 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var ed = document.getElementById("editor");
+
+ // Simulate click twice
+ synthesizeMouse(ed, 10, 10, {});
+ synthesizeMouse(ed, 50, 50, {});
+ setTimeout(function() {
+ synthesizeKey("x", {});
+
+ is(ed.innerHTML, "x", "Editor should work after being clicked twice");
+ SimpleTest.finish();
+ }, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug551704.html b/editor/libeditor/tests/test_bug551704.html
new file mode 100644
index 000000000..8f335276f
--- /dev/null
+++ b/editor/libeditor/tests/test_bug551704.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551704
+-->
+<head>
+ <title>Test for Bug 551704</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551704">Mozilla Bug 551704</a>
+<p id="display"></p>
+<div id="content">
+ <div id="preformatted" style="white-space: pre" contenteditable>a&#10;b</div>
+ <div id="test1" contenteditable><br></div>
+ <div id="test2" contenteditable>a<br></div>
+ <div id="test3" contenteditable style="white-space: pre"><br></div>
+ <div id="test4" contenteditable style="white-space: pre">a<br></div>
+ <div id="test5" contenteditable></div>
+ <div id="test6" contenteditable>a</div>
+ <div id="test7" contenteditable style="white-space: pre"></div>
+ <div id="test8" contenteditable style="white-space: pre">a</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function testLineBreak(div, type, expectedText, expectedHTML, callback)
+{
+ div.focus();
+ getSelection().collapse(div, 0);
+ type();
+ is(div.innerHTML, expectedHTML, "The expected HTML after editing should be correct");
+ SimpleTest.waitForClipboard(expectedText,
+ function() {
+ getSelection().selectAllChildren(div);
+ synthesizeKey("C", {accelKey: true});
+ },
+ function() {
+ var t = document.createElement("textarea");
+ document.body.appendChild(t);
+ t.focus();
+ synthesizeKey("V", {accelKey: true});
+ is(t.value, expectedText, "The expected text should be copied to the clipboard");
+ callback();
+ },
+ function() {
+ SimpleTest.finish();
+ }
+ );
+}
+
+function typeABCDEF() {
+ synthesizeKey("a", {});
+ typeBCDEF_chars();
+}
+
+function typeBCDEF() {
+ synthesizeKey("VK_RIGHT", {});
+ typeBCDEF_chars();
+}
+
+function typeBCDEF_chars() {
+ synthesizeKey("b", {});
+ synthesizeKey("c", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("d", {});
+ synthesizeKey("e", {});
+ synthesizeKey("f", {});
+}
+
+/** Test for Bug 551704 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var preformatted = document.getElementById("preformatted");
+ is(preformatted.innerHTML, "a\nb", "No BR node should be injected for preformatted editable fields");
+
+ var iframe = document.createElement("iframe");
+ iframe.addEventListener("load", function() {
+ var sel = iframe.contentWindow.getSelection();
+ is(sel.rangeCount, 0, "There should be no range in the selection initially");
+ iframe.contentDocument.designMode = "on";
+ sel = iframe.contentWindow.getSelection();
+ is(sel.rangeCount, 1, "There should be a single range in the selection after setting designMode");
+ var range = sel.getRangeAt(0);
+ ok(range.collapsed, "The range should be collapsed");
+ is(range.startContainer, iframe.contentDocument.body.firstChild, "The range should start on the text");
+ is(range.startOffset, 0, "The start offset should be zero");
+
+ continueTest();
+ }, false);
+ iframe.src = "data:text/html,foo";
+ document.getElementById("content").appendChild(iframe);
+});
+
+function continueTest() {
+ var divs = [];
+ for (var i = 0; i < 8; ++i) {
+ divs[i] = document.getElementById("test" + (i+1));
+ }
+ var current = 0;
+ function doNextTest() {
+ if (current == divs.length) {
+ SimpleTest.finish();
+ return;
+ }
+ var div = divs[current++];
+ if (div.textContent == "a") {
+ var type = typeBCDEF;
+ } else {
+ var type = typeABCDEF;
+ }
+ var expectedHTML = "abc<br>def<br>";
+ var expectedText = "abc\ndef";
+ testLineBreak(div, type, expectedText, expectedHTML, doNextTest);
+ }
+
+ doNextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug552782.html b/editor/libeditor/tests/test_bug552782.html
new file mode 100644
index 000000000..5c53e92c1
--- /dev/null
+++ b/editor/libeditor/tests/test_bug552782.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=552782
+-->
+<head>
+ <title>Test for Bug 552782</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=290026">Mozilla Bug 552782</a>
+<p id="display"></p>
+<div id="editor" contenteditable></div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 552782 **/
+SimpleTest.waitForExplicitFinish();
+
+var original = '<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li><li>Item 4</li></ol></ol>';
+var editor = document.getElementById("editor");
+editor.innerHTML = original;
+editor.focus();
+
+addLoadEvent(function() {
+
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var lis = document.getElementsByTagName("li");
+ sel.selectAllChildren(lis[2]);
+ document.execCommand("outdent", false, false);
+ var expected = '<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li><ol><li>Item 4</li></ol></ol>';
+ is(editor.innerHTML, expected, "outdenting third item in a partially indented numbered list");
+ document.execCommand("indent", false, false);
+ todo_is(editor.innerHTML, original, "re-indenting third item in a partially indented numbered list");
+
+ // done
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug567213.html b/editor/libeditor/tests/test_bug567213.html
new file mode 100644
index 000000000..22418f9e2
--- /dev/null
+++ b/editor/libeditor/tests/test_bug567213.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567213
+-->
+
+<head>
+ <title>Test for Bug 567213</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=567213">Mozilla Bug 567213</a>
+ <p id="display"></p>
+ <div id="content">
+ <div id="target" contenteditable="true">test</div>
+ <button id="thief">theif</button>
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 567213 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var target = document.getElementById("target");
+ var thief = document.getElementById("thief");
+ var sel = window.getSelection();
+
+ // select the contents of the editable area
+ sel.removeAllRanges();
+ sel.selectAllChildren(target);
+ target.focus();
+
+ // press some key
+ synthesizeKey("X", {});
+ is(target.textContent, "X", "Text input should work (sanity check)");
+
+ // select the contents of the editable area again
+ sel.removeAllRanges();
+ sel.selectAllChildren(target);
+ thief.focus();
+
+ // press some key with the thief having focus
+ synthesizeKey("Y", {});
+ is(target.textContent, "X", "Text entry should not work with another element focused");
+
+ SimpleTest.finish();
+ });
+
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug569988.html b/editor/libeditor/tests/test_bug569988.html
new file mode 100644
index 000000000..e42bbb985
--- /dev/null
+++ b/editor/libeditor/tests/test_bug569988.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=569988
+-->
+<head>
+ <title>Test for Bug 569988</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=569988">Mozilla Bug 569988</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 569988 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+
+function runTest()
+{
+ var script = SpecialPowers.loadChromeScript(function() {
+ var gPromptInput = null;
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+
+ os.addObserver(onPromptLoad, "common-dialog-loaded", false);
+ os.addObserver(onPromptLoad, "tabmodal-dialog-loaded", false);
+
+ function onPromptLoad(subject, topic, data) {
+ sendAsyncMessage("ok", [true, "onPromptLoad is called"]);
+ gPromptInput = subject.Dialog.ui.loginTextbox;
+ gPromptInput.addEventListener("focus", onPromptFocus, false);
+ // shift focus to ensure it fires.
+ subject.Dialog.ui.button0.focus();
+ gPromptInput.focus();
+ }
+
+ function onPromptFocus() {
+ sendAsyncMessage("ok", [true, "onPromptFocus is called"]);
+ gPromptInput.removeEventListener("focus", onPromptFocus, false);
+
+ var listenerService =
+ Components.classes["@mozilla.org/eventlistenerservice;1"]
+ .getService(Components.interfaces.nsIEventListenerService);
+
+ var listener = {
+ handleEvent: function _hv(aEvent) {
+ var isPrevented = aEvent.defaultPrevented;
+ sendAsyncMessage("ok", [!isPrevented,
+ "ESC key event is prevented by editor"]);
+ listenerService.removeSystemEventListener(gPromptInput, "keypress",
+ listener, false);
+ }
+ };
+ listenerService.addSystemEventListener(gPromptInput, "keypress",
+ listener, false);
+
+ sendAsyncMessage("info", "sending key");
+ var EventUtils = {};
+ EventUtils.window = {};
+ EventUtils._EU_Ci = Components.interfaces;
+ EventUtils._EU_Cc = Components.classes;
+ Components.classes['@mozilla.org/moz/jssubscript-loader;1']
+ .getService(Components.interfaces.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+ EventUtils);
+ EventUtils.synthesizeKey("VK_ESCAPE", {},
+ gPromptInput.ownerDocument.defaultView);
+ }
+
+ addMessageListener("destroy", function() {
+ os.removeObserver(onPromptLoad, "tabmodal-dialog-loaded");
+ os.removeObserver(onPromptLoad, "common-dialog-loaded");
+ });
+ });
+ script.addMessageListener("ok", ([val, msg]) => ok(val, msg));
+ script.addMessageListener("info", msg => info(msg));
+
+ info("opening prompt...");
+ prompt("summary", "text");
+ info("prompt is closed");
+
+ script.sendSyncMessage("destroy");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug570144.html b/editor/libeditor/tests/test_bug570144.html
new file mode 100644
index 000000000..e3b98b8d6
--- /dev/null
+++ b/editor/libeditor/tests/test_bug570144.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=570144
+-->
+<head>
+ <title>Test for Bug 570144</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570144">Mozilla Bug 570144</a>
+<p id="display"></p>
+<div id="content">
+ <!-- editable paragraphs in list item -->
+ <section id="test1">
+ <ol>
+ <li><p contenteditable>foo</p></li>
+ </ol>
+ <ul>
+ <li><p contenteditable>foo</p></li>
+ </ul>
+ <dl>
+ <dt>foo</dt>
+ <dd><p contenteditable>bar</p></dd>
+ </dl>
+ </section>
+ <!-- paragraphs in editable list item -->
+ <section id="test2">
+ <ol>
+ <li contenteditable><p>foo</p></li>
+ </ol>
+ <ul>
+ <li contenteditable><p>foo</p></li>
+ </ul>
+ <dl>
+ <dt>foo</dt>
+ <dd contenteditable><p>bar</p></dd>
+ </dl>
+ </section>
+ <!-- paragraphs in editable list -->
+ <section id="test3">
+ <ol contenteditable>
+ <li><p>foo</p></li>
+ </ol>
+ <ul contenteditable>
+ <li><p>foo</p></li>
+ </ul>
+ <dl contenteditable>
+ <dt>foo</dt>
+ <dd><p>bar</p></dd>
+ </dl>
+ </section>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 570144 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function try2split(list) {
+ var editor = list.hasAttribute("contenteditable")
+ ? list : list.querySelector("*[contenteditable]");
+ editor.focus();
+ // put the caret at the end of the paragraph
+ var selection = window.getSelection();
+ if (editor.nodeName.toLowerCase() == "p")
+ selection.selectAllChildren(editor);
+ else
+ selection.selectAllChildren(editor.querySelector("p"));
+ selection.collapseToEnd();
+ // simulate a [Return] keypress
+ synthesizeKey("VK_RETURN", {});
+}
+
+function testSection(element, context, shouldCreateLI, shouldCreateP) {
+ var nbLI = shouldCreateLI ? 2 : 1; // number of expected list items
+ var nbP = shouldCreateP ? 2 : 1; // number of expected paragraphs
+
+ function message(nodeName, dup) {
+ return context + ":[Return] should " + (dup ? "" : "not ")
+ + "create another <" + nodeName + ">."
+ }
+ var msgP = message("p", shouldCreateP);
+ var msgLI = message("li", shouldCreateLI);
+ var msgDT = message("dt", shouldCreateLI);
+ var msgDD = message("dd", false);
+
+ const ol = element.querySelector("ol");
+ try2split(ol);
+ is(ol.querySelectorAll("li").length, nbLI, msgLI);
+ is(ol.querySelectorAll("p").length, nbP, msgP);
+
+ const ul = element.querySelector("ul");
+ try2split(ul);
+ is(ul.querySelectorAll("li").length, nbLI, msgLI);
+ is(ul.querySelectorAll("p").length, nbP, msgP);
+
+ const dl = element.querySelector("dl");
+ try2split(dl);
+ is(dl.querySelectorAll("dt").length, nbLI, msgDT);
+ is(dl.querySelectorAll("dd").length, 1, msgDD);
+ is(dl.querySelectorAll("p").length, nbP, msgP);
+}
+
+function runTests() {
+ testSection(document.getElementById("test1"), "editable paragraph in list item", false, false);
+ testSection(document.getElementById("test2"), "paragraph in editable list item", false, true);
+ testSection(document.getElementById("test3"), "paragraph in editable list", true, false);
+ /* Note: concerning #test3, it would be preferrable that [Return] creates
+ * another paragraph in another list item (i.e. last argument = 'true').
+ * Currently it just creates an empty list item, which is acceptable.
+ */
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug578771.html b/editor/libeditor/tests/test_bug578771.html
new file mode 100644
index 000000000..09f163c51
--- /dev/null
+++ b/editor/libeditor/tests/test_bug578771.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=578771
+-->
+
+<head>
+ <title>Test for Bug 578771</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=578771">Mozilla Bug 578771</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 578771 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function testElem(elem, elemTag) {
+ var ce = document.getElementById("ce");
+ ce.focus();
+
+ synthesizeMouse(elem, 5, 5, {clickCount: 2 });
+ ok(elem.selectionStart == 0 && elem.selectionEnd == 7,
+ " Double-clicking on another " + elemTag + " works correctly");
+
+ ce.focus();
+ synthesizeMouse(elem, 5, 5, {clickCount: 3 });
+ ok(elem.selectionStart == 0 && elem.selectionEnd == 14,
+ "Triple-clicking on another " + elemTag + " works correctly");
+ }
+ // Avoid platform selection differences
+ SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["layout.word_select.eat_space_to_next_word", false]]}, startTest);
+ });
+
+ function startTest() {
+ var input = document.getElementById("ip");
+ testElem(input, "input");
+
+ var textarea = document.getElementById("ta");
+ testElem(textarea, "textarea");
+
+ SimpleTest.finish();
+ }
+ </script>
+ </pre>
+
+ <input id="ip" type="text" value="Mozilla editor" />
+ <textarea id="ta">Mozilla editor</textarea>
+ <div id="ce" contenteditable="true">Contenteditable div that could interfere with focus</div>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug586662.html b/editor/libeditor/tests/test_bug586662.html
new file mode 100644
index 000000000..36c56d759
--- /dev/null
+++ b/editor/libeditor/tests/test_bug586662.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=586662
+-->
+
+<head>
+ <title>Test for Bug 586662</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=586662">Mozilla Bug 586662</a>
+ <p id="display"><textarea onkeypress="this.style.overflow = 'hidden'"></textarea></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var t = document.querySelector("textarea");
+ t.focus();
+ synthesizeKey("a", {});
+ is(getComputedStyle(t, null).overflow, "hidden", "The event handler should be executed");
+ is(t.value, "a", "The key entry should result in a character being added to the field");
+
+ var win = window.open("file_bug586662.html", "_blank",
+ "width=600,height=600,scrollbars=yes");
+ SimpleTest.waitForFocus(function() {
+ // Make sure that focusing the textarea will cause the page to scroll
+ var ed = win.document.getElementById("editor");
+ ed.focus();
+ setTimeout(function() {
+ isnot(win.scrollY, 0, "Page is scrolled down");
+ // Scroll back up
+ win.scrollTo(0, 0);
+ setTimeout(function() {
+ is(win.scrollY, 0, "Page is scrolled back up");
+ // Make sure that typing something into the textarea will cause the
+ // page to scroll down
+ synthesizeKey("a", {}, win);
+ setTimeout(function() {
+ isnot(win.scrollY, 0, "Page is scrolled down again");
+
+ win.close();
+ SimpleTest.finish();
+ }, 0);
+ }, 0);
+ }, 0);
+ }, win);
+});
+
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug587461.html b/editor/libeditor/tests/test_bug587461.html
new file mode 100644
index 000000000..2cf9f29fc
--- /dev/null
+++ b/editor/libeditor/tests/test_bug587461.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=587461
+-->
+<title>Test for Bug 587461</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=587461">Mozilla Bug 587461</a>
+<div contenteditable><b>foobar</b></div>
+<script>
+var div = document.querySelector("div");
+getSelection().collapse(div.firstChild.firstChild, 3);
+document.execCommand("inserthtml", false, "a");
+is(div.innerHTML, "<b>fooabar</b>", "innerHTML");
+</script>
diff --git a/editor/libeditor/tests/test_bug590554.html b/editor/libeditor/tests/test_bug590554.html
new file mode 100644
index 000000000..bc98503ed
--- /dev/null
+++ b/editor/libeditor/tests/test_bug590554.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590554
+-->
+
+<head>
+ <title>Test for Bug 590554</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+
+ <script type="application/javascript">
+
+ /** Test for Bug 590554 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(function() {
+ var t = document.querySelector("textarea");
+ t.focus();
+ synthesizeKey("VK_RETURN", {});
+ is(t.value, "\n", "Pressing enter should work the first time");
+ synthesizeKey("VK_RETURN", {});
+ is(t.value, "\n", "Pressing enter should not work the second time");
+ SimpleTest.finish();
+ });
+
+ </script>
+
+ <textarea maxlength="1"></textarea>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug592592.html b/editor/libeditor/tests/test_bug592592.html
new file mode 100644
index 000000000..834ecbe1d
--- /dev/null
+++ b/editor/libeditor/tests/test_bug592592.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=592592
+-->
+<head>
+ <title>Test for Bug 592592</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=592592">Mozilla Bug 592592</a>
+<p id="display"></p>
+<div id="content">
+ <div id="editor" contenteditable="true" style="white-space:pre-wrap">a b</div>
+ <div id="editor2" contenteditable="true" style="white-space:pre-wrap">a b</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 592592 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var ed = document.getElementById("editor");
+
+ // Put the selection right after "a"
+ ed.focus();
+ window.getSelection().collapse(ed.firstChild, 1);
+
+ // Press space
+ synthesizeKey(" ", {});
+
+ // Make sure we haven't added an nbsp
+ is(ed.innerHTML, "a b", "We should not be adding an &nbsp; for preformatted text");
+
+ // Remove the preformatted style
+ ed.removeAttribute("style");
+
+ // Reset the DOM
+ ed.innerHTML = "a b";
+
+ // Reset the selection
+ ed.focus();
+ window.getSelection().collapse(ed.firstChild, 1);
+
+ // Press space
+ synthesizeKey(" ", {});
+
+ // Make sure that we have added an nbsp
+ is(ed.innerHTML, "a&nbsp; b", "We should add an &nbsp; for non-preformatted text");
+
+ ed = document.getElementById("editor2");
+
+ // Put the selection after the second space in the second editable field
+ ed.focus();
+ window.getSelection().collapse(ed.firstChild, 3);
+
+ // Press the back-space key
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ // Make sure that we've only deleted a single space
+ is(ed.innerHTML, "a b", "We should only be deleting a single space");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug596001.html b/editor/libeditor/tests/test_bug596001.html
new file mode 100644
index 000000000..c677df359
--- /dev/null
+++ b/editor/libeditor/tests/test_bug596001.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=596001
+-->
+<head>
+ <title>Test for Bug 596001</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=596001">Mozilla Bug 596001</a>
+<p id="display"></p>
+<div id="content">
+<textarea id="src">a&#9;b</textarea>
+<textarea id="dst"></textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 596001 **/
+
+function testTab(prefix, callback) {
+ var src = document.getElementById("src");
+ var dst = document.getElementById("dst");
+ dst.value = prefix;
+ src.focus();
+ src.select();
+ SimpleTest.waitForClipboard("a\tb",
+ function() {
+ synthesizeKey("c", {accelKey: true});
+ },
+ function() {
+ dst.focus();
+ var inputReceived = false;
+ dst.addEventListener("input", function() { inputReceived = true; }, false);
+ synthesizeKey("v", {accelKey: true});
+ ok(inputReceived, "An input event should be raised");
+ is(dst.value, prefix + src.value, "The value should be pasted verbatim");
+ callback();
+ },
+ callback
+ );
+}
+
+testTab("", function() {
+ testTab("foo", function() {
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug596333.html b/editor/libeditor/tests/test_bug596333.html
new file mode 100644
index 000000000..a94726325
--- /dev/null
+++ b/editor/libeditor/tests/test_bug596333.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=596333
+-->
+<head>
+ <title>Test for Bug 596333</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="spellcheck.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=596333">Mozilla Bug 596333</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 596333 **/
+const Ci = SpecialPowers.Ci;
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+var gMisspeltWords;
+
+function getEditor() {
+ return SpecialPowers.wrap(document.getElementById("edit")).editor;
+}
+
+function append(str) {
+ var edit = document.getElementById("edit");
+ edit.focus();
+ edit.selectionStart = edit.selectionEnd = edit.value.length;
+ var editor = getEditor();
+
+ for (var i = 0; i < str.length; ++i) {
+ synthesizeKey(str[i], {});
+ }
+}
+
+function getLoadContext() {
+ return SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function paste(str) {
+ var edit = document.getElementById("edit");
+ var Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci;
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ var s = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ s.data = str;
+ trans.setTransferData("text/unicode", s, str.length * 2);
+
+ getEditor().pasteTransferable(trans);
+}
+
+function runOnFocus() {
+ var edit = document.getElementById("edit");
+
+ gMisspeltWords = ["haz", "cheezburger"];
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings before editing are accounted for.");
+ append(" becaz I'm a lulcat!");
+ onSpellCheck(edit, function () {
+ gMisspeltWords.push("becaz");
+ gMisspeltWords.push("lulcat");
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings after typing are accounted for.");
+
+ // Now, type an invalid word, and instead of hitting "space" at the end, just blur
+ // the textarea and see if the spell check after the blur event catches it.
+ append(" workd");
+ edit.blur();
+ onSpellCheck(edit, function () {
+ gMisspeltWords.push("workd");
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "All misspellings after blur are accounted for.");
+
+ // Also, test the case when we're entering the first word in a textarea
+ gMisspeltWords = ["workd"];
+ edit.value = "";
+ append("workd ");
+ onSpellCheck(edit, function () {
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "Misspelling in the first entered word is accounted for.");
+
+ // Make sure that pasting would also trigger spell checking for the previous word
+ gMisspeltWords = ["workd"];
+ edit.value = "";
+ append("workd");
+ paste(" x");
+ onSpellCheck(edit, function () {
+ ok(isSpellingCheckOk(getEditor(), gMisspeltWords),
+ "Misspelling is accounted for after pasting.");
+
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+
+function runTest()
+{
+ var edit = document.getElementById("edit");
+ edit.focus();
+
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+ onSpellCheck(edit, runOnFocus);
+}
+</script>
+</pre>
+
+<textarea id="edit">I can haz cheezburger</textarea>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug596506.html b/editor/libeditor/tests/test_bug596506.html
new file mode 100644
index 000000000..0fc1adabf
--- /dev/null
+++ b/editor/libeditor/tests/test_bug596506.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=596506
+-->
+<head>
+ <title>Test for Bug 596506</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=596506">Mozilla Bug 596506</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 596506 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+
+function append(str) {
+ for (var i = 0; i < str.length; ++i) {
+ synthesizeKey(str[i], {});
+ }
+}
+
+function runTest() {
+ var edit = document.getElementById("edit");
+ edit.focus();
+
+ append("First");
+ synthesizeKey("VK_RETURN", {});
+ append("Second");
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("VK_UP", {});
+ if (kIsMac) {
+ synthesizeKey("VK_RIGHT", {accelKey: true});
+ } else {
+ synthesizeKey("VK_END", {});
+ }
+ append("ly");
+ is(edit.value, "Firstly\nSecond",
+ "Pressing end should position the cursor before the terminating newline");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+
+<textarea id="edit"></textarea>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug597331.html b/editor/libeditor/tests/test_bug597331.html
new file mode 100644
index 000000000..f35413cb6
--- /dev/null
+++ b/editor/libeditor/tests/test_bug597331.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=597331
+-->
+<head>
+ <title>Test for Bug 597331</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=597331">Mozilla Bug 597331</a>
+<p id="display"></p>
+<div id="content">
+<textarea>line1
+line2
+line3
+</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 597331 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ SimpleTest.executeSoon(function() {
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = 4;
+ t.selectionEnd = 4;
+ SimpleTest.executeSoon(function() {
+ t.getBoundingClientRect(); // flush layout
+ var before = snapshotWindow(window, true);
+ t.selectionStart = 5;
+ t.selectionEnd = 5;
+ t.addEventListener("keydown", function() {
+ t.removeEventListener("keydown", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ t.style.display = 'block';
+ document.body.offsetWidth;
+ t.style.display = '';
+ document.body.offsetWidth;
+
+ is(t.selectionStart, 4, "Cursor should be moved correctly");
+ is(t.selectionEnd, 4, "Cursor should be moved correctly");
+
+ var after = snapshotWindow(window, true);
+
+ var result = compareSnapshots(before, after, true);
+ var msg = "The caret should be displayed correctly after reframing";
+ if (!result[0]) {
+ msg += "\nRESULT:\n" + result[2];
+ msg += "\nREFERENCE:\n" + result[1];
+ }
+ ok(result[0], msg);
+
+ SimpleTest.finish();
+ });
+ }, false);
+ synthesizeKey("VK_LEFT", {});
+ });
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug597784.html b/editor/libeditor/tests/test_bug597784.html
new file mode 100644
index 000000000..321f2ad1c
--- /dev/null
+++ b/editor/libeditor/tests/test_bug597784.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=597784
+-->
+<head>
+ <title>Test for Bug 597784</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=597784">Mozilla Bug 597784</a>
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 597784 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ document.designMode = "on";
+ var content = document.getElementById("content");
+ getSelection().collapse(content, 0);
+ var html = "<test:tag>test:tag</test:tag>" +
+ "<a href=\"http://mozilla.org/\" test:attr=\"test:attr\" custom=\"value\">link</a>";
+ document.execCommand("insertHTML", false, html);
+ is(content.innerHTML, html,
+ "The custom tags and attributes should be inserted into the document using the insertHTML command");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug599322.html b/editor/libeditor/tests/test_bug599322.html
new file mode 100644
index 000000000..578bcb11a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug599322.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599322.patch
+-->
+<head>
+ <title>Test for Bug 599322.patch</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=599322.patch">Mozilla Bug 599322.patch</a>
+<p id="display"></p>
+<div id="content">
+<div id="src">src<img src="/tests/editor/libeditor/tests/green.png"></div>
+<iframe id="dst" src="javascript:;"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 599322.patch **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var src = document.getElementById("src");
+ var dst = document.getElementById("dst");
+ var doc = dst.contentDocument;
+ doc.open();
+ doc.write("<html><head><base href='http://mochi.test:8888/'></head><body></body></html>");
+ doc.close();
+ SimpleTest.waitForFocus(function() {
+ getSelection().selectAllChildren(src);
+ SimpleTest.waitForClipboard("src",
+ function() {
+ synthesizeKey("c", {accelKey: true});
+ },
+ function() {
+ dst.contentDocument.designMode = "on";
+ dst.focus();
+ dst.contentDocument.body.focus();
+ synthesizeKey("v", {accelKey: true});
+ is(dst.contentDocument.querySelector("img").src,
+ document.querySelector("img").src,
+ "The source should be correctly set based on the base URI");
+ SimpleTest.finish();
+ },
+ function() {
+ SimpleTest.finish();
+ }
+ );
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug599983.html b/editor/libeditor/tests/test_bug599983.html
new file mode 100644
index 000000000..08fc9a228
--- /dev/null
+++ b/editor/libeditor/tests/test_bug599983.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599983
+-->
+<title>Test for Bug 599983</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=599983">Mozilla Bug 599983</a>
+<div contenteditable>foo</div>
+<script>
+getSelection().selectAllChildren(document.querySelector("div"));
+document.execCommand("bold");
+is(document.querySelector("[_moz_dirty]"), null,
+ "No _moz_dirty allowed in webpages");
+</script>
diff --git a/editor/libeditor/tests/test_bug599983.xul b/editor/libeditor/tests/test_bug599983.xul
new file mode 100644
index 000000000..8b5d52a8c
--- /dev/null
+++ b/editor/libeditor/tests/test_bug599983.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599983
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 599983" onload="runTest()">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=599983"
+ target="_blank">Mozilla Bug 599983</a>
+ <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="editor"
+ editortype="html"
+ src="about:blank" />
+ </body>
+ <script type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ const kAllowInteraction = Components.interfaces.nsIPlaintextEditor
+ .eEditorAllowInteraction;
+ const kMailMask = Components.interfaces.nsIPlaintextEditor.eEditorMailMask;
+
+ function runTest() {
+ testEditor(false, false);
+ testEditor(false, true);
+ testEditor(true, false);
+ testEditor(true, true);
+
+ SimpleTest.finish();
+ }
+
+ function testEditor(setAllowInteraction, setMailMask) {
+ var desc = " with " + (setAllowInteraction ? "" : "no ") +
+ "eEditorAllowInteraction and " +
+ (setMailMask ? "" : "no ") + "eEditorMailMask";
+
+ var editorElem = document.getElementById("editor");
+
+ var editorObj = editorElem.getEditor(editorElem.contentWindow);
+ editorObj.flags = (setAllowInteraction ? kAllowInteraction : 0) |
+ (setMailMask ? kMailMask : 0);
+
+ var editorDoc = editorElem.contentDocument;
+ editorDoc.body.innerHTML = "<p>foo<p>bar";
+ editorDoc.getSelection().selectAllChildren(editorDoc.body.firstChild);
+ editorDoc.execCommand("bold");
+
+ var createsDirty = !setAllowInteraction || setMailMask;
+
+ (createsDirty ? isnot : is)(editorDoc.querySelector("[_moz_dirty]"), null,
+ "Elements with _moz_dirty" + desc);
+
+ // Even if we do create _moz_dirty, we should strip it for innerHTML.
+ is(editorDoc.body.innerHTML, "<p><b>foo</b></p><p>bar</p>",
+ "innerHTML" + desc);
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/editor/libeditor/tests/test_bug600570.html b/editor/libeditor/tests/test_bug600570.html
new file mode 100644
index 000000000..0a5a814f8
--- /dev/null
+++ b/editor/libeditor/tests/test_bug600570.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=600570
+-->
+<head>
+ <title>Test for Bug 600570</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=600570">Mozilla Bug 600570</a>
+<p id="display"></p>
+<div id="content">
+<textarea spellcheck="false">
+aaa
+[bbb]</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 600570 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var t = document.querySelector("textarea");
+ t.value = "[aaa\nbbb]";
+ t.focus();
+ synthesizeKey("A", {accelKey: true});
+
+ SimpleTest.executeSoon(function() {
+ t.getBoundingClientRect(); // flush layout
+ var afterSetValue = snapshotWindow(window);
+
+ t.value = t.defaultValue;
+
+ t.selectionStart = 0;
+ t.selectionEnd = 4;
+ SimpleTest.waitForClipboard("aaa\n",
+ function() {
+ synthesizeKey("X", {accelKey: true});
+ },
+ function() {
+ t.addEventListener("input", function() {
+ t.removeEventListener("input", arguments.callee, false);
+
+ setTimeout(function() { // Avoid the assertion in bug 649797
+ is(t.value, "[aaa\nbbb]", "The value of the textarea should be correct");
+ synthesizeKey("A", {accelKey: true});
+ is(t.selectionStart, 0, "Select all should set the selection start to the beginning of textarea");
+ is(t.selectionEnd, 9, "Select all should set the selection end to the end of textarea");
+
+ var afterPaste = snapshotWindow(window);
+
+ var res = compareSnapshots(afterSetValue, afterPaste, true);
+ var msg = "Pasting and setting the value directly should result in the same rendering";
+ if (!res[0]) {
+ msg += "\nRESULT:\n" + res[2] + "\nREFERENCE:\n" + res[1];
+ }
+ ok(res[0], msg);
+
+ SimpleTest.finish();
+ }, 0);
+ }, false);
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("V", {accelKey: true});
+ },
+ function() {
+ SimpleTest.finish();
+ }
+ );
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug602130.html b/editor/libeditor/tests/test_bug602130.html
new file mode 100644
index 000000000..a61e5c9c3
--- /dev/null
+++ b/editor/libeditor/tests/test_bug602130.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602130
+-->
+<head>
+ <title>Test for Bug 602130</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602130">Mozilla Bug 602130</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602130 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var i = document.createElement("input");
+ document.body.appendChild(i);
+ SpecialPowers.wrap(i).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement);
+ i.select();
+ i.focus();
+ is(SpecialPowers.wrap(i).editor.transactionManager.numberOfUndoItems, 0,
+ "The number of undo items should be 0 after initing the editor");
+ i.style.display = "none";
+ document.offsetWidth;
+ i.style.display = "";
+ document.offsetWidth;
+ i.select();
+ i.focus();
+ is(SpecialPowers.wrap(i).editor.transactionManager.numberOfUndoItems, 0,
+ "The number of undo items should be 0 after re-initing the editor");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug603556.html b/editor/libeditor/tests/test_bug603556.html
new file mode 100644
index 000000000..0e0a70464
--- /dev/null
+++ b/editor/libeditor/tests/test_bug603556.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=603556
+-->
+<head>
+ <title>Test for Bug 603556</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=603556">Mozilla Bug 603556</a>
+<p id="display"></p>
+<div id="content">
+ <div id="src">testing</div>
+ <input maxlength="4">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 603556 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var i = document.querySelector("input");
+ var src = document.getElementById("src");
+ SimpleTest.waitForClipboard(src.textContent,
+ function() {
+ getSelection().selectAllChildren(src);
+ synthesizeKey("C", {accelKey: true});
+ },
+ function() {
+ i.focus();
+ synthesizeKey("V", {accelKey: true});
+ is(i.value, src.textContent.substr(0, i.maxLength),
+ "Pasting should paste maxlength chars worth of the clipboard contents");
+ SimpleTest.finish();
+ },
+ function() {
+ SimpleTest.finish();
+ }
+ );
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug604532.html b/editor/libeditor/tests/test_bug604532.html
new file mode 100644
index 000000000..519a179b1
--- /dev/null
+++ b/editor/libeditor/tests/test_bug604532.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=604532
+-->
+<head>
+ <title>Test for Bug 604532</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=604532">Mozilla Bug 604532</a>
+<p id="display"></p>
+<div id="content">
+<input>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 604532 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var i = document.querySelector("input");
+ i.focus();
+ i.value = "foo";
+ synthesizeKey("A", {accelKey: true});
+ is(i.selectionStart, 0, "Selection should start at 0 before appending");
+ is(i.selectionEnd, 3, "Selection should end at 3 before appending");
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("x", {});
+ is(i.value, "foox", "The text should be appended correctly");
+ synthesizeKey("A", {accelKey: true});
+ is(i.selectionStart, 0, "Selection should start at 0 after appending");
+ is(i.selectionEnd, 4, "Selection should end at 4 after appending");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug607584.html b/editor/libeditor/tests/test_bug607584.html
new file mode 100644
index 000000000..aa22b6f30
--- /dev/null
+++ b/editor/libeditor/tests/test_bug607584.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=607584
+-->
+<head>
+ <title>Test for Bug 607584</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=607584">Mozilla Bug 607584</a>
+<p id="display"></p>
+<div id="content" contenteditable>
+<p id="foo">Hello world</p>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 607584 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var content = document.getElementById("content");
+ content.focus();
+ var sel = getSelection();
+ sel.collapse(document.getElementById("foo").firstChild, 5);
+ synthesizeKey("VK_RETURN", {});
+ var paragraphs = content.querySelectorAll("p");
+ is(paragraphs.length, 2, "The paragraph should be split in two");
+ is(paragraphs[0].textContent, "Hello", "The first paragraph should have the correct content");
+ is(paragraphs[1].textContent, " world", "The second paragraph should have the correct content");
+ is(paragraphs[0].getAttribute("id"), "foo", "The id of the first paragraph should be retained");
+ is(paragraphs[1].hasAttribute("id"), false, "The second paragraph shouldn't have an ID");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug607584.xul b/editor/libeditor/tests/test_bug607584.xul
new file mode 100644
index 000000000..fb16cee83
--- /dev/null
+++ b/editor/libeditor/tests/test_bug607584.xul
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=607584
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 607584" onload="runTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=607584"
+ target="_blank">Mozilla Bug 607584</a>
+ <p/>
+ <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="editor"
+ type="content-primary"
+ editortype="html"
+ style="width: 400px; height: 100px; border: thin solid black"/>
+ <p/>
+ <pre id="test">
+ </pre>
+ </body>
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function EditorContentListener(aEditor)
+ {
+ this.init(aEditor);
+ }
+
+ EditorContentListener.prototype = {
+ init : function(aEditor)
+ {
+ this.mEditor = aEditor;
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ var editor = this.mEditor.getEditor(this.mEditor.contentWindow);
+ if (editor) {
+ this.mEditor.focus();
+ editor instanceof Components.interfaces.nsIHTMLEditor;
+ editor.returnInParagraphCreatesNewParagraph = true;
+ editor.insertHTML("<p id='foo'>this is a paragraph carrying id 'foo'</p>");
+ var p = editor.document.getElementById('foo')
+ editor.beginningOfDocument();
+ sendKey("return");
+ var firstP = p.parentNode.firstElementChild;
+ var lastP = p.parentNode.lastElementChild;
+ var isOk = firstP.nodeName.toLowerCase() == "p" &&
+ firstP.id == "foo" &&
+ lastP.id == "";
+ ok(isOk, "CR in a paragraph with an ID should not create two paragraphs of same ID");
+ progress.removeProgressListener(this);
+ SimpleTest.finish();
+ }
+ }
+
+ },
+
+
+ onProgressChange : function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState)
+ {
+ },
+
+ mEditor: null
+ };
+
+ var progress, progressListener;
+
+ function runTest() {
+ var newEditorElement = document.getElementById("editor");
+ newEditorElement.makeEditable("html", true);
+ var docShell = newEditorElement.boxObject.docShell;
+ progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);
+ progressListener = new EditorContentListener(newEditorElement);
+ progress.addProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ newEditorElement.setAttribute("src", "data:text/html,");
+ }
+]]>
+</script>
+</window>
diff --git a/editor/libeditor/tests/test_bug611182.html b/editor/libeditor/tests/test_bug611182.html
new file mode 100644
index 000000000..e6ecc6716
--- /dev/null
+++ b/editor/libeditor/tests/test_bug611182.html
@@ -0,0 +1,239 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=611182
+-->
+<head>
+ <title>Test for Bug 611182</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=611182">Mozilla Bug 611182</a>
+<p id="display"></p>
+<div id="content">
+ <iframe></iframe>
+ <iframe id="ref" src="data:text/html,foo bar"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 611182 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var iframe = document.querySelector("iframe");
+ var refElem = document.querySelector("#ref");
+ var ref = snapshotWindow(refElem.contentWindow, false);
+
+ function findTextNode(doc) {
+ var body = doc.documentElement;
+ var result = findTextNodeWorker(body);
+ ok(result, "Failed to find the text node");
+ return result;
+ }
+
+ function findTextNodeWorker(root) {
+ if (root.isContentEditable) {
+ root.focus();
+ }
+ for (var i = 0; i < root.childNodes.length; ++i) {
+ var node = root.childNodes[i];
+ if (node.nodeType == node.TEXT_NODE &&
+ node.nodeValue == "fooz bar") {
+ return node;
+ }
+ if (node.nodeType == node.ELEMENT_NODE) {
+ node = findTextNodeWorker(node);
+ if (node) {
+ return node;
+ }
+ }
+ }
+ return null;
+ }
+
+ function testBackspace(src, callback) {
+ ok(true, "Testing " + src);
+ iframe.addEventListener("load", function() {
+ iframe.removeEventListener("load", arguments.callee, false);
+
+ var doc = iframe.contentDocument;
+ var win = iframe.contentWindow;
+ doc.body.setAttribute("spellcheck", "false");
+
+ iframe.focus();
+ var textNode = findTextNode(doc);
+ var sel = win.getSelection();
+ sel.collapse(textNode, 4);
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(textNode.textContent, "foo bar", "Backspace should work correctly");
+
+ var snapshot = snapshotWindow(win, false);
+ ok(compareSnapshots(snapshot, ref, true)[0], "No bogus node should exist in the document");
+
+ callback();
+ }, false);
+ iframe.src = src;
+ }
+
+ const TEST_URIS = [
+ "data:text/html,<html contenteditable>fooz bar</html>",
+ "data:text/html,<html contenteditable><body>fooz bar</body></html>",
+ "data:text/html,<body contenteditable>fooz bar</body>",
+ "data:text/html,<body contenteditable><p>fooz bar</p></body>",
+ "data:text/html,<body contenteditable><div>fooz bar</div></body>",
+ "data:text/html,<body contenteditable><span>fooz bar</span></body>",
+ "data:text/html,<p contenteditable style='outline:none'>fooz bar</p>",
+ "data:text/html,<!DOCTYPE html><html><body contenteditable>fooz bar</body></html>",
+ "data:text/html,<!DOCTYPE html><html contenteditable><body>fooz bar</body></html>",
+ 'data:application/xhtml+xml,<html xmlns="http://www.w3.org/1999/xhtml"><body contenteditable="true">fooz bar</body></html>',
+ 'data:application/xhtml+xml,<html xmlns="http://www.w3.org/1999/xhtml" contenteditable="true"><body>fooz bar</body></html>',
+ "data:text/html,<body onload=\"document.designMode='on'\">fooz bar</body>",
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'r.appendChild(b);' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ 'b.contentEditable = "true";' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ 'b.contentEditable = "true";' +
+ 'r.appendChild(b);' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'r.appendChild(b);' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ 'b.setAttribute("contenteditable", "true");' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ 'b.setAttribute("contenteditable", "true");' +
+ 'r.appendChild(b);' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'r.appendChild(b);' +
+ 'b.contentEditable = "true";' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'b.contentEditable = "true";' +
+ 'r.appendChild(b);' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'r.appendChild(b);' +
+ 'b.setAttribute("contenteditable", "true");' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'var old = document.body;' +
+ 'old.parentNode.removeChild(old);' +
+ 'var r = document.documentElement;' +
+ 'var b = document.createElement("body");' +
+ 'b.setAttribute("contenteditable", "true");' +
+ 'r.appendChild(b);' +
+ 'b.appendChild(document.createTextNode("fooz bar"));' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'document.open();' +
+ 'document.write("<body contenteditable>fooz bar</body>");' +
+ 'document.close();' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'document.open();' +
+ 'document.write("<body contenteditable><div>fooz bar</div></body>");' +
+ 'document.close();' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'document.open();' +
+ 'document.write("<body contenteditable><span>fooz bar</span></body>");' +
+ 'document.close();' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'document.open();' +
+ 'document.write("<p contenteditable style=\\"outline: none\\">fooz bar</p>");' +
+ 'document.close();' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'document.open();' +
+ 'document.write("<html contenteditable>fooz bar</html>");' +
+ 'document.close();' +
+ '};' +
+ '<\/script><body></body></html>',
+ 'data:text/html,<html><script>' +
+ 'onload = function() {' +
+ 'document.open();' +
+ 'document.write("<html contenteditable><body>fooz bar</body></html>");' +
+ 'document.close();' +
+ '};' +
+ '<\/script><body></body></html>',
+ ];
+ var currentTest = 0;
+ function runAllTests() {
+ if (currentTest == TEST_URIS.length) {
+ SimpleTest.finish();
+ return;
+ }
+ testBackspace(TEST_URIS[currentTest++], runAllTests);
+ }
+ runAllTests();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug612128.html b/editor/libeditor/tests/test_bug612128.html
new file mode 100644
index 000000000..b23d6f12a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug612128.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=612128
+-->
+<head>
+ <title>Test for Bug 612128</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=612128">Mozilla Bug 612128</a>
+<p id="display"></p>
+<div id="content">
+<input>
+<div contenteditable></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 612128 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ document.querySelector("input").focus();
+ var threw = false;
+ try {
+ is(document.execCommand("inserthtml", null, "<span>f" + "oo</span>"),
+ false, "The insertHTML command should return false");
+ } catch (e) {
+ ok(false, "insertHTML should not throw here");
+ }
+ is(document.querySelectorAll("span").length, 0, "No span element should be injected inside the page");
+ is(document.body.innerHTML.indexOf("f" + "oo"), -1, "No text should be injected inside the page");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug612447.html b/editor/libeditor/tests/test_bug612447.html
new file mode 100644
index 000000000..b06739288
--- /dev/null
+++ b/editor/libeditor/tests/test_bug612447.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=612447
+-->
+<head>
+ <title>Test for Bug 612447</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=612447">Mozilla Bug 612447</a>
+<p id="display"></p>
+<div id="content">
+<iframe></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 612447 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ function editorCommandsEnabled() {
+ var caught = false;
+ try {
+ doc.execCommand("justifyfull", false, null);
+ } catch (e) {
+ caught = true;
+ }
+ return !caught;
+ }
+
+ var i = document.querySelector("iframe");
+ var doc = i.contentDocument;
+ var win = i.contentWindow;
+ var b = doc.body;
+ doc.designMode = "on";
+ i.focus();
+ b.focus();
+ var beforeA = snapshotWindow(win, true);
+ synthesizeKey("X", {});
+ var beforeB = snapshotWindow(win, true);
+ is(b.textContent, "X", "Typing should work");
+ while (b.firstChild) {
+ b.removeChild(b.firstChild);
+ }
+ ok(editorCommandsEnabled(), "The editor commands should work");
+
+ i.style.display = "block";
+ document.clientWidth;
+
+ i.focus();
+ b.focus();
+ var afterA = snapshotWindow(win, true);
+ synthesizeKey("X", {});
+ var afterB = snapshotWindow(win, true);
+ is(b.textContent, "X", "Typing should work");
+ while (b.firstChild) {
+ b.removeChild(b.firstChild);
+ }
+ ok(editorCommandsEnabled(), "The editor commands should work");
+
+ ok(compareSnapshots(beforeA, afterA, true)[0], "The iframes should look the same before typing");
+ ok(compareSnapshots(beforeB, afterB, true)[0], "The iframes should look the same after typing");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug616590.xul b/editor/libeditor/tests/test_bug616590.xul
new file mode 100644
index 000000000..57c29a028
--- /dev/null
+++ b/editor/libeditor/tests/test_bug616590.xul
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=616590
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 616590" onload="runTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=616590"
+ target="_blank">Mozilla Bug 616590</a>
+ <p/>
+ <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="editor"
+ type="content"
+ editortype="htmlmail"
+ style="width: 400px; height: 100px;"/>
+ <p/>
+ <pre id="test">
+ </pre>
+ </body>
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function EditorContentListener(aEditor)
+ {
+ this.init(aEditor);
+ }
+
+ EditorContentListener.prototype = {
+ init : function(aEditor)
+ {
+ this.mEditor = aEditor;
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ var editor = this.mEditor.getEditor(this.mEditor.contentWindow);
+ if (editor) {
+ editor.QueryInterface(Components.interfaces.nsIEditorMailSupport);
+ editor.insertAsCitedQuotation("<html><body><div contenteditable>foo</div></body></html>", "", true);
+ document.documentElement.clientWidth;
+ progress.removeProgressListener(this);
+ ok(true, "Test complete");
+ SimpleTest.finish();
+ }
+ }
+ },
+
+
+ onProgressChange : function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState)
+ {
+ },
+
+ mEditor: null
+ };
+
+ var progress, progressListener;
+
+ function runTest() {
+ var editorElement = document.getElementById("editor");
+ editorElement.makeEditable("htmlmail", true);
+ var docShell = editorElement.boxObject.docShell;
+ progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);
+ progressListener = new EditorContentListener(editorElement);
+ progress.addProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ editorElement.setAttribute("src", "data:text/html,");
+ }
+]]>
+</script>
+</window>
diff --git a/editor/libeditor/tests/test_bug620906.html b/editor/libeditor/tests/test_bug620906.html
new file mode 100644
index 000000000..208bdfd28
--- /dev/null
+++ b/editor/libeditor/tests/test_bug620906.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=620906
+-->
+<head>
+ <title>Test for Bug 620906</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=620906">Mozilla Bug 620906</a>
+<p id="display"></p>
+<div id="content">
+ <iframe src="data:text/html,
+ <body contenteditable
+ onmousedown='
+ document.designMode=&quot;on&quot;;
+ document.designMode=&quot;off&quot;;
+ '
+ >
+ <div style='height: 1000px;'></div>
+ </body>">
+ </iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 620906 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var iframe = document.querySelector("iframe");
+ is(iframe.contentWindow.scrollY, 0, "Sanity check");
+ var rect = iframe.getBoundingClientRect();
+ setTimeout(function() {
+ var onscroll = function () {
+ iframe.contentWindow.removeEventListener("scroll", onscroll, false);
+ isnot(iframe.contentWindow.scrollY, 0, "The scrollbar should work");
+ SimpleTest.finish();
+ }
+ iframe.contentWindow.addEventListener("scroll", onscroll, false);
+ synthesizeMouse(iframe, rect.width - 5, rect.height / 2, {});
+ }, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug622371.html b/editor/libeditor/tests/test_bug622371.html
new file mode 100644
index 000000000..d08ba8214
--- /dev/null
+++ b/editor/libeditor/tests/test_bug622371.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622371
+-->
+<head>
+ <title>Test for Bug 622371</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622371">Mozilla Bug 622371</a>
+<p id="display"></p>
+<div id="content">
+ <iframe src="data:text/html,<body contenteditable>abc</body>"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622371 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var i = document.querySelector("iframe");
+ var sel = i.contentWindow.getSelection();
+ var doc = i.contentDocument;
+ var body = doc.body;
+ i.focus();
+ sel.collapse(body, 1);
+ doc.designMode = "on";
+ doc.designMode = "off";
+ is(sel.getRangeAt(0).startOffset, 1, "The start offset of the selection shouldn't change");
+ is(sel.getRangeAt(0).endOffset, 1, "The end offset of the selection shouldn't change");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug625452.html b/editor/libeditor/tests/test_bug625452.html
new file mode 100644
index 000000000..e2292d753
--- /dev/null
+++ b/editor/libeditor/tests/test_bug625452.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=625452
+-->
+<head>
+ <title>Test for Bug 625452</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625452">Mozilla Bug 625452</a>
+<p id="display"></p>
+<div id="content">
+<input>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 625452 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var i = document.querySelector("input");
+ var inputCount = 0;
+ i.addEventListener("input", function() { inputCount++; }, false);
+
+ // test cut
+ i.focus();
+ i.value = "foo bar";
+ i.selectionStart = 0;
+ i.selectionEnd = 4;
+ synthesizeKey("X", {accelKey: true});
+ is(i.value, "bar", "Cut should work correctly");
+ is(inputCount, 1, "input event should be raised correctly");
+
+ // test undo
+ synthesizeKey("Z", {accelKey: true});
+ is(i.value, "foo bar", "Undo should work correctly");
+ is(inputCount, 2, "input event should be raised correctly");
+
+ // test redo
+ synthesizeKey("Z", {accelKey: true, shiftKey: true});
+ is(i.value, "bar", "Redo should work correctly");
+ is(inputCount, 3, "input event should be raised correctly");
+
+ // test delete
+ i.selectionStart = 0;
+ i.selectionEnd = 2;
+ synthesizeKey("VK_DELETE", {});
+ is(i.value, "r", "Delete should work correctly");
+ is(inputCount, 4, "input event should be raised correctly");
+
+ // test DeleteSelection(eNone)
+ i.value = "retest"; // the "r" common prefix is crucial here
+ is(inputCount, 4, "input event should not have been raised");
+
+ // paste is tested in test_bug596001.html
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug629845.html b/editor/libeditor/tests/test_bug629845.html
new file mode 100644
index 000000000..9eb24f904
--- /dev/null
+++ b/editor/libeditor/tests/test_bug629845.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=629845
+-->
+<head>
+ <title>Test for Bug 629845</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629845">Mozilla Bug 629845</a>
+<p id="display"></p>
+
+<script>
+function initFrame(frame)
+{
+ frame.contentWindow.document.designMode="on";
+ frame.contentWindow.document.writeln("<body></body>");
+
+ document.getElementsByTagName('button')[0].click();
+}
+
+function command(aName)
+{
+ var frame = document.getElementsByTagName('iframe')[0];
+
+ is(frame.contentDocument.designMode, "on", "design mode should be on!");
+ var caught = false;
+ try {
+ frame.contentDocument.execCommand(aName, false, null);
+ } catch (e) {
+ ok(false, "exception " + e + " was thrown");
+ caught = true;
+ }
+
+ ok(!caught, "No exception should have been thrown.");
+
+ // Stop the document load before finishing, just to be clean.
+ document.getElementsByTagName('iframe')[0].contentWindow.document.close();
+ SimpleTest.finish();
+}
+</script>
+
+<div id="content">
+ <button type="button" onclick="command('bold');">Bold</button>
+ <iframe onload="initFrame(this);"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 629845 **/
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug635636.html b/editor/libeditor/tests/test_bug635636.html
new file mode 100644
index 000000000..e5bbb5322
--- /dev/null
+++ b/editor/libeditor/tests/test_bug635636.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635636
+-->
+<head>
+ <title>Test for Bug 635636</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=635636">Mozilla Bug 635636</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 635636 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var w, d;
+
+ function b1()
+ {
+ w = window.open('data:application/xhtml+xml,<html xmlns="http://www.w3.org/1999/xhtml"><div>1</div></html>');
+ SimpleTest.waitForFocus(b2, w);
+ }
+
+ function b2()
+ {
+ w.document.designMode = 'on';
+ w.location = "data:text/plain,2";
+ d = w.document.getElementsByTagName("div")[0];
+ const Ci = SpecialPowers.Ci;
+ var mainWindow = SpecialPowers.wrap(w)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var browser = mainWindow.gBrowser.selectedBrowser;
+ browser.addEventListener("pageshow", function() {
+ setTimeout(b3, 0);
+ }, false);
+ }
+
+ function b3()
+ {
+ d.parentNode.removeChild(d);
+ ok(true, "Should not crash");
+ // Not needed for the crash
+ w.close();
+ SimpleTest.finish();
+ }
+
+ b1();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug636465.html b/editor/libeditor/tests/test_bug636465.html
new file mode 100644
index 000000000..37ceebe5a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug636465.html
@@ -0,0 +1,54 @@
+<!doctype html>
+<title>Mozilla bug 636465</title>
+<link rel=stylesheet href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=636465"
+ target="_blank">Mozilla Bug 636465</a>
+<input id="x" value="foobarbaz" spellcheck="true" style="background-color: transparent; border: transparent;">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm",
+ window);
+ var x = document.getElementById("x");
+ x.focus();
+ onSpellCheck(x, function () {
+ x.blur();
+ var spellCheckTrue = snapshotWindow(window);
+ x.setAttribute("spellcheck", "false");
+ var spellCheckFalse = snapshotWindow(window);
+ x.setAttribute("spellcheck", "true");
+ x.focus();
+ onSpellCheck(x, function () {
+ x.blur();
+ var spellCheckTrueAgain = snapshotWindow(window);
+ x.removeAttribute("spellcheck");
+ var spellCheckNone = snapshotWindow(window);
+ var after = snapshotWindow(window);
+ var ret = compareSnapshots(spellCheckTrue, spellCheckFalse, false)[0];
+ ok(ret,
+ "Setting the spellcheck attribute to false should work");
+ if (!ret) {
+ ok(false, "\nspellCheckTrue: " + spellCheckTrue.toDataURL() + "\nspellCheckFalse: " + spellCheckFalse.toDataURL());
+ }
+ ret = compareSnapshots(spellCheckTrue, spellCheckTrueAgain, true)[0];
+ ok(ret,
+ "Setting the spellcheck attribute back to true should work");
+ if (!ret) {
+ ok(false, "\nspellCheckTrue: " + spellCheckTrue.toDataURL() + "\nspellCheckTrueAgain: " + spellCheckTrueAgain.toDataURL());
+ }
+ ret = compareSnapshots(spellCheckNone, spellCheckFalse, true)[0];
+ ok(ret,
+ "Unsetting the spellcheck attribute should work");
+ if (!ret) {
+ ok(false, "\spellCheckNone: " + spellCheckNone.toDataURL() + "\nspellCheckFalse: " + spellCheckFalse.toDataURL());
+ }
+ SimpleTest.finish();
+ });
+ });
+}
+addLoadEvent(runTest);
+</script>
diff --git a/editor/libeditor/tests/test_bug638596.html b/editor/libeditor/tests/test_bug638596.html
new file mode 100644
index 000000000..62ef103f0
--- /dev/null
+++ b/editor/libeditor/tests/test_bug638596.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=638596
+-->
+<head>
+ <title>Test for Bug 638596</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=638596">Mozilla Bug 638596</a>
+<p id="display"></p>
+<div id="content">
+ <input type="password" style="font-size: 0">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 638596 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var i = document.querySelector("input");
+ i.focus();
+ synthesizeKey("t", {});
+ synthesizeKey("e", {});
+ synthesizeKey("s", {});
+ synthesizeKey("t", {});
+ is(i.value, "test", "The correct value should be stored in the field");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug640321.html b/editor/libeditor/tests/test_bug640321.html
new file mode 100644
index 000000000..984ea295a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug640321.html
@@ -0,0 +1,190 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=640321
+-->
+<head>
+ <title>Test for Bug 640321</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=640321">Mozilla Bug 640321</a>
+<p id="display"></p>
+<div id="content" contenteditable style="text-align: center">
+ <img src="green.png">
+</div>
+<div id="clickaway" style="width: 10px; height: 10px"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 640321 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var img = document.querySelector("img");
+
+ function cancel(e) { e.stopPropagation(); }
+ var content = document.getElementById("content");
+ content.addEventListener("mousedown", cancel, false);
+ content.addEventListener("mousemove", cancel, false);
+ content.addEventListener("mouseup", cancel, false);
+
+ /**
+ * This function is a generic resizer test.
+ * We have 8 resizers that we'd like to test, and each can be moved in 8 different directions.
+ * In specifying baseX, W can be considered to be the width of the image, and for baseY, H
+ * can be considered to be the height of the image. deltaX and deltaY are regular pixel values
+ * which can be positive or negative.
+ */
+ const W = 1;
+ const H = 1;
+ function testResizer(baseX, baseY, deltaX, deltaY, expectedDeltaX, expectedDeltaY) {
+ ok(true, "testResizer(" + [baseX, baseY, deltaX, deltaY, expectedDeltaX, expectedDeltaY].join(", ") + ")");
+
+ // Reset the dimensions of the image
+ img.style.width = "100px";
+ img.style.height = "100px";
+ var rect = img.getBoundingClientRect();
+ is(rect.width, 100, "Sanity check the length");
+ is(rect.height, 100, "Sanity check the height");
+
+ // Click on the image to show the resizers
+ synthesizeMouseAtCenter(img, {});
+
+ // Determine which resizer we're dealing with
+ var basePosX = rect.width * baseX;
+ var basePosY = rect.height * baseY;
+
+ // Click on the correct resizer
+ synthesizeMouse(img, basePosX, basePosY, {type: "mousedown"});
+ // Drag it delta pixels to the right and bottom (or maybe left and top!)
+ synthesizeMouse(img, basePosX + deltaX, basePosY + deltaY, {type: "mousemove"});
+ // Release the mouse button
+ synthesizeMouse(img, basePosX + deltaX, basePosY + deltaY, {type: "mouseup"});
+ // Move the mouse delta more pixels to the same direction to make sure that the
+ // resize operation has stopped.
+ synthesizeMouse(img, basePosX + deltaX * 2, basePosY + deltaY * 2, {type: "mousemove"});
+ // Click outside of the image to hide the resizers
+ synthesizeMouseAtCenter(document.getElementById("clickaway"), {});
+
+ // Get the new dimensions for the image
+ var newRect = img.getBoundingClientRect();
+ is(newRect.width, rect.width + expectedDeltaX, "The width should be increased by " + expectedDeltaX + " pixels");
+ is(newRect.height, rect.height + expectedDeltaY, "The height should be increased by " + expectedDeltaY + "pixels");
+ }
+
+ function runTests(preserveRatio) {
+ // Account for changes in the resizing behavior when we're trying to preserve
+ // the aspect ration.
+ // ignoredGrowth means we don't change the size of a dimension because otherwise
+ // the aspect ratio would change undesirably.
+ // needlessGrowth means that we change the size of a dimension perpendecular to
+ // the mouse movement axis in order to preserve the aspect ratio.
+ // reversedGrowth means that we change the size of a dimension in the opposite
+ // direction to the mouse movement in order to maintain the aspect ratio.
+ const ignoredGrowth = preserveRatio ? 0 : 1;
+ const needlessGrowth = preserveRatio ? 1 : 0;
+ const reversedGrowth = preserveRatio ? -1 : 1;
+
+ // top resizer
+ testResizer(W/2, 0, -10, -10, 0, 10);
+ testResizer(W/2, 0, -10, 0, 0, 0);
+ testResizer(W/2, 0, -10, 10, 0, -10);
+ testResizer(W/2, 0, 0, -10, 0, 10);
+ testResizer(W/2, 0, 0, 0, 0, 0);
+ testResizer(W/2, 0, 0, 10, 0, -10);
+ testResizer(W/2, 0, 10, -10, 0, 10);
+ testResizer(W/2, 0, 10, 0, 0, 0);
+ testResizer(W/2, 0, 10, 10, 0, -10);
+
+ // top right resizer
+ testResizer( W, 0, -10, -10, -10 * reversedGrowth, 10);
+ testResizer( W, 0, -10, 0, -10 * ignoredGrowth, 0);
+ testResizer( W, 0, -10, 10, -10, -10);
+ testResizer( W, 0, 0, -10, 10 * needlessGrowth, 10);
+ testResizer( W, 0, 0, 0, 0, 0);
+ testResizer( W, 0, 0, 10, 0, -10 * ignoredGrowth);
+ testResizer( W, 0, 10, -10, 10, 10);
+ testResizer( W, 0, 10, 0, 10, 10 * needlessGrowth);
+ testResizer( W, 0, 10, 10, 10, -10 * reversedGrowth);
+
+ // right resizer
+ testResizer( W, H/2, -10, -10, -10, 0);
+ testResizer( W, H/2, -10, 0, -10, 0);
+ testResizer( W, H/2, -10, 10, -10, 0);
+ testResizer( W, H/2, 0, -10, 0, 0);
+ testResizer( W, H/2, 0, 0, 0, 0);
+ testResizer( W, H/2, 0, 10, 0, 0);
+ testResizer( W, H/2, 10, -10, 10, 0);
+ testResizer( W, H/2, 10, 0, 10, 0);
+ testResizer( W, H/2, 10, 10, 10, 0);
+
+ // bottom right resizer
+ testResizer( W, H, -10, -10, -10, -10);
+ testResizer( W, H, -10, 0, -10 * ignoredGrowth, 0);
+ testResizer( W, H, -10, 10, -10 * reversedGrowth, 10);
+ testResizer( W, H, 0, -10, 0, -10 * ignoredGrowth);
+ testResizer( W, H, 0, 0, 0, 0);
+ testResizer( W, H, 0, 10, 10 * needlessGrowth, 10);
+ testResizer( W, H, 10, -10, 10, -10 * reversedGrowth);
+ testResizer( W, H, 10, 0, 10, 10 * needlessGrowth);
+ testResizer( W, H, 10, 10, 10, 10);
+
+ // bottom resizer
+ testResizer(W/2, H, -10, -10, 0, -10);
+ testResizer(W/2, H, -10, 0, 0, 0);
+ testResizer(W/2, H, -10, 10, 0, 10);
+ testResizer(W/2, H, 0, -10, 0, -10);
+ testResizer(W/2, H, 0, 0, 0, 0);
+ testResizer(W/2, H, 0, 10, 0, 10);
+ testResizer(W/2, H, 10, -10, 0, -10);
+ testResizer(W/2, H, 10, 0, 0, 0);
+ testResizer(W/2, H, 10, 10, 0, 10);
+
+ // bottom left resizer
+ testResizer( 0, H, -10, -10, 10, -10 * reversedGrowth);
+ testResizer( 0, H, -10, 0, 10, 10 * needlessGrowth);
+ testResizer( 0, H, -10, 10, 10, 10);
+ testResizer( 0, H, 0, -10, 0, -10 * ignoredGrowth);
+ testResizer( 0, H, 0, 0, 0, 0);
+ testResizer( 0, H, 0, 10, 10 * needlessGrowth, 10);
+ testResizer( 0, H, 10, -10, -10, -10);
+ testResizer( 0, H, 10, 0, -10 * ignoredGrowth, 0);
+ testResizer( 0, H, 10, 10, -10 * reversedGrowth, 10);
+
+ // left resizer
+ testResizer( 0, H/2, -10, -10, 10, 0);
+ testResizer( 0, H/2, -10, 0, 10, 0);
+ testResizer( 0, H/2, -10, 10, 10, 0);
+ testResizer( 0, H/2, 0, -10, 0, 0);
+ testResizer( 0, H/2, 0, 0, 0, 0);
+ testResizer( 0, H/2, 0, 10, 0, 0);
+ testResizer( 0, H/2, 10, -10, -10, 0);
+ testResizer( 0, H/2, 10, 0, -10, 0);
+ testResizer( 0, H/2, 10, 10, -10, 0);
+
+ // top left resizer
+ testResizer( 0, 0, -10, -10, 10, 10);
+ testResizer( 0, 0, -10, 0, 10, 10 * needlessGrowth);
+ testResizer( 0, 0, -10, 10, 10, -10 * reversedGrowth);
+ testResizer( 0, 0, 0, -10, 10 * needlessGrowth, 10);
+ testResizer( 0, 0, 0, 0, 0, 0);
+ testResizer( 0, 0, 0, 10, 0, -10 * ignoredGrowth);
+ testResizer( 0, 0, 10, -10, -10 * reversedGrowth, 10);
+ testResizer( 0, 0, 10, 0, -10 * ignoredGrowth, 0);
+ testResizer( 0, 0, 10, 10, -10, -10);
+ }
+ SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", false]]}, function() {
+ runTests(false);
+ SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", true]]}, function() {
+ runTests(true);
+ SimpleTest.finish();
+ });
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug641466.html b/editor/libeditor/tests/test_bug641466.html
new file mode 100644
index 000000000..4a77b0b89
--- /dev/null
+++ b/editor/libeditor/tests/test_bug641466.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=641466
+-->
+<head>
+ <title>Test for Bug 641466</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641466">Mozilla Bug 641466</a>
+<p id="display"></p>
+<div id="content">
+<input value="&#x10451;&#x10467;&#x10455;&#x10451;">
+<textarea>&#x10451;&#x10467;&#x10455;&#x10451;</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 641466 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ function doTest(element) {
+ element.focus();
+ element.selectionStart = 4;
+ element.selectionEnd = 4;
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ ok(element.value, "", "4 backspaces should delete all of the characters in the " + element.localName);
+ }
+
+ doTest(document.querySelector("input"));
+ doTest(document.querySelector("textarea"));
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug645914.html b/editor/libeditor/tests/test_bug645914.html
new file mode 100644
index 000000000..cdf799e56
--- /dev/null
+++ b/editor/libeditor/tests/test_bug645914.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=645914
+-->
+<head>
+ <title>Test for Bug 645914</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=645914">Mozilla Bug 645914</a>
+<p id="display"></p>
+<div id="content">
+<textarea>foo
+bar</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 645914 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["layout.word_select.eat_space_to_next_word", true],
+ ["browser.triple_click_selects_paragraph", false]]}, startTest);
+});
+function startTest() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = textarea.selectionEnd = 0;
+
+ // Simulate a double click on foo
+ synthesizeMouse(textarea, 5, 5, {clickCount: 2});
+
+ ok(true, "Testing word selection");
+ is(textarea.selectionStart, 0, "The start of the selection should be at the beginning of the text");
+ is(textarea.selectionEnd, 3, "The end of the selection should not include a newline character");
+
+ textarea.selectionStart = textarea.selectionEnd = 0;
+
+ // Simulate a triple click on foo
+ synthesizeMouse(textarea, 5, 5, {clickCount: 3});
+
+ ok(true, "Testing line selection");
+ is(textarea.selectionStart, 0, "The start of the selection should be at the beginning of the text");
+ is(textarea.selectionEnd, 3, "The end of the selection should not include a newline character");
+
+ textarea.selectionStart = textarea.selectionEnd = 0;
+ textarea.value = "Very very long value which would eventually overflow the visible section of the textarea";
+
+ // Simulate a quadruple click on Very
+ synthesizeMouse(textarea, 5, 5, {clickCount: 4});
+
+ ok(true, "Testing paragraph selection");
+ is(textarea.selectionStart, 0, "The start of the selection should be at the beginning of the text");
+ is(textarea.selectionEnd, textarea.value.length, "The end of the selection should be the end of the paragraph");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug646194.html b/editor/libeditor/tests/test_bug646194.html
new file mode 100644
index 000000000..8a0e4a829
--- /dev/null
+++ b/editor/libeditor/tests/test_bug646194.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<title>Mozilla Bug 646194</title>
+<link rel=stylesheet href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=646194"
+ target="_blank">Mozilla Bug 646194</a>
+<iframe id="i" src="data:text/html,&lt;div contenteditable=true id=t&gt;test me now&lt;/div&gt;"></iframe>
+<script>
+SimpleTest.expectAssertions(1);
+
+function runTest() {
+ var i = document.getElementById("i");
+ i.focus();
+ var win = i.contentWindow;
+ var doc = i.contentDocument;
+ var t = doc.getElementById("t");
+ t.focus();
+ // put the caret at the end
+ win.getSelection().collapse(t.firstChild, 11);
+
+ // Simulate pression Option+Delete on Mac
+ // We do things this way because not every platform can invoke this
+ // command using the available key bindings.
+ SpecialPowers.doCommand(window, "cmd_wordPrevious");
+ SpecialPowers.doCommand(window, "cmd_wordPrevious");
+ SpecialPowers.doCommand(window, "cmd_deleteWordBackward");
+ SpecialPowers.doCommand(window, "cmd_deleteWordBackward");
+
+ // If we reach here, we haven't crashed. Phew!
+ // But let's check the value too, now that we're here.
+ is(t.textContent, "me now", "The command has worked correctly");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
diff --git a/editor/libeditor/tests/test_bug668599.html b/editor/libeditor/tests/test_bug668599.html
new file mode 100644
index 000000000..8405d08ab
--- /dev/null
+++ b/editor/libeditor/tests/test_bug668599.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=668599
+-->
+<head>
+ <title>Test for Bug 668599</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=668599">Mozilla Bug 668599</a>
+<p id="display"></p>
+<div id="content">
+ <div id="test1">
+ block <span contenteditable>type here</span> block
+ </div>
+ <div id="test2">
+ <p contenteditable>
+ block <span>type here</span> block
+ </p>
+ </div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 668599 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function select(element) {
+ // select the element text content
+ var userSelection = window.getSelection();
+ window.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(element.firstChild, 0);
+ range.setEnd(element.firstChild, element.textContent.length);
+ userSelection.addRange(range);
+};
+
+function runTests() {
+ var span = document.querySelector("#test1 span");
+
+ // editable <span> => the <span> *content* should be deleted
+ select(span);
+ span.focus();
+ synthesizeKey("x", {});
+ is(span.textContent, "x", "The <span> content should have been replaced by 'x'.");
+
+ // same thing, but using [Del] instead of typing some text
+ document.execCommand("Undo", false, null);
+ select(span);
+ span.focus();
+ synthesizeKey("VK_DELETE", {});
+ is(span.textContent, "", "The <span> content should have been deleted.");
+
+ // <span> in editable block => the <span> *element* should be deleted
+ select(document.querySelector("#test2 span"));
+ document.querySelector("#test2 [contenteditable]").focus();
+ synthesizeKey("VK_DELETE", {});
+ is(document.querySelector("#test2 span"), null,
+ "The <span> element should have been deleted.");
+
+ // done
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug674770-1.html b/editor/libeditor/tests/test_bug674770-1.html
new file mode 100644
index 000000000..4ba65f507
--- /dev/null
+++ b/editor/libeditor/tests/test_bug674770-1.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674770
+-->
+<head>
+ <title>Test for Bug 674770</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674770">Mozilla Bug 674770</a>
+<p id="display"></p>
+<div id="content">
+<a href="file_bug674770-1.html" id="link1">test</a>
+<div contenteditable>
+<a href="file_bug674770-1.html" id="link2">test</a>
+</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["middlemouse.paste", true], ["dom.ipc.processCount", 1]]}, startTests);
+});
+
+function startTests() {
+ var tests = [
+ { description: "Testing link in <div>: ",
+ target: function () { return document.querySelector("#link1"); },
+ linkShouldWork: true },
+ { description: "Testing link in <div contenteditable>: ",
+ target: function () { return document.querySelector("#link2"); },
+ linkShouldWork: false },
+ ];
+ var currentTest;
+ function runNextTest() {
+ localStorage.removeItem("clicked");
+ currentTest = tests.shift();
+ if (!currentTest) {
+ SimpleTest.finish();
+ return;
+ }
+ ok(true, currentTest.description + "Starting to test...");
+ synthesizeMouseAtCenter(currentTest.target(), { button: 1 });
+ }
+
+
+ addEventListener("storage", function(e) {
+ is(e.key, "clicked", currentTest.description + "Key should always be 'clicked'");
+ is(e.newValue, "true", currentTest.description + "Value should always be 'true'");
+ ok(currentTest.linkShouldWork, currentTest.description + "The click operation on the link " + (currentTest.linkShouldWork ? "should work" : "shouldn't work"));
+ SimpleTest.executeSoon(runNextTest);
+ }, false);
+
+ SpecialPowers.addSystemEventListener(window, "click", function (aEvent) {
+ // When the click event should cause default action, e.g., opening the link,
+ // the event shouldn't have been consumed except the link handler.
+ // However, in e10s mode, it's not consumed during propagating the event but
+ // in non-e10s mode, it's consumed during the propagation. Therefore,
+ // we cannot check defaultPrevented value when the link should work as is
+ // if there is no way to detect if it's running in e10s mode or not.
+ // So, let's skip checking Event.defaultPrevented value when the link should
+ // work. In such case, we should receive "storage" event later.
+ if (currentTest.linkShouldWork) {
+ return;
+ }
+
+ ok(SpecialPowers.defaultPreventedInAnyGroup(aEvent),
+ currentTest.description + "The default action should be consumed because the link should work as is");
+ if (SpecialPowers.defaultPreventedInAnyGroup(aEvent)) {
+ // In this case, "storage" event won't be fired.
+ SimpleTest.executeSoon(runNextTest);
+ }
+ }, false);
+
+ SimpleTest.executeSoon(runNextTest);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug674770-2.html b/editor/libeditor/tests/test_bug674770-2.html
new file mode 100644
index 000000000..c69311e95
--- /dev/null
+++ b/editor/libeditor/tests/test_bug674770-2.html
@@ -0,0 +1,395 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674770
+-->
+<head>
+ <title>Test for Bug 674770</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674770">Mozilla Bug 674770</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="iframe" style="display: block; height: 14em;"
+ src="data:text/html,<input id='text' value='pasted'><input id='input' style='display: block;'><p id='editor1' contenteditable>editor1:</p><p id='editor2' contenteditable>editor2:</p>"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 674770 **/
+SimpleTest.waitForExplicitFinish();
+
+var iframe = document.getElementById("iframe");
+var frameWindow, frameDocument;
+
+var gClicked = false;
+var gClicking = null;
+var gDoPreventDefault1 = null;
+var gDoPreventDefault2 = null;
+
+function clickEventHandler(aEvent)
+{
+ if (aEvent.button == 1 && aEvent.target == gClicking) {
+ gClicked = true;
+ }
+ if (gDoPreventDefault1 == aEvent.target) {
+ aEvent.preventDefault();
+ }
+ if (gDoPreventDefault2 == aEvent.target) {
+ aEvent.preventDefault();
+ }
+}
+
+// NOTE: tests need to check the result *after* the content is actually
+// modified. Sometimes, the modification is delayed. Therefore, there
+// are a lot of functions and SimpleTest.executeSoon()s.
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["middlemouse.contentLoadURL", false],
+ ["middlemouse.paste", true]]}, startTest);
+});
+function startTest() {
+ frameWindow = iframe.contentWindow;
+ frameDocument = iframe.contentDocument;
+
+ frameDocument.getElementById("input").addEventListener("click", clickEventHandler, false);
+ frameDocument.getElementById("editor1").addEventListener("click", clickEventHandler, false);
+ frameDocument.getElementById("editor2").addEventListener("click", clickEventHandler, false);
+
+ var text = frameDocument.getElementById("text");
+
+ text.focus();
+ if (navigator.platform.indexOf("Linux") == 0) {
+ synthesizeKey("a", { altKey: true }, frameWindow);
+ } else {
+ synthesizeKey("a", { accelKey: true }, frameWindow);
+ }
+ // Windows and Mac don't have primary selection, we should copy the text to
+ // the global clipboard.
+ if (!SpecialPowers.supportsSelectionClipboard()) {
+ SimpleTest.waitForClipboard("pasted",
+ function() { synthesizeKey("c", { accelKey: true }, frameWindow); },
+ function() { SimpleTest.executeSoon(runInputTests1) },
+ cleanup);
+ } else {
+ // Otherwise, don't call waitForClipboard since it breaks primary
+ // selection.
+ runInputTests1();
+ }
+}
+
+function runInputTests1()
+{
+ var input = frameDocument.getElementById("input");
+
+ // first, copy text.
+
+ // when middle clicked in focused input element, text should be pasted.
+ input.value = "";
+ input.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = input;
+ gDoPreventDefault1 = null;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(input, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runInputTests1");
+ is(input.value, "pasted", "failed to paste in input element");
+
+ SimpleTest.executeSoon(runInputTests2);
+ });
+ });
+}
+
+function runInputTests2()
+{
+ var input = frameDocument.getElementById("input");
+
+ // even if the input element hasn't had focus, middle click should set focus
+ // to it and paste the text.
+ input.value = "";
+ input.blur();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = input;
+ gDoPreventDefault1 = null;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(input, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runInputTests2");
+ is(input.value, "pasted",
+ "failed to paste in input element when it hasn't had focus yet");
+
+ SimpleTest.executeSoon(runInputTests3);
+ });
+ });
+}
+
+function runInputTests3()
+{
+ var input = frameDocument.getElementById("input");
+ var editor1 = frameDocument.getElementById("editor1");
+
+ // preventDefault() of HTML editor's click event handler shouldn't prevent
+ // middle click pasting in input element.
+ input.value = "";
+ input.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = input;
+ gDoPreventDefault1 = editor1;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(input, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runInputTests3");
+ is(input.value, "pasted",
+ "failed to paste in input element when editor1 does preventDefault()");
+
+ SimpleTest.executeSoon(runInputTests4);
+ });
+ });
+}
+
+function runInputTests4()
+{
+ var input = frameDocument.getElementById("input");
+ var editor1 = frameDocument.getElementById("editor1");
+
+ // preventDefault() of input element's click event handler should prevent
+ // middle click pasting in it.
+ input.value = "";
+ input.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = input;
+ gDoPreventDefault1 = input;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(input, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runInputTests4");
+ todo_is(input.value, "",
+ "pasted in input element when it does preventDefault()");
+
+ SimpleTest.executeSoon(runContentEditableTests1);
+ });
+ });
+}
+
+function runContentEditableTests1()
+{
+ var editor1 = frameDocument.getElementById("editor1");
+
+ // when middle clicked in focused contentediable editor, text should be
+ // pasted.
+ editor1.innerHTML = "editor1:";
+ editor1.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = editor1;
+ gDoPreventDefault1 = null;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(editor1, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runContentEditableTests1");
+ is(editor1.innerHTML, "editor1:pasted",
+ "failed to paste text in contenteditable editor");
+ SimpleTest.executeSoon(runContentEditableTests2);
+ });
+ });
+}
+
+function runContentEditableTests2()
+{
+ var editor1 = frameDocument.getElementById("editor1");
+
+ // even if the contenteditable editor hasn't had focus, middle click should
+ // set focus to it and paste the text.
+ editor1.innerHTML = "editor1:";
+ editor1.blur();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = editor1;
+ gDoPreventDefault1 = null;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(editor1, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runContentEditableTests2");
+ is(editor1.innerHTML, "editor1:pasted",
+ "failed to paste in contenteditable editor #1 when it hasn't had focus yet");
+ SimpleTest.executeSoon(runContentEditableTests3);
+ });
+ });
+}
+
+function runContentEditableTests3()
+{
+ var editor1 = frameDocument.getElementById("editor1");
+ var editor2 = frameDocument.getElementById("editor2");
+
+ // When editor1 has focus but editor2 is middle clicked, should be pasted
+ // in the editor2.
+ editor1.innerHTML = "editor1:";
+ editor2.innerHTML = "editor2:";
+ editor1.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = editor2;
+ gDoPreventDefault1 = null;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(editor2, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runContentEditableTests3");
+ is(editor1.innerHTML, "editor1:",
+ "pasted in contenteditable editor #1 when editor2 is clicked");
+ is(editor2.innerHTML, "editor2:pasted",
+ "failed to paste in contenteditable editor #2 when editor2 is clicked");
+ SimpleTest.executeSoon(runContentEditableTests4);
+ });
+ });
+}
+
+function runContentEditableTests4()
+{
+ var editor1 = frameDocument.getElementById("editor1");
+
+ // preventDefault() of editor1's click event handler should prevent
+ // middle click pasting in it.
+ editor1.innerHTML = "editor1:";
+ editor1.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = editor1;
+ gDoPreventDefault1 = editor1;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(editor1, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runContentEditableTests4");
+ todo_is(editor1.innerHTML, "editor1:",
+ "pasted in contenteditable editor #1 when it does preventDefault()");
+ SimpleTest.executeSoon(runContentEditableTests5);
+ });
+ });
+}
+
+function runContentEditableTests5()
+{
+ var editor1 = frameDocument.getElementById("editor1");
+ var editor2 = frameDocument.getElementById("editor2");
+
+ // preventDefault() of editor1's click event handler shouldn't prevent
+ // middle click pasting in editor2.
+ editor1.innerHTML = "editor1:";
+ editor2.innerHTML = "editor2:";
+ editor2.focus();
+
+ SimpleTest.executeSoon(function() {
+ gClicked = false;
+ gClicking = editor2;
+ gDoPreventDefault1 = editor1;
+ gDoPreventDefault2 = null;
+
+ synthesizeMouseAtCenter(editor2, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ todo(gClicked, "click event hasn't been fired for runContentEditableTests5");
+ is(editor1.innerHTML, "editor1:",
+ "pasted in contenteditable editor #1?");
+ is(editor2.innerHTML, "editor2:pasted",
+ "failed to paste in contenteditable editor #2");
+
+ SimpleTest.executeSoon(initForBodyEditableDocumentTests);
+ });
+ });
+}
+
+function initForBodyEditableDocumentTests()
+{
+ frameDocument.getElementById("input").removeEventListener("click", clickEventHandler, false);
+ frameDocument.getElementById("editor1").removeEventListener("click", clickEventHandler, false);
+ frameDocument.getElementById("editor2").removeEventListener("click", clickEventHandler, false);
+
+ iframe.onload =
+ function (aEvent) { SimpleTest.executeSoon(runBodyEditableDocumentTests1); };
+ iframe.src =
+ "data:text/html,<body contenteditable>body:</body>";
+}
+
+function runBodyEditableDocumentTests1()
+{
+ frameWindow = iframe.contentWindow;
+ frameDocument = iframe.contentDocument;
+
+ var body = frameDocument.body;
+
+ is(body.innerHTML, "body:",
+ "failed to initialize at runBodyEditableDocumentTests1");
+
+ // middle click on html element should cause pasting text in its body.
+ synthesizeMouseAtCenter(frameDocument.documentElement, {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ is(body.innerHTML,
+ "body:pasted",
+ "failed to paste in editable body element when clicked on html element");
+
+ SimpleTest.executeSoon(runBodyEditableDocumentTests2);
+ });
+}
+
+function runBodyEditableDocumentTests2()
+{
+ frameDocument.body.innerHTML = "body:<span id='span' contenteditable='false'>non-editable</span>";
+
+ var body = frameDocument.body;
+
+ is(body.innerHTML, "body:<span id=\"span\" contenteditable=\"false\">non-editable</span>",
+ "failed to initialize at runBodyEditableDocumentTests2");
+
+ synthesizeMouseAtCenter(frameDocument.getElementById("span"), {button: 1}, frameWindow);
+
+ SimpleTest.executeSoon(function() {
+ is(body.innerHTML,
+ "body:<span id=\"span\" contenteditable=\"false\">non-editable</span>",
+ "pasted when middle clicked in non-editable element");
+
+ SimpleTest.executeSoon(cleanup);
+ });
+}
+
+function cleanup()
+{
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug674861.html b/editor/libeditor/tests/test_bug674861.html
new file mode 100644
index 000000000..5974b4aed
--- /dev/null
+++ b/editor/libeditor/tests/test_bug674861.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674861
+-->
+<head>
+ <title>Test for Bug 674861</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674861">Mozilla Bug 674861</a>
+<p id="display"></p>
+<div id="content">
+ <section id="test1">
+ <h2> Editable Bullet List </h2>
+ <ul contenteditable>
+ <li> item A </li>
+ <li> item B </li>
+ <li> item C </li>
+ </ul>
+
+ <h2> Editable Ordered List </h2>
+ <ol contenteditable>
+ <li> item A </li>
+ <li> item B </li>
+ <li> item C </li>
+ </ol>
+
+ <h2> Editable Definition List </h2>
+ <dl contenteditable>
+ <dt> term A </dt>
+ <dd> definition A </dd>
+ <dt> term B </dt>
+ <dd> definition B </dd>
+ <dt> term C </dt>
+ <dd> definition C </dd>
+ </dl>
+ </section>
+
+ <section id="test2" contenteditable>
+ <h2> Bullet List In Editable Section </h2>
+ <ul>
+ <li> item A </li>
+ <li> item B </li>
+ <li> item C </li>
+ </ul>
+
+ <h2> Ordered List In Editable Section </h2>
+ <ol>
+ <li> item A </li>
+ <li> item B </li>
+ <li> item C </li>
+ </ol>
+
+ <h2> Definition List In Editable Section </h2>
+ <dl>
+ <dt> term A </dt>
+ <dd> definition A </dd>
+ <dt> term B </dt>
+ <dd> definition B </dd>
+ <dt> term C </dt>
+ <dd> definition C </dd>
+ </dl>
+ </section>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 674861 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+const CARET_BEGIN = 0;
+const CARET_MIDDLE = 1;
+const CARET_END = 2;
+
+function try2split(element, caretPos) {
+ // compute the requested position
+ var len = element.textContent.length;
+ var pos = -1;
+ switch (caretPos) {
+ case CARET_BEGIN:
+ pos = 0;
+ break;
+ case CARET_MIDDLE:
+ pos = Math.floor(len/2);
+ break;
+ case CARET_END:
+ pos = len;
+ break;
+ }
+
+ // put the caret on the requested position
+ var sel = window.getSelection();
+ for (var i = 0; i < sel.rangeCount; i++) {
+ var range = sel.getRangeAt(i);
+ sel.removeRange(range);
+ }
+ range = document.createRange();
+ range.setStart(element.firstChild, pos);
+ range.setEnd(element.firstChild, pos);
+ sel.addRange(range);
+
+ // simulates two [Return] keypresses
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_RETURN", {});
+}
+
+function runTests() {
+ const test1 = document.getElementById("test1");
+ const test2 = document.getElementById("test2");
+
+ // -----------------------------------------------------------------------
+ // #test1: editable lists should NOT be splittable
+ // -----------------------------------------------------------------------
+ const ul = test1.querySelector("ul");
+ const ol = test1.querySelector("ol");
+ const dl = test1.querySelector("dl");
+
+ // bullet list
+ ul.focus();
+ try2split(ul.querySelector("li"), CARET_END);
+ is(test1.querySelectorAll("ul").length, 1,
+ "The <ul contenteditable> list should not be splittable.");
+ is(ul.querySelectorAll("li").length, 5,
+ "Two new <li> elements should have been created.");
+
+ // ordered list
+ ol.focus();
+ try2split(ol.querySelector("li"), CARET_END);
+ is(test1.querySelectorAll("ol").length, 1,
+ "The <ol contenteditable> list should not be splittable.");
+ is(ol.querySelectorAll("li").length, 5,
+ "Two new <li> elements should have been created.");
+
+ // definition list
+ dl.focus();
+ try2split(dl.querySelector("dd"), CARET_END);
+ is(test1.querySelectorAll("dl").length, 1,
+ "The <dl contenteditable> list should not be splittable.");
+ is(dl.querySelectorAll("dt").length, 5,
+ "Two new <dt> elements should have been created.");
+
+ // -----------------------------------------------------------------------
+ // #test2: lists in editable blocks should be splittable
+ // -----------------------------------------------------------------------
+ test2.focus();
+
+ // bullet list
+ try2split(test2.querySelector("ul li"), CARET_END);
+ is(test2.querySelectorAll("ul").length, 2,
+ "The <ul> list should have been splitted.");
+ is(test2.querySelectorAll("ul li").length, 3,
+ "No new <li> element should have been created.");
+ is(test2.querySelectorAll("ul+p").length, 1,
+ "A new paragraph should have been created.");
+
+ // ordered list
+ try2split(test2.querySelector("ol li"), CARET_END);
+ is(test2.querySelectorAll("ol").length, 2,
+ "The <ol> list should have been splitted.");
+ is(test2.querySelectorAll("ol li").length, 3,
+ "No new <li> element should have been created.");
+ is(test2.querySelectorAll("ol+p").length, 1,
+ "A new paragraph should have been created.");
+
+ // definition list
+ try2split(test2.querySelector("dl dd"), CARET_END);
+ is(test2.querySelectorAll("dl").length, 2,
+ "The <dl> list should have been splitted.");
+ is(test2.querySelectorAll("dt").length, 3,
+ "No new <dt> element should have been created.");
+ is(test2.querySelectorAll("dl+p").length, 1,
+ "A new paragraph should have been created.");
+
+ // done
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug676401.html b/editor/libeditor/tests/test_bug676401.html
new file mode 100644
index 000000000..aa468fdc6
--- /dev/null
+++ b/editor/libeditor/tests/test_bug676401.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=676401
+-->
+<head>
+ <title>Test for Bug 676401</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=676401">Mozilla Bug 676401</a>
+<p id="display"></p>
+<div id="content">
+ <!-- we need a blockquote to test the "outdent" command -->
+ <section>
+ <blockquote> not editable </blockquote>
+ </section>
+ <section contenteditable>
+ <blockquote> editable </blockquote>
+ </section>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 676401 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+var gBlock1, gBlock2;
+
+var alwaysEnabledCommands = [
+ "contentReadOnly",
+ "copy",
+ "cut",
+ "enableInlineTableEditing",
+ "enableObjectResizing",
+ "insertBrOnReturn",
+ "selectAll",
+ "styleWithCSS",
+];
+
+function IsCommandEnabled(command) {
+ var enabled;
+
+ // non-editable div: should return false unless alwaysEnabled
+ window.getSelection().selectAllChildren(gBlock1);
+ enabled = document.queryCommandEnabled(command);
+ is(enabled, alwaysEnabledCommands.indexOf(command) != -1,
+ "'" + command + "' should not be enabled on a non-editable block.");
+
+ // editable div: should return true
+ window.getSelection().selectAllChildren(gBlock2);
+ enabled = document.queryCommandEnabled(command);
+ is(enabled, true, "'" + command + "' should be enabled on an editable block.");
+}
+
+function runTests() {
+ var i, commands;
+ gBlock1 = document.querySelector("#content section blockquote");
+ gBlock2 = document.querySelector("#content [contenteditable] blockquote");
+
+ // common commands: test with and without "styleWithCSS"
+ commands = [
+ "bold", "italic", "underline", "strikeThrough",
+ "subscript", "superscript", "foreColor", "backColor", "hiliteColor",
+ "fontName", "fontSize",
+ "justifyLeft", "justifyCenter", "justifyRight", "justifyFull",
+ "indent", "outdent",
+ "insertOrderedList", "insertUnorderedList", "insertParagraph",
+ "heading", "formatBlock",
+ "contentReadOnly", "createLink",
+ "decreaseFontSize", "increaseFontSize",
+ "insertHTML", "insertHorizontalRule", "insertImage",
+ "removeFormat", "selectAll", "styleWithCSS"
+ ];
+ document.execCommand("styleWithCSS", false, false);
+ for (i = 0; i < commands.length; i++)
+ IsCommandEnabled(commands[i]);
+ document.execCommand("styleWithCSS", false, true);
+ for (i = 0; i < commands.length; i++)
+ IsCommandEnabled(commands[i]);
+
+ // Mozilla-specific stuff
+ commands = ["enableInlineTableEditing", "enableObjectResizing", "insertBrOnReturn"];
+ for (i = 0; i < commands.length; i++)
+ IsCommandEnabled(commands[i]);
+
+ // These are privileged, and available only to chrome.
+ commands = ["paste"];
+ for (i = 0; i < commands.length; i++) {
+ is(document.queryCommandEnabled(commands[i]), false,
+ "Command should not be enabled for non-privileged code");
+ is(SpecialPowers.wrap(document).queryCommandEnabled(commands[i]), true,
+ "Command should be enabled for privileged code");
+ is(document.execCommand(commands[i], false, false), false, "Should return false: " + commands[i]);
+ is(SpecialPowers.wrap(document).execCommand(commands[i], false, false), true, "Should return true: " + commands[i]);
+ }
+
+ // delete/undo/redo -- we have to execute this commands because:
+ // * there's nothing to undo if we haven't modified the selection first
+ // * there's nothing to redo if we haven't undone something first
+ commands = ["delete", "undo", "redo"];
+ for (i = 0; i < commands.length; i++) {
+ IsCommandEnabled(commands[i]);
+ document.execCommand(commands[i], false, false);
+ }
+
+ // done
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug677752.html b/editor/libeditor/tests/test_bug677752.html
new file mode 100644
index 000000000..8809c1ead
--- /dev/null
+++ b/editor/libeditor/tests/test_bug677752.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677752
+-->
+<head>
+ <title>Test for Bug 677752</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677752">Mozilla Bug 677752</a>
+<p id="display"></p>
+<div id="content">
+ <section contenteditable> foo bar </section>
+ <div contenteditable> foo bar </div>
+ <p contenteditable> foo bar </p>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 677752 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function selectEditor(aEditor) {
+ aEditor.focus();
+ var selection = window.getSelection();
+ selection.selectAllChildren(aEditor);
+ selection.collapseToStart();
+}
+
+function runTests() {
+ var editor, node, initialHTML;
+ document.execCommand('styleWithCSS', false, true);
+
+ // editable <section>
+ editor = document.querySelector("section[contenteditable]");
+ initialHTML = editor.innerHTML;
+ selectEditor(editor);
+ // editable <section>: justify
+ document.execCommand("justifyright", false, null);
+ node = editor.querySelector("*");
+ is(node.nodeName.toLowerCase(), "div", "'justifyright' should create a <div> in the editable <section>.");
+ is(node.style.textAlign, "right", "'justifyright' should create a 'text-align: right' CSS rule.");
+ document.execCommand("undo", false, null);
+ // editable <section>: indent
+ document.execCommand("indent", false, null);
+ node = editor.querySelector("*");
+ is(node.nodeName.toLowerCase(), "div", "'indent' should create a <div> in the editable <section>.");
+ is(node.style.marginLeft, "40px", "'indent' should create a 'margin-left: 40px' CSS rule.");
+ // editable <section>: undo with outdent
+ // this should remove the whole <div> but only removing the CSS rule would be acceptable, too
+ document.execCommand("outdent", false, null);
+ is(editor.innerHTML, initialHTML, "'outdent' should undo the 'indent' action.");
+ // editable <section>: outdent again
+ document.execCommand("outdent", false, null);
+ is(editor.innerHTML, initialHTML, "another 'outdent' should not modify the <section> element.");
+
+ // editable <div>
+ editor = document.querySelector("div[contenteditable]");
+ initialHTML = editor.innerHTML;
+ selectEditor(editor);
+ // editable <div>: justify
+ document.execCommand("justifyright", false, null);
+ node = editor.querySelector("*");
+ is(node.nodeName.toLowerCase(), "div", "'justifyright' should create a <div> in the editable <div>.");
+ is(node.style.textAlign, "right", "'justifyright' should create a 'text-align: right' CSS rule.");
+ document.execCommand("undo", false, null);
+ // editable <div>: indent
+ document.execCommand("indent", false, null);
+ node = editor.querySelector("*");
+ is(node.nodeName.toLowerCase(), "div", "'indent' should create a <div> in the editable <div>.");
+ is(node.style.marginLeft, "40px", "'indent' should create a 'margin-left: 40px' CSS rule.");
+ // editable <div>: undo with outdent
+ // this should remove the whole <div> but only removing the CSS rule would be acceptable, too
+ document.execCommand("outdent", false, null);
+ is(editor.innerHTML, initialHTML, "'outdent' should undo the 'indent' action.");
+ // editable <div>: outdent again
+ document.execCommand("outdent", false, null);
+ is(editor.innerHTML, initialHTML, "another 'outdent' should not modify the <div> element.");
+
+ // editable <p>
+ // all block-level commands should be ignored (<p><div/></p> is not valid)
+ editor = document.querySelector("p[contenteditable]");
+ initialHTML = editor.innerHTML;
+ selectEditor(editor);
+ // editable <p>: justify
+ document.execCommand("justifyright", false, null);
+ is(editor.innerHTML, initialHTML, "'justifyright' should have no effect on <p>.");
+ // editable <p>: indent
+ document.execCommand("indent", false, null);
+ is(editor.innerHTML, initialHTML, "'indent' should have no effect on <p>.");
+ // editable <p>: outdent
+ document.execCommand("outdent", false, null);
+ is(editor.innerHTML, initialHTML, "'outdent' should have no effect on <p>.");
+
+ // done
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug681229.html b/editor/libeditor/tests/test_bug681229.html
new file mode 100644
index 000000000..6debcfde7
--- /dev/null
+++ b/editor/libeditor/tests/test_bug681229.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=681229
+-->
+<head>
+ <title>Test for Bug 681229</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=681229">Mozilla Bug 681229</a>
+<p id="display"></p>
+<div id="content">
+<textarea spellcheck="false"></textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 681229 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var t = document.querySelector("textarea");
+ t.focus();
+
+ const kValue = "a\r\nb";
+ const kExpectedValue = (navigator.platform.indexOf("Win") == 0) ?
+ "a\nb" : kValue;
+
+ SimpleTest.waitForClipboard(kExpectedValue,
+ function() {
+ SpecialPowers.clipboardCopyString(kValue);
+ },
+ function() {
+ synthesizeKey("V", {accelKey: true});
+ is(t.value, "a\nb", "The carriage return has been correctly sanitized");
+ SimpleTest.finish();
+ },
+ function() {
+ SimpleTest.finish();
+ }
+ );
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug686203.html b/editor/libeditor/tests/test_bug686203.html
new file mode 100644
index 000000000..c1a856aae
--- /dev/null
+++ b/editor/libeditor/tests/test_bug686203.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=686203
+-->
+
+<head>
+ <title>Test for Bug 686203</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=686203">Mozilla Bug 686203</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 686203 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ var ce = document.getElementById("ce");
+ var input = document.getElementById("input");
+ ce.focus();
+
+ var eventDetails = { button : 2 };
+ synthesizeMouseAtCenter(input, eventDetails);
+
+ synthesizeKey("Z", {});
+
+ /* check values */
+ is(input.value, "Z", "input correctly focused after right-click");
+ is(ce.textContent, "abc", "contenteditable correctly blurred after right-click on input");
+
+ SimpleTest.finish();
+ });
+ </script>
+ </pre>
+
+ <input type="text" value="" id="input" />
+ <div id="ce" contenteditable="true">abc</div>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug692520.html b/editor/libeditor/tests/test_bug692520.html
new file mode 100644
index 000000000..6dfefd8db
--- /dev/null
+++ b/editor/libeditor/tests/test_bug692520.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=692520
+-->
+<head>
+ <title>Test for Bug 692520</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=692520">Mozilla Bug 692520</a>
+<p id="display"></p>
+<div id="content">
+<textarea></textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 692520 **/
+function test(prop, value) {
+ var t = document.querySelector("textarea");
+ t.value = "testing";
+ t.selectionStart = 1;
+ t.selectionEnd = 3;
+ t.selectionDirection = "backward";
+ t.style.display = "";
+ document.body.clientWidth;
+ t.style.display = "none";
+ is(t[prop], value, "Correct value for the " + prop + " property");
+}
+
+test("selectionStart", 1);
+test("selectionEnd", 3);
+test("selectionDirection", "backward");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug697842.html b/editor/libeditor/tests/test_bug697842.html
new file mode 100644
index 000000000..463ff76dc
--- /dev/null
+++ b/editor/libeditor/tests/test_bug697842.html
@@ -0,0 +1,117 @@
+<!DOCTYPE>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=697842
+-->
+<head>
+ <title>Test for Bug 697842</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <p id="editor" contenteditable style="min-height: 1.5em;"></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 697842 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests()
+{
+ var editor = document.getElementById("editor");
+ editor.focus();
+
+ SimpleTest.executeSoon(function() {
+ var composingString = "";
+
+ function handler(aEvent) {
+ switch (aEvent.type) {
+ case "compositionstart":
+ // Selected string at starting composition must be empty in this test.
+ is(aEvent.data, "", "mismatch selected string");
+ break;
+ case "compositionupdate":
+ case "compositionend":
+ is(aEvent.data, composingString, "mismatch composition string");
+ break;
+ default:
+ break;
+ }
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+
+ editor.addEventListener("compositionstart", handler, true);
+ editor.addEventListener("compositionend", handler, true);
+ editor.addEventListener("compositionupdate", handler, true);
+ editor.addEventListener("text", handler, true);
+
+ // input first character
+ composingString = "\u306B";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": composingString,
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ // input second character
+ composingString = "\u306B\u3085";
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": composingString,
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ // convert them
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": composingString,
+ "clauses":
+ [
+ { "length": 2,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(editor.innerHTML, composingString,
+ "editor has unexpected result");
+
+ editor.removeEventListener("compositionstart", handler, true);
+ editor.removeEventListener("compositionend", handler, true);
+ editor.removeEventListener("compositionupdate", handler, true);
+ editor.removeEventListener("text", handler, true);
+
+ SimpleTest.finish();
+ });
+}
+
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug725069.html b/editor/libeditor/tests/test_bug725069.html
new file mode 100644
index 000000000..5096ede3c
--- /dev/null
+++ b/editor/libeditor/tests/test_bug725069.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=725069
+-->
+<head>
+ <title>Test for Bug 725069</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body contenteditable>abc<!-- XXX -->def<span></span>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725069">Mozilla Bug 725069</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 725069 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var body = document.querySelector("body");
+ is(body.firstChild.nodeType, body.TEXT_NODE, "The first node is a text node");
+ is(body.firstChild.nodeValue, "abc", "The first text node is there");
+ is(body.firstChild.nextSibling.nodeType, body.COMMENT_NODE, "The second node is a comment node");
+ is(body.firstChild.nextSibling.nodeValue, " XXX ", "The value of the comment node is not changed");
+ is(body.firstChild.nextSibling.nextSibling.nodeType, body.TEXT_NODE, "The last text node is a text node");
+ is(body.firstChild.nextSibling.nextSibling.nodeValue, "def", "The last next node is there");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug735059.html b/editor/libeditor/tests/test_bug735059.html
new file mode 100644
index 000000000..3b81ce48b
--- /dev/null
+++ b/editor/libeditor/tests/test_bug735059.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=735059
+-->
+<title>Test for Bug 735059</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=735059">Mozilla Bug 735059</a>
+<div id="display" contenteditable>foo</div>
+<pre id="test">
+<script>
+/** Test for Bug 735059 **/
+
+// Value defaults to the empty string, which evaluates to true, so this
+// disables CSS styling
+document.execCommand("usecss");
+getSelection().selectAllChildren(document.getElementById("display"));
+document.execCommand("bold");
+is(document.getElementById("display").innerHTML, "<b>foo</b>",
+ "execCommand() needs to work with only one parameter");
+</script>
+</pre>
diff --git a/editor/libeditor/tests/test_bug738366.html b/editor/libeditor/tests/test_bug738366.html
new file mode 100644
index 000000000..a54aec7a2
--- /dev/null
+++ b/editor/libeditor/tests/test_bug738366.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=738366
+-->
+<title>Test for Bug 738366</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=738366">Mozilla Bug 738366</a>
+<div id="display" contenteditable>foobarbaz</div>
+<script>
+/** Test for Bug 738366 **/
+
+getSelection().collapse(document.getElementById("display").firstChild, 3);
+getSelection().extend(document.getElementById("display").firstChild, 6);
+document.execCommand("bold");
+is(document.getElementById("display").innerHTML, "foo<b>bar</b>baz",
+ "styleWithCSS must default to false");
+document.execCommand("stylewithcss", false, "true");
+document.execCommand("bold");
+document.execCommand("bold");
+is(document.getElementById("display").innerHTML,
+ 'foo<span style="font-weight: bold;">bar</span>baz',
+ "styleWithCSS must be settable to true");
+</script>
diff --git a/editor/libeditor/tests/test_bug740784.html b/editor/libeditor/tests/test_bug740784.html
new file mode 100644
index 000000000..26c918241
--- /dev/null
+++ b/editor/libeditor/tests/test_bug740784.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=740784
+-->
+
+<head>
+ <title>Test for Bug 740784</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=740784">Mozilla Bug 740784</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 740784 **/
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ var t1 = $("t1");
+
+ t1.focus();
+ synthesizeKey("VK_END", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("Z", {accelKey: true});
+
+ // Was the former bogus node changed to a mozBR?
+ is(t1.value, "a", "trailing <br> correctly ignored");
+
+ SimpleTest.finish();
+ });
+ </script>
+ </pre>
+
+ <textarea id="t1" rows="2" columns="80">a</textarea>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug742261.html b/editor/libeditor/tests/test_bug742261.html
new file mode 100644
index 000000000..9ad41dd52
--- /dev/null
+++ b/editor/libeditor/tests/test_bug742261.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742261
+-->
+<title>Test for Bug 742261</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<body>
+<script>
+is(document.execCommandShowHelp, undefined,
+ "execCommandShowHelp shouldn't exist");
+is(document.queryCommandText, undefined,
+ "queryCommandText shouldn't exist");
+</script>
diff --git a/editor/libeditor/tests/test_bug757371.html b/editor/libeditor/tests/test_bug757371.html
new file mode 100644
index 000000000..5ca41a595
--- /dev/null
+++ b/editor/libeditor/tests/test_bug757371.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=757371
+-->
+<title>Test for Bug 757371</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=757371">Mozilla Bug 757371</a>
+<div contenteditable></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.querySelector("div");
+ div.focus();
+ getSelection().collapse(div, 0);
+ document.execCommand("bold");
+ sendString("ab");
+ sendKey("BACK_SPACE");
+ sendChar("b");
+
+ is(div.innerHTML, "<b>ab</b>");
+
+ SimpleTest.finish();
+});
+</script>
diff --git a/editor/libeditor/tests/test_bug757771.html b/editor/libeditor/tests/test_bug757771.html
new file mode 100644
index 000000000..9ef980b66
--- /dev/null
+++ b/editor/libeditor/tests/test_bug757771.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=757771
+-->
+<title>Test for Bug 757771</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=757771">Mozilla Bug 757771</a>
+<input value=foo maxlength=4>
+<input type=password value=password>
+<script>
+/** Test for Bug 757771 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var textInput = document.querySelector("input");
+ textInput.focus();
+ textInput.select();
+ sendString("abcde");
+
+ var passwordInput = document.querySelector("input + input");
+ passwordInput.focus();
+ passwordInput.select();
+ sendString("hunter2");
+
+ ok(true, "No real tests, just crashes/asserts");
+
+ SimpleTest.finish();
+});
+</script>
diff --git a/editor/libeditor/tests/test_bug767684.html b/editor/libeditor/tests/test_bug767684.html
new file mode 100644
index 000000000..0e65a88a7
--- /dev/null
+++ b/editor/libeditor/tests/test_bug767684.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=767684
+-->
+<title>Test for Bug 767684</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=767684">Mozilla Bug 767684</a>
+<div contenteditable>foo<b>bar</b>baz</div>
+<script>
+getSelection().selectAllChildren(document.querySelector("div"));
+document.execCommand("increaseFontSize");
+is(document.querySelector("div").innerHTML, "<big>foo<b>bar</b>baz</big>",
+ "All selected text must be embiggened");
+</script>
diff --git a/editor/libeditor/tests/test_bug772796.html b/editor/libeditor/tests/test_bug772796.html
new file mode 100644
index 000000000..9a15dccd2
--- /dev/null
+++ b/editor/libeditor/tests/test_bug772796.html
@@ -0,0 +1,223 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772796
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 772796</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style> .pre { white-space: pre } </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 772796</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="editable" contenteditable></div>
+
+<pre id="test">
+
+ <script type="application/javascript">
+ var tests = [
+/*00*/[ "<div>test</div><pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>" ],
+/*01*/[ "<div>test</div><pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>" ],
+/*02*/[ "<div>test</div><pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>" ],
+/*03*/[ "<div>test</div><pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>" ],
+/*04*/[ "<div>test</div><pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>" ],
+ /* The <br> after the foobar is unfortunate but is behaviour that hasn't changed in bug 772796. */
+/*05*/[ "<div>test</div><pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>" ],
+/*06*/[ "<div>test</div><pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>" ],
+
+ /*
+ * Some tests with block elements.
+ * Tests 07, 09 and 11 don't use "MoveBlock", they use "JoinNodesSmart".
+ * Test 11 is a pain: <div>foo\bar</div> is be joined to "test", losing the visible line break.
+ */
+/*07*/[ "<div>test</div><pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>" ],
+/*08*/[ "<div>test</div><pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>" ],
+/*09*/[ "<div>test</div><pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>" ],
+/*10*/[ "<div>test</div><pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>" ],
+/*11*/[ "<div>test</div><pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>" ], // BAD
+/*12*/[ "<div>test</div><pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>" ],
+
+ /*
+ * Repeating all tests above with the <pre> on a new line.
+ * We know that backspace doesn't work (bug 1190161). Third argument shows the current outcome.
+ */
+/*13-00*/[ "<div>test</div>\n<pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>",
+ "<div>test</div>foobar\n<pre>baz</pre>" ],
+/*14-01*/[ "<div>test</div>\n<pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>",
+ "<div>test</div><b>foobar\n</b><pre><b>baz</b></pre>" ],
+/*15-02*/[ "<div>test</div>\n<pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>",
+ "<div>test</div><b>foo</b>bar\n<pre>baz</pre>" ],
+/*16-03*/[ "<div>test</div>\n<pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>",
+ "<div>test</div><b>foo</b>\n<pre>bar</pre>" ],
+/*17-04*/[ "<div>test</div>\n<pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>",
+ "<div>test</div><b>foo\n</b><pre>bar\nbaz</pre>" ],
+/*18-05*/[ "<div>test</div>\n<pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>",
+ "<div>test</div>foobar<br><pre>baz</pre>" ],
+/*19-06*/[ "<div>test</div>\n<pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>",
+ "<div>test</div><b>foobar</b><br><pre><b>baz</b></pre>" ],
+/*20-07*/[ "<div>test</div>\n<pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>",
+ "<div>test</div>foobar<pre>baz</pre>" ],
+/*21-08*/[ "<div>test</div>\n<pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>",
+ "<div>test</div>foobar<pre><div>baz</div></pre>" ],
+/*22-09*/[ "<div>test</div>\n<pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>",
+ "<div>test</div>foobar<pre>baz\nfred</pre>" ],
+/*23-10*/[ "<div>test</div>\n<pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>",
+ "<div>test</div>foobar<pre><div>baz</div>\nfred</pre>" ],
+/*24-11*/[ "<div>test</div>\n<pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>", // BAD
+ "<div>test</div>foo\n<pre><div>bar</div>baz\nfred</pre>" ],
+/*25-12*/[ "<div>test</div>\n<pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>",
+ "<div>test</div>foo<pre><div>bar</div>baz\nfred</pre>" ],
+
+ /* Some tests without <div>. These exercise the MoveBlock "right in left" */
+/*26-00*/[ "test<pre>foobar\nbaz</pre>", "testfoobar\n<pre>baz</pre>" ],
+/*27-01*/[ "test<pre><b>foobar\nbaz</b></pre>", "test<b>foobar\n</b><pre><b>baz</b></pre>" ],
+/*28-02*/[ "test<pre><b>foo</b>bar\nbaz</pre>", "test<b>foo</b>bar\n<pre>baz</pre>" ],
+/*29-03*/[ "test<pre><b>foo</b>\nbar</pre>", "test<b>foo</b>\n<pre>bar</pre>" ],
+/*30-04*/[ "test<pre><b>foo\n</b>bar\nbaz</pre>", "test<b>foo\n</b><pre>bar\nbaz</pre>" ],
+/*31-05*/[ "test<pre>foobar<br>baz</pre>", "testfoobar<br><pre>baz</pre>" ],
+/*32-06*/[ "test<pre><b>foobar<br>baz</b></pre>", "test<b>foobar</b><br><pre><b>baz</b></pre>" ],
+/*33-07*/[ "test<pre><div>foobar</div>baz</pre>", "testfoobar<pre>baz</pre>" ],
+/*34-08*/[ "test<pre>foobar<div>baz</div></pre>", "testfoobar<pre><div>baz</div></pre>" ],
+/*35-09*/[ "test<pre><div>foobar</div>baz\nfred</pre>", "testfoobar<pre>baz\nfred</pre>" ],
+/*36-10*/[ "test<pre>foobar<div>baz</div>\nfred</pre>", "testfoobar<pre><div>baz</div>\nfred</pre>" ],
+/*37-11*/[ "test<pre><div>foo\nbar</div>baz\nfred</pre>", "testfoo\n<pre><div>bar</div>baz\nfred</pre>" ],
+/*38-12*/[ "test<pre>foo<div>bar</div>baz\nfred</pre>", "testfoo<pre><div>bar</div>baz\nfred</pre>" ],
+
+ /*
+ * Some tests with <span class="pre">. Again 07, 09 and 11 use "JoinNodesSmart".
+ * All these exercise MoveBlock "left in right". The "right" is the surrounding "contenteditable" div.
+ */
+/*39-00*/[ "<div>test</div><span class=\"pre\">foobar\nbaz</span>", "<div>test<span class=\"pre\">foobar\n</span></div><span class=\"pre\">baz</span>" ],
+/*40-01*/[ "<div>test</div><span class=\"pre\"><b>foobar\nbaz</b></span>", "<div>test<span class=\"pre\"><b>foobar\n</b></span></div><span class=\"pre\"><b>baz</b></span>" ],
+/*41-02*/[ "<div>test</div><span class=\"pre\"><b>foo</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo</b>bar\n</span></div><span class=\"pre\">baz</span>" ],
+/*42-03*/[ "<div>test</div><span class=\"pre\"><b>foo</b>\nbar</span>", "<div>test<span class=\"pre\"><b>foo</b>\n</span></div><span class=\"pre\">bar</span>" ],
+/*43-04*/[ "<div>test</div><span class=\"pre\"><b>foo\n</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo\n</b></span></div><span class=\"pre\">bar\nbaz</span>" ],
+/*44-05*/[ "<div>test</div><span class=\"pre\">foobar<br>baz</span>", "<div>test<span class=\"pre\">foobar</span><br><span class=\"pre\"></span></div><span class=\"pre\">baz</span>" ],
+/*45-06*/[ "<div>test</div><span class=\"pre\"><b>foobar<br>baz</b></span>", "<div>test<span class=\"pre\"><b>foobar</b></span><br><span class=\"pre\"></span></div><span class=\"pre\"><b>baz</b></span>" ],
+/*46-07*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz</span>", "<div>testfoobar</div><span class=\"pre\">baz</span>" ],
+/*47-08*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div></span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div></span>" ],
+/*48-09*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz\nfred</span>", "<div>testfoobar</div><span class=\"pre\">baz\nfred</span>" ],
+/*49-10*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div>\nfred</span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div>\nfred</span>" ],
+/*50-11*/[ "<div>test</div><span class=\"pre\"><div>foo\nbar</div>baz\nfred</span>", "<div>testfoo\nbar</div><span class=\"pre\">baz\nfred</span>" ], // BAD
+/*51-12*/[ "<div>test</div><span class=\"pre\">foo<div>bar</div>baz\nfred</span>", "<div>test<span class=\"pre\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>" ],
+
+ /* Some tests with <div class="pre">. */
+ /*
+ * The results are pretty ugly, since joining two <divs> sadly carrys the properties of the right to the left,
+ * but not in all cases: 07, 09, 11 are actually right. All cases use "JoinNodesSmart".
+ * Here we merely document the ugly behaviour. See bug 1191875 for more information.
+ */
+/*52-00*/[ "<div>test</div><div class=\"pre\">foobar\nbaz</div>", "<div class=\"pre\">testfoobar\nbaz</div>" ],
+/*53-01*/[ "<div>test</div><div class=\"pre\"><b>foobar\nbaz</b></div>", "<div class=\"pre\">test<b>foobar\nbaz</b></div>" ],
+/*54-02*/[ "<div>test</div><div class=\"pre\"><b>foo</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo</b>bar\nbaz</div>" ],
+/*55-03*/[ "<div>test</div><div class=\"pre\"><b>foo</b>\nbar</div>", "<div class=\"pre\">test<b>foo</b>\nbar</div>" ],
+/*56-04*/[ "<div>test</div><div class=\"pre\"><b>foo\n</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo\n</b>bar\nbaz</div>" ],
+/*57-05*/[ "<div>test</div><div class=\"pre\">foobar<br>baz</div>", "<div class=\"pre\">testfoobar<br>baz</div>" ],
+/*58-06*/[ "<div>test</div><div class=\"pre\"><b>foobar<br>baz</b></div>", "<div class=\"pre\">test<b>foobar<br>baz</b></div>" ],
+/*59-07*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz</div>", "<div>testfoobar</div><div class=\"pre\">baz</div>" ],
+/*60-08*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div></div>", "<div class=\"pre\">testfoobar<div>baz</div></div>" ],
+/*61-09*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz\nfred</div>", "<div>testfoobar</div><div class=\"pre\">baz\nfred</div>" ],
+/*62-10*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div>\nfred</div>", "<div class=\"pre\">testfoobar<div>baz</div>\nfred</div>" ],
+/*63-11*/[ "<div>test</div><div class=\"pre\"><div>foo\nbar</div>baz\nfred</div>", "<div>testfoo\nbar</div><div class=\"pre\">baz\nfred</div>" ], // BAD
+/*64-12*/[ "<div>test</div><div class=\"pre\">foo<div>bar</div>baz\nfred</div>", "<div class=\"pre\">testfoo<div>bar</div>baz\nfred</div>" ],
+
+ /* Some tests with lists. These exercise the MoveBlock "left in right". */
+/*65*/[ "<ul><pre><li>test</li>foobar\nbaz</pre></ul>", "<ul><pre><li>testfoobar\n</li>baz</pre></ul>" ],
+/*66*/[ "<ul><pre><li>test</li><b>foobar\nbaz</b></pre></ul>", "<ul><pre><li>test<b>foobar\n</b></li><b>baz</b></pre></ul>" ],
+/*67*/[ "<ul><pre><li>test</li><b>foo</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo</b>bar\n</li>baz</pre></ul>" ],
+/*68*/[ "<ul><pre><li>test</li><b>foo</b>\nbar</pre></ul>", "<ul><pre><li>test<b>foo</b>\n</li>bar</pre></ul>" ],
+/*69*/[ "<ul><pre><li>test</li><b>foo\n</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo\n</b></li>bar\nbaz</pre></ul>" ],
+
+ /* Last not least, some simple edge case tests. */
+/*70*/[ "<div>test</div><pre>foobar\n</pre>baz", "<div>testfoobar\n</div>baz" ],
+/*71*/[ "<div>test</div><pre>\nfoo\nbar</pre>", "<div>testfoo\n</div><pre>bar</pre>" ],
+/*72*/[ "<div>test</div><pre>\n\nfoo\nbar</pre>", "<div>test</div><pre>foo\nbar</pre>", "<div>test\n</div><pre>foo\nbar</pre>" ],
+ ];
+
+ /** Test for Bug 772796 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(function() {
+
+ var sel = window.getSelection();
+ var theEdit = document.getElementById("editable");
+ var testName;
+ var theDiv;
+
+ for (i = 0; i < tests.length; i++) {
+ testName = "test" + i.toString();
+ dump (testName+"\n");
+ dump (tests[i][0]+"\n");
+
+ /* Set up the selection. */
+ theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
+ theDiv = document.getElementById(testName);
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
+
+ /** First round: Forward delete. **/
+ synthesizeKey("VK_DELETE", {});
+ is(theDiv.innerHTML, tests[i][1], "delete(collapsed): inner HTML for " + testName);
+
+ /* Set up the selection. */
+ theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
+ theDiv = document.getElementById(testName);
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
+
+ /** Second round: Backspace. **/
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ if (tests[i].length == 2) {
+ is(theDiv.innerHTML, tests[i][1], "backspace: inner HTML for " + testName);
+ } else {
+ todo_is(theDiv.innerHTML, tests[i][1], "backspace(should be): inner HTML for " + testName);
+ is(theDiv.innerHTML, tests[i][2], "backspace(currently is): inner HTML for " + testName);
+ }
+
+ /* Set up the selection. */
+ theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
+ theDiv = document.getElementById(testName);
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
+
+ /** Third round: Delete with non-collapsed selection. **/
+ if (i == 72) {
+ // This test doesn't work, since we can't select only one newline using the right arrow key.
+ continue;
+ }
+ synthesizeKey("VK_LEFT", {});
+ /* Strangely enough we need to hit "right arrow" three times to select two characters. */
+ synthesizeKey("VK_RIGHT", {shiftKey:true});
+ synthesizeKey("VK_RIGHT", {shiftKey:true});
+ synthesizeKey("VK_RIGHT", {shiftKey:true});
+ synthesizeKey("VK_DELETE", {});
+
+ /* We always expect to the delete the "tf" in "testfoo". */
+ var expected = tests[i][1].replace("testfoo", "tesoo")
+ .replace("test<b>foo", "tes<b>oo")
+ .replace("test<span class=\"pre\">foo", "tes<span class=\"pre\">oo")
+ .replace("test<span class=\"pre\"><b>foo", "tes<span class=\"pre\"><b>oo");
+ is(theDiv.innerHTML, expected, "delete(non-collapsed): inner HTML for " + testName);
+ }
+
+ SimpleTest.finish();
+
+ });
+
+ </script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug773262.html b/editor/libeditor/tests/test_bug773262.html
new file mode 100644
index 000000000..b0dc82755
--- /dev/null
+++ b/editor/libeditor/tests/test_bug773262.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=773262
+-->
+<title>Test for Bug 773262</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=773262">Mozilla Bug 773262</a></p>
+<iframe></iframe>
+<script>
+function runTest(doc, desc) {
+ is(doc.queryCommandEnabled("undo"), false,
+ desc + ": Undo shouldn't be enabled yet");
+ is(doc.queryCommandEnabled("redo"), false,
+ desc + ": Redo shouldn't be enabled yet");
+ is(doc.body.innerHTML, "<p>Hello</p>", desc + ": Wrong initial innerHTML");
+
+ doc.getSelection().selectAllChildren(doc.body.firstChild);
+ doc.execCommand("bold");
+ is(doc.queryCommandEnabled("undo"), true,
+ desc + ": Undo should be enabled after bold");
+ is(doc.queryCommandEnabled("redo"), false,
+ desc + ": Redo still shouldn't be enabled");
+ is(doc.body.innerHTML, "<p><b>Hello</b></p>",
+ desc + ": Wrong innerHTML after bold");
+
+ doc.execCommand("undo");
+ is(doc.queryCommandEnabled("undo"), false,
+ desc + ": Undo should be disabled again");
+ is(doc.queryCommandEnabled("redo"), true,
+ desc + ": Redo should be enabled now");
+ is(doc.body.innerHTML, "<p>Hello</p>",
+ desc + ": Wrong innerHTML after undo");
+
+ doc.execCommand("redo");
+ is(doc.queryCommandEnabled("undo"), true,
+ desc + ": Undo should be enabled after redo");
+ is(doc.queryCommandEnabled("redo"), false,
+ desc + ": Redo should be disabled again");
+ is(doc.body.innerHTML, "<p><b>Hello</b></p>",
+ desc + ": Wrong innerHTML after redo");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var doc = document.querySelector("iframe").contentDocument;
+
+ // First turn on designMode and run the test like that, as a sanity check.
+ doc.body.innerHTML = "<p>Hello</p>";
+ doc.designMode = "on";
+ runTest(doc, "1");
+
+ // Now to test the actual bug: repeat all the above, but with designMode
+ // toggled. This should clear the undo history, so everything should be
+ // exactly as before.
+ doc.designMode = "off";
+ doc.body.innerHTML = "<p>Hello</p>";
+ doc.designMode = "on";
+ runTest(doc, "2");
+
+ SimpleTest.finish();
+});
+</script>
diff --git a/editor/libeditor/tests/test_bug780035.html b/editor/libeditor/tests/test_bug780035.html
new file mode 100644
index 000000000..7c99b9ff5
--- /dev/null
+++ b/editor/libeditor/tests/test_bug780035.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780035
+-->
+<title>Test for Bug 780035</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=780035">Mozilla Bug 780035</a>
+<div contenteditable style="font-size: 13.3333px"></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ document.querySelector("div").focus();
+ document.execCommand("stylewithcss", false, true);
+ sendKey("RETURN");
+ sendChar("x");
+ is(document.querySelector("div").innerHTML, "x<br>",
+ "No <font> tag should be generated");
+ SimpleTest.finish();
+});
+</script>
diff --git a/editor/libeditor/tests/test_bug780908.xul b/editor/libeditor/tests/test_bug780908.xul
new file mode 100644
index 000000000..312f02787
--- /dev/null
+++ b/editor/libeditor/tests/test_bug780908.xul
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin"
+ type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780908
+
+adapted from test_bug607584.xul by Kent James <kent@caspia.com>
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Mozilla Bug 780908" onload="runTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780908"
+ target="_blank">Mozilla Bug 780908</a>
+ <p/>
+ <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="editor"
+ type="content-primary"
+ editortype="html"
+ style="width: 400px; height: 100px; border: thin solid black"/>
+ <p/>
+ <pre id="test">
+ </pre>
+ </body>
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ function EditorContentListener(aEditor)
+ {
+ this.init(aEditor);
+ }
+
+ EditorContentListener.prototype = {
+ init : function(aEditor)
+ {
+ this.mEditor = aEditor;
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ var editor = this.mEditor.getEditor(this.mEditor.contentWindow);
+ if (editor) {
+ this.mEditor.focus();
+ editor instanceof Components.interfaces.nsIHTMLEditor;
+ editor.returnInParagraphCreatesNewParagraph = true;
+ source = "<html><body><table><head></table></body></html>";
+ editor.rebuildDocumentFromSource(source);
+ ok(true, "Don't crash when head appears after body");
+ source = "<html></head><head><body></body></html>";
+ editor.rebuildDocumentFromSource(source);
+ ok(true, "Don't crash when /head appears before head");
+ SimpleTest.finish();
+ progress.removeProgressListener(this);
+ }
+ }
+
+ },
+
+
+ onProgressChange : function(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress)
+ {
+ },
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState)
+ {
+ },
+
+ mEditor: null
+ };
+
+ var progress, progressListener;
+
+ function runTest() {
+ var newEditorElement = document.getElementById("editor");
+ newEditorElement.makeEditable("html", true);
+ var docShell = newEditorElement.boxObject.docShell;
+ progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);
+ progressListener = new EditorContentListener(newEditorElement);
+ progress.addProgressListener(progressListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ newEditorElement.setAttribute("src", "data:text/html,");
+ }
+]]>
+</script>
+</window>
diff --git a/editor/libeditor/tests/test_bug787432.html b/editor/libeditor/tests/test_bug787432.html
new file mode 100644
index 000000000..c73bb3c7e
--- /dev/null
+++ b/editor/libeditor/tests/test_bug787432.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=787432
+-->
+<title>Test for Bug 787432</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=787432">Mozilla Bug 787432</a>
+<div id="test" contenteditable><span class="insert">%</span><br></div>
+<script>
+var div = document.getElementById("test");
+getSelection().collapse(div.firstChild, 0);
+getSelection().extend(div.firstChild, 1);
+document.execCommand("inserttext", false, "x");
+is(div.innerHTML, '<span class="insert">x</span><br>',
+ "Empty <span> needs to not be removed");
+</script>
diff --git a/editor/libeditor/tests/test_bug790475.html b/editor/libeditor/tests/test_bug790475.html
new file mode 100644
index 000000000..d7685458b
--- /dev/null
+++ b/editor/libeditor/tests/test_bug790475.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=790475
+-->
+<head>
+ <title>Test for Bug 790475</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=790475">Mozilla Bug 790475</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 790475
+ *
+ * Tests that inline spell checking works properly through adjacent text nodes.
+ */
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+var gMisspeltWords;
+
+function getEditor() {
+ const Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(window);
+}
+
+function getSpellCheckSelection() {
+ var editor = getEditor();
+ var selcon = editor.selectionController;
+ return selcon.getSelection(selcon.SELECTION_SPELLCHECK);
+}
+
+function runTest() {
+ gMisspeltWords = [];
+ var edit = document.getElementById("edit");
+ edit.focus();
+
+ SimpleTest.executeSoon(function() {
+ gMisspeltWords = [];
+ is(isSpellingCheckOk(), true, "Should not find any misspellings yet.");
+
+ var newTextNode = document.createTextNode("ing string");
+ edit.appendChild(newTextNode);
+ var editor = getEditor();
+ var sel = editor.selection;
+ sel.collapse(newTextNode, newTextNode.textContent.length);
+ synthesizeKey("!", {});
+
+ edit.blur();
+
+ SimpleTest.executeSoon(function() {
+ is(isSpellingCheckOk(), true, "Should not have found any misspellings. ");
+ SimpleTest.finish();
+ });
+ });
+}
+
+function isSpellingCheckOk() {
+ var sel = getSpellCheckSelection();
+ var numWords = sel.rangeCount;
+
+ is(numWords, gMisspeltWords.length, "Correct number of misspellings and words.");
+
+ if (numWords != gMisspeltWords.length)
+ return false;
+
+ for (var i = 0; i < numWords; i++) {
+ var word = sel.getRangeAt(i);
+ is (word, gMisspeltWords[i], "Misspelling is what we think it is.");
+ if (word != gMisspeltWords[i])
+ return false;
+ }
+ return true;
+}
+
+</script>
+</pre>
+
+<div id="edit" contenteditable="true">This is a test</div>
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795418-2.html b/editor/libeditor/tests/test_bug795418-2.html
new file mode 100644
index 000000000..3f44900ee
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795418-2.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test #2 for Bug 772796</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 795418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="copySource">Copy this</div>
+<iframe src="data:application/xhtml+xml,<html contenteditable='' xmlns='http://www.w3.org/1999/xhtml'><span>AB</span></html>"></iframe>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 795418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("copySource");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Select the text from the text node in div.
+ var r = document.createRange();
+ r.setStart(div.firstChild, 0);
+ r.setEnd(div.firstChild, 9);
+ sel.addRange(r);
+
+ function checkResult() {
+ var iframe = document.querySelector("iframe");
+ var iframeWindow = iframe.contentWindow;
+ var theEdit = iframe.contentDocument.firstChild;
+ theEdit.offsetHeight;
+ is(theEdit.innerHTML,
+ "<blockquote xmlns=\"http://www.w3.org/1999/xhtml\" type=\"cite\">Copy this</blockquote><span xmlns=\"http://www.w3.org/1999/xhtml\">AB</span>",
+ "unexpected HTML for test");
+ SimpleTest.finish();
+ }
+
+ function pasteQuote() {
+ var iframe = document.querySelector("iframe");
+ var iframeWindow = iframe.contentWindow;
+ var theEdit = iframe.contentDocument.firstChild;
+ theEdit.offsetHeight;
+ iframeWindow.focus();
+ SimpleTest.waitForFocus(function() {
+ var iframeSel = iframeWindow.getSelection();
+ iframeSel.removeAllRanges();
+ let span = iframe.contentDocument.querySelector('span');
+ iframeSel.collapse(span, 1);
+
+ SpecialPowers.doCommand(iframeWindow, "cmd_pasteQuote");
+ setTimeout(checkResult, 0);
+ }, iframeWindow);
+ }
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ return true;
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ setTimeout(pasteQuote, 0);
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795418-3.html b/editor/libeditor/tests/test_bug795418-3.html
new file mode 100644
index 000000000..bbe1a58b3
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795418-3.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test #3 for Bug 772796</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 795418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="copySource">Copy this</div>
+<iframe src="data:text/html,<html><body><span>AB</span>"></iframe>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 795418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("copySource");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Select the text from the text node in div.
+ var r = document.createRange();
+ r.setStart(div.firstChild, 0);
+ r.setEnd(div.firstChild, 9);
+ sel.addRange(r);
+
+ function checkResult() {
+ var iframe = document.querySelector("iframe");
+ var iframeWindow = iframe.contentWindow;
+ var theEdit = iframe.contentDocument.body;
+ theEdit.offsetHeight;
+ is(theEdit.innerHTML,
+ "<span>AB<blockquote type=\"cite\">Copy this</blockquote></span>",
+ "unexpected HTML for test");
+ SimpleTest.finish();
+ }
+
+ function pasteQuote() {
+ var iframe = document.querySelector("iframe");
+ var iframeWindow = iframe.contentWindow;
+ var theEdit = iframe.contentDocument.body;
+ iframe.contentDocument.designMode='on';
+ iframe.contentDocument.body.offsetHeight;
+ iframeWindow.focus();
+ SimpleTest.waitForFocus(function() {
+ var iframeSel = iframeWindow.getSelection();
+ iframeSel.removeAllRanges();
+ iframeSel.collapse(theEdit.firstChild, 1);
+
+ SpecialPowers.doCommand(iframeWindow, "cmd_pasteQuote");
+ setTimeout(checkResult, 0);
+ }, iframeWindow);
+ }
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ return true;
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ setTimeout(pasteQuote, 0);
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795418-4.html b/editor/libeditor/tests/test_bug795418-4.html
new file mode 100644
index 000000000..6c1ae05d1
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795418-4.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test #4 for Bug 795418</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795418">Mozilla Bug 795418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="copySource">Copy this</div>
+<div id="editable" contenteditable style="display:grid">AB</div>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 795418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("copySource");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Select the text from the text node in div.
+ var r = document.createRange();
+ r.setStart(div.firstChild, 0);
+ r.setEnd(div.firstChild, 9);
+ sel.addRange(r);
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ var theEdit = document.getElementById("editable");
+ sel.collapse(theEdit.firstChild, 2);
+
+ SpecialPowers.doCommand(window, "cmd_paste");
+ is(theEdit.innerHTML,
+ "ABCopy this",
+ "unexpected HTML for test");
+ return true;
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ SimpleTest.finish();
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795418-5.html b/editor/libeditor/tests/test_bug795418-5.html
new file mode 100644
index 000000000..5ff90b15a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795418-5.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test #5 for Bug 795418</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795418">Mozilla Bug 795418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="copySource">Copy this</div>
+<div id="editable" contenteditable style="display:ruby">AB</div>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 795418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("copySource");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Select the text from the text node in div.
+ var r = document.createRange();
+ r.setStart(div.firstChild, 0);
+ r.setEnd(div.firstChild, 9);
+ sel.addRange(r);
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ var theEdit = document.getElementById("editable");
+ sel.collapse(theEdit.firstChild, 2);
+
+ SpecialPowers.doCommand(window, "cmd_paste");
+ is(theEdit.innerHTML,
+ "ABCopy this",
+ "unexpected HTML for test");
+ return true;
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ SimpleTest.finish();
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795418-6.html b/editor/libeditor/tests/test_bug795418-6.html
new file mode 100644
index 000000000..798a6534b
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795418-6.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test #5 for Bug 795418</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795418">Mozilla Bug 795418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="copySource">Copy this</div>
+<div id="editable" contenteditable style="display:table">AB</div>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 795418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("copySource");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Select the text from the text node in div.
+ var r = document.createRange();
+ r.setStart(div.firstChild, 0);
+ r.setEnd(div.firstChild, 9);
+ sel.addRange(r);
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ var theEdit = document.getElementById("editable");
+ sel.collapse(theEdit.firstChild, 2);
+
+ SpecialPowers.doCommand(window, "cmd_pasteQuote");
+ is(theEdit.innerHTML,
+ "AB<blockquote type=\"cite\">Copy this</blockquote>",
+ "unexpected HTML for test");
+ return true;
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ SimpleTest.finish();
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795418.html b/editor/libeditor/tests/test_bug795418.html
new file mode 100644
index 000000000..1db8cf026
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795418.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795418
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 795418</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795418">Mozilla Bug 795418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div id="copySource">Copy this</div>
+<div id="editable" contenteditable><span>AB</span></div>
+
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 795418 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var div = document.getElementById("copySource");
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Select the text from the text node in div.
+ var r = document.createRange();
+ r.setStart(div.firstChild, 0);
+ r.setEnd(div.firstChild, 9);
+ sel.addRange(r);
+
+ SimpleTest.waitForClipboard(
+ function compare(value) {
+ var theEdit = document.getElementById("editable");
+ sel.collapse(theEdit.firstChild, 1);
+
+ SpecialPowers.doCommand(window, "cmd_pasteQuote");
+ is(theEdit.innerHTML,
+ "<span>AB<blockquote type=\"cite\">Copy this</blockquote></span>",
+ "unexpected HTML for test");
+ return true;
+ },
+ function setup() {
+ synthesizeKey("C", {accelKey: true});
+ },
+ function onSuccess() {
+ SimpleTest.finish();
+ },
+ function onFailure() {
+ SimpleTest.finish();
+ },
+ "text/html"
+ );
+});
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug795785.html b/editor/libeditor/tests/test_bug795785.html
new file mode 100644
index 000000000..5f93d5142
--- /dev/null
+++ b/editor/libeditor/tests/test_bug795785.html
@@ -0,0 +1,168 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795785
+-->
+<head>
+ <title>Test for Bug 795785</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795785">Mozilla Bug 795785</a>
+<div id="display">
+ <textarea id="textarea" style="overflow: hidden; height: 3em; width: 5em; word-wrap: normal;"></textarea>
+ <div id="div" contenteditable style="overflow: hidden; height: 3em; width: 5em;"></div>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test uses setTimeouts in order to fix an intermittent failure.");
+
+// Turn off spatial navigation because it hijacks arrow key events and VK_RETURN
+// events.
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, runTests);
+});
+var textarea = document.getElementById("textarea");
+var div = document.getElementById("div");
+
+function hitEventLoop(aFunc, aTimes)
+{
+ if (--aTimes) {
+ setTimeout(hitEventLoop, 0, aFunc, aTimes);
+ } else {
+ setTimeout(aFunc, 100);
+ }
+}
+
+function doKeyEventTest(aElement, aElementDescription, aCallback)
+{
+ aElement.focus();
+ aElement.scrollTop = 0;
+ hitEventLoop(function () {
+ is(aElement.scrollTop, 0,
+ aElementDescription + "'s scrollTop isn't 0");
+ synthesizeKey("VK_RETURN", { });
+ synthesizeKey("VK_RETURN", { });
+ synthesizeKey("VK_RETURN", { });
+ synthesizeKey("VK_RETURN", { });
+ synthesizeKey("VK_RETURN", { });
+ synthesizeKey("VK_RETURN", { });
+ hitEventLoop(function () {
+ isnot(aElement.scrollTop, 0,
+ aElementDescription + " was not scrolled by inserting line breaks");
+ var scrollTop = aElement.scrollTop;
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_UP", { });
+ synthesizeKey("VK_UP", { });
+ hitEventLoop(function () {
+ isnot(aElement.scrollTop, scrollTop,
+ aElementDescription + " was not scrolled by up key events");
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ synthesizeKey("VK_DOWN", { });
+ hitEventLoop(function () {
+ is(aElement.scrollTop, scrollTop,
+ aElementDescription + " was not scrolled by down key events");
+ var longWord = "aaaaaaaaaaaaaaaaaaaa";
+ sendString(longWord);
+ hitEventLoop(function () {
+ isnot(aElement.scrollLeft, 0,
+ aElementDescription + " was not scrolled by typing long word");
+ var scrollLeft = aElement.scrollLeft;
+ var i;
+ for (i = 0; i < longWord.length; i++) {
+ synthesizeKey("VK_LEFT", { });
+ }
+ hitEventLoop(function () {
+ isnot(aElement.scrollLeft, scrollLeft,
+ aElementDescription + " was not scrolled by left key events");
+ for (i = 0; i < longWord.length; i++) {
+ synthesizeKey("VK_RIGHT", { });
+ }
+ hitEventLoop(function () {
+ is(aElement.scrollLeft, scrollLeft,
+ aElementDescription + " was not scrolled by right key events");
+ aCallback();
+ }, 20);
+ }, 20);
+ }, 20);
+ }, 20);
+ }, 20);
+ }, 20);
+ }, 20);
+}
+
+function doCompositionTest(aElement, aElementDescription, aCallback)
+{
+ aElement.focus();
+ aElement.scrollTop = 0;
+ hitEventLoop(function () {
+ is(aElement.scrollTop, 0,
+ aElementDescription + "'s scrollTop isn't 0");
+ var str = "Web \u958b\u767a\u8005\u306e\u7686\u3055\u3093\u306f\u3001" +
+ "Firefox \u306b\u5b9f\u88c5\u3055\u308c\u3066\u3044\u308b HTML5" +
+ " \u3084 CSS \u306e\u65b0\u6a5f\u80fd\u3092\u6d3b\u7528\u3059" +
+ "\u308b\u3053\u3068\u3067\u3001\u9b45\u529b\u3042\u308b Web " +
+ "\u30b5\u30a4\u30c8\u3084\u9769\u65b0\u7684\u306a Web \u30a2" +
+ "\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u3088\u308a" +
+ "\u77ed\u6642\u9593\u3067\u7c21\u5358\u306b\u4f5c\u6210\u3067" +
+ "\u304d\u307e\u3059\u3002";
+ synthesizeCompositionChange({
+ composition: {
+ string: str,
+ clauses: [
+ { length: str.length, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: str.length, length: 0 }
+ });
+ hitEventLoop(function () {
+ isnot(aElement.scrollTop, 0,
+ aElementDescription + " was not scrolled by composition");
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+ hitEventLoop(function () {
+ is(aElement.scrollTop, 0,
+ aElementDescription + " was not scrolled back to the top by canceling composition");
+ aCallback();
+ }, 20);
+ }, 20);
+ }, 20);
+}
+
+function runTests()
+{
+ doKeyEventTest(textarea, "textarea",
+ function () {
+ textarea.value = "";
+ doKeyEventTest(div, "div (contenteditable)",
+ function () {
+ div.innerHTML = "";
+ doCompositionTest(textarea, "textarea",
+ function () {
+ doCompositionTest(div, "div (contenteditable)",
+ function () {
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_bug796839.html b/editor/libeditor/tests/test_bug796839.html
new file mode 100644
index 000000000..be4be316c
--- /dev/null
+++ b/editor/libeditor/tests/test_bug796839.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=796839
+-->
+<title>Test for Bug 796839</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=796839">Mozilla Bug 796839</a>
+<div id="test" contenteditable><br></div>
+<script>
+var div = document.getElementById("test");
+var text = document.createTextNode("");
+div.insertBefore(text, div.firstChild);
+getSelection().collapse(text, 0);
+document.execCommand("inserthtml", false, "x");
+is(div.textContent, 'x', "Empty textnodes should be editable");
+</script>
diff --git a/editor/libeditor/tests/test_bug830600.html b/editor/libeditor/tests/test_bug830600.html
new file mode 100644
index 000000000..39ced297a
--- /dev/null
+++ b/editor/libeditor/tests/test_bug830600.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=830600
+-->
+<head>
+ <title>Test for Bug 830600</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830600">Mozilla Bug 830600</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <input type="text" id="t1" />
+ <pre id="test">
+ <script type="application/javascript;version=1.7">
+
+ /** Test for Bug 830600 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ const Ci = SpecialPowers.Ci;
+ function test(str, expected, callback) {
+ var t = document.getElementById("t1");
+ SpecialPowers.wrap(t).QueryInterface(Ci.nsIDOMNSEditableElement);
+ t.focus();
+ t.value = "";
+ var editor = SpecialPowers.wrap(t).editor;
+ editor.QueryInterface(Ci.nsIPlaintextEditor);
+ var origNewlineHandling = editor.newlineHandling;
+ editor.newlineHandling = Ci.nsIPlaintextEditor.eNewlinesStripSurroundingWhitespace
+ SimpleTest.waitForClipboard(str,
+ function() {
+ SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(str);
+ },
+ function() {
+ synthesizeKey("V", {accelKey: true});
+ is(t.value, expected, "New line handling works correctly");
+ t.value = "";
+ callback();
+ },
+ function() {
+ ok(false, "Failed to copy the string");
+ SimpleTest.finish();
+ }
+ );
+ }
+
+ function runNextTest() {
+ if (tests.length) {
+ var currentTest = tests.shift();
+ test(currentTest[0], currentTest[1], runNextTest);
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ var tests = [
+ ["abc", "abc"],
+ ["\n", ""],
+ [" \n", ""],
+ ["\n ", ""],
+ [" \n ", ""],
+ [" a", " a"],
+ ["a ", "a "],
+ [" a ", " a "],
+ [" \nabc", "abc"],
+ ["\n abc", "abc"],
+ [" \n abc", "abc"],
+ [" \nabc ", "abc "],
+ ["\n abc ", "abc "],
+ [" \n abc ", "abc "],
+ ["abc\n ", "abc"],
+ ["abc \n", "abc"],
+ ["abc \n ", "abc"],
+ [" abc\n ", " abc"],
+ [" abc \n", " abc"],
+ [" abc \n ", " abc"],
+ [" abc \n def \n ", " abcdef"],
+ ["\n abc \n def \n ", "abcdef"],
+ [" \n abc \n def ", "abcdef "],
+ [" abc\n\ndef ", " abcdef "],
+ [" abc \n\n def ", " abcdef "],
+ ];
+
+ runNextTest();
+ });
+
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug832025.html b/editor/libeditor/tests/test_bug832025.html
new file mode 100644
index 000000000..40f4f4734
--- /dev/null
+++ b/editor/libeditor/tests/test_bug832025.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=832025
+-->
+<head>
+ <title>Test for Bug 832025</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=832025">Mozilla Bug 832025</a>
+<div id="test" contenteditable="true">header1</div>
+<script type="application/javascript">
+
+/**
+ * Test for Bug 832025
+ *
+ */
+
+document.execCommand("stylewithcss", false, "true");
+var test = document.getElementById("test");
+test.focus();
+
+// place caret at end of editable area
+var sel = getSelection();
+sel.collapse(test, test.childNodes.length);
+
+// make it a H1
+document.execCommand("heading", false, "H1");
+// simulate a CR key
+sendKey("return");
+// insert some text
+document.execCommand("insertText", false, "abc");
+
+is(test.innerHTML == '<h1>header1</h1><p>abc<br></p>',
+ true, "A paragraph automatically created after a CR at the end of an H1 should not be bold");
+
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug850043.html b/editor/libeditor/tests/test_bug850043.html
new file mode 100644
index 000000000..b811c86a6
--- /dev/null
+++ b/editor/libeditor/tests/test_bug850043.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=850043
+-->
+<head>
+ <title>Test for Bug 850043</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=850043">Mozilla Bug 850043</a>
+<div id="display">
+<textarea id="textarea">b&#x9080;&#xe010f;&#x8fba;&#xe0101;</textarea>
+<div contenteditable id="edit">b&#x9080;&#xe010f;&#x8fba;&#xe0101;</div>
+</div>
+<div id="content" style="display: none">
+</div>
+
+<pre id="test">
+</pre>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(() => {
+ let fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"].
+ getService(SpecialPowers.Ci.nsIFocusManager);
+
+ let element = document.getElementById("textarea");
+ element.setSelectionRange(element.value.length, element.value.length);
+ element.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), element, "failed to move focus");
+
+ synthesizeKey("VK_END", { });
+ synthesizeKey("a", { });
+ is(element.value, "b\u{9080}\u{e010f}\u{8fba}\u{e0101}a", "a isn't last character");
+
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(element.value, 'b', "cannot remove all IVS characters");
+
+ element = document.getElementById("edit");
+ element.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), element, "failed to move focus");
+
+ let sel = window.getSelection();
+ sel.collapse(element.childNodes[0], element.textContent.length);
+
+ synthesizeKey("a", { });
+ is(element.textContent, "b\u{9080}\u{e010f}\u{8fba}\u{e0101}a", "a isn't last character");
+
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(element.textContent, 'b', "cannot remove all IVS characters");
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug857487.html b/editor/libeditor/tests/test_bug857487.html
new file mode 100644
index 000000000..a3746d44c
--- /dev/null
+++ b/editor/libeditor/tests/test_bug857487.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=857487
+-->
+<head>
+ <title>Test for Bug 857487</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=857487">Mozilla Bug 857487</a>
+<div id="edit" contenteditable="true">
+ <table id="table" border="1" width="100%">
+ <tbody>
+ <tr>
+ <td>a</td>
+ <td>b</td>
+ <td>c</td>
+ </tr>
+ <tr>
+ <td>d</td>
+ <td id="cell">e</td>
+ <td>f</td>
+ </tr>
+ <tr>
+ <td>g</td>
+ <td>h</td>
+ <td>i</td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+<script type="application/javascript">
+
+/**
+ * Test for Bug 857487
+ *
+ * Tests that removing a table row through nsIHTMLEditor works
+ */
+
+function getEditor() {
+ const Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
+}
+
+var cell = document.getElementById("cell");
+cell.focus();
+
+// place caret at end of center cell
+var sel = getSelection();
+sel.collapse(cell, cell.childNodes.length);
+
+var editor = getEditor();
+editor.deleteTableRow(1);
+
+var table = document.getElementById("table");
+
+is(table.innerHTML == "\n <tbody>\n <tr>\n <td>a</td>\n <td>b</td>\n <td>c</td>\n </tr>\n \n <tr>\n <td>g</td>\n <td>h</td>\n <td>i</td>\n </tr>\n </tbody>\n ",
+ true, "editor.deleteTableRow(1) should delete the row containing the selection");
+
+</script>
+
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug858918.html b/editor/libeditor/tests/test_bug858918.html
new file mode 100644
index 000000000..46f841bbc
--- /dev/null
+++ b/editor/libeditor/tests/test_bug858918.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=858918
+-->
+<title>Test for Bug 858918</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858918">Mozilla Bug 858918</a>
+<span contenteditable style="display:block;min-height:1em"></span>
+<script>
+var span = document.querySelector("span");
+getSelection().collapse(span, 0);
+document.execCommand("inserthtml", false, "<div>doesn't go in span</div>");
+is(span.innerHTML, "<div>doesn't go in span</div>");
+</script>
diff --git a/editor/libeditor/tests/test_bug915962.html b/editor/libeditor/tests/test_bug915962.html
new file mode 100644
index 000000000..32968b310
--- /dev/null
+++ b/editor/libeditor/tests/test_bug915962.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=915962
+-->
+<head>
+ <title>Test for Bug 915962</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915962">Mozilla Bug 915962</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 915962 **/
+
+var smoothScrollPref = "general.smoothScroll";
+SimpleTest.waitForExplicitFinish();
+var win = window.open("file_bug915962.html", "_blank",
+ "width=600,height=600,scrollbars=yes");
+
+// grab the timer right at the start
+var cwu = SpecialPowers.getDOMWindowUtils(win);
+function step() {
+ cwu.advanceTimeAndRefresh(100);
+}
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, startTest);
+}, win);
+function startTest() {
+ // Make sure that pressing Space when a tabindex=-1 element is focused
+ // will scroll the page.
+ var button = win.document.querySelector("button");
+ var sc = win.document.querySelector("div");
+ sc.focus();
+ is(win.scrollY, 0, "Sanity check");
+ synthesizeKey(" ", {}, win);
+
+ step();
+
+ isnot(win.scrollY, 0, "Page is scrolled down");
+ var oldY = win.scrollY;
+ synthesizeKey(" ", {shiftKey: true}, win);
+
+ step();
+
+ ok(win.scrollY < oldY, "Page is scrolled up");
+
+ // Make sure that pressing Space when a tabindex=-1 element is focused
+ // will not scroll the page, and will activate the element.
+ button.focus();
+ var clicked = false;
+ button.onclick = () => clicked = true;
+ oldY = win.scrollY;
+ synthesizeKey(" ", {}, win);
+
+ step();
+
+ ok(win.scrollY <= oldY, "Page is not scrolled down");
+ ok(clicked, "The button should be clicked");
+ synthesizeKey("VK_TAB", {}, win);
+
+ step();
+
+ oldY = win.scrollY;
+ synthesizeKey(" ", {}, win);
+
+ step()
+
+ ok(win.scrollY >= oldY, "Page is scrolled down");
+
+ win.close();
+ cwu.restoreNormalRefresh();
+
+ win = window.open("file_bug915962.html", "_blank",
+ "width=600,height=600,scrollbars=yes");
+ cwu = SpecialPowers.getDOMWindowUtils(win);
+ SimpleTest.waitForFocus(function() {
+ is(win.scrollY, 0, "Sanity check");
+ synthesizeKey(" ", {}, win);
+
+ step();
+
+ isnot(win.scrollY, 0, "Page is scrolled down without crashing");
+
+ win.close();
+ cwu.restoreNormalRefresh();
+
+ SimpleTest.finish();
+ }, win);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug966155.html b/editor/libeditor/tests/test_bug966155.html
new file mode 100644
index 000000000..524b15d69
--- /dev/null
+++ b/editor/libeditor/tests/test_bug966155.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=966155
+-->
+<head>
+ <title>Test for Bug 966155</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966155">Mozilla Bug 966155</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var win = window.open("data:text/html,<input><iframe onload=\"contentDocument.designMode = 'on';\">", "", "test-966155");
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ runTest(win);
+ }, false);
+});
+
+function runTest(win) {
+ SimpleTest.waitForFocus(function() {
+ var doc = win.document;
+ var iframe = doc.querySelector("iframe");
+ var iframeDoc = iframe.contentDocument;
+ var input = doc.querySelector("input");
+ iframe.focus();
+ iframeDoc.body.focus();
+ // Type some text
+ "test".split("").forEach(function(letter) {
+ synthesizeKey(letter, {}, win);
+ });
+ is(iframeDoc.body.textContent, "test", "entered the text");
+ // focus the input box
+ input.focus();
+ // press tab
+ synthesizeKey("VK_TAB", {}, win);
+ // Now press Ctrl+Backspace
+ synthesizeKey("VK_BACK_SPACE", {ctrlKey: true}, win);
+ is(iframeDoc.body.textContent, "", "deleted the text");
+ win.close();
+ SimpleTest.finish();
+ }, win);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug966552.html b/editor/libeditor/tests/test_bug966552.html
new file mode 100644
index 000000000..3d0ec5fe3
--- /dev/null
+++ b/editor/libeditor/tests/test_bug966552.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=966552
+-->
+<head>
+ <title>Test for Bug 966552</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966552">Mozilla Bug 966552</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var win = window.open("data:text/html,<body onload=\"document.designMode='on'\">test</body>", "", "test-966552");
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ runTest(win);
+ }, false);
+});
+
+function runTest(win) {
+ SimpleTest.waitForFocus(function() {
+ var doc = win.document;
+ var sel = win.getSelection();
+ doc.body.focus();
+ sel.collapse(doc.body.firstChild, 2);
+ synthesizeKey("VK_BACK_SPACE", {ctrlKey: true}, win);
+ is(doc.body.textContent, "st");
+ win.close();
+ SimpleTest.finish();
+ }, win);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug974309.html b/editor/libeditor/tests/test_bug974309.html
new file mode 100644
index 000000000..e3caa87fb
--- /dev/null
+++ b/editor/libeditor/tests/test_bug974309.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=974309
+-->
+<head>
+ <title>Test for Bug 974309</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=974309">Mozilla Bug 974309</a>
+<div id="edit_not_table_parent" contenteditable="true"></div>
+<div>
+ <table id="table" border="1" width="100%">
+ <tbody>
+ <tr>
+ <td>a</td>
+ <td>b</td>
+ <td>c</td>
+ </tr>
+ <tr>
+ <td>d</td>
+ <td id="cell">e</td>
+ <td>f</td>
+ </tr>
+ <tr>
+ <td>g</td>
+ <td>h</td>
+ <td>i</td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+<script type="application/javascript">
+
+/**
+ * Test for Bug 974309
+ *
+ * Tests that editing a table row fails when the table or row is _not_ a child of a contenteditable node.
+ * See bug 857487 for tests that cover when the table or row _is_ a child of a contenteditable node.
+ */
+
+function getEditor() {
+ const Ci = SpecialPowers.Ci;
+ var editingSession = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
+}
+
+var cell = document.getElementById("cell");
+cell.focus();
+
+// place caret at end of center cell
+var sel = getSelection();
+sel.collapse(cell, cell.childNodes.length);
+
+var table = document.getElementById("table");
+
+var tableHTML = table.innerHTML;
+
+var editor = getEditor();
+editor.deleteTableRow(1);
+
+is(table.innerHTML == tableHTML, true, "editor should not modify non-editable table" );
+
+isnot(table.innerHTML == "\n <tbody>\n <tr>\n <td>a</td>\n <td>b</td>\n <td>c</td>\n </tr>\n \n <tr>\n <td>g</td>\n <td>h</td>\n <td>i</td>\n </tr>\n </tbody>\n ",
+ true, "editor.deleteTableRow(1) should not delete a non-editable row containing the selection");
+
+</script>
+
+
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_bug998188.html b/editor/libeditor/tests/test_bug998188.html
new file mode 100644
index 000000000..2d167f0bd
--- /dev/null
+++ b/editor/libeditor/tests/test_bug998188.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565392
+-->
+<head>
+ <title>Test for Bug 998188</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=998188">Mozilla Bug 998188</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="editor" contenteditable>abc</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 998188 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests()
+{
+ var editor = document.getElementById("editor");
+ editor.focus();
+
+ var textNode1 = document.createTextNode("def");
+ var textNode2 = document.createTextNode("ghi");
+
+ editor.appendChild(textNode1);
+ editor.appendChild(textNode2);
+
+ window.getSelection().collapse(textNode2, 3);
+
+ for (var i = 0; i < 9; i++) {
+ var caretRect = synthesizeQueryCaretRect(i);
+ ok(caretRect.succeeded, "QueryCaretRect should succeeded (" + i + ")");
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_composition_event_created_in_chrome.html b/editor/libeditor/tests/test_composition_event_created_in_chrome.html
new file mode 100644
index 000000000..18b72ccd4
--- /dev/null
+++ b/editor/libeditor/tests/test_composition_event_created_in_chrome.html
@@ -0,0 +1,82 @@
+<!doctype html>
+<html>
+
+<head>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+
+<input id="input">
+
+<script type="application/javascript">
+
+// In nsEditorEventListener, when listening event is not created with proper
+// event interface, it asserts the fact.
+SimpleTest.waitForExplicitFinish();
+
+var gInputElement = document.getElementById("input");
+
+function getEditorIMESupport(aInputElement)
+{
+ var editableElement = SpecialPowers.wrap(aInputElement).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement);
+ ok(editableElement, "The input element doesn't have nsIDOMNSEditableElement interface");
+ ok(editableElement.editor, "There is no editor for the input element");
+ var editorIMESupport = SpecialPowers.wrap(editableElement).editor.QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport);
+ ok(editorIMESupport, "The input element doesn't have nsIEditorIMESupport interface");
+ return editorIMESupport;
+}
+
+var gEditorIMESupport;
+
+function testNotGenerateCompositionByCreatedEvents(aEventInterface)
+{
+ var compositionEvent = document.createEvent(aEventInterface);
+ if (compositionEvent.initCompositionEvent) {
+ compositionEvent.initCompositionEvent("compositionstart", true, true, window, "", "");
+ } else if (compositionEvent.initMouseEvent) {
+ compositionEvent.initMouseEvent("compositionstart", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ }
+ gInputElement.dispatchEvent(compositionEvent);
+ ok(!gEditorIMESupport.composing, "Composition shouldn't be started with a created compositionstart event (" + aEventInterface + ")");
+
+ compositionEvent = document.createEvent(aEventInterface);
+ if (compositionEvent.initCompositionEvent) {
+ compositionEvent.initCompositionEvent("compositionupdate", true, false, window, "abc", "");
+ } else if (compositionEvent.initMouseEvent) {
+ compositionEvent.initMouseEvent("compositionupdate", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ }
+ gInputElement.dispatchEvent(compositionEvent);
+ ok(!gEditorIMESupport.composing, "Composition shouldn't be started with a created compositionupdate event (" + aEventInterface + ")");
+ is(gInputElement.value, "", "Input element shouldn't be modified with a created compositionupdate event (" + aEventInterface + ")");
+
+ compositionEvent = document.createEvent(aEventInterface);
+ if (compositionEvent.initCompositionEvent) {
+ compositionEvent.initCompositionEvent("compositionend", true, false, window, "abc", "");
+ } else if (compositionEvent.initMouseEvent) {
+ compositionEvent.initMouseEvent("compositionend", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ }
+ gInputElement.dispatchEvent(compositionEvent);
+ ok(!gEditorIMESupport.composing, "Composition shouldn't be committed with a created compositionend event (" + aEventInterface + ")");
+ is(gInputElement.value, "", "Input element shouldn't be committed with a created compositionend event (" + aEventInterface + ")");
+}
+
+function doTests()
+{
+ gInputElement.focus();
+ gEditorIMESupport = getEditorIMESupport(gInputElement);
+
+ testNotGenerateCompositionByCreatedEvents("CompositionEvent");
+ testNotGenerateCompositionByCreatedEvents("MouseEvent");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(doTests);
+
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_contenteditable_focus.html b/editor/libeditor/tests/test_contenteditable_focus.html
new file mode 100644
index 000000000..051ac7b2f
--- /dev/null
+++ b/editor/libeditor/tests/test_contenteditable_focus.html
@@ -0,0 +1,209 @@
+<html>
+<head>
+ <title>Test for contenteditable focus</title>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ First text in this document.<br>
+ <input id="inputText" type="text"><br>
+ <input id="inputTextReadonly" type="text" readonly><br>
+ <input id="inputButton" type="button" value="input[type=button]"><br>
+ <button id="button">button</button><br>
+ <div id="editor" contenteditable="true">
+ editable contents.<br>
+ <input id="inputTextInEditor" type="text"><br>
+ <input id="inputTextReadonlyInEditor" type="text" readonly><br>
+ <input id="inputButtonInEditor" type="button" value="input[type=button]"><br>
+ <button id="buttonInEditor">button</button><br>
+ <div id="noeditableInEditor" contenteditable="false">
+ <span id="spanInNoneditableInEditor">span element in noneditable in editor</span><br>
+ <input id="inputTextInNoneditableInEditor" type="text"><br>
+ <input id="inputTextReadonlyInNoneditableInEditor" type="text" readonly><br>
+ <input id="inputButtonInNoneditableInEditor" type="button" value="input[type=button]"><br>
+ <button id="buttonInNoneditableInEditor">button</button><br>
+ </div>
+ <span id="spanInEditor">span element in editor</span><br>
+ </div>
+ <div id="otherEditor" contenteditable="true">
+ other editor.
+ </div>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+function runTests()
+{
+ runTestsInternal();
+ SimpleTest.finish();
+}
+
+function runTestsInternal()
+{
+ var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"].
+ getService(SpecialPowers.Ci.nsIFocusManager);
+ // XXX using selCon for checking the visibility of the caret, however,
+ // selCon is shared in document, cannot get the element of owner of the
+ // caret from javascript?
+ var selCon = SpecialPowers.wrap(window).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsISelectionDisplay).
+ QueryInterface(SpecialPowers.Ci.nsISelectionController);
+ var selection = window.getSelection();
+
+ var inputText = document.getElementById("inputText");
+ var inputTextReadonly = document.getElementById("inputTextReadonly");
+ var inputButton = document.getElementById("inputButton");
+ var button = document.getElementById("button");
+ var editor = document.getElementById("editor");
+ var inputTextInEditor = document.getElementById("inputTextInEditor");
+ var inputTextReadonlyInEditor = document.getElementById("inputTextReadonlyInEditor");
+ var inputButtonInEditor = document.getElementById("inputButtonInEditor");
+ var noeditableInEditor = document.getElementById("noeditableInEditor");
+ var spanInNoneditableInEditor = document.getElementById("spanInNoneditableInEditor");
+ var inputTextInNoneditableInEditor = document.getElementById("inputTextInNoneditableInEditor");
+ var inputTextReadonlyInNoneditableInEditor = document.getElementById("inputTextReadonlyInNoneditableInEditor");
+ var inputButtonInNoneditableInEditor = document.getElementById("inputButtonInNoneditableInEditor");
+ var buttonInNoneditableInEditor = document.getElementById("buttonInNoneditableInEditor");
+ var spanInEditor = document.getElementById("spanInEditor");
+ var otherEditor = document.getElementById("otherEditor");
+
+ // XXX if there is a contenteditable element, HTML editor sets dom selection
+ // to first editable node, but this makes inconsistency with normal document
+ // behavior.
+ todo_is(selection.rangeCount, 0, "unexpected selection range is there");
+ ok(!selCon.caretVisible, "caret is visible in the document");
+ // Move focus to inputTextInEditor
+ inputTextInEditor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), inputTextInEditor,
+ "inputTextInEditor didn't get focus");
+ todo_is(selection.rangeCount, 0, "unexpected selection range is there");
+ ok(selCon.caretVisible, "caret isn't visible in the inputTextInEditor");
+ // Move focus to the editor
+ editor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), editor,
+ "editor didn't get focus");
+ is(selection.rangeCount, 1,
+ "there is no selection range when editor has focus");
+ var range = selection.getRangeAt(0);
+ ok(range.collapsed, "the selection range isn't collapsed");
+ var startNode = range.startContainer;
+ is(startNode.nodeType, 1, "the caret isn't set to the div node");
+ is(startNode, editor, "the caret isn't set to the editor");
+ ok(selCon.caretVisible, "caret isn't visible in the editor");
+ // Move focus to other editor
+ otherEditor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), otherEditor,
+ "the other editor didn't get focus");
+ is(selection.rangeCount, 1,
+ "there is no selection range when the other editor has focus");
+ range = selection.getRangeAt(0);
+ ok(range.collapsed, "the selection range isn't collapsed");
+ var startNode = range.startContainer;
+ is(startNode.nodeType, 1, "the caret isn't set to the div node");
+ is(startNode, otherEditor, "the caret isn't set to the other editor");
+ ok(selCon.caretVisible, "caret isn't visible in the other editor");
+ // Move focus to inputTextInEditor
+ inputTextInEditor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), inputTextInEditor,
+ "inputTextInEditor didn't get focus #2");
+ is(selection.rangeCount, 1, "selection range is lost from the document");
+ range = selection.getRangeAt(0);
+ ok(range.collapsed, "the selection range isn't collapsed");
+ var startNode = range.startContainer;
+ is(startNode.nodeType, 1, "the caret isn't set to the div node");
+ // XXX maybe, the caret can stay on the other editor if it's better.
+ is(startNode, editor,
+ "the caret should stay on the other editor");
+ ok(selCon.caretVisible,
+ "caret isn't visible in the inputTextInEditor");
+ // Move focus to the other editor again
+ otherEditor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), otherEditor,
+ "the other editor didn't get focus #2");
+ // Set selection to the span element in the editor (unfocused)
+ range = document.createRange();
+ range.setStart(spanInEditor.firstChild, 5);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ is(selection.rangeCount, 1, "selection range is lost from the document");
+ is(SpecialPowers.unwrap(fm.focusedElement), otherEditor,
+ "the other editor shouldn't lose focus by selection range change");
+ ok(selCon.caretVisible, "caret isn't visible in inputTextInEditor");
+ // Move focus to the editor
+ editor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), editor,
+ "the editor didn't get focus #2");
+ is(selection.rangeCount, 1, "selection range is lost from the document");
+ range = selection.getRangeAt(0);
+ ok(range.collapsed, "the selection range isn't collapsed");
+ is(range.startOffset, 5,
+ "the caret is moved when the editor was focused (offset)");
+ var startNode = range.startContainer;
+ is(startNode.nodeType, 3, "the caret isn't in text node");
+ is(startNode.parentNode, spanInEditor,
+ "the caret is moved when the editor was focused (node)");
+ ok(selCon.caretVisible, "caret isn't visible in the editor (spanInEditor)");
+
+ // Move focus to each focusable element in the editor.
+ function testFocusMove(aSetFocusElementID, aFocusable, aCaretVisible)
+ {
+ editor.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), editor,
+ "testFocusMove: the editor didn't get focus at initializing (" +
+ aSetFocusElementID + ")");
+ var setFocusElement = document.getElementById(aSetFocusElementID);
+ setFocusElement.focus();
+ if (aFocusable) {
+ is(SpecialPowers.unwrap(fm.focusedElement), setFocusElement,
+ "testFocusMove: the " + aSetFocusElementID +
+ " didn't get focus");
+ } else {
+ is(SpecialPowers.unwrap(fm.focusedElement), editor,
+ "testFocusMove: the editor lost focus by focus() of the " +
+ aSetFocusElementID);
+ }
+ if (aCaretVisible) {
+ ok(selCon.caretVisible,
+ "testFocusMove: caret isn't visible when the " +
+ aSetFocusElementID + " has focus");
+ } else {
+ ok(!selCon.caretVisible,
+ "testFocusMove: caret is visible when the " +
+ aSetFocusElementID + " has focus");
+ }
+ }
+ testFocusMove("inputTextInEditor", true, true);
+ testFocusMove("inputTextReadonlyInEditor", true, true);
+ // XXX shouldn't the caret become invisible?
+ testFocusMove("inputButtonInEditor", true, true);
+ testFocusMove("noeditableInEditor", false, true);
+ testFocusMove("spanInNoneditableInEditor", false, true);
+ testFocusMove("inputTextInNoneditableInEditor", true, true);
+ testFocusMove("inputTextReadonlyInNoneditableInEditor", true, true);
+ testFocusMove("inputButtonInNoneditableInEditor", true, false);
+ testFocusMove("buttonInNoneditableInEditor", true, false);
+ testFocusMove("spanInEditor", false, true);
+ testFocusMove("inputText", true, true);
+ testFocusMove("inputTextReadonly", true, true);
+ testFocusMove("inputButton", true, false);
+ testFocusMove("button", true, false);
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_contenteditable_text_input_handling.html b/editor/libeditor/tests/test_contenteditable_text_input_handling.html
new file mode 100644
index 000000000..06b95fbb8
--- /dev/null
+++ b/editor/libeditor/tests/test_contenteditable_text_input_handling.html
@@ -0,0 +1,329 @@
+<html>
+<head>
+ <title>Test for text input event handling on contenteditable editor</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <p id="static">static content<input id="inputInStatic"><textarea id="textareaInStatic"></textarea></p>
+ <p id="editor"contenteditable="true">content editable<input id="inputInEditor"><textarea id="textareaInEditor"></textarea></p>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+const kLF = !navigator.platform.indexOf("Win") ? "\r\n" : "\n";
+
+function runTests()
+{
+ var fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+
+ var listener = {
+ handleEvent: function _hv(aEvent)
+ {
+ aEvent.preventDefault(); // prevent the browser default behavior
+ }
+ };
+ var els = Components.classes["@mozilla.org/eventlistenerservice;1"].
+ getService(Components.interfaces.nsIEventListenerService);
+ els.addSystemEventListener(window, "keypress", listener, false);
+
+ var staticContent = document.getElementById("static");
+ staticContent._defaultValue = getTextValue(staticContent);
+ staticContent._isFocusable = false;
+ staticContent._isEditable = false;
+ staticContent._isContentEditable = false;
+ staticContent._description = "non-editable p element";
+ var inputInStatic = document.getElementById("inputInStatic");
+ inputInStatic._defaultValue = getTextValue(inputInStatic);
+ inputInStatic._isFocusable = true;
+ inputInStatic._isEditable = true;
+ inputInStatic._isContentEditable = false;
+ inputInStatic._description = "input element in static content";
+ var textareaInStatic = document.getElementById("textareaInStatic");
+ textareaInStatic._defaultValue = getTextValue(textareaInStatic);
+ textareaInStatic._isFocusable = true;
+ textareaInStatic._isEditable = true;
+ textareaInStatic._isContentEditable = false;
+ textareaInStatic._description = "textarea element in static content";
+ var editor = document.getElementById("editor");
+ editor._defaultValue = getTextValue(editor);
+ editor._isFocusable = true;
+ editor._isEditable = true;
+ editor._isContentEditable = true;
+ editor._description = "contenteditable editor";
+ var inputInEditor = document.getElementById("inputInEditor");
+ inputInEditor._defaultValue = getTextValue(inputInEditor);
+ inputInEditor._isFocusable = true;
+ inputInEditor._isEditable = true;
+ inputInEditor._isContentEditable = false;
+ inputInEditor._description = "input element in contenteditable editor";
+ var textareaInEditor = document.getElementById("textareaInEditor");
+ textareaInEditor._defaultValue = getTextValue(textareaInEditor);
+ textareaInEditor._isFocusable = true;
+ textareaInEditor._isEditable = true;
+ textareaInEditor._isContentEditable = false;
+ textareaInEditor._description = "textarea element in contenteditable editor";
+
+ function getTextValue(aElement)
+ {
+ if (aElement == editor) {
+ var value = "";
+ for (var node = aElement.firstChild; node; node = node.nextSibling) {
+ if (node.nodeType == Node.TEXT_NODE) {
+ value += node.data;
+ } else if (node.nodeType == Node.ELEMENT_NODE) {
+ var tagName = node.tagName.toLowerCase();
+ switch (tagName) {
+ case "input":
+ case "textarea":
+ value += kLF;
+ break;
+ default:
+ ok(false, "Undefined tag is used in the editor: " + tagName);
+ break;
+ }
+ }
+ }
+ return value;
+ }
+ return aElement.value;
+ }
+
+ function testTextInput(aFocus)
+ {
+ var when = " when " +
+ ((aFocus && aFocus._isFocusable) ? aFocus._description + " has focus" :
+ "nobody has focus");
+
+ function checkValue(aElement, aInsertedText)
+ {
+ if (aElement == aFocus && aElement._isEditable) {
+ is(getTextValue(aElement), aInsertedText + aElement._defaultValue,
+ aElement._description +
+ " wasn't edited by synthesized key events" + when);
+ return;
+ }
+ is(getTextValue(aElement), aElement._defaultValue,
+ aElement._description +
+ " was edited by synthesized key events" + when);
+ }
+
+ if (aFocus && aFocus._isFocusable) {
+ aFocus.focus();
+ is(fm.focusedElement, aFocus,
+ aFocus._description + " didn't get focus at preparing tests" + when);
+ } else {
+ var focusedElement = fm.focusedElement;
+ if (focusedElement) {
+ focusedElement.blur();
+ }
+ ok(!fm.focusedElement,
+ "Failed to blur at preparing tests" + when);
+ }
+
+ if (aFocus && aFocus._isFocusable) {
+ synthesizeKey("A", { });
+ synthesizeKey("B", { });
+ synthesizeKey("C", { });
+ checkValue(staticContent, "ABC");
+ checkValue(inputInStatic, "ABC");
+ checkValue(textareaInStatic, "ABC");
+ checkValue(editor, "ABC");
+ checkValue(inputInEditor, "ABC");
+ checkValue(textareaInEditor, "ABC");
+
+ if (aFocus._isEditable) {
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ synthesizeKey("VK_BACK_SPACE", { });
+ checkValue(staticContent, "");
+ checkValue(inputInStatic, "");
+ checkValue(textareaInStatic, "");
+ checkValue(editor, "");
+ checkValue(inputInEditor, "");
+ checkValue(textareaInEditor, "");
+ }
+ }
+
+ // When key events are fired on unfocused editor.
+ function testDispatchedKeyEvent(aTarget)
+ {
+ var targetDescription = " (dispatched to " + aTarget._description + ")";
+ function dispatchKeyEvent(aKeyCode, aChar, aTarget)
+ {
+ var keyEvent = document.createEvent("KeyboardEvent");
+ keyEvent.initKeyEvent("keypress", true, true, null, false, false,
+ false, false, aKeyCode,
+ aChar ? aChar.charCodeAt(0) : 0);
+ aTarget.dispatchEvent(keyEvent);
+ }
+
+ function checkValueForDispatchedKeyEvent(aElement, aInsertedText)
+ {
+ if (aElement == aTarget && aElement._isEditable &&
+ (!aElement._isContentEditable || aElement == aFocus)) {
+ is(getTextValue(aElement), aInsertedText + aElement._defaultValue,
+ aElement._description +
+ " wasn't edited by dispatched key events" +
+ when + targetDescription);
+ return;
+ }
+ if (aElement == aTarget) {
+ is(getTextValue(aElement), aElement._defaultValue,
+ aElement._description +
+ " was edited by dispatched key events" +
+ when + targetDescription);
+ return;
+ }
+ is(getTextValue(aElement), aElement._defaultValue,
+ aElement._description +
+ " was edited by key events unexpectedly" +
+ when + targetDescription);
+ }
+
+ dispatchKeyEvent(0, "A", aTarget);
+ dispatchKeyEvent(0, "B", aTarget);
+ dispatchKeyEvent(0, "C", aTarget);
+
+ checkValueForDispatchedKeyEvent(staticContent, "ABC");
+ checkValueForDispatchedKeyEvent(inputInStatic, "ABC");
+ checkValueForDispatchedKeyEvent(textareaInStatic, "ABC");
+ checkValueForDispatchedKeyEvent(editor, "ABC");
+ checkValueForDispatchedKeyEvent(inputInEditor, "ABC");
+ checkValueForDispatchedKeyEvent(textareaInEditor, "ABC");
+
+ const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
+ dispatchKeyEvent(nsIDOMKeyEvent.DOM_VK_BACK_SPACE, 0, aTarget);
+ dispatchKeyEvent(nsIDOMKeyEvent.DOM_VK_BACK_SPACE, 0, aTarget);
+ dispatchKeyEvent(nsIDOMKeyEvent.DOM_VK_BACK_SPACE, 0, aTarget);
+
+ checkValueForDispatchedKeyEvent(staticContent, "");
+ checkValueForDispatchedKeyEvent(inputInStatic, "");
+ checkValueForDispatchedKeyEvent(textareaInStatic, "");
+ checkValueForDispatchedKeyEvent(editor, "");
+ checkValueForDispatchedKeyEvent(inputInEditor, "");
+ checkValueForDispatchedKeyEvent(textareaInEditor, "");
+ }
+
+ testDispatchedKeyEvent(staticContent);
+ testDispatchedKeyEvent(inputInStatic);
+ testDispatchedKeyEvent(textareaInStatic);
+ testDispatchedKeyEvent(editor);
+ testDispatchedKeyEvent(inputInEditor);
+ testDispatchedKeyEvent(textareaInEditor);
+
+ if (!aFocus._isEditable) {
+ return;
+ }
+
+ // IME
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ var queryText = synthesizeQueryTextContent(0, 100);
+ ok(queryText, "query text event result is null" + when);
+ if (!queryText) {
+ return;
+ }
+ ok(queryText.succeeded, "query text event failed" + when);
+ if (!queryText.succeeded) {
+ return;
+ }
+ is(queryText.text, "\u3089" + aFocus._defaultValue,
+ "composing text is incorrect" + when);
+ var querySelectedText = synthesizeQuerySelectedText();
+ ok(querySelectedText, "query selected text event result is null" + when);
+ if (!querySelectedText) {
+ return;
+ }
+ ok(querySelectedText.succeeded, "query selected text event failed" + when);
+ if (!querySelectedText.succeeded) {
+ return;
+ }
+ is(querySelectedText.offset, 1,
+ "query selected text event returns wrong offset" + when);
+ is(querySelectedText.text, "",
+ "query selected text event returns wrong selected text" + when);
+ // commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ queryText = synthesizeQueryTextContent(0, 100);
+ ok(queryText, "query text event result is null after commit" + when);
+ if (!queryText) {
+ return;
+ }
+ ok(queryText.succeeded, "query text event failed after commit" + when);
+ if (!queryText.succeeded) {
+ return;
+ }
+ is(queryText.text, "\u3089" + aFocus._defaultValue,
+ "composing text is incorrect after commit" + when);
+ querySelectedText = synthesizeQuerySelectedText();
+ ok(querySelectedText,
+ "query selected text event result is null after commit" + when);
+ if (!querySelectedText) {
+ return;
+ }
+ ok(querySelectedText.succeeded,
+ "query selected text event failed after commit" + when);
+ if (!querySelectedText.succeeded) {
+ return;
+ }
+ is(querySelectedText.offset, 1,
+ "query selected text event returns wrong offset after commit" + when);
+ is(querySelectedText.text, "",
+ "query selected text event returns wrong selected text after commit" +
+ when);
+
+ checkValue(staticContent, "\u3089");
+ checkValue(inputInStatic, "\u3089");
+ checkValue(textareaInStatic, "\u3089");
+ checkValue(editor, "\u3089");
+ checkValue(inputInEditor, "\u3089");
+ checkValue(textareaInEditor, "\u3089");
+
+ synthesizeKey("VK_BACK_SPACE", { });
+ checkValue(staticContent, "");
+ checkValue(inputInStatic, "");
+ checkValue(textareaInStatic, "");
+ checkValue(editor, "");
+ checkValue(inputInEditor, "");
+ checkValue(textareaInEditor, "");
+ }
+
+ testTextInput(inputInStatic);
+ testTextInput(textareaInStatic);
+ testTextInput(editor);
+ testTextInput(inputInEditor);
+ testTextInput(textareaInEditor);
+
+ els.removeSystemEventListener(window, "keypress", listener, false);
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_css_chrome_load_access.html b/editor/libeditor/tests/test_css_chrome_load_access.html
new file mode 100644
index 000000000..b6bb3fb46
--- /dev/null
+++ b/editor/libeditor/tests/test_css_chrome_load_access.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1245681
+-->
+<head>
+ <title>Test for Bug 1245681</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245681">Mozilla Bug 1245681</a>
+<p id="display"></p>
+<div id="content">
+ <iframe></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const Ci = SpecialPowers.Ci;
+var styleSheets = null;
+
+function runTest() {
+
+ var editframe = window.frames[0];
+ var editdoc = editframe.document;
+ editdoc.designMode = 'on';
+ var editor = SpecialPowers.wrap(editframe)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession)
+ .getEditorForWindow(editframe);
+
+ styleSheets = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+
+ // test 1: try to access chrome:// url that is accessible to content
+ try
+ {
+ styleSheets.addOverrideStyleSheet("chrome://browser/content/pageinfo/pageInfo.css");
+ ok(true, "should be allowed to access chrome://*.css if contentaccessible");
+ }
+ catch (ex) {
+ ok(false, "should be allowed to access chrome://*.css if contentaccessible");
+ }
+
+ // test 2: try to access chrome:// url that is *not* accessible to content
+ // please note that addOverrideStyleSheet() is triggered by the system,
+ // so the load should also *always* succeed.
+ try
+ {
+ styleSheets.addOverrideStyleSheet("chrome://mozapps/skin/aboutNetworking.css");
+ ok(true, "should be allowed to access chrome://*.css even if *not* contentaccessible");
+ }
+ catch (ex) {
+ ok(false, "should be allowed to access chrome://*.css even if *not* contentaccessible");
+ }
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html b/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
new file mode 100644
index 000000000..d1716a228
--- /dev/null
+++ b/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
@@ -0,0 +1,182 @@
+<html>
+<head>
+ <title>Test for input event of text editor</title>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <iframe id="editor1" src="data:text/html,<html><body contenteditable id='eventTarget'></body></html>"></iframe>
+ <iframe id="editor2" src="data:text/html,<html contenteditable id='eventTarget'><body></body></html>"></iframe>
+ <iframe id="editor3" src="data:text/html,<html><body><div contenteditable id='eventTarget'></div></body></html>"></iframe>
+ <iframe id="editor4" src="data:text/html,<html contenteditable id='eventTarget'><body><div contenteditable id='editTarget'></div></body></html>"></iframe>
+ <iframe id="editor5" src="data:text/html,<html><body id='eventTarget'></body><script>document.designMode='on';</script></html>"></iframe>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+
+function runTests()
+{
+ function doTests(aDocument, aWindow, aDescription)
+ {
+ aDescription += ": ";
+ aWindow.focus();
+
+ var body = aDocument.body;
+
+ var eventTarget = aDocument.getElementById("eventTarget");
+ // The event target must be focusable because it's the editing host.
+ eventTarget.focus();
+
+ var editTarget = aDocument.getElementById("editTarget");
+ if (!editTarget) {
+ editTarget = eventTarget;
+ }
+
+ // Root element never can be edit target. If the editTarget is the root
+ // element, replace with its body.
+ if (editTarget == aDocument.documentElement) {
+ editTarget = body;
+ }
+
+ editTarget.innerHTML = "";
+
+ // If the editTarget isn't its editing host, move caret to the start of it.
+ if (eventTarget != editTarget) {
+ aDocument.getSelection().collapse(editTarget, 0);
+ }
+
+ var inputEvent = null;
+
+ var handler = function (aEvent) {
+ is(aEvent.target, eventTarget,
+ "input event is fired on unexpected element: " + aEvent.target.tagName);
+ ok(!aEvent.cancelable, "input event must not be cancelable");
+ ok(aEvent.bubbles, "input event must be bubbles");
+ if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
+ var duration = Math.abs(window.performance.now() - aEvent.timeStamp);
+ ok(duration < 30 * 1000,
+ "perhaps, timestamp wasn't set correctly :" + aEvent.timeStamp +
+ " (expected it to be within 30s of the current time but it " +
+ "differed by " + duration + "ms)");
+ } else {
+ var eventTime = new Date(aEvent.timeStamp);
+ var duration = Math.abs(Date.now() - aEvent.timeStamp);
+ ok(duration < 30 * 1000,
+ "perhaps, timestamp wasn't set correctly :" +
+ eventTime.toLocaleString() +
+ " (expected it to be within 30s of the current time but it " +
+ "differed by " + duration + "ms)");
+ }
+ inputEvent = aEvent;
+ };
+
+ aWindow.addEventListener("input", handler, true);
+
+ inputEvent = null;
+ synthesizeKey("a", { }, aWindow);
+ is(editTarget.innerHTML, "a", aDescription + "wrong element was edited");
+ ok(inputEvent, aDescription + "input event wasn't fired by 'a' key");
+ ok(inputEvent.isTrusted, aDescription + "input event by 'a' key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("VK_BACK_SPACE", { }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by BackSpace key");
+ ok(inputEvent.isTrusted, aDescription + "input event by BackSpace key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("B", { shiftKey: true }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by 'B' key");
+ ok(inputEvent.isTrusted, aDescription + "input event by 'B' key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("VK_RETURN", { }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by Enter key");
+ ok(inputEvent.isTrusted, aDescription + "input event by Enter key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("C", { shiftKey: true }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by 'C' key");
+ ok(inputEvent.isTrusted, aDescription + "input event by 'C' key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("VK_RETURN", { }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by Enter key (again)");
+ ok(inputEvent.isTrusted, aDescription + "input event by Enter key (again) wasn't trusted event");
+
+ inputEvent = null;
+ editTarget.innerHTML = "foo-bar";
+ ok(!inputEvent, aDescription + "input event was fired by setting value");
+
+ inputEvent = null;
+ editTarget.innerHTML = "";
+ ok(!inputEvent, aDescription + "input event was fired by setting empty value");
+
+ inputEvent = null;
+ synthesizeKey(" ", { }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by Space key");
+ ok(inputEvent.isTrusted, aDescription + "input event by Space key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("VK_DELETE", { }, aWindow);
+ ok(!inputEvent, aDescription + "input event was fired by Delete key at the end");
+
+ inputEvent = null;
+ synthesizeKey("VK_LEFT", { }, aWindow);
+ ok(!inputEvent, aDescription + "input event was fired by Left key");
+
+ inputEvent = null;
+ synthesizeKey("VK_DELETE", { }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by Delete key at the start");
+ ok(inputEvent.isTrusted, aDescription + "input event by Delete key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("z", { accelKey: true }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by Undo");
+ ok(inputEvent.isTrusted, aDescription + "input event by Undo wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("z", { accelKey: true, shiftKey: true }, aWindow);
+ ok(inputEvent, aDescription + "input event wasn't fired by Redo");
+ ok(inputEvent.isTrusted, aDescription + "input event by Redo wasn't trusted event");
+
+ aWindow.removeEventListener("input", handler, true);
+ }
+
+ doTests(document.getElementById("editor1").contentDocument,
+ document.getElementById("editor1").contentWindow,
+ "Editor1, body has contenteditable attribute");
+ doTests(document.getElementById("editor2").contentDocument,
+ document.getElementById("editor2").contentWindow,
+ "Editor2, html has contenteditable attribute");
+ doTests(document.getElementById("editor3").contentDocument,
+ document.getElementById("editor3").contentWindow,
+ "Editor3, div has contenteditable attribute");
+ doTests(document.getElementById("editor4").contentDocument,
+ document.getElementById("editor4").contentWindow,
+ "Editor4, html and div has contenteditable attribute");
+ doTests(document.getElementById("editor5").contentDocument,
+ document.getElementById("editor5").contentWindow,
+ "Editor5, html and div has contenteditable attribute");
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_dom_input_event_on_texteditor.html b/editor/libeditor/tests/test_dom_input_event_on_texteditor.html
new file mode 100644
index 000000000..b1395e99c
--- /dev/null
+++ b/editor/libeditor/tests/test_dom_input_event_on_texteditor.html
@@ -0,0 +1,140 @@
+<html>
+<head>
+ <title>Test for input event of text editor</title>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <input type="text" id="input">
+ <textarea id="textarea"></textarea>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+
+function runTests()
+{
+ function doTests(aElement, aDescription, aIsTextarea)
+ {
+ aDescription += ": ";
+ aElement.focus();
+ aElement.value = "";
+
+ var inputEvent = null;
+
+ var handler = function (aEvent) {
+ is(aEvent.target, aElement,
+ "input event is fired on unexpected element: " + aEvent.target.tagName);
+ ok(!aEvent.cancelable, "input event must not be cancelable");
+ ok(aEvent.bubbles, "input event must be bubbles");
+ if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
+ var duration = Math.abs(window.performance.now() - aEvent.timeStamp);
+ ok(duration < 30 * 1000,
+ "perhaps, timestamp wasn't set correctly :" + aEvent.timeStamp +
+ " (expected it to be within 30s of the current time but it " +
+ "differed by " + duration + "ms)");
+ } else {
+ var eventTime = new Date(aEvent.timeStamp);
+ var duration = Math.abs(Date.now() - aEvent.timeStamp);
+ ok(duration < 30 * 1000,
+ "perhaps, timestamp wasn't set correctly :" +
+ eventTime.toLocaleString() +
+ " (expected it to be within 30s of the current time but it " +
+ "differed by " + duration + "ms)");
+ }
+ inputEvent = aEvent;
+ };
+
+ aElement.addEventListener("input", handler, true);
+
+ inputEvent = null;
+ synthesizeKey("a", { });
+ is(aElement.value, "a", aDescription + "'a' key didn't change the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by 'a' key");
+ ok(inputEvent.isTrusted, aDescription + "input event by 'a' key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("VK_BACK_SPACE", { });
+ is(aElement.value, "", aDescription + "BackSpace key didn't remove the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by BackSpace key");
+ ok(inputEvent.isTrusted, aDescription + "input event by BackSpace key wasn't trusted event");
+
+ if (aIsTextarea) {
+ inputEvent = null;
+ synthesizeKey("VK_RETURN", { });
+ is(aElement.value, "\n", aDescription + "Enter key didn't change the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by Enter key");
+ ok(inputEvent.isTrusted, aDescription + "input event by Enter key wasn't trusted event");
+ }
+
+ inputEvent = null;
+ aElement.value = "foo-bar";
+ is(aElement.value, "foo-bar", aDescription + "value wasn't set");
+ ok(!inputEvent, aDescription + "input event was fired by setting value");
+
+ inputEvent = null;
+ aElement.value = "";
+ is(aElement.value, "", aDescription + "value wasn't set (empty)");
+ ok(!inputEvent, aDescription + "input event was fired by setting empty value");
+
+ inputEvent = null;
+ synthesizeKey(" ", { });
+ is(aElement.value, " ", aDescription + "Space key didn't change the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by Space key");
+ ok(inputEvent.isTrusted, aDescription + "input event by Space key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("VK_DELETE", { });
+ is(aElement.value, " ", aDescription + "Delete key removed the value");
+ ok(!inputEvent, aDescription + "input event was fired by Delete key at the end");
+
+ inputEvent = null;
+ synthesizeKey("VK_LEFT", { });
+ is(aElement.value, " ", aDescription + "Left key removed the value");
+ ok(!inputEvent, aDescription + "input event was fired by Left key");
+
+ inputEvent = null;
+ synthesizeKey("VK_DELETE", { });
+ is(aElement.value, "", aDescription + "Delete key didn't remove the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by Delete key at the start");
+ ok(inputEvent.isTrusted, aDescription + "input event by Delete key wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("z", { accelKey: true });
+ is(aElement.value, " ", aDescription + "Accel+Z key didn't undo the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by Undo");
+ ok(inputEvent.isTrusted, aDescription + "input event by Undo wasn't trusted event");
+
+ inputEvent = null;
+ synthesizeKey("z", { accelKey: true, shiftKey: true });
+ is(aElement.value, "", aDescription + "Accel+Y key didn't redo the value");
+ ok(inputEvent, aDescription + "input event wasn't fired by Redo");
+ ok(inputEvent.isTrusted, aDescription + "input event by Redo wasn't trusted event");
+
+ aElement.removeEventListener("input", handler, true);
+ }
+
+ doTests(document.getElementById("input"), "<input type=\"text\">", false);
+ doTests(document.getElementById("textarea"), "<textarea>", true);
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_dragdrop.html b/editor/libeditor/tests/test_dragdrop.html
new file mode 100644
index 000000000..c992b7142
--- /dev/null
+++ b/editor/libeditor/tests/test_dragdrop.html
@@ -0,0 +1,178 @@
+<!doctype html>
+<html>
+
+<head>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body>
+ <span id="text" style="font-size: 40px;">Some Text</span>
+
+ <input id="input" value="Drag Me">
+ <textarea id="textarea">Some Text To Drag</textarea>
+ <p id="contenteditable" contenteditable="true">This is some <b id="bold">editable</b> text.</p>
+ <p id="nestedce" contenteditable="true"><span id="first"> </span>First letter <span id="noneditable" contenteditable="false">Middle</span> Last part</p>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// This listener allows us to clear the default data for the selection added for the drag.
+var shouldClear = false;
+window.addEventListener("dragstart", function (event) { if (shouldClear) event.dataTransfer.clearData() }, true);
+
+function doTest()
+{
+ const htmlContextData = { type: 'text/_moz_htmlcontext',
+ data: '<html><body></body></html>' };
+ const htmlInfoData = { type: 'text/_moz_htmlinfo', data: '0,0' };
+ const htmlData = { type: 'text/html', data: '<span id="text" style="font-size: 40px;">Some Text</span>' };
+
+ const htmlContextDataEditable = { type: 'text/_moz_htmlcontext',
+ data: '<html><body><p id="contenteditable" contenteditable="true"></p></body></html>' };
+
+ var text = document.getElementById("text");
+ var input = document.getElementById("input");
+ var contenteditable = document.getElementById("contenteditable");
+
+ var selection = window.getSelection();
+
+ // -------- Test dragging regular text
+ selection.selectAllChildren(text);
+ var result = synthesizeDragStart(text, [[htmlContextData, htmlInfoData, htmlData,
+ {type: "text/plain", data: "Some Text"}]], window, 40, 10);
+ is(result, null, "Test dragging regular text");
+
+ // -------- Test dragging text from an <input>
+ input.setSelectionRange(1, 4);
+ result = synthesizeDragStart(input, [[{type: "text/plain", data: "rag"}]], window, 25, 6);
+ is(result, null, "Test dragging input");
+
+ // -------- Test dragging text from a <textarea>
+ textarea.setSelectionRange(1, 7);
+ result = synthesizeDragStart(textarea, [[{type: "text/plain", data: "ome Te"}]], window, 25, 6);
+ is(result, null, "Test dragging textarea");
+ textarea.blur();
+
+ // -------- Test dragging text from a contenteditable
+ selection.selectAllChildren(contenteditable.childNodes[1]);
+ result = synthesizeDragStart(contenteditable.childNodes[1],
+ [[htmlContextDataEditable, htmlInfoData,
+ {type: 'text/html', data: '<b id="bold">editable</b>' },
+ {type: "text/plain", data: "editable"}]], window, 5, 6);
+ is(result, null, "Test dragging contenteditable");
+ contenteditable.blur();
+
+ // -------- Test dragging regular text of text/html to <input>
+
+ selection.selectAllChildren(text);
+ input.value = "";
+ synthesizeDrop(text, input, [], "copy");
+ is(input.value, "Some Text", "Drag text/html onto input");
+
+ // -------- Test dragging regular text of text/html to disabled <input>
+
+ selection.selectAllChildren(text);
+ input.value = "";
+ input.disabled = true;
+ synthesizeDrop(text, input, [], "copy");
+ is(input.value, "", "Drag text/html onto disabled input");
+ input.disabled = false;
+
+ // -------- Test dragging regular text of text/html to readonly <input>
+
+ selection.selectAllChildren(text);
+ input.readOnly = true;
+ synthesizeDrop(text, input, [], "copy");
+ is(input.value, "", "Drag text/html onto readonly input");
+ input.readOnly = false;
+
+ // -------- Test dragging regular text of text/html to <input>. This sets
+ // shouldClear to true so that the default drag data is not present
+ // and we can use the data passed to synthesizeDrop. This allows
+ // testing of a drop with just text/html.
+ shouldClear = true;
+ selection.selectAllChildren(text);
+ input.value = "";
+ synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"}]], "copy");
+ is(input.value, "", "Drag text/html onto input");
+
+ // -------- Test dragging regular text of text/plain and text/html to <input>
+
+ selection.selectAllChildren(text);
+ input.value = "";
+ synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"},
+ {type: "text/plain", data: "Some Plain Text"}]], "copy");
+ is(input.value, "Some Plain Text", "Drag text/html and text/plain onto input");
+
+ // -------- Test dragging regular text of text/plain to <textarea>
+
+// XXXndeakin Can't test textareas due to some event handling issue
+// selection.selectAllChildren(text);
+// synthesizeDrop(text, textarea, [[{type: "text/plain", data: "Somewhat Longer Text"}]], "copy");
+// is(textarea.value, "Somewhat Longer Text", "Drag text/plain onto textarea");
+
+ // -------- Test dragging special text type of text/plain to contenteditable
+
+ selection.selectAllChildren(text);
+ synthesizeDrop(text, input, [[{type: "text/x-moz-text-internal", data: "Some Special Text"}]], "copy");
+ is(input.value, "Some Plain Text", "Drag text/x-moz-text-internal onto input");
+
+ // -------- Test dragging regular text of text/plain to contenteditable
+
+ selection.selectAllChildren(text);
+ synthesizeDrop(text, contenteditable, [[{type: "text/plain", data: "Sample Text"}]], "copy");
+ is(contenteditable.childNodes.length, 3, "Drag text/plain onto contenteditable child nodes");
+ is(contenteditable.textContent, "This is some editable text.Sample Text",
+ "Drag text/plain onto contenteditable text");
+
+ // -------- Test dragging regular text of text/html to contenteditable
+
+ selection.selectAllChildren(text);
+ synthesizeDrop(text, contenteditable, [[{type: "text/html", data: "Sample <i>Italic</i> Text"}]], "copy");
+ is(contenteditable.childNodes.length, 6, "Drag text/html onto contenteditable child nodes");
+ is(contenteditable.childNodes[4].tagName, "I", "Drag text/html onto contenteditable italic");
+ is(contenteditable.childNodes[4].textContent, "Italic", "Drag text/html onto contenteditable italic text");
+
+ // -------- Test dragging contenteditable to <input>
+
+ selection.selectAllChildren(document.getElementById("bold"));
+ synthesizeDrop(bold, input, [[{type: "text/html", data: "<b>editable</b>"},
+ {type: "text/plain", data: "editable"}]], "copy");
+ is(input.value, "Some Plain Texteditable", "Move text/html and text/plain from contenteditable onto input");
+
+ // -------- Test dragging contenteditable to contenteditable
+
+ shouldClear = false;
+
+ selection.selectAllChildren(contenteditable.childNodes[4]);
+ synthesizeDrop(contenteditable.childNodes[4], contenteditable, [], "copy");
+ is(contenteditable.childNodes.length, 7, "Move text/html and text/plain from contenteditable onto itself child nodes");
+ is(contenteditable.childNodes[6].tagName, "I", "Move text/html and text/plain from contenteditable onto itself italic");
+ is(contenteditable.childNodes[6].textContent, "Italic", "Move text/html and text/plain from contenteditable onto itself text");
+
+ // We'd test 'move' here as well as 'copy', but that requires knowledge of
+ // the source of the drag which drag simulation doesn't provide.
+
+ // -------- Test dragging non-editable nested inside contenteditable to contenteditable
+
+ input.focus(); // this resets some state in the selection otherwise an inexplicable error occurs calling selectAllChildren.
+ input.blur();
+
+ var nonEditable = document.getElementById("noneditable");
+ selection.selectAllChildren(nonEditable);
+ synthesizeDrop(nonEditable, document.getElementById("first"), [], "copy");
+ is(document.getElementById("nestedce").textContent, " MiddleFirst letter Middle Last part",
+ "Drag non-editable text/html onto contenteditable text");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(doTest);
+
+</script>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_htmleditor_keyevent_handling.html b/editor/libeditor/tests/test_htmleditor_keyevent_handling.html
new file mode 100644
index 000000000..bfec290a5
--- /dev/null
+++ b/editor/libeditor/tests/test_htmleditor_keyevent_handling.html
@@ -0,0 +1,664 @@
+<html>
+<head>
+ <title>Test for key event handler of HTML editor</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <div id="htmlEditor" contenteditable="true"><br></div>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+var htmlEditor = document.getElementById("htmlEditor");
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsLinux = navigator.platform.indexOf("Linux") == 0 || navigator.platform.indexOf("SunOS") == 0 ;
+
+function runTests()
+{
+ document.execCommand("stylewithcss", false, "true");
+
+ var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"].
+ getService(SpecialPowers.Ci.nsIFocusManager);
+
+ var capturingPhase = { fired: false, prevented: false };
+ var bubblingPhase = { fired: false, prevented: false };
+
+ var listener = {
+ handleEvent: function _hv(aEvent)
+ {
+ is(aEvent.type, "keypress", "unexpected event is handled");
+ switch (aEvent.eventPhase) {
+ case aEvent.CAPTURING_PHASE:
+ capturingPhase.fired = true;
+ capturingPhase.prevented = aEvent.defaultPrevented;
+ break;
+ case aEvent.BUBBLING_PHASE:
+ bubblingPhase.fired = true;
+ bubblingPhase.prevented = aEvent.defaultPrevented;
+ aEvent.preventDefault(); // prevent the browser default behavior
+ break;
+ default:
+ ok(false, "event is handled in unexpected phase");
+ }
+ }
+ };
+
+ function check(aDescription,
+ aFiredOnCapture, aFiredOnBubbling, aPreventedOnBubbling)
+ {
+ function getDesciption(aExpected)
+ {
+ return aDescription + (aExpected ? " wasn't " : " was ");
+ }
+ is(capturingPhase.fired, aFiredOnCapture,
+ getDesciption(aFiredOnCapture) + "fired on capture phase");
+ is(bubblingPhase.fired, aFiredOnBubbling,
+ getDesciption(aFiredOnBubbling) + "fired on bubbling phase");
+
+ // If the event is fired on bubbling phase and it was already prevented
+ // on capture phase, it must be prevented on bubbling phase too.
+ if (capturingPhase.prevented) {
+ todo(false, aDescription +
+ " was consumed already, so, we cannot test the editor behavior actually");
+ aPreventedOnBubbling = true;
+ }
+
+ is(bubblingPhase.prevented, aPreventedOnBubbling,
+ getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase");
+ }
+
+ SpecialPowers.addSystemEventListener(window, "keypress", listener, true);
+ SpecialPowers.addSystemEventListener(window, "keypress", listener, false);
+
+ function doTest(aElement, aDescription,
+ aIsReadonly, aIsTabbable, aIsPlaintext)
+ {
+ function reset(aText)
+ {
+ capturingPhase.fired = false;
+ capturingPhase.prevented = false;
+ bubblingPhase.fired = false;
+ bubblingPhase.prevented = false;
+ aElement.innerHTML = aText;
+ var sel = window.getSelection();
+ var range = document.createRange();
+ range.setStart(aElement, aElement.childNodes.length);
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+
+ function resetForIndent(aText)
+ {
+ capturingPhase.fired = false;
+ capturingPhase.prevented = false;
+ bubblingPhase.fired = false;
+ bubblingPhase.prevented = false;
+ aElement.innerHTML = aText;
+ var sel = window.getSelection();
+ var range = document.createRange();
+ var target = document.getElementById("target").firstChild;
+ range.setStart(target, target.length);
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ aDescription += ": "
+
+ aElement.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement, aDescription + "failed to move focus");
+
+ // Backspace key:
+ // If editor is readonly, it doesn't consume.
+ // If editor is editable, it consumes backspace and shift+backspace.
+ // Otherwise, editor doesn't consume the event.
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { });
+ check(aDescription + "Backspace", true, true, true);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { shiftKey: true });
+ check(aDescription + "Shift+Backspace", true, true, true);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { ctrlKey: true });
+ check(aDescription + "Ctrl+Backspace", true, true, aIsReadonly);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { altKey: true });
+ check(aDescription + "Alt+Backspace", true, true, aIsReadonly || kIsMac);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { metaKey: true });
+ check(aDescription + "Meta+Backspace", true, true, aIsReadonly);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { osKey: true });
+ check(aDescription + "OS+Backspace", true, true, aIsReadonly);
+
+ // Delete key:
+ // If editor is readonly, it doesn't consume.
+ // If editor is editable, delete is consumed.
+ // Otherwise, editor doesn't consume the event.
+ reset("");
+ synthesizeKey("VK_DELETE", { });
+ check(aDescription + "Delete", true, true, !aIsReadonly || kIsMac);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { shiftKey: true });
+ check(aDescription + "Shift+Delete", true, true, kIsMac);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { ctrlKey: true });
+ check(aDescription + "Ctrl+Delete", true, true, false);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { altKey: true });
+ check(aDescription + "Alt+Delete", true, true, kIsMac);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { metaKey: true });
+ check(aDescription + "Meta+Delete", true, true, false);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { osKey: true });
+ check(aDescription + "OS+Delete", true, true, false);
+
+ // Return key:
+ // If editor is readonly, it doesn't consume.
+ // If editor is editable and not single line editor, it consumes Return
+ // and Shift+Return.
+ // Otherwise, editor doesn't consume the event.
+ reset("a");
+ synthesizeKey("VK_RETURN", { });
+ check(aDescription + "Return",
+ true, true, !aIsReadonly);
+ is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
+ aDescription + "Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { shiftKey: true });
+ check(aDescription + "Shift+Return",
+ true, true, !aIsReadonly);
+ is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
+ aDescription + "Shift+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { ctrlKey: true });
+ check(aDescription + "Ctrl+Return", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "Ctrl+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { altKey: true });
+ check(aDescription + "Alt+Return", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "Alt+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { metaKey: true });
+ check(aDescription + "Meta+Return", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "Meta+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { osKey: true });
+ check(aDescription + "OS+Return", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "OS+Return");
+
+ // Tab key:
+ // If editor is tabbable, editor doesn't consume all tab key events.
+ // Otherwise, editor consumes tab key event without any modifier keys.
+ reset("a");
+ synthesizeKey("VK_TAB", { });
+ check(aDescription + "Tab",
+ true, true, !aIsTabbable && !aIsReadonly);
+ is(aElement.innerHTML,
+ aIsTabbable || aIsReadonly ? "a" :
+ aIsPlaintext ? "a\t" : "a&nbsp;&nbsp;&nbsp; <br>",
+ aDescription + "Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "Shift+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab)");
+
+ // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
+ // event should never be fired.
+ reset("a");
+ synthesizeKey("VK_TAB", { ctrlKey: true });
+ check(aDescription + "Ctrl+Tab", false, false, false);
+ is(aElement.innerHTML, "a", aDescription + "Ctrl+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Ctrl+Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { altKey: true });
+ check(aDescription + "Alt+Tab", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "Alt+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Alt+Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { metaKey: true });
+ check(aDescription + "Meta+Tab", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "Meta+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Meta+Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { osKey: true });
+ check(aDescription + "OS+Tab", true, true, false);
+ is(aElement.innerHTML, "a", aDescription + "OS+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (OS+Tab)");
+
+ // Indent/Outdent tests:
+ // UL
+ resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+ synthesizeKey("VK_TAB", { });
+ check(aDescription + "Tab on UL",
+ true, true, !aIsTabbable && !aIsReadonly);
+ is(aElement.innerHTML,
+ aIsReadonly || aIsTabbable ?
+ "<ul><li id=\"target\">ul list item</li></ul>" :
+ aIsPlaintext ? "<ul><li id=\"target\">ul list item\t</li></ul>" :
+ "<ul><ul><li id=\"target\">ul list item</li></ul></ul>",
+ aDescription + "Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab on UL)");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab after Tab on UL",
+ true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+ is(aElement.innerHTML,
+ aIsReadonly || aIsTabbable || (!aIsPlaintext) ?
+ "<ul><li id=\"target\">ul list item</li></ul>" :
+ "<ul><li id=\"target\">ul list item\t</li></ul>",
+ aDescription + "Shift+Tab after Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab after Tab on UL)");
+
+ resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab on UL",
+ true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+ is(aElement.innerHTML,
+ aIsReadonly || aIsTabbable || aIsPlaintext ?
+ "<ul><li id=\"target\">ul list item</li></ul>" : "ul list item",
+ aDescription + "Shift+Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab on UL)");
+
+ // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
+ // event should never be fired.
+ resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+ synthesizeKey("VK_TAB", { ctrlKey: true });
+ check(aDescription + "Ctrl+Tab on UL", false, false, false);
+ is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+ aDescription + "Ctrl+Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Ctrl+Tab on UL)");
+
+ resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+ synthesizeKey("VK_TAB", { altKey: true });
+ check(aDescription + "Alt+Tab on UL", true, true, false);
+ is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+ aDescription + "Alt+Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Alt+Tab on UL)");
+
+ resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+ synthesizeKey("VK_TAB", { metaKey: true });
+ check(aDescription + "Meta+Tab on UL", true, true, false);
+ is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+ aDescription + "Meta+Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Meta+Tab on UL)");
+
+ resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+ synthesizeKey("VK_TAB", { osKey: true });
+ check(aDescription + "OS+Tab on UL", true, true, false);
+ is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+ aDescription + "OS+Tab on UL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (OS+Tab on UL)");
+
+ // OL
+ resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+ synthesizeKey("VK_TAB", { });
+ check(aDescription + "Tab on OL",
+ true, true, !aIsTabbable && !aIsReadonly);
+ is(aElement.innerHTML,
+ aIsReadonly || aIsTabbable ?
+ "<ol><li id=\"target\">ol list item</li></ol>" :
+ aIsPlaintext ? "<ol><li id=\"target\">ol list item\t</li></ol>" :
+ "<ol><ol><li id=\"target\">ol list item</li></ol></ol>",
+ aDescription + "Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab on OL)");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab after Tab on OL",
+ true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+ is(aElement.innerHTML,
+ aIsReadonly || aIsTabbable || (!aIsPlaintext) ?
+ "<ol><li id=\"target\">ol list item</li></ol>" :
+ "<ol><li id=\"target\">ol list item\t</li></ol>",
+ aDescription + "Shift+Tab after Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab after Tab on OL)");
+
+ resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab on OL",
+ true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+ is(aElement.innerHTML,
+ aIsReadonly || aIsTabbable || aIsPlaintext ?
+ "<ol><li id=\"target\">ol list item</li></ol>" : "ol list item",
+ aDescription + "Shfit+Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab on OL)");
+
+ // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
+ // event should never be fired.
+ resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+ synthesizeKey("VK_TAB", { ctrlKey: true });
+ check(aDescription + "Ctrl+Tab on OL", false, false, false);
+ is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+ aDescription + "Ctrl+Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Ctrl+Tab on OL)");
+
+ resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+ synthesizeKey("VK_TAB", { altKey: true });
+ check(aDescription + "Alt+Tab on OL", true, true, false);
+ is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+ aDescription + "Alt+Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Alt+Tab on OL)");
+
+ resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+ synthesizeKey("VK_TAB", { metaKey: true });
+ check(aDescription + "Meta+Tab on OL", true, true, false);
+ is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+ aDescription + "Meta+Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Meta+Tab on OL)");
+
+ resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+ synthesizeKey("VK_TAB", { osKey: true });
+ check(aDescription + "OS+Tab on OL", true, true, false);
+ is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+ aDescription + "OS+Tab on OL");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (OS+Tab on OL)");
+
+ // TD
+ resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+ synthesizeKey("VK_TAB", { });
+ check(aDescription + "Tab on TD",
+ true, true, !aIsTabbable && !aIsReadonly);
+ is(aElement.innerHTML,
+ aIsTabbable || aIsReadonly ?
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
+ aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
+ "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+ aDescription + "Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab on TD)");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab after Tab on TD",
+ true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+ is(aElement.innerHTML,
+ aIsTabbable || aIsReadonly ?
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
+ aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
+ "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+ aDescription + "Shift+Tab after Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TD)");
+
+ resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab on TD", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+ aDescription + "Shift+Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab on TD)");
+
+ // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
+ // event should never be fired.
+ resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+ synthesizeKey("VK_TAB", { ctrlKey: true });
+ check(aDescription + "Ctrl+Tab on TD", false, false, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+ aDescription + "Ctrl+Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Ctrl+Tab on TD)");
+
+ resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+ synthesizeKey("VK_TAB", { altKey: true });
+ check(aDescription + "Alt+Tab on TD", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+ aDescription + "Alt+Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Alt+Tab on TD)");
+
+ resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+ synthesizeKey("VK_TAB", { metaKey: true });
+ check(aDescription + "Meta+Tab on TD", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+ aDescription + "Meta+Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Meta+Tab on TD)");
+
+ resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+ synthesizeKey("VK_TAB", { osKey: true });
+ check(aDescription + "OS+Tab on TD", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+ aDescription + "OS+Tab on TD");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (OS+Tab on TD)");
+
+ // TH
+ resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+ synthesizeKey("VK_TAB", { });
+ check(aDescription + "Tab on TH",
+ true, true, !aIsTabbable && !aIsReadonly);
+ is(aElement.innerHTML,
+ aIsTabbable || aIsReadonly ?
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
+ aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
+ "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+ aDescription + "Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab on TH)");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab after Tab on TH",
+ true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+ is(aElement.innerHTML,
+ aIsTabbable || aIsReadonly ?
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
+ aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
+ "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+ aDescription + "Shift+Tab after Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TH)");
+
+ resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab on TH", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+ aDescription + "Shift+Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab on TH)");
+
+ // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
+ // event should never be fired.
+ resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+ synthesizeKey("VK_TAB", { ctrlKey: true });
+ check(aDescription + "Ctrl+Tab on TH", false, false, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+ aDescription + "Ctrl+Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Ctrl+Tab on TH)");
+
+ resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+ synthesizeKey("VK_TAB", { altKey: true });
+ check(aDescription + "Alt+Tab on TH", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+ aDescription + "Alt+Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Alt+Tab on TH)");
+
+ resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+ synthesizeKey("VK_TAB", { metaKey: true });
+ check(aDescription + "Meta+Tab on TH", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+ aDescription + "Meta+Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Meta+Tab on TH)");
+
+ resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+ synthesizeKey("VK_TAB", { osKey: true });
+ check(aDescription + "OS+Tab on TH", true, true, false);
+ is(aElement.innerHTML,
+ "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+ aDescription + "OS+Tab on TH");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (OS+Tab on TH)");
+
+ // Esc key:
+ // In all cases, esc key events are not consumed
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { });
+ check(aDescription + "Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { shiftKey: true });
+ check(aDescription + "Shift+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { ctrlKey: true });
+ check(aDescription + "Ctrl+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { altKey: true });
+ check(aDescription + "Alt+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { metaKey: true });
+ check(aDescription + "Meta+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { osKey: true });
+ check(aDescription + "OS+Esc", true, true, false);
+
+ // typical typing tests:
+ reset("");
+ synthesizeKey("M", { shiftKey: true });
+ check(aDescription + "M", true, true, !aIsReadonly);
+ synthesizeKey("o", { });
+ check(aDescription + "o", true, true, !aIsReadonly);
+ synthesizeKey("z", { });
+ check(aDescription + "z", true, true, !aIsReadonly);
+ synthesizeKey("i", { });
+ check(aDescription + "i", true, true, !aIsReadonly);
+ synthesizeKey("l", { });
+ check(aDescription + "l", true, true, !aIsReadonly);
+ synthesizeKey("l", { });
+ check(aDescription + "l", true, true, !aIsReadonly);
+ synthesizeKey("a", { });
+ check(aDescription + "a", true, true, !aIsReadonly);
+ synthesizeKey(" ", { });
+ check(aDescription + "' '", true, true, !aIsReadonly);
+ is(aElement.innerHTML,
+ aIsReadonly ? "" : aIsPlaintext ? "Mozilla " : "Mozilla <br>",
+ aDescription + "typed \"Mozilla \"");
+ }
+
+ doTest(htmlEditor, "contenteditable=\"true\"", false, true, false);
+
+ const nsIPlaintextEditor = SpecialPowers.Ci.nsIPlaintextEditor;
+ var editor = SpecialPowers.wrap(window).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIDocShell).editor;
+ var flags = editor.flags;
+ // readonly
+ editor.flags = flags | nsIPlaintextEditor.eEditorReadonlyMask;
+ doTest(htmlEditor, "readonly HTML editor", true, true, false);
+
+ // non-tabbable
+ editor.flags = flags & ~(nsIPlaintextEditor.eEditorAllowInteraction);
+ doTest(htmlEditor, "non-tabbable HTML editor", false, false, false);
+
+ // readonly and non-tabbable
+ editor.flags =
+ (flags | nsIPlaintextEditor.eEditorReadonlyMask) &
+ ~(nsIPlaintextEditor.eEditorAllowInteraction);
+ doTest(htmlEditor, "readonly and non-tabbable HTML editor",
+ true, false, false);
+
+ // plaintext
+ editor.flags = flags | nsIPlaintextEditor.eEditorPlaintextMask;
+ doTest(htmlEditor, "HTML editor but plaintext mode", false, true, true);
+
+ // plaintext and non-tabbable
+ editor.flags = (flags | nsIPlaintextEditor.eEditorPlaintextMask) &
+ ~(nsIPlaintextEditor.eEditorAllowInteraction);
+ doTest(htmlEditor, "non-tabbable HTML editor but plaintext mode",
+ false, false, true);
+
+
+ // readonly and plaintext
+ editor.flags = flags | nsIPlaintextEditor.eEditorPlaintextMask |
+ nsIPlaintextEditor.eEditorReadonlyMask;
+ doTest(htmlEditor, "readonly HTML editor but plaintext mode",
+ true, true, true);
+
+ // readonly, plaintext and non-tabbable
+ editor.flags = (flags | nsIPlaintextEditor.eEditorPlaintextMask |
+ nsIPlaintextEditor.eEditorReadonlyMask) &
+ ~(nsIPlaintextEditor.eEditorAllowInteraction);
+ doTest(htmlEditor, "readonly and non-tabbable HTML editor but plaintext mode",
+ true, false, true);
+
+ SpecialPowers.removeSystemEventListener(window, "keypress", listener, true);
+ SpecialPowers.removeSystemEventListener(window, "keypress", listener, false);
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_keypress_untrusted_event.html b/editor/libeditor/tests/test_keypress_untrusted_event.html
new file mode 100644
index 000000000..6875c5a33
--- /dev/null
+++ b/editor/libeditor/tests/test_keypress_untrusted_event.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622245
+-->
+<head>
+ <title>Test for untrusted keypress events</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=622245">Mozilla Bug 622245</a>
+<p id="display"></p>
+<div id="content">
+<input id="i"><br>
+<textarea id="t"></textarea><br>
+<div id="d" contenteditable style="min-height: 1em;"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 674770 **/
+SimpleTest.waitForExplicitFinish();
+
+var input = document.getElementById("i");
+var textarea = document.getElementById("t");
+var div = document.getElementById("d");
+
+addLoadEvent(function() {
+ input.focus();
+
+ SimpleTest.executeSoon(function() {
+ input.addEventListener("keypress",
+ function(aEvent) {
+ input.removeEventListener("keypress", arguments.callee, false);
+ is(aEvent.target, input,
+ "The keypress event target isn't the input element");
+
+ SimpleTest.executeSoon(function() {
+ is(input.value, "",
+ "Did keypress event cause modifying the input element?");
+ textarea.focus();
+ SimpleTest.executeSoon(runTextareaTest);
+ });
+ }, false);
+ var keypress = document.createEvent("KeyboardEvent");
+ keypress.initKeyEvent("keypress", true, true, document.defaultView,
+ false, false, false, false, 0, "a".charCodeAt(0));
+ input.dispatchEvent(keypress);
+ });
+});
+
+function runTextareaTest()
+{
+ textarea.addEventListener("keypress",
+ function(aEvent) {
+ textarea.removeEventListener("keypress", arguments.callee, false);
+ is(aEvent.target, textarea,
+ "The keypress event target isn't the textarea element");
+
+ SimpleTest.executeSoon(function() {
+ is(textarea.value, "",
+ "Did keypress event cause modifying the textarea element?");
+ div.focus();
+ SimpleTest.executeSoon(runContentediableTest);
+ });
+ }, false);
+ var keypress = document.createEvent("KeyboardEvent");
+ keypress.initKeyEvent("keypress", true, true, document.defaultView,
+ false, false, false, false, 0, "b".charCodeAt(0));
+ textarea.dispatchEvent(keypress);
+}
+
+function runContentediableTest()
+{
+ div.addEventListener("keypress",
+ function(aEvent) {
+ div.removeEventListener("keypress", arguments.callee, false);
+ is(aEvent.target, div,
+ "The keypress event target isn't the div element");
+
+ SimpleTest.executeSoon(function() {
+ is(div.innerHTML, "",
+ "Did keypress event cause modifying the div element?");
+
+ SimpleTest.finish();
+ });
+ }, false);
+ var keypress = document.createEvent("KeyboardEvent");
+ keypress.initKeyEvent("keypress", true, true, document.defaultView,
+ false, false, false, false, 0, "c".charCodeAt(0));
+ div.dispatchEvent(keypress);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/editor/libeditor/tests/test_root_element_replacement.html b/editor/libeditor/tests/test_root_element_replacement.html
new file mode 100644
index 000000000..f8b6f4336
--- /dev/null
+++ b/editor/libeditor/tests/test_root_element_replacement.html
@@ -0,0 +1,148 @@
+<html>
+<head>
+ <title>Test for root element replacement</title>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+function runDesignModeTest(aDoc, aFocus, aNewSource)
+{
+ aDoc.designMode = "on";
+
+ if (aFocus) {
+ aDoc.documentElement.focus();
+ }
+
+ aDoc.open();
+ aDoc.write(aNewSource);
+ aDoc.close();
+ aDoc.documentElement.focus();
+}
+
+function runContentEditableTest(aDoc, aFocus, aNewSource)
+{
+ if (aFocus) {
+ aDoc.body.setAttribute("contenteditable", "true");
+ aDoc.body.focus();
+ }
+
+ aDoc.open();
+ aDoc.write(aNewSource);
+ aDoc.close();
+ aDoc.getElementById("focus").focus();
+}
+
+var gTestIndex = 0;
+
+const kTests = [
+ { description: "Replace to '<body></body>', designMode",
+ initializer: runDesignModeTest,
+ args: [ "<body></body>" ] },
+ { description: "Replace to '<html><body></body></html>', designMode",
+ initializer: runDesignModeTest,
+ args: [ "<html><body></body></html>" ] },
+ { description: "Replace to '<html>&nbsp;<body></body></html>', designMode",
+ initializer: runDesignModeTest,
+ args: [ "<html> <body></body></html>" ] },
+ { description: "Replace to '&nbsp;<html>&nbsp;<body></body></html>', designMode",
+ initializer: runDesignModeTest,
+ args: [ " <html> <body></body></html>" ] },
+
+ { description: "Replace to '<html contenteditable='true'><body></body></html>",
+ initializer: runContentEditableTest,
+ args: [ "<html contenteditable='true' id='focus'><body></body></html>" ] },
+ { description: "Replace to '<html><body contenteditable='true'></body></html>",
+ initializer: runContentEditableTest,
+ args: [ "<html><body contenteditable='true' id='focus'></body></html>" ] },
+ { description: "Replace to '<body contenteditable='true'></body>",
+ initializer: runContentEditableTest,
+ args: [ "<body contenteditable='true' id='focus'></body>" ] },
+];
+
+var gIFrame;
+var gSetFocusToIFrame = false;
+
+function onLoadIFrame()
+{
+ var frameDoc = gIFrame.contentWindow.document;
+
+ var selCon = SpecialPowers.wrap(gIFrame).contentWindow.
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsISelectionDisplay).
+ QueryInterface(SpecialPowers.Ci.nsISelectionController);
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ const nsIDOMNode = SpecialPowers.Ci.nsIDOMNode;
+
+ // move focus to the HTML editor
+ const kTest = kTests[gTestIndex];
+ ok(true, "Running " + kTest.description);
+ if (kTest.args.length == 1) {
+ kTest.initializer(frameDoc, gSetFocusToIFrame, kTest.args[0]);
+ ok(selCon.caretVisible, "caret isn't visible -- " + kTest.description);
+ } else {
+ ok(false, "kTests is broken at index=" + gTestIndex);
+ }
+
+ is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
+ "IME isn't enabled -- " + kTest.description);
+ synthesizeKey("A", { }, gIFrame.contentWindow);
+ synthesizeKey("B", { }, gIFrame.contentWindow);
+ synthesizeKey("C", { }, gIFrame.contentWindow);
+ var content = frameDoc.body.firstChild;
+ ok(content, "body doesn't have contents -- " + kTest.description);
+ if (content) {
+ is(content.nodeType, nsIDOMNode.TEXT_NODE,
+ "the content of body isn't text node -- " + kTest.description);
+ if (content.nodeType == nsIDOMNode.TEXT_NODE) {
+ is(content.data, "ABC",
+ "the content of body text isn't 'ABC' -- " + kTest.description);
+ is(frameDoc.body.innerHTML, "ABC",
+ "the innerHTML of body isn't 'ABC' -- " + kTest.description);
+ }
+ }
+
+ document.getElementById("display").removeChild(gIFrame);
+
+ // Do next test or finish the tests.
+ if (++gTestIndex < kTests.length) {
+ setTimeout(runTest, 0);
+ } else if (!gSetFocusToIFrame) {
+ gSetFocusToIFrame = true;
+ gTestIndex = 0;
+ setTimeout(runTest, 0);
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+function runTest()
+{
+ gIFrame = document.createElement("iframe");
+ document.getElementById("display").appendChild(gIFrame);
+ gIFrame.src = "about:blank";
+ gIFrame.onload = onLoadIFrame;
+}
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_select_all_without_body.html b/editor/libeditor/tests/test_select_all_without_body.html
new file mode 100644
index 000000000..d947400c4
--- /dev/null
+++ b/editor/libeditor/tests/test_select_all_without_body.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+ <title>Test select all in HTML editor without body element</title>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+window.open("file_select_all_without_body.html", "_blank",
+ "width=600,height=600");
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_selection_move_commands.html b/editor/libeditor/tests/test_selection_move_commands.html
new file mode 100644
index 000000000..e217f8fdf
--- /dev/null
+++ b/editor/libeditor/tests/test_selection_move_commands.html
@@ -0,0 +1,219 @@
+<!doctype html>
+<title>Test for nsSelectionMoveCommands</title>
+<link rel=stylesheet href="/tests/SimpleTest/test.css">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/SpawnTask.js"></script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=454004">Mozilla Bug 454004</a>
+
+<iframe id="edit" width="200" height="100" src="about:blank"></iframe>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Legacy test, possibly no good reason");
+
+var winUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function* setup() {
+ yield SpecialPowers.pushPrefEnv({set: [["general.smoothScroll", false]]});
+ winUtils.advanceTimeAndRefresh(100);
+}
+
+function* runTests() {
+ var e = document.getElementById("edit");
+ var doc = e.contentDocument;
+ var win = e.contentWindow;
+ var root = doc.documentElement;
+ var body = doc.body;
+
+ body.style.fontSize='16px';
+ body.style.lineHeight='16px';
+ body.style.height='400px';
+ body.style.padding='0px';
+ body.style.margin='0px';
+ body.style.borderWidth='0px';
+
+ var sel = win.getSelection();
+ doc.designMode='on';
+ body.innerHTML = "1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>";
+ win.focus();
+ // Flush out layout to make sure that the subdocument will be the size we
+ // expect by the time we try to scroll it.
+ is(body.getBoundingClientRect().height, 400,
+ "Body height should be what we set it to");
+ yield;
+
+ function testScrollCommand(cmd, expectTop) {
+ // http://dev.w3.org/csswg/cssom-view/#dom-element-getboundingclientrect
+ // doesn't explicitly rule out -0 here, but for now assume that only
+ // positive zeroes are permitted.
+ if (navigator.appVersion.indexOf("Android") != -1 && expectTop != 0) {
+ // Android doesn't get the values exactly correct for some reason
+ todo_is(root.getBoundingClientRect().top, -expectTop + 0, cmd);
+ ok(Math.abs(root.getBoundingClientRect().top + expectTop) < 0.2,
+ cmd + " (approximately)");
+ } else {
+ is(root.getBoundingClientRect().top, -expectTop + 0, cmd);
+ }
+ }
+
+ function testMoveCommand(cmd, expectNode, expectOffset) {
+ SpecialPowers.doCommand(window, cmd);
+ is(sel.isCollapsed, true, "collapsed after " + cmd);
+ is(sel.anchorNode, expectNode, "node after " + cmd);
+ is(sel.anchorOffset, expectOffset, "offset after " + cmd);
+ }
+
+ function findChildNum(e, child) {
+ var i = 0;
+ var n = e.firstChild;
+ while (n && n != child) {
+ n = n.nextSibling;
+ ++i;
+ }
+ if (!n)
+ return -1;
+ return i;
+ }
+
+ function testPageMoveCommand(cmd, expectOffset) {
+ SpecialPowers.doCommand(window, cmd);
+ is(sel.isCollapsed, true, "collapsed after " + cmd);
+ is(sel.anchorOffset, expectOffset, "offset after " + cmd);
+ return findChildNum(body, sel.anchorNode);
+ }
+
+ function testSelectCommand(cmd, expectNode, expectOffset) {
+ var anchorNode = sel.anchorNode;
+ var anchorOffset = sel.anchorOffset;
+ SpecialPowers.doCommand(window, cmd);
+ is(sel.isCollapsed, false, "not collapsed after " + cmd);
+ is(sel.anchorNode, anchorNode, "anchor not moved after " + cmd);
+ is(sel.anchorOffset, anchorOffset, "anchor not moved after " + cmd);
+ is(sel.focusNode, expectNode, "node after " + cmd);
+ is(sel.focusOffset, expectOffset, "offset after " + cmd);
+ }
+
+ function testPageSelectCommand(cmd, expectOffset) {
+ var anchorNode = sel.anchorNode;
+ var anchorOffset = sel.anchorOffset;
+ SpecialPowers.doCommand(window, cmd);
+ is(sel.isCollapsed, false, "not collapsed after " + cmd);
+ is(sel.anchorNode, anchorNode, "anchor not moved after " + cmd);
+ is(sel.anchorOffset, anchorOffset, "anchor not moved after " + cmd);
+ is(sel.focusOffset, expectOffset, "offset after " + cmd);
+ return findChildNum(body, sel.focusNode);
+ }
+
+ function node(i) {
+ var n = body.firstChild;
+ while (i > 0) {
+ n = n.nextSibling;
+ --i;
+ }
+ return n;
+ }
+
+ SpecialPowers.doCommand(window, "cmd_scrollBottom");
+ yield;
+ testScrollCommand("cmd_scrollBottom", root.scrollHeight - 100);
+ SpecialPowers.doCommand(window, "cmd_scrollTop");
+ yield;
+ testScrollCommand("cmd_scrollTop", 0);
+
+ SpecialPowers.doCommand(window, "cmd_scrollPageDown");
+ yield;
+ var pageHeight = -root.getBoundingClientRect().top;
+ ok(pageHeight > 0, "cmd_scrollPageDown works");
+ ok(pageHeight <= 100, "cmd_scrollPageDown doesn't scroll too much");
+ SpecialPowers.doCommand(window, "cmd_scrollBottom");
+ SpecialPowers.doCommand(window, "cmd_scrollPageUp");
+ yield;
+ testScrollCommand("cmd_scrollPageUp", root.scrollHeight - 100 - pageHeight);
+
+ SpecialPowers.doCommand(window, "cmd_scrollTop");
+ SpecialPowers.doCommand(window, "cmd_scrollLineDown");
+ yield;
+ var lineHeight = -root.getBoundingClientRect().top;
+ ok(lineHeight > 0, "Can scroll by lines");
+ SpecialPowers.doCommand(window, "cmd_scrollBottom");
+ SpecialPowers.doCommand(window, "cmd_scrollLineUp");
+ yield;
+ testScrollCommand("cmd_scrollLineUp", root.scrollHeight - 100 - lineHeight);
+
+ var runSelectionTests = function(selectWordNextNode, selectWordNextOffset) {
+ testMoveCommand("cmd_moveBottom", body, 23);
+ testMoveCommand("cmd_moveTop", node(0), 0);
+ testSelectCommand("cmd_selectBottom", body, 23);
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ testSelectCommand("cmd_selectTop", node(0), 0);
+
+ SpecialPowers.doCommand(window, "cmd_moveTop");
+ testMoveCommand("cmd_lineNext", node(2), 0);
+ testMoveCommand("cmd_linePrevious", node(0), 0);
+ testSelectCommand("cmd_selectLineNext", node(2), 0);
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ testSelectCommand("cmd_selectLinePrevious", node(20), 2);
+
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ testMoveCommand("cmd_charPrevious", node(22), 1);
+ testMoveCommand("cmd_charNext", node(22), 2);
+ testSelectCommand("cmd_selectCharPrevious", node(22), 1);
+ SpecialPowers.doCommand(window, "cmd_moveTop");
+ testSelectCommand("cmd_selectCharNext", node(0), 1);
+
+ SpecialPowers.doCommand(window, "cmd_moveTop");
+ testMoveCommand("cmd_endLine", node(0), 1);
+ testMoveCommand("cmd_beginLine", node(0), 0);
+ testSelectCommand("cmd_selectEndLine", node(0), 1);
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ testSelectCommand("cmd_selectBeginLine", node(22), 0);
+
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ testMoveCommand("cmd_wordPrevious", node(22), 0);
+ testMoveCommand("cmd_wordNext", body, 23);
+ testSelectCommand("cmd_selectWordPrevious", node(22), 0);
+ SpecialPowers.doCommand(window, "cmd_moveTop");
+ testSelectCommand("cmd_selectWordNext", selectWordNextNode, selectWordNextOffset);
+
+ SpecialPowers.doCommand(window, "cmd_moveTop");
+ var lineNum = testPageMoveCommand("cmd_movePageDown", 0);
+ ok(lineNum > 0, "cmd_movePageDown works");
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ SpecialPowers.doCommand(window, "cmd_beginLine");
+ is(testPageMoveCommand("cmd_movePageUp", 0), 22 - lineNum, "cmd_movePageUp");
+
+ SpecialPowers.doCommand(window, "cmd_moveTop");
+ is(testPageSelectCommand("cmd_selectPageDown", 0), lineNum, "cmd_selectPageDown");
+ SpecialPowers.doCommand(window, "cmd_moveBottom");
+ SpecialPowers.doCommand(window, "cmd_beginLine");
+ is(testPageSelectCommand("cmd_selectPageUp", 0), 22 - lineNum, "cmd_selectPageUp");
+ }
+
+ yield SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", false]]});
+ runSelectionTests(body, 1);
+ yield SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", true]]});
+ runSelectionTests(node(2), 0);
+}
+
+function cleanup() {
+ winUtils.restoreNormalRefresh();
+ SimpleTest.finish();
+}
+
+function* testRunner() {
+ let curTest = runTests();
+ while (true) {
+ winUtils.advanceTimeAndRefresh(100);
+ if (curTest.next().done) {
+ break;
+ }
+ winUtils.advanceTimeAndRefresh(100);
+ yield new Promise(resolve => setTimeout(resolve, 20));
+ }
+}
+
+spawn_task(setup)
+ .then(() => spawn_task(testRunner))
+ .then(() => spawn_task(cleanup))
+ .catch(err => ok(false, err));
+</script>
diff --git a/editor/libeditor/tests/test_set_document_title_transaction.html b/editor/libeditor/tests/test_set_document_title_transaction.html
new file mode 100644
index 000000000..d745d4f13
--- /dev/null
+++ b/editor/libeditor/tests/test_set_document_title_transaction.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for SetDocumentTitleTransaction</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body onload="runTests()">
+<div id="display">
+ <iframe src="data:text/html,<!DOCTYPE html><html><head><title>first title</title></head><body></body></html>"></iframe>
+</div>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+function runTests() {
+ var iframe = document.getElementsByTagName("iframe")[0];
+ function isDocumentTitleEquals(aDescription, aExpectedTitle) {
+ is(iframe.contentDocument.title, aExpectedTitle, aDescription + ": document.title should be " + aExpectedTitle);
+ is(iframe.contentDocument.getElementsByTagName("title")[0].textContent, aExpectedTitle, aDescription + ": The text in the title element should be " + aExpectedTitle);
+ }
+
+ isDocumentTitleEquals("Checking isDocumentTitleEquals()", "first title");
+
+ const kTests = [
+ { description: "designMode=\"on\"",
+ init: function () {
+ iframe.contentDocument.designMode = "on";
+ },
+ cleanUp: function () {
+ iframe.contentDocument.designMode = "off";
+ }
+ },
+ { description: "html element has contenteditable attribute",
+ init: function () {
+ iframe.contentDocument.documentElement.setAttribute("contenteditable", "true");
+ },
+ cleanUp: function () {
+ iframe.contentDocument.documentElement.removeAttribute("contenteditable");
+ }
+ },
+ ];
+
+ for (var i = 0; i < kTests.length; i++) {
+ const kTest = kTests[i];
+ kTest.init();
+
+ var editor = SpecialPowers.wrap(iframe.contentWindow).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIWebNavigation).
+ QueryInterface(SpecialPowers.Ci.nsIDocShell).editor;
+ ok(editor, kTest.description + ": The docshell should have editor");
+ var htmlEditor = editor.QueryInterface(SpecialPowers.Ci.nsIHTMLEditor);
+ ok(htmlEditor, kTest.description + ": The editor should have nsIHTMLEditor interface");
+
+ // Replace existing title.
+ htmlEditor.setDocumentTitle("Modified title");
+ isDocumentTitleEquals(kTest.description, "Modified title");
+
+ // When the document doesn't have <title> element, title element should be created automatically.
+ iframe.contentDocument.head.removeChild(iframe.contentDocument.getElementsByTagName("title")[0]);
+ is(iframe.contentDocument.getElementsByTagName("title").length, 0, kTest.description + ": There should be no title element");
+ htmlEditor.setDocumentTitle("new title");
+ is(iframe.contentDocument.getElementsByTagName("title").length, 1, kTest.description + ": There should be a title element");
+ isDocumentTitleEquals(kTest.description, "new title");
+
+ kTest.cleanUp();
+ }
+
+ SimpleTest.finish();
+}
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_spellcheck_pref.html b/editor/libeditor/tests/test_spellcheck_pref.html
new file mode 100644
index 000000000..9faff45f3
--- /dev/null
+++ b/editor/libeditor/tests/test_spellcheck_pref.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <title>Test if spellcheck is turned on</title>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+ is(SpecialPowers.getIntPref("layout.spellcheckDefault"), 1, "Check if the layout.spellcheckDefault pref is turned on");
+
+</script>
+</body>
+
+</html>
diff --git a/editor/libeditor/tests/test_texteditor_keyevent_handling.html b/editor/libeditor/tests/test_texteditor_keyevent_handling.html
new file mode 100644
index 000000000..5c4a8d1c2
--- /dev/null
+++ b/editor/libeditor/tests/test_texteditor_keyevent_handling.html
@@ -0,0 +1,386 @@
+<html>
+<head>
+ <title>Test for key event handler of text editor</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+ <input type="text" id="inputField">
+ <input type="password" id="passwordField">
+ <textarea id="textarea"></textarea>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+var inputField = document.getElementById("inputField");
+var passwordField = document.getElementById("passwordField");
+var textarea = document.getElementById("textarea");
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsLinux = navigator.platform.indexOf("Linux") == 0;
+
+function runTests()
+{
+ var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"].
+ getService(SpecialPowers.Ci.nsIFocusManager);
+
+ var capturingPhase = { fired: false, prevented: false };
+ var bubblingPhase = { fired: false, prevented: false };
+
+ var listener = {
+ handleEvent: function _hv(aEvent)
+ {
+ is(aEvent.type, "keypress", "unexpected event is handled");
+ switch (aEvent.eventPhase) {
+ case aEvent.CAPTURING_PHASE:
+ capturingPhase.fired = true;
+ capturingPhase.prevented = aEvent.defaultPrevented;
+ break;
+ case aEvent.BUBBLING_PHASE:
+ bubblingPhase.fired = true;
+ bubblingPhase.prevented = aEvent.defaultPrevented;
+ aEvent.preventDefault(); // prevent the browser default behavior
+ break;
+ default:
+ ok(false, "event is handled in unexpected phase");
+ }
+ }
+ };
+
+ function check(aDescription,
+ aFiredOnCapture, aFiredOnBubbling, aPreventedOnBubbling)
+ {
+ function getDesciption(aExpected)
+ {
+ return aDescription + (aExpected ? " wasn't " : " was ");
+ }
+
+ is(capturingPhase.fired, aFiredOnCapture,
+ getDesciption(aFiredOnCapture) + "fired on capture phase");
+ is(bubblingPhase.fired, aFiredOnBubbling,
+ getDesciption(aFiredOnBubbling) + "fired on bubbling phase");
+
+ // If the event is fired on bubbling phase and it was already prevented
+ // on capture phase, it must be prevented on bubbling phase too.
+ if (capturingPhase.prevented) {
+ todo(false, aDescription +
+ " was consumed already, so, we cannot test the editor behavior actually");
+ aPreventedOnBubbling = true;
+ }
+
+ is(bubblingPhase.prevented, aPreventedOnBubbling,
+ getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase");
+ }
+
+ var parentElement = document.getElementById("display");
+ SpecialPowers.addSystemEventListener(parentElement, "keypress", listener,
+ true);
+ SpecialPowers.addSystemEventListener(parentElement, "keypress", listener,
+ false);
+
+ function doTest(aElement, aDescription, aIsSingleLine, aIsReadonly,
+ aIsTabbable)
+ {
+ function reset(aText)
+ {
+ capturingPhase.fired = false;
+ capturingPhase.prevented = false;
+ bubblingPhase.fired = false;
+ bubblingPhase.prevented = false;
+ aElement.value = aText;
+ }
+
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+
+ aDescription += ": "
+
+ aElement.focus();
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement, aDescription + "failed to move focus");
+
+ // Backspace key:
+ // If editor is readonly, it doesn't consume.
+ // If editor is editable, it consumes backspace and shift+backspace.
+ // Otherwise, editor doesn't consume the event but the native key
+ // bindings on nsTextControlFrame may consume it.
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { });
+ check(aDescription + "Backspace", true, true, true);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { shiftKey: true });
+ check(aDescription + "Shift+Backspace", true, true, true);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { ctrlKey: true });
+ // Win: cmd_deleteWordBackward
+ check(aDescription + "Ctrl+Backspace",
+ true, true, aIsReadonly || kIsWin);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { altKey: true });
+ // Win: cmd_undo
+ // Mac: cmd_deleteWordBackward
+ check(aDescription + "Alt+Backspace",
+ true, true, aIsReadonly || kIsWin || kIsMac);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { metaKey: true });
+ check(aDescription + "Meta+Backspace", true, true, aIsReadonly);
+
+ reset("");
+ synthesizeKey("VK_BACK_SPACE", { osKey: true });
+ check(aDescription + "OS+Backspace", true, true, aIsReadonly);
+
+ // Delete key:
+ // If editor is readonly, it doesn't consume.
+ // If editor is editable, delete is consumed.
+ // Otherwise, editor doesn't consume the event but the native key
+ // bindings on nsTextControlFrame may consume it.
+ reset("");
+ synthesizeKey("VK_DELETE", { });
+ // Linux: native handler
+ // Mac: cmd_deleteCharForward
+ check(aDescription + "Delete",
+ true, true, !aIsReadonly || kIsLinux || kIsMac);
+
+ reset("");
+ // Win: cmd_cutOrDelete
+ // Linux: cmd_cut
+ // Mac: cmd_deleteCharForward
+ synthesizeKey("VK_DELETE", { shiftKey: true });
+ check(aDescription + "Shift+Delete",
+ true, true, true);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { ctrlKey: true });
+ // Win: cmd_deleteWordForward
+ // Linux: cmd_copy
+ check(aDescription + "Ctrl+Delete",
+ true, true, kIsWin || kIsLinux);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { altKey: true });
+ // Mac: cmd_deleteWordForward
+ check(aDescription + "Alt+Delete",
+ true, true, kIsMac);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { metaKey: true });
+ // Linux: native handler consumed.
+ check(aDescription + "Meta+Delete",
+ true, true, kIsLinux);
+
+ reset("");
+ synthesizeKey("VK_DELETE", { osKey: true });
+ check(aDescription + "OS+Delete",
+ true, true, false);
+
+ // XXX input.value returns "\n" when it's empty, so, we should use dummy
+ // value ("a") for the following tests.
+
+ // Return key:
+ // If editor is readonly, it doesn't consume.
+ // If editor is editable and not single line editor, it consumes Return
+ // and Shift+Return.
+ // Otherwise, editor doesn't consume the event.
+ reset("a");
+ synthesizeKey("VK_RETURN", { });
+ check(aDescription + "Return",
+ true, true, !aIsSingleLine && !aIsReadonly);
+ is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
+ aDescription + "Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { shiftKey: true });
+ check(aDescription + "Shift+Return",
+ true, true, !aIsSingleLine && !aIsReadonly);
+ is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
+ aDescription + "Shift+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { ctrlKey: true });
+ check(aDescription + "Ctrl+Return", true, true, false);
+ is(aElement.value, "a", aDescription + "Ctrl+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { altKey: true });
+ check(aDescription + "Alt+Return", true, true, false);
+ is(aElement.value, "a", aDescription + "Alt+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { metaKey: true });
+ check(aDescription + "Meta+Return", true, true, false);
+ is(aElement.value, "a", aDescription + "Meta+Return");
+
+ reset("a");
+ synthesizeKey("VK_RETURN", { osKey: true });
+ check(aDescription + "OS+Return", true, true, false);
+ is(aElement.value, "a", aDescription + "OS+Return");
+
+ // Tab key:
+ // If editor is tabbable, editor doesn't consume all tab key events.
+ // Otherwise, editor consumes tab key event without any modifier keys.
+ reset("a");
+ synthesizeKey("VK_TAB", { });
+ check(aDescription + "Tab",
+ true, true, !aIsTabbable && !aIsReadonly);
+ is(aElement.value, !aIsTabbable && !aIsReadonly ? "a\t" : "a",
+ aDescription + "Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab)");
+
+ // If the editor is not tabbable, make sure that it accepts tab characters
+ // even if it's empty.
+ if (!aIsTabbable && !aIsReadonly) {
+ reset("");
+ synthesizeKey("VK_TAB", {});
+ check(aDescription + "Tab on empty textarea",
+ true, true, !aIsReadonly);
+ is(aElement.value, "\t", aDescription + "Tab on empty textarea");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Tab on empty textarea");
+ }
+
+ reset("a");
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ check(aDescription + "Shift+Tab", true, true, false);
+ is(aElement.value, "a", aDescription + "Shift+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Shift+Tab)");
+
+ // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
+ // event should never be fired.
+ reset("a");
+ synthesizeKey("VK_TAB", { ctrlKey: true });
+ check(aDescription + "Ctrl+Tab", false, false, false);
+ is(aElement.value, "a", aDescription + "Ctrl+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Ctrl+Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { altKey: true });
+ check(aDescription + "Alt+Tab", true, true, false);
+ is(aElement.value, "a", aDescription + "Alt+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Alt+Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { metaKey: true });
+ check(aDescription + "Meta+Tab", true, true, false);
+ is(aElement.value, "a", aDescription + "Meta+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (Meta+Tab)");
+
+ reset("a");
+ synthesizeKey("VK_TAB", { osKey: true });
+ check(aDescription + "OS+Tab", true, true, false);
+ is(aElement.value, "a", aDescription + "OS+Tab");
+ is(SpecialPowers.unwrap(fm.focusedElement), aElement,
+ aDescription + "focus moved unexpectedly (OS+Tab)");
+
+ // Esc key:
+ // In all cases, esc key events are not consumed
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { });
+ check(aDescription + "Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { shiftKey: true });
+ check(aDescription + "Shift+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { ctrlKey: true });
+ check(aDescription + "Ctrl+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { altKey: true });
+ check(aDescription + "Alt+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { metaKey: true });
+ check(aDescription + "Meta+Esc", true, true, false);
+
+ reset("abc");
+ synthesizeKey("VK_ESCAPE", { osKey: true });
+ check(aDescription + "OS+Esc", true, true, false);
+
+ // typical typing tests:
+ reset("");
+ synthesizeKey("M", { shiftKey: true });
+ check(aDescription + "M", true, true, !aIsReadonly);
+ synthesizeKey("o", { });
+ check(aDescription + "o", true, true, !aIsReadonly);
+ synthesizeKey("z", { });
+ check(aDescription + "z", true, true, !aIsReadonly);
+ synthesizeKey("i", { });
+ check(aDescription + "i", true, true, !aIsReadonly);
+ synthesizeKey("l", { });
+ check(aDescription + "l", true, true, !aIsReadonly);
+ synthesizeKey("l", { });
+ check(aDescription + "l", true, true, !aIsReadonly);
+ synthesizeKey("a", { });
+ check(aDescription + "a", true, true, !aIsReadonly);
+ synthesizeKey(" ", { });
+ check(aDescription + "' '", true, true, !aIsReadonly);
+ is(aElement.value, !aIsReadonly ? "Mozilla " : "",
+ aDescription + "typed \"Mozilla \"");
+ }
+
+ doTest(inputField, "<input type=\"text\">", true, false, true);
+
+ inputField.setAttribute("readonly", "readonly");
+ doTest(inputField, "<input type=\"text\" readonly>", true, true, true);
+
+ doTest(passwordField, "<input type=\"password\">", true, false, true);
+
+ passwordField.setAttribute("readonly", "readonly");
+ doTest(passwordField, "<input type=\"password\" readonly>", true, true, true);
+
+ doTest(textarea, "<textarea>", false, false, true);
+
+ textarea.setAttribute("readonly", "readonly");
+ doTest(textarea, "<textarea readonly>", false, true, true);
+
+ // make non-tabbable plaintext editor
+ textarea.removeAttribute("readonly");
+ const nsIPlaintextEditor = SpecialPowers.Ci.nsIPlaintextEditor;
+ const nsIDOMNSEditableElement = SpecialPowers.Ci.nsIDOMNSEditableElement;
+ var editor = SpecialPowers.wrap(textarea).editor;
+ var flags = editor.flags;
+ editor.flags = flags & ~(nsIPlaintextEditor.eEditorWidgetMask |
+ nsIPlaintextEditor.eEditorAllowInteraction);
+ doTest(textarea, "non-tabbable <textarea>", false, false, false);
+
+ textarea.setAttribute("readonly", "readonly");
+ doTest(textarea, "non-tabbable <textarea readonly>", false, true, false);
+
+ editor.flags = flags;
+
+ SpecialPowers.removeSystemEventListener(parentElement, "keypress", listener,
+ true);
+ SpecialPowers.removeSystemEventListener(parentElement, "keypress", listener,
+ false);
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>