diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /editor/libeditor/tests | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'editor/libeditor/tests')
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_, "&").replace(goog$string$ltRe_, "<").replace(goog$string$gtRe_, ">").replace(goog$string$quotRe_, """); + else { + if(!goog$string$allRe_.test(str))return str; + if(str.indexOf("&") != -1)str = str.replace(goog$string$amperRe_, "&"); + if(str.indexOf("<") != -1)str = str.replace(goog$string$ltRe_, "<"); + if(str.indexOf(">") != -1)str = str.replace(goog$string$gtRe_, ">"); + if(str.indexOf('"') != -1)str = str.replace(goog$string$quotRe_, """); + 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, '<').replace(/\>/g, '>');
+ 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, '<').replace(/\>/g, '>');
+ 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, '<').replace(/\>/g, '>');
+ td.innerHTML = success ? ' ' : 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, '<').replace(/>/g, '>');
+ 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, '<').replace(/\>/g, '>');;
+ tr.appendChild(td);
+
+ // Column 5: resulting html
+ td = document.createElement('TD');
+ td.innerHTML = editorDoc.body.innerHTML.replace(/\</g, '<').replace(/\>/g, '>');;
+ 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, '<').replace(/\>/g, '>') : ''; +} + +/** + * 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 = ' '; + var valOutput = ' '; + + 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_, "&").replace(goog.string.ltRe_, "<").replace(goog.string.gtRe_, ">").replace(goog.string.quotRe_, """) + }else { + if(!goog.string.allRe_.test(str)) { + return str + } + if(str.indexOf("&") != -1) { + str = str.replace(goog.string.amperRe_, "&") + } + if(str.indexOf("<") != -1) { + str = str.replace(goog.string.ltRe_, "<") + } + if(str.indexOf(">") != -1) { + str = str.replace(goog.string.gtRe_, ">") + } + if(str.indexOf('"') != -1) { + str = str.replace(goog.string.quotRe_, """) + } + 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, "  "), 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> </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 = '●'; +var OUTSTR_NO = '○'; +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"> PASS </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"> PASS </TD></TR> + <TR CLASS="lo"><TD CLASS="accept" ALIGN="CENTER"> ACC. </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"> ACC. </TD></TR> + <TR CLASS="lo"><TD CLASS="fail" ALIGN="CENTER"> FAIL </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"> FAIL </TD></TR> + <TR CLASS="lo"><TD CLASS="canary" ALIGN="CENTER"> CANARY </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"> CANARY </TD></TR> + <TR CLASS="lo"><TD CLASS="unsupported" ALIGN="CENTER"> UNS. </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"> UNS. </TD></TR> + <TR CLASS="lo"><TD CLASS="exception" ALIGN="CENTER"> EXC. </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"> EXC. </TD></TR> + <TR CLASS="lo"><TD CLASS="na" ALIGN="CENTER"> N/A </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"> N/A </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> </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> </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"> </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> 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"> </TD> + <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:val"> </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"> </TD> + <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:sty" ALIGN="CENTER"> </TD> + {% endifnotequal %}{% endifnotequal %} + <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:cnt:dM" TITLE="designMode="on"" 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"> </TD> + <TD ROWSPAN=3 ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:exp"> </TD> + <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:act:dM"><I>Processing...</I></TD> + <TD ROWSPAN=3>{{ t.desc|default:" " }}</TD> + </TR> + <TR ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:TR:body" CLASS="{% cycle shade %}"> + <TD ID="{{ commonIDPrefix }}-{{ s.id }}_{{ t.id }}_:cnt:body" TITLE="<body contentEditable="true">" 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="<div contentEditable="true">" 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ö^barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-3_SC', + 'desc': 'Delete 1 character with combining diaeresis above', + 'pad': 'foö^barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-4_SC', + 'desc': 'Delete 1 character with combining diaeresis below', + 'pad': 'foo̤^barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-5_SC', + 'desc': 'Delete 1 character with combining diaeresis above and below', + 'pad': 'foö̤^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[̈]̤barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-5_SI-2', + 'desc': 'Delete 1 character with combining diaeresis above and below, selection on diaeresis below', + 'pad': 'foö[̤]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': 'foö[̤bar]baz', + 'expected': 'fo^baz' }, + + { 'id': 'CHAR-6_SC', + 'desc': 'Delete 1 character with enclosing square', + 'pad': 'foo⃞^barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-7_SC', + 'desc': 'Delete 1 character with combining long solidus overlay', + 'pad': 'foo̸^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^öbarbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-3_SC', + 'desc': 'Delete 1 character with combining diaeresis above', + 'pad': 'fo^öbarbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-4_SC', + 'desc': 'Delete 1 character with combining diaeresis below', + 'pad': 'fo^o̤barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-5_SC', + 'desc': 'Delete 1 character with combining diaeresis above and below', + 'pad': 'fo^ö̤barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-6_SC', + 'desc': 'Delete 1 character with enclosing square', + 'pad': 'fo^o⃞barbaz', + 'expected': 'fo^barbaz' }, + + { 'id': 'CHAR-7_SC', + 'desc': 'Delete 1 character with combining long solidus overlay', + 'pad': 'fo^o̸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^öbarbaz', + 'expected': 'foö^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^öbarbaz', + 'expected': 'foö^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̤barbaz', + 'expected': 'foo̤^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^ö̤barbaz', + 'expected': 'foö̤^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[̈]̤barbaz', + 'expected': 'foö̤^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': 'foö[̤]barbaz', + 'expected': 'foö̤^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[ö]̤barbaz', + 'expected': 'foö̤^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': 'foö[̤bar]baz', + 'expected': 'foö̤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⃞barbaz', + 'expected': 'foo⃞^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̸barbaz', + 'expected': 'foo̸^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ö^barbaz', + 'expected': 'fo^ö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': 'foö^barbaz', + 'expected': 'fo^ö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̤^barbaz', + 'expected': 'fo^o̤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': 'foö̤^barbaz', + 'expected': 'fo^ö̤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[̈]̤barbaz', + 'expected': 'fo^ö̤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': 'foö[̤]barbaz', + 'expected': 'fo^ö̤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[ö]̤barbaz', + 'expected': 'fo^ö̤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': 'foö[̤bar]baz', + 'expected': 'fo^ö̤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⃞^barbaz', + 'expected': 'fo^o⃞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̸^barbaz', + 'expected': 'fo^o̸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': 'مرح^با العالم', + 'expected': 'مرحب^ا العالم' }, + + { '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': 'مرح^با العالم', + 'expected': 'مر^حبا العالم' }, + + { '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': 'של^ום עולם', + 'expected': 'שלו^ם עולם' }, + + { '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': 'של^ום עולם', + 'expected': 'ש^לום עולם' }, + + + { '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)‫car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫car يعني سيا^رة‬(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)‫car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫car يعني س^يارة‬(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)‫car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫car يعني س^يارة‬(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)‫car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫car يعني سيا^رة‬(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)‫c^ar يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫ca^r يعني سيارة‬(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)‫ca^r يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫c^ar يعني سيارة‬(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)‫c^ar يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫ca^r يعني سيارة‬(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)‫ca^r يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLE)‫c^ar يعني سيارة‬(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)‮car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮car يعني سيا^رة‬(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)‮car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮car يعني س^يارة‬(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)‮car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮car يعني س^يارة‬(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)‮car يعني سي^ارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮car يعني سيا^رة‬(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)‮c^ar يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮ca^r يعني سيارة‬(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)‮ca^r يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮c^ar يعني سيارة‬(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)‮ca^r يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮c^ar يعني سيارة‬(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)‮c^ar يعني سيارة‬(PDF)".', + 'expected': 'I said, "(RLO)‮ca^r يعني سيارة‬(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, "يعني سيارة!^?!‏(RLM)".', + 'expected': 'I said, "يعني سيارة!?^!‏(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, "يعني سيارة!?^!‏(RLM)".', + 'expected': 'I said, "يعني سيارة!^?!‏(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, "يعني سيارة!?^!‏(RLM)".', + 'expected': 'I said, "يعني سيارة!^?!‏(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, "يعني سيارة!^?!‏(RLM)".', + 'expected': 'I said, "يعني سيارة!?^!‏(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': '^面白い例文をテストしましょう。', + 'expected': '面白い^例文をテストしましょう。' }, + + { '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': '面^白い例文をテストしましょう。', + 'expected': '面白い^例文をテストしましょう。' }, + + { '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': '面白い^例文をテストしましょう。', + 'expected': [ '面白い例文^をテストしましょう。', + '面白い例文を^テストしましょう。' ] }, + + { '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': '面白い例文を^テストしましょう。', + 'expected': '面白い例文をテスト^しましょう。' }, + + { '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': '面白い例文をテスト^しましょう。', + 'expected': '面白い例文をテストしましょう^。' } + ] + }, + + { '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 Binary files differnew file mode 100644 index 000000000..7e0253715 --- /dev/null +++ b/editor/libeditor/tests/data/cfhtml-chromium.txt diff --git a/editor/libeditor/tests/data/cfhtml-firefox.txt b/editor/libeditor/tests/data/cfhtml-firefox.txt Binary files differnew file mode 100644 index 000000000..cc686d856 --- /dev/null +++ b/editor/libeditor/tests/data/cfhtml-firefox.txt diff --git a/editor/libeditor/tests/data/cfhtml-ie.txt b/editor/libeditor/tests/data/cfhtml-ie.txt Binary files differnew file mode 100644 index 000000000..a30bc5295 --- /dev/null +++ b/editor/libeditor/tests/data/cfhtml-ie.txt 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 Binary files differnew file mode 100644 index 000000000..0bcf7616e --- /dev/null +++ b/editor/libeditor/tests/data/cfhtml-ooo.txt 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 Binary files differnew file mode 100644 index 000000000..0aaec2093 --- /dev/null +++ b/editor/libeditor/tests/green.png 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☺️b</div><!-- BMP symbol with VS16 --> + <div id="edit2" contenteditable="true">a🌐︎b</div><!-- plane 1 symbol with VS15 --> + <div id="edit3" contenteditable="true">a㐂󠄀b</div><!-- BMP ideograph with VS17 --> + <div id="edit4" contenteditable="true">a𠀀󠄁b</div><!-- SMP ideograph with VS18 --> + <div id="edit5" contenteditable="true">a☺︁︂︃b</div><!-- BMP symbol with extra VSes --> + <div id="edit6" contenteditable="true">a𠀀󠄀󠄁󠄂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🇨🇳b</div><!-- Regional Indicator flag: CN --> + <div id="edit8" contenteditable="true">a🇨🇳🇩🇪b</div><!-- two RI flags: CN, DE --> + <div id="edit9" contenteditable="true">a🇨🇳🇩🇪🇪🇸b</div><!-- three RI flags: CN, DE, ES --> + <div id="edit10" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷b</div><!-- four RI flags: CN, DE, ES, FR --> + <div id="edit11" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷🇬🇧b</div><!-- five RI flags: CN, DE, ES, FR, GB --> + + <div id="edit1b" contenteditable="true">a☺️b</div><!-- BMP symbol with VS16 --> + <div id="edit2b" contenteditable="true">a🌐︎b</div><!-- plane 1 symbol with VS15 --> + <div id="edit3b" contenteditable="true">a㐂󠄀b</div><!-- BMP ideograph with VS17 --> + <div id="edit4b" contenteditable="true">a𠀀󠄁b</div><!-- SMP ideograph with VS18 --> + <div id="edit5b" contenteditable="true">a☺︁︂︃b</div><!-- BMP symbol with extra VSes --> + <div id="edit6b" contenteditable="true">a𠀀󠄀󠄁󠄂b</div><!-- SMP symbol with extra VSes --> + <div id="edit7b" contenteditable="true">a🇨🇳b</div><!-- Regional Indicator flag: CN --> + <div id="edit8b" contenteditable="true">a🇨🇳🇩🇪b</div><!-- two RI flags: CN, DE --> + <div id="edit9b" contenteditable="true">a🇨🇳🇩🇪🇪🇸b</div><!-- three RI flags: CN, DE, ES --> + <div id="edit10b" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷b</div><!-- four RI flags: CN, DE, ES, FR --> + <div id="edit11b" contenteditable="true">a🇨🇳🇩🇪🇪🇸🇫🇷🇬🇧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">𝐀</div> + <div id="editor2" contenteditable="true">a<u>𝐁</u>b</div> + <div id="editor3" contenteditable="true">a𝐂<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;\">> mailcite<br></span>", 0, + "x<br><span style=\"display: block;\">> mailcite<br></span>" ], + [ "<span _moz_quote=true style=\"display: block;\">> mailcite<br></span>", 5, + "<span style=\"display: block;\">> mai<br></span>x<br><span style=\"display: block;\">lcite<br></span>"], + [ "<span _moz_quote=true style=\"display: block;\">> mailcite<br></span>", 10, + "<span style=\"display: block;\">> 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;\">> mailcite</span>", 10, + "<span style=\"display: block;\">> mailcite<br></span>x<br>" ], + + // Without style="display: block;". + [ "<span _moz_quote=true>> mailcite<br></span>", 0, + "x<br><span>> mailcite<br></span>" ], + [ "<span _moz_quote=true>> mailcite<br></span>", 5, + "<span>> mai</span><br>x<br><span>lcite<br></span>" ], + [ "<span _moz_quote=true>> mailcite<br></span>", 10, + "<span>> mailcite<br></span>x<br>" ], + // No <br> at the end to simulate prior deletion to the end of the quote. + [ "<span _moz_quote=true>> mailcite</span>", 10, + "<span>> 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">äb</div><!-- reference: plane 0 diacritic --> + <div id="edit2" contenteditable="true">a𐐀b</div><!-- plane 1 base character --> + <div id="edit3" contenteditable="true">a𐨏b</div><!-- plane 1 diacritic --> + + <div id="edit0b" contenteditable="true">axb</div><!-- reference: plane 0 base character --> + <div id="edit1b" contenteditable="true">äb</div><!-- reference: plane 0 diacritic --> + <div id="edit2b" contenteditable="true">a𐐀b</div><!-- plane 1 base character --> + <div id="edit3b" contenteditable="true">a𐨏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 <div> 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, " 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" ? " " : " <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="1.0"><binding xmlns="http://www.mozilla.org/xbl"/>');\">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="1.0"><binding xmlns="http://www.mozilla.org/xbl" id="binding-1"/>');<style></style>#bar2{-moz-binding:url('data:text/xml,<?xml version="1.0"><binding xmlns="http://www.mozilla.org/xbl" id="binding-2"/>');</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 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 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 b", "We should add an 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	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="on"; + document.designMode="off"; + ' + > + <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="𐑑𐑧𐑕𐑑"> +<textarea>𐑑𐑧𐑕𐑑</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,<div contenteditable=true id=t>test me now</div>"></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邀󠄏辺󠄁</textarea> +<div contenteditable id="edit">b邀󠄏辺󠄁</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 <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> <body></body></html>', designMode", + initializer: runDesignModeTest, + args: [ "<html> <body></body></html>" ] }, + { description: "Replace to ' <html> <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> |