diff --git a/b2g/Makefile.in b/b2g/Makefile.in index f28113192bb..58affc5b02c 100644 --- a/b2g/Makefile.in +++ b/b2g/Makefile.in @@ -42,7 +42,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -DIRS = chrome locales app +DIRS = chrome components locales app include $(topsrcdir)/config/rules.mk include $(topsrcdir)/testing/testsuite-targets.mk diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 230de4f418d..c9b7c7ab224 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -65,11 +65,7 @@ pref("browser.cache.memory.capacity", 1024); // kilobytes pref("image.cache.size", 1048576); // bytes /* offline cache prefs */ -pref("browser.offline-apps.notify", true); -pref("browser.cache.offline.enable", true); -pref("browser.cache.offline.capacity", 5120); // kilobytes -pref("offline-apps.quota.max", 2048); // kilobytes -pref("offline-apps.quota.warn", 1024); // kilobytes +pref("browser.offline-apps.notify", false); /* protocol warning prefs */ pref("network.protocol-handler.warn-external.tel", false); diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 0e7f83c6c77..01996bb115e 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -68,6 +68,7 @@ function startupHttpd(baseDir, port) { Services.scriptloader.loadSubScript(httpdURL, httpd); let server = new httpd.nsHttpServer(); server.registerDirectory('/', new LocalFile(baseDir)); + server.registerContentType('appcache', 'text/cache-manifest'); server.start(port); } @@ -114,6 +115,7 @@ var shell = { window.controllers.appendController(this); window.addEventListener('keypress', this); + window.addEventListener('MozApplicationManifest', this); this.home.addEventListener('load', this, true); try { @@ -147,6 +149,7 @@ var shell = { stop: function shell_stop() { window.controllers.removeController(this); window.removeEventListener('keypress', this); + window.removeEventListener('MozApplicationManifest', this); }, supportsCommand: function shell_supportsCommand(cmd) { @@ -195,6 +198,29 @@ var shell = { this.home.removeEventListener('load', this, true); this.sendEvent(window, 'ContentStart'); break; + case 'MozApplicationManifest': + try { + let contentWindow = evt.originalTarget.defaultView; + let documentElement = contentWindow.document.documentElement; + if (!documentElement) + return; + + let manifest = documentElement.getAttribute("manifest"); + if (!manifest) + return; + + let documentURI = contentWindow.document.documentURIObject; + let manifestURI = Services.io.newURI(manifest, null, documentURI); + Services.perms.add(documentURI, 'offline-app', + Ci.nsIPermissionManager.ALLOW_ACTION); + + let updateService = Cc['@mozilla.org/offlinecacheupdate-service;1'] + .getService(Ci.nsIOfflineCacheUpdateService); + updateService.scheduleUpdate(manifestURI, documentURI, window); + } catch (e) { + dump('Error while creating offline cache: ' + e + '\n'); + } + break; } }, sendEvent: function shell_sendEvent(content, type, details) { diff --git a/b2g/components/B2GComponents.manifest b/b2g/components/B2GComponents.manifest new file mode 100644 index 00000000000..e69de29bb2d diff --git a/intl/uconv/util/external/Makefile.in b/b2g/components/Makefile.in similarity index 72% rename from intl/uconv/util/external/Makefile.in rename to b2g/components/Makefile.in index b73caddd4f5..a837f027699 100644 --- a/intl/uconv/util/external/Makefile.in +++ b/b2g/components/Makefile.in @@ -1,4 +1,3 @@ -# # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # @@ -12,18 +11,18 @@ # for the specific language governing rights and limitations under the # License. # -# The Original Code is mozilla.org code. +# The Original Code is Mozilla. # # The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 +# the Mozilla Foundation . +# Portions created by the Initial Developer are Copyright (C) 2011 # the Initial Developer. All Rights Reserved. # # Contributor(s): # # Alternatively, the contents of this file may be used under the terms of -# either of the GNU General Public License Version 2 or later (the "GPL"), -# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to @@ -35,23 +34,18 @@ # # ***** END LICENSE BLOCK ***** -DEPTH = ../../../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -MODULE = uconv -LIBRARY_NAME = ucvxutil_s -EXPORT_LIBRARY = 1 -FORCE_STATIC_LIB = 1 +MODULE = B2GComponents +XPIDL_MODULE = B2GComponents -MODULE_NAME = nsExternalUCUtil - -VPATH += $(srcdir)/.. - -include $(srcdir)/../objs.mk +EXTRA_PP_COMPONENTS = \ + B2GComponents.manifest \ + $(NULL) include $(topsrcdir)/config/rules.mk - diff --git a/b2g/makefiles.sh b/b2g/makefiles.sh index b7d723519e6..68b28946222 100644 --- a/b2g/makefiles.sh +++ b/b2g/makefiles.sh @@ -42,6 +42,7 @@ security/manager/locales/Makefile b2g/app/Makefile $MOZ_BRANDING_DIRECTORY/Makefile b2g/chrome/Makefile +b2g/components/Makefile b2g/installer/Makefile b2g/locales/Makefile b2g/Makefile" diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 445fe9b023a..07e5548e2cc 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -945,6 +945,7 @@ pref("services.sync.prefs.sync.browser.safebrowsing.enabled", true); pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true); pref("services.sync.prefs.sync.browser.search.selectedEngine", true); pref("services.sync.prefs.sync.browser.search.update", true); +pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true); pref("services.sync.prefs.sync.browser.startup.homepage", true); pref("services.sync.prefs.sync.browser.startup.page", true); pref("services.sync.prefs.sync.browser.tabs.autoHide", true); diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index 44c3ded0020..51096d14f6b 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -221,8 +221,9 @@ var gEditItemOverlay = { this._allTags = this._getCommonTags(); this._initTextField("tagsField", this._allTags.join(", "), false); this._element("itemsCountText").value = - PlacesUIUtils.getFormattedString("detailsPane.multipleItems", - [this._itemIds.length]); + PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", + this._itemIds.length, + [this._itemIds.length]); } // tags selector diff --git a/browser/components/places/content/places.js b/browser/components/places/content/places.js index 9cf3db93510..1e24d9e659f 100644 --- a/browser/components/places/content/places.js +++ b/browser/components/places/content/places.js @@ -713,8 +713,8 @@ var PlacesOrganizer = { var itemsCountLabel = document.getElementById("itemsCountText"); selectItemDesc.hidden = false; itemsCountLabel.value = - PlacesUIUtils.getFormattedString("detailsPane.multipleItems", - [aNodeList.length]); + PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", + aNodeList.length, [aNodeList.length]); infoBox.hidden = true; return; } @@ -743,13 +743,9 @@ var PlacesOrganizer = { } else { selectItemDesc.hidden = false; - if (rowCount == 1) - itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.oneItem"); - else { - itemsCountLabel.value = - PlacesUIUtils.getFormattedString("detailsPane.multipleItems", - [rowCount]); - } + itemsCountLabel.value = + PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", + rowCount, [rowCount]); } } }, diff --git a/browser/components/places/src/PlacesUIUtils.jsm b/browser/components/places/src/PlacesUIUtils.jsm index ab9679b8257..0b63574b53c 100644 --- a/browser/components/places/src/PlacesUIUtils.jsm +++ b/browser/components/places/src/PlacesUIUtils.jsm @@ -50,6 +50,9 @@ var Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { Cu.import("resource://gre/modules/PlacesUtils.jsm"); return PlacesUtils; @@ -79,6 +82,31 @@ var PlacesUIUtils = { return bundle.formatStringFromName(key, params, params.length); }, + /** + * Get a localized plural string for the specified key name and numeric value + * substituting parameters. + * + * @param aKey + * String, key for looking up the localized string in the bundle + * @param aNumber + * Number based on which the final localized form is looked up + * @param aParams + * Array whose items will substitute #1, #2,... #n parameters + * in the string. + * + * @see https://developer.mozilla.org/en/Localization_and_Plurals + * @return The localized plural string. + */ + getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) { + let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey)); + + // Replace #1 with aParams[0], #2 with aParams[1], and so on. + return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) { + let param = aParams[parseInt(matchedNumber, 10) - 1]; + return param !== undefined ? param : matchedId; + }); + }, + getString: function PUIU_getString(key) { return bundle.GetStringFromName(key); }, diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js index 33962566503..ac611d846c7 100644 --- a/browser/components/preferences/main.js +++ b/browser/components/preferences/main.js @@ -50,7 +50,7 @@ var gMainPane = { // set up the "use current page" label-changing listener this._updateUseCurrentButton(); - window.addEventListener("focus", this._updateUseCurrentButton, false); + window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false); this.updateBrowserStartupLastSession(); this.startupPagePrefChanged(); @@ -128,23 +128,13 @@ var gMainPane = { */ setHomePageToCurrent: function () { - var win; - if (document.documentElement.instantApply) { - // If we're in instant-apply mode, use the most recent browser window - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); - win = wm.getMostRecentWindow("navigator:browser"); - } - else - win = window.opener; + let homePage = document.getElementById("browser.startup.homepage"); + let tabs = this._getTabsForHomePage(); + function getTabURI(t) t.linkedBrowser.currentURI.spec; - if (win) { - var homePage = document.getElementById("browser.startup.homepage"); - var tabs = win.gBrowser.visibleTabs; - function getTabURI(t) t.linkedBrowser.currentURI.spec; - // FIXME Bug 244192: using dangerous "|" joiner! + // FIXME Bug 244192: using dangerous "|" joiner! + if (tabs.length) homePage.value = tabs.map(getTabURI).join("|"); - } }, /** @@ -170,10 +160,26 @@ var gMainPane = { * forms. */ _updateUseCurrentButton: function () { - var useCurrent = document.getElementById("useCurrent"); + let useCurrent = document.getElementById("useCurrent"); - var windowIsPresent; + let tabs = this._getTabsForHomePage(); + if (tabs.length > 1) + useCurrent.label = useCurrent.getAttribute("label2"); + else + useCurrent.label = useCurrent.getAttribute("label1"); + + // In this case, the button's disabled state is set by preferences.xml. + if (document.getElementById + ("pref.browser.homepage.disable_button.current_page").locked) + return; + + useCurrent.disabled = !tabs.length + }, + + _getTabsForHomePage: function () + { var win; + var tabs = []; if (document.documentElement.instantApply) { const Cc = Components.classes, Ci = Components.interfaces; // If we're in instant-apply mode, use the most recent browser window @@ -181,30 +187,17 @@ var gMainPane = { .getService(Ci.nsIWindowMediator); win = wm.getMostRecentWindow("navigator:browser"); } - else + else { win = window.opener; + } if (win && win.document.documentElement .getAttribute("windowtype") == "navigator:browser") { - windowIsPresent = true; - - var tabbrowser = win.document.getElementById("content"); - if (tabbrowser.browsers.length > 1) - useCurrent.label = useCurrent.getAttribute("label2"); - else - useCurrent.label = useCurrent.getAttribute("label1"); - } - else { - windowIsPresent = false; - useCurrent.label = useCurrent.getAttribute("label1"); + // We should only include visible & non-pinned tabs + tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs); } - // In this case, the button's disabled state is set by preferences.xml. - if (document.getElementById - ("pref.browser.homepage.disable_button.current_page").locked) - return; - - useCurrent.disabled = !windowIsPresent; + return tabs; }, /** diff --git a/browser/components/sessionstore/test/browser/browser_644409-scratchpads.js b/browser/components/sessionstore/test/browser/browser_644409-scratchpads.js index a81746e9357..0058c44aa7b 100644 --- a/browser/components/sessionstore/test/browser/browser_644409-scratchpads.js +++ b/browser/components/sessionstore/test/browser/browser_644409-scratchpads.js @@ -38,11 +38,19 @@ function test() { function windowObserver(aSubject, aTopic, aData) { if (aTopic == "domwindowopened") { let win = aSubject.QueryInterface(Ci.nsIDOMWindow); - win.addEventListener("load", function() { + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + if (win.Scratchpad) { - let state = win.Scratchpad.getState(); - win.close(); - addState(state); + win.Scratchpad.addObserver({ + onReady: function() { + win.Scratchpad.removeObserver(this); + + let state = win.Scratchpad.getState(); + win.close(); + addState(state); + }, + }); } }, false); } @@ -56,4 +64,4 @@ function statesMatch(restored, states) { state.executionContext == restoredState.executionContext; }) }); -} \ No newline at end of file +} diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index 9a35144980b..5c367906873 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -2,7 +2,7 @@ browser.jar: * content/browser/inspector.html (highlighter/inspector.html) content/browser/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml) * content/browser/scratchpad.xul (scratchpad/scratchpad.xul) -* content/browser/scratchpad.js (scratchpad/scratchpad.js) + content/browser/scratchpad.js (scratchpad/scratchpad.js) * content/browser/styleeditor.xul (styleeditor/styleeditor.xul) content/browser/splitview.css (styleeditor/splitview.css) content/browser/styleeditor.css (styleeditor/styleeditor.css) diff --git a/browser/devtools/scratchpad/scratchpad-manager.jsm b/browser/devtools/scratchpad/scratchpad-manager.jsm index 42a88d86363..ac4446d4cf2 100644 --- a/browser/devtools/scratchpad/scratchpad-manager.jsm +++ b/browser/devtools/scratchpad/scratchpad-manager.jsm @@ -104,7 +104,7 @@ var ScratchpadManager = { let enumerator = Services.wm.getEnumerator("devtools:scratchpad"); while (enumerator.hasMoreElements()) { let win = enumerator.getNext(); - if (!win.closed) { + if (!win.closed && win.Scratchpad.initialized) { this._scratchpads.push(win.Scratchpad.getState()); } } diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index 7ed3b496ad9..19525596ebe 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -86,6 +86,13 @@ var Scratchpad = { */ executionContext: SCRATCHPAD_CONTEXT_CONTENT, + /** + * Tells if this Scratchpad is initialized and ready for use. + * @boolean + * @see addObserver + */ + initialized: false, + /** * Retrieve the xul:notificationbox DOM element. It notifies the user when * the current code execution context is SCRATCHPAD_CONTEXT_BROWSER. @@ -733,7 +740,7 @@ var Scratchpad = { }, /** - * The Scratchpad window DOMContentLoaded event handler. This method + * The Scratchpad window load event handler. This method * initializes the Scratchpad window and source editor. * * @param nsIDOMEvent aEvent @@ -784,7 +791,9 @@ var Scratchpad = { this.onContextMenu); this.editor.focus(); this.editor.setCaretOffset(this.editor.getCharCount()); - + + this.initialized = true; + if (this.filename && !this.saved) { this.onTextChanged(); } @@ -866,7 +875,7 @@ var Scratchpad = { if (aStatus && !Components.isSuccessCode(aStatus)) { return; } - if (!document) { + if (!document || !this.initialized) { return; // file saved to disk after window has closed } document.title = document.title.replace(/^\*/, ""); @@ -904,6 +913,7 @@ var Scratchpad = { this.onContextMenu); this.editor.destroy(); this.editor = null; + this.initialized = false; }, /** @@ -1033,6 +1043,6 @@ XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () { return Services.strings.createBundle(SCRATCHPAD_L10N); }); -addEventListener("DOMContentLoaded", Scratchpad.onLoad.bind(Scratchpad), false); +addEventListener("load", Scratchpad.onLoad.bind(Scratchpad), false); addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false); addEventListener("close", Scratchpad.onClose.bind(Scratchpad), false); diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js index b2c1bdb1a04..28f6b08fec3 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js @@ -2,24 +2,21 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -let gOldPref; let DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; function test() { waitForExplicitFinish(); - gOldPref = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED); Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, true); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); - ok(Scratchpad, "Scratchpad variable exists"); + ok(window.Scratchpad, "Scratchpad variable exists"); - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + openScratchpad(runTests); }, true); content.location = "data:text/html,Scratchpad test for bug 646070 - chrome context preference"; @@ -27,8 +24,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - let sp = gScratchpadWindow.Scratchpad; ok(sp, "Scratchpad object exists in new window"); @@ -50,7 +45,7 @@ function runTests() ok(!chromeContextCommand.hasAttribute("disabled"), "Chrome context command is disabled"); - Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, gOldPref); + Services.prefs.clearUserPref(DEVTOOLS_CHROME_ENABLED); finish(); } diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js index 9704d1b98cb..c85ec8e5f68 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js @@ -17,10 +17,10 @@ function done() } } -var ScratchpadManager = Scratchpad.ScratchpadManager; var gFile; var oldPrompt = Services.prompt; +var promptButton = -1; function test() { @@ -29,6 +29,12 @@ function test() gFile = createTempFile("fileForBug653427.tmp"); writeFile(gFile, "text", testUnsaved.call(this)); + Services.prompt = { + confirmEx: function() { + return promptButton; + } + }; + testNew(); testSavedFile(); @@ -37,28 +43,23 @@ function test() function testNew() { - let win = ScratchpadManager.openScratchpad(); - - win.addEventListener("load", function() { + openScratchpad(function(win) { win.Scratchpad.close(); - ok(win.closed, "new scratchpad window should close without prompting") done(); - }); + }, {noFocus: true}); } function testSavedFile() { - let win = ScratchpadManager.openScratchpad(); - - win.addEventListener("load", function() { + openScratchpad(function(win) { win.Scratchpad.filename = "test.js"; win.Scratchpad.saved = true; win.Scratchpad.close(); ok(win.closed, "scratchpad from file with no changes should close") done(); - }); + }, {noFocus: true}); } function testUnsaved() @@ -70,17 +71,11 @@ function testUnsaved() function testUnsavedFileCancel() { - let win = ScratchpadManager.openScratchpad(); - - win.addEventListener("load", function() { + openScratchpad(function(win) { win.Scratchpad.filename = "test.js"; win.Scratchpad.saved = false; - Services.prompt = { - confirmEx: function() { - return win.BUTTON_POSITION_CANCEL; - } - } + promptButton = win.BUTTON_POSITION_CANCEL; win.Scratchpad.close(); @@ -88,14 +83,12 @@ function testUnsavedFileCancel() win.close(); done(); - }); + }, {noFocus: true}); } function testUnsavedFileSave() { - let win = ScratchpadManager.openScratchpad(); - - win.addEventListener("load", function() { + openScratchpad(function(win) { win.Scratchpad.importFromFile(gFile, true, function(status, content) { win.Scratchpad.filename = gFile.path; win.Scratchpad.onTextSaved(); @@ -103,11 +96,7 @@ function testUnsavedFileSave() let text = "new text"; win.Scratchpad.setText(text); - Services.prompt = { - confirmEx: function() { - return win.BUTTON_POSITION_SAVE; - } - } + promptButton = win.BUTTON_POSITION_SAVE; win.Scratchpad.close(function() { readFile(gFile, function(savedContent) { @@ -118,28 +107,22 @@ function testUnsavedFileSave() ok(win.closed, 'pressing "Save" in dialog should close scratchpad'); }); - }); + }, {noFocus: true}); } function testUnsavedFileDontSave() { - let win = ScratchpadManager.openScratchpad(); - - win.addEventListener("load", function() { + openScratchpad(function(win) { win.Scratchpad.filename = gFile.path; win.Scratchpad.saved = false; - Services.prompt = { - confirmEx: function() { - return win.BUTTON_POSITION_DONT_SAVE; - } - } + promptButton = win.BUTTON_POSITION_DONT_SAVE; win.Scratchpad.close(); ok(win.closed, 'pressing "Don\'t Save" in dialog should close scratchpad'); done(); - }); + }, {noFocus: true}); } function cleanup() diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js index 2c505f91b65..3687c8173a2 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js @@ -2,8 +2,6 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -var ScratchpadManager = Scratchpad.ScratchpadManager; - function test() { waitForExplicitFinish(); @@ -16,14 +14,7 @@ function test() Services.prefs.setIntPref("devtools.editor.tabsize", 5); - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", function onScratchpadLoad() { - gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false); - - gScratchpadWindow.Scratchpad.addObserver({ - onReady: runTests - }); - }, false); + openScratchpad(runTests); }, true); content.location = "data:text/html,Scratchpad test for the Tab key, bug 660560"; @@ -34,9 +25,6 @@ function runTests() let sp = gScratchpadWindow.Scratchpad; ok(sp, "Scratchpad object exists in new window"); - is(this.onReady, runTests, "the handler runs in the context of the observer"); - sp.removeObserver(this); - ok(sp.editor.hasFocus(), "the editor has focus"); sp.setText("window.foo;"); @@ -70,19 +58,12 @@ function runTests() Services.prefs.setIntPref("devtools.editor.tabsize", 6); Services.prefs.setBoolPref("devtools.editor.expandtab", false); - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", function onScratchpadLoad() { - gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false); - gScratchpadWindow.Scratchpad.addObserver({ - onReady: runTests2 - }); - }, false); + openScratchpad(runTests2); } function runTests2() { let sp = gScratchpadWindow.Scratchpad; - sp.removeObserver(this); sp.setText("window.foo;"); sp.editor.setCaretOffset(0); diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js index 10823e5e2fd..71c8d6d24d6 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js @@ -30,57 +30,37 @@ function test() function testListeners() { - let win = ScratchpadManager.openScratchpad(); + openScratchpad(function(aWin, aScratchpad) { + aScratchpad.setText("new text"); + ok(!isStar(aWin), "no star if scratchpad isn't from a file"); - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); + aScratchpad.onTextSaved(); + ok(!isStar(aWin), "no star before changing text"); - win.Scratchpad.addObserver({ - onReady: function (aScratchpad) { - aScratchpad.removeObserver(this); + aScratchpad.setText("new text2"); + ok(isStar(aWin), "shows star if scratchpad text changes"); - aScratchpad.setText("new text"); - ok(!isStar(win), "no star if scratchpad isn't from a file"); + aScratchpad.onTextSaved(); + ok(!isStar(aWin), "no star if scratchpad was just saved"); - aScratchpad.onTextSaved(); - ok(!isStar(win), "no star before changing text"); + aScratchpad.undo(); + ok(isStar(aWin), "star if scratchpad undo"); - aScratchpad.setText("new text2"); - ok(isStar(win), "shows star if scratchpad text changes"); - - aScratchpad.onTextSaved(); - ok(!isStar(win), "no star if scratchpad was just saved"); - - aScratchpad.undo(); - ok(isStar(win), "star if scratchpad undo"); - - win.close(); - done(); - } - }); - }, false); + aWin.close(); + done(); + }, {noFocus: true}); } function testErrorStatus() { - let win = ScratchpadManager.openScratchpad(); + openScratchpad(function(aWin, aScratchpad) { + aScratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE); + aScratchpad.setText("new text"); + ok(!isStar(aWin), "no star if file save failed"); - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); - - win.Scratchpad.addObserver({ - onReady: function (aScratchpad) { - aScratchpad.removeObserver(this); - - aScratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE); - aScratchpad.setText("new text"); - ok(!isStar(win), "no star if file save failed"); - - win.close(); - done(); - } - }); - }, false); + aWin.close(); + done(); + }, {noFocus: true}); } @@ -92,21 +72,13 @@ function testRestoreNotFromFile() }]; let [win] = ScratchpadManager.restoreSession(session); - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); + openScratchpad(function(aWin, aScratchpad) { + aScratchpad.setText("new text"); + ok(!isStar(win), "no star if restored scratchpad isn't from a file"); - win.Scratchpad.addObserver({ - onReady: function (aScratchpad) { - aScratchpad.removeObserver(this); - - aScratchpad.setText("new text"); - ok(!isStar(win), "no star if restored scratchpad isn't from a file"); - - win.close(); - done(); - } - }); - }, false); + win.close(); + done(); + }, {window: win, noFocus: true}); } function testRestoreFromFileSaved() @@ -119,23 +91,15 @@ function testRestoreFromFileSaved() }]; let [win] = ScratchpadManager.restoreSession(session); - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); + openScratchpad(function(aWin, aScratchpad) { + ok(!isStar(win), "no star before changing text in scratchpad restored from file"); - win.Scratchpad.addObserver({ - onReady: function (aScratchpad) { - aScratchpad.removeObserver(this); + aScratchpad.setText("new text"); + ok(isStar(win), "star when text changed from scratchpad restored from file"); - ok(!isStar(win), "no star before changing text in scratchpad restored from file"); - - aScratchpad.setText("new text"); - ok(isStar(win), "star when text changed from scratchpad restored from file"); - - win.close(); - done(); - } - }); - }, false); + win.close(); + done(); + }, {window: win, noFocus: true}); } function testRestoreFromFileUnsaved() @@ -148,20 +112,12 @@ function testRestoreFromFileUnsaved() }]; let [win] = ScratchpadManager.restoreSession(session); - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); + openScratchpad(function() { + ok(isStar(win), "star with scratchpad restored with unsaved text"); - win.Scratchpad.addObserver({ - onReady: function (aScratchpad) { - aScratchpad.removeObserver(this); - - ok(isStar(win), "star with scratchpad restored with unsaved text"); - - win.close(); - done(); - } - }); - }, false); + win.close(); + done(); + }, {window: win, noFocus: true}); } function isStar(win) diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js index e4ab6734987..e19754fb4f2 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js @@ -2,28 +2,21 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; - function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", testFalsy, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(testFalsy); }, true); content.location = "data:text/html,

test falsy display() values in Scratchpad"; } -function testFalsy(sp) +function testFalsy() { - gScratchpadWindow.removeEventListener("load", testFalsy, false); - let sp = gScratchpadWindow.Scratchpad; verifyFalsies(sp); diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js b/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js index d9d4b001e10..06f95ef3b60 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js @@ -13,9 +13,7 @@ function test() gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onLoad() { gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + openScratchpad(runTests); }, true); content.location = "data:text/html,test Edit menu updates Scratchpad - bug 699130"; @@ -23,8 +21,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", runTests, false); - let sp = gScratchpadWindow.Scratchpad; let doc = gScratchpadWindow.document; let winUtils = gScratchpadWindow.QueryInterface(Ci.nsIInterfaceRequestor). diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js b/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js index 4d9f8ebee2d..51919b65d12 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js @@ -2,19 +2,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; - function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); }, true); content.location = "data:text/html,test context switch in Scratchpad"; @@ -22,8 +17,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - let sp = gScratchpadWindow.Scratchpad; let contentMenu = gScratchpadWindow.document.getElementById("sp-menu-content"); @@ -42,7 +35,7 @@ function runTests() is(contentMenu.getAttribute("checked"), "true", "content menuitem is checked"); - ok(!chromeMenu.hasAttribute("checked"), + isnot(chromeMenu.getAttribute("checked"), "true", "chrome menuitem is not checked"); ok(!notificationBox.currentNotification, @@ -66,7 +59,7 @@ function runTests() is(chromeMenu.getAttribute("checked"), "true", "chrome menuitem is checked"); - ok(!contentMenu.hasAttribute("checked"), + isnot(contentMenu.getAttribute("checked"), "true", "content menuitem is not checked"); ok(notificationBox.currentNotification, diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js index 0acf948e95c..5278fcffc32 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js @@ -7,11 +7,9 @@ function test() waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true); + openScratchpad(runTests); }, true); content.location = "data:text/html,

test run() and display() in Scratchpad"; @@ -19,8 +17,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - let sp = gScratchpadWindow.Scratchpad; content.wrappedJSObject.foobarBug636725 = 1; diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_files.js b/browser/devtools/scratchpad/test/browser_scratchpad_files.js index e5ffddaed70..5e50c24bc4e 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_files.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_files.js @@ -5,9 +5,6 @@ Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; - // Reference to the Scratchpad object. let gScratchpad; @@ -22,11 +19,9 @@ function test() waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); }, true); content.location = "data:text/html,

test file open and save in Scratchpad"; @@ -34,8 +29,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - gScratchpad = gScratchpadWindow.Scratchpad; // Create a temporary file. diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js b/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js index b926598f1ce..67bca826a20 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js @@ -2,21 +2,17 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; - function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); - ok(Scratchpad, "Scratchpad variable exists"); + ok(window.Scratchpad, "Scratchpad variable exists"); - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + openScratchpad(runTests); }, true); content.location = "data:text/html,initialization test for Scratchpad"; @@ -24,8 +20,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - let sp = gScratchpadWindow.Scratchpad; ok(sp, "Scratchpad object exists in new window"); is(typeof sp.run, "function", "Scratchpad.run() exists"); diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js b/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js index cd52d7a0349..ed4c19f0d08 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js @@ -2,19 +2,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; - function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); }, true); content.location = "data:text/html,foobarBug636725" + @@ -23,8 +18,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - let sp = gScratchpadWindow.Scratchpad; sp.setText("document"); @@ -34,8 +27,8 @@ function runTests() let propPanel = document.querySelector(".scratchpad_propertyPanel"); ok(propPanel, "property panel is open"); - propPanel.addEventListener("popupshown", function() { - propPanel.removeEventListener("popupshown", arguments.callee, false); + propPanel.addEventListener("popupshown", function onPopupShown() { + propPanel.removeEventListener("popupshown", onPopupShown, false); let tree = propPanel.querySelector("tree"); ok(tree, "property panel tree found"); diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_open.js b/browser/devtools/scratchpad/test/browser_scratchpad_open.js index 2847c3ecb9d..5af2ffaf2c5 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_open.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js @@ -2,8 +2,6 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -var ScratchpadManager = Scratchpad.ScratchpadManager; - // only finish() when correct number of tests are done const expected = 3; var count = 0; @@ -15,7 +13,6 @@ function done() } } - function test() { waitForExplicitFinish(); @@ -26,11 +23,7 @@ function test() function testOpen() { - let win = ScratchpadManager.openScratchpad(); - - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); - + openScratchpad(function(win) { is(win.Scratchpad.filename, undefined, "Default filename is undefined"); is(win.Scratchpad.getText(), win.Scratchpad.strings.GetStringFromName("scratchpadIntro"), @@ -40,7 +33,7 @@ function testOpen() win.close(); done(); - }, false); + }, {noFocus: true}); } function testOpenWithState() @@ -51,25 +44,19 @@ function testOpenWithState() text: "test text" }; - let win = ScratchpadManager.openScratchpad(state); - - win.addEventListener("load", function onScratchpadLoad() { - win.removeEventListener("load", onScratchpadLoad, false); - + openScratchpad(function(win) { is(win.Scratchpad.filename, state.filename, "Filename loaded from state"); is(win.Scratchpad.executionContext, state.executionContext, "Execution context loaded from state"); is(win.Scratchpad.getText(), state.text, "Content loaded from state"); win.close(); done(); - }, false); + }, {state: state, noFocus: true}); } function testOpenInvalidState() { - let state = 7; - - let win = ScratchpadManager.openScratchpad(state); + let win = openScratchpad(null, {state: 7}); ok(!win, "no scratchpad opened if state is not an object"); done(); } diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_restore.js b/browser/devtools/scratchpad/test/browser_scratchpad_restore.js index c1e1b0b921d..a83c4213ce7 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_restore.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_restore.js @@ -48,11 +48,7 @@ function testRestore() asyncMap(states, function(state, done) { // Open some scratchpad windows - let win = ScratchpadManager.openScratchpad(state); - win.addEventListener("load", function onScratchpadLoad() { - removeEventListener("load", onScratchpadLoad, false); - done(win); - }, false) + openScratchpad(done, {state: state, noFocus: true}); }, function(wins) { // Then save the windows to session store ScratchpadManager.saveOpenWindows(); @@ -74,12 +70,11 @@ function testRestore() is(restoredWins.length, 3, "Three scratchad windows restored"); asyncMap(restoredWins, function(restoredWin, done) { - restoredWin.addEventListener("load", function onScratchpadLoad() { - restoredWin.removeEventListener("load", onScratchpadLoad, false); - let state = restoredWin.Scratchpad.getState(); - restoredWin.close(); + openScratchpad(function(aWin) { + let state = aWin.Scratchpad.getState(); + aWin.close(); done(state); - }, false); + }, {window: restoredWin, noFocus: true}); }, function(restoredStates) { // Then make sure they were restored with the right states ok(statesMatch(restoredStates, states), diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js b/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js index 754adf99d15..2db5a3c5a54 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js @@ -2,8 +2,6 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; let tab1; let tab2; let sp; @@ -14,15 +12,14 @@ function test() tab1 = gBrowser.addTab(); gBrowser.selectedTab = tab1; - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + gBrowser.selectedBrowser.addEventListener("load", function onLoad1() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad1, true); tab2 = gBrowser.addTab(); gBrowser.selectedTab = tab2; - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad2() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad2, true); + openScratchpad(runTests); }, true); content.location = "data:text/html,test context switch in Scratchpad tab 2"; }, true); @@ -32,8 +29,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", runTests, true); - sp = gScratchpadWindow.Scratchpad; let contentMenu = gScratchpadWindow.document.getElementById("sp-menu-content"); @@ -52,7 +47,7 @@ function runTests() is(contentMenu.getAttribute("checked"), "true", "content menuitem is checked"); - ok(!browserMenu.hasAttribute("checked"), + isnot(browserMenu.getAttribute("checked"), "true", "chrome menuitem is not checked"); is(notificationBox.currentNotification, null, diff --git a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js index 14058607076..e388e0490ce 100644 --- a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js @@ -2,19 +2,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -// Reference to the Scratchpad chrome window object. -let gScratchpadWindow; - function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - - gScratchpadWindow = Scratchpad.openScratchpad(); - gScratchpadWindow.addEventListener("load", runTests, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); }, true); content.location = "data:text/html,foobarBug636725" + @@ -23,8 +18,6 @@ function test() function runTests() { - gScratchpadWindow.removeEventListener("load", arguments.callee, false); - let sp = gScratchpadWindow.Scratchpad; let doc = gScratchpadWindow.document; diff --git a/browser/devtools/scratchpad/test/head.js b/browser/devtools/scratchpad/test/head.js index e8244441c75..210dc57e094 100644 --- a/browser/devtools/scratchpad/test/head.js +++ b/browser/devtools/scratchpad/test/head.js @@ -6,6 +6,61 @@ let gScratchpadWindow; // Reference to the Scratchpad chrome window object +/** + * Open a Scratchpad window. + * + * @param function aReadyCallback + * Optional. The function you want invoked when the Scratchpad instance + * is ready. + * @param object aOptions + * Optional. Options for opening the scratchpad: + * - window + * Provide this if there's already a Scratchpad window you want to wait + * loading for. + * - state + * Scratchpad state object. This is used when Scratchpad is open. + * - noFocus + * Boolean that tells you do not want the opened window to receive + * focus. + * @return nsIDOMWindow + * The new window object that holds Scratchpad. Note that the + * gScratchpadWindow global is also updated to reference the new window + * object. + */ +function openScratchpad(aReadyCallback, aOptions) +{ + aOptions = aOptions || {}; + + let win = aOptions.window || + Scratchpad.ScratchpadManager.openScratchpad(aOptions.state); + if (!win) { + return; + } + + let onLoad = function() { + win.removeEventListener("load", onLoad, false); + + win.Scratchpad.addObserver({ + onReady: function(aScratchpad) { + aScratchpad.removeObserver(this); + + if (aOptions.noFocus) { + aReadyCallback(win, aScratchpad); + } else { + waitForFocus(aReadyCallback.bind(null, win, aScratchpad), win); + } + } + }); + }; + + if (aReadyCallback) { + win.addEventListener("load", onLoad, false); + } + + gScratchpadWindow = win; + return gScratchpadWindow; +} + function cleanup() { if (gScratchpadWindow) { diff --git a/browser/devtools/sourceeditor/orion/Makefile.dryice.js b/browser/devtools/sourceeditor/orion/Makefile.dryice.js index cafc3e87bb6..efcb73f640c 100755 --- a/browser/devtools/sourceeditor/orion/Makefile.dryice.js +++ b/browser/devtools/sourceeditor/orion/Makefile.dryice.js @@ -45,12 +45,17 @@ var js_src = copy.createDataObject(); copy({ source: [ + ORION_EDITOR + "/orion/textview/global.js", + ORION_EDITOR + "/orion/textview/eventTarget.js", + ORION_EDITOR + "/orion/editor/regex.js", ORION_EDITOR + "/orion/textview/keyBinding.js", ORION_EDITOR + "/orion/textview/rulers.js", ORION_EDITOR + "/orion/textview/undoStack.js", ORION_EDITOR + "/orion/textview/textModel.js", + ORION_EDITOR + "/orion/textview/annotations.js", ORION_EDITOR + "/orion/textview/tooltip.js", ORION_EDITOR + "/orion/textview/textView.js", + ORION_EDITOR + "/orion/textview/textDND.js", ORION_EDITOR + "/orion/editor/htmlGrammar.js", ORION_EDITOR + "/orion/editor/textMateStyler.js", ORION_EDITOR + "/examples/textview/textStyler.js", @@ -69,6 +74,7 @@ copy({ source: [ ORION_EDITOR + "/orion/textview/textview.css", ORION_EDITOR + "/orion/textview/rulers.css", + ORION_EDITOR + "/orion/textview/annotations.css", ORION_EDITOR + "/examples/textview/textstyler.css", ORION_EDITOR + "/examples/editor/htmlStyles.css", ], diff --git a/browser/devtools/sourceeditor/orion/README b/browser/devtools/sourceeditor/orion/README index c8703c947f4..2465f87bdb2 100644 --- a/browser/devtools/sourceeditor/orion/README +++ b/browser/devtools/sourceeditor/orion/README @@ -8,25 +8,11 @@ The Orion editor web site: http://www.eclipse.org/orion To upgrade Orion to a newer version see the UPGRADE file. -Orion version: git clone from 2011-10-26 - commit hash 0ab295660e1f7d33ca2bfb8558b3b7492d2c5aa5 - + patch for Eclipse Bug 358623 - Drag and Drop support: - https://github.com/mihaisucan/orion.client/tree/bug-358623 - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=358623 - + patch for Eclipse Bug 362286 - Monaco font line height: - https://github.com/mihaisucan/orion.client/tree/bug-362286 - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362286 - + patch for Eclipse Bug 362107 - Ctrl-Up/Down failure on Linux: - https://github.com/mihaisucan/orion.client/tree/bug-362107 - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362107 - + patch for Eclipse Bug 362428 - _getXToOffset() throws: - https://github.com/mihaisucan/orion.client/tree/bug-362428 - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362428 - + patch for Eclipse Bug 362835 - Pasted HTML shows twice: - https://github.com/mihaisucan/orion.client/tree/bug-362835 - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362835 - + patch for Eclipse Bug 363508 - Selection is broken after TextView hide/unhide - see https://bugs.eclipse.org/bugs/show_bug.cgi?id=363508 +Orion version: git clone from 2011-12-09 + commit hash d8a6dc01d9c561d6eb99f03b64c8c78ce785c59d + + patch for Eclipse Bug 366312 - right-clicking outside of the selection causes the caret to move + https://github.com/mihaisucan/orion.client/tree/bug-366312 + see https://bugs.eclipse.org/bugs/show_bug.cgi?id=366312 # License diff --git a/browser/devtools/sourceeditor/orion/mozilla.css b/browser/devtools/sourceeditor/orion/mozilla.css index 8d476a8f96e..214a8477014 100644 --- a/browser/devtools/sourceeditor/orion/mozilla.css +++ b/browser/devtools/sourceeditor/orion/mozilla.css @@ -2,15 +2,124 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ .viewContainer { - font-size: inherit; /* inherit browser's default monospace font size */ + background: -moz-Dialog; + font-family: monospace; + font-size: inherit; /* inherit browser's default monospace font size */ } +.view { + background: #fff; +} + +.readonly > .view { + background: #f6f6f6; +} + +/* Styles for rulers */ +.ruler { + background-color: white; +} +.ruler.lines { + border-right: 1px solid lightgray; + text-align: right; +} + +/* Styles for the line number ruler */ .rulerLines { - background: -moz-Dialog; - color: -moz-DialogText; - min-width: 1.4em; - padding-left: 4px; - padding-right: 4px; - text-align: end; + background: -moz-Dialog; + color: -moz-DialogText; + min-width: 1.4em; + padding-left: 4px; + padding-right: 4px; + text-align: end; +} + +.token_singleline_comment { + color: green; +} + +.token_multiline_comment { + color: green; +} + +.token_doc_comment { + color: #00008F; +} + +.token_doc_html_markup { + color: #7F7F9F; +} + +.token_doc_tag { + color: #7F9FBF; +} + +.token_task_tag { + color: #7F9FBF; +} + +.token_string { + color: blue; +} + +.token_keyword { + color: darkred; + font-weight: bold; +} + +.token_space { + /* images/white_space.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAABnRSTlMA/wAAAACkwsAdAAAAIUlEQVR4nGP4z8CAC+GUIEXuABhgkTuABEiRw2cmae4EAH05X7xDolNRAAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + background-position: center center; +} + +.token_tab { + /* images/white_tab.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAIAAACJ2loDAAAABnRSTlMA/wD/AP83WBt9AAAAMklEQVR4nGP4TwRgoK6i52c3bz5w6zMSA6tJn28d2Lx589nnCAYu63AaSLxJRLoJPwAAeNk0aG4opfMAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; +} + +.line_caret { + background: #EAF2FE; +} + +.readonly .line_caret { + background: #fcfcfc; +} + +/* Styling for html syntax highlighting */ +.entity-name-tag { + color: #3f7f7f; +} + +.entity-other-attribute-name { + color: #7f007f; +} + +.punctuation-definition-comment { + color: #3f5fbf; +} + +.comment { + color: #3f5fbf +} + +.string-quoted { + color: #2a00ff; + font-style: italic; +} + +.invalid { + color: red; + font-weight: bold; +} + +.annotationRange.currentBracket { +} + +.annotationRange.matchingBracket { + outline: 1px solid red; } diff --git a/browser/devtools/sourceeditor/orion/orion.css b/browser/devtools/sourceeditor/orion/orion.css index 48207cf0a18..85515975528 100644 --- a/browser/devtools/sourceeditor/orion/orion.css +++ b/browser/devtools/sourceeditor/orion/orion.css @@ -3,9 +3,13 @@ } .viewContainer { + background-color: #eeeeee; font-family: monospace; font-size: 10pt; } +::-webkit-scrollbar-corner { + background-color: #eeeeee; +} .viewContent { }/* Styles for rulers */ @@ -25,16 +29,172 @@ text-align: right; } .ruler.overview { + border-left: 1px solid lightgray; width: 14px; } /* Styles for the line number ruler */ .rulerLines { - background-color: white; } .rulerLines.even .rulerLines.odd { -}.token_singleline_comment { +}/* Styles for the annotation ruler (all lines) */ +.annotation { +} +.annotation.error, +.annotation.warning +.annotation.task, +.annotation.bookmark, +.annotation.breakpoint, +.annotation.collapsed +.annotation.expanded { +} + +/* Styles for the annotation ruler (first line) */ +.annotationHTML { + cursor: pointer; + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + background-position: center; + background-repeat: no-repeat; +} +.annotationHTML.error { + /* images/error.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPVvcvWHiPVucvRuc+ttcfV6f91KVN5LU99PV/FZY/JhaM4oN84pONE4Rd1ATfJLWutVYPRgbdxpcsgWKMgZKs4lNfE/UvE/U+artcpdSc5uXveimslHPuBhW/eJhfV5efaCgO2CgP+/v+PExP///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACUALAAAAAAQABAAAAZ+wJJwSCwaScgkySgkjTQZTkYzWhadnE5oE+pwqkSshwQqkzxfa4kkQXxEpA9J9EFI1KQGQQBAigYCBA14ExEWF0gXihETeA0QD3AkD5QQg0NsDnAJmwkOd5gYFSQKpXAFDBhqaxgLBwQBBAapq00YEg0UDRKqTGtKSL7Cw8JBADs="); +} +.annotationHTML.warning { + /* images/warning.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAP7bc//egf/ij/7ijv/jl/7kl//mnv7lnv/uwf7CTP7DTf7DT/7IW//Na/7Na//NbP7QdP/dmbltAIJNAF03AMSAJMSCLKqASa2DS6uBSquCSrGHTq6ETbCHT7WKUrKIUcCVXL+UXMOYX8GWXsSZYMiib6+ETbOIUcOXX86uhd3Muf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACsALAAAAAAQABAAAAZowJVwSCwaj0ihikRSJYcoBEL0XKlGkcjImQQhJBREKFnyICoThKeE/AAW6AXgdPyUAgrLJBEo0YsbAQyDhAEdRRwDDw8OaA4NDQImRBgFEJdglxAEGEQZKQcHBqOkKRpFF6mqq1WtrUEAOw=="); +} +.annotationHTML.task { + /* images/task.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQAMQAAN7s4uTy6ICvY423c2WdP2ugR3mqWYeza2ejOl6VNVqPM1aJMURsJ2GaOnKlT8PbsbPDqGmmO1OCLk98LEhxKGWfOWKaN0t2KkJoJf///////wAAAAAAAAAAAAAAAAAAACH5BAEAABoALAAAAAAQABAAAAVmoCaOZDk+UaquDxkNcCxHJHLceI6QleD/vkCmQrIYjkiDMGAhJRzQ6NKRICkKgYJ2qVWQFktCmEBYkCSNZSbQaDckpAl5TCZMSBdtAaDXX0gUUYJRFCQMSYgGDCQQGI6PkBAmkyUhADs="); +} +.annotationHTML.bookmark { + /* images/bookmark.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQALMAAP7//+/VNPzZS/vifeumAPrBOOSlHOSuRP///wAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAgALAAAAAAQABAAAARLEMlJq5Xn3EvIrkenfRIhCB5pmKhRdbAJAGhssuc8n6eJoAKdkOaTAIdEQeWoA1oGsiZhYAnIcqiApVPjElyUbkFSgCkn5XElLYkAADs="); +} +.annotationHTML.breakpoint { + /* images/breakpoint.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAFheoFxkoFxnpmt0pmZxpnF7rYyWwmJwpnaFs3aDrWt8rXGBrYycwmZ3mXuNs42cu77F03GIs3aJrYGVu2J5oKCuxeDj6LK/03GLrYieu3aIoIygu6m4zcLN3MTM1m6Rs2aLriRgkSZilXGXtoGcs7LD0QBLhSZikihol3ScubrO2Yaqu5q4xpO0wpm7yabF0ZO9yaXI0r3X3tHj6P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAADQALAAAAAAQABAAAAafQJpwSCwWLYZBIDAwWIw0A+FFpW6aRUPCxe1yE4ahhdCCxWSzmSwGgxGeUceKpUqhUCkVa7UK0wgkJCUjJoUmIyWBBEIEGhoeJ4YmJx6OAUIADQ0QIZIhEJoAQgEUFBUgkiAVpZdRCxIPFx8iIh8XDw4FfhYHDhgZHB0dHBkYEwdwUQoTEc3OEwp+QwYHCBMMDBMIB9JESAJLAk5Q5EVBADs="); +} +.annotationHTML.collapsed { + /* images/collapsed.png */ + width: 14px; + height: 14px; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNpi/P//PwMlgImBQkCxASzoAp++fo+6de+Z+fXbD/Jev/nAICoiwKCpqrBBTUlqNR835zJ09YzIYfDxy7eo/cevLmXlYGNQUJAEahZieP3mHcODB08Zfv/4w+BoqR3Nz8O1DKcXzt94HPqXmZlBU1+LgZNfkMHazIOBA0hr6uswgMTP33gYijcMLlx/EMAnLs7w7sc/hg9AG0HgPZB+B8S84hJA+UcBeMPg+at3DJIMnAxZzt5wsUhnXzDdsmIVWB6vAcLCfAys3z4wzN64huEfkJ/uH8IwexOQDQymD2/fgeXxekFLRWHD51evGDhZGRi4WSFSnCwgNjB2Xr1m0AbK4zXAQkdhNdPf3wx3r91g+PruLcOqnasYvn54x3Dv2k0G5r+/GMyB8nijEQTefvoadeH6w9Cbtx8GvH//kUFQkJ9BQ1V+g76m/GphPu5lBA0YenmBYgMAAgwA34GIKjmLxOUAAAAASUVORK5CYII="); +} +.annotationHTML.expanded { + /* images/expanded.png */ + width: 14px; + height: 14px; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT5JREFUeNrUksFKw0AURW+mTWw67SSEiG209U90r4jddFO34l+5U0HdZCHiFwiCOz9AlMSmGEpMOqk1TWJSFGyFbATR2dyZd+Dw3mOENE3xkyP8PYHrBT3OX7uW43ZefA6FUaw1dJPSyrmu1k8KBYOh37Od4XFZLEPXFdRrFMGIw3U9TKMYqw1tb0VjcxLy9eEF425CCIxWE5JcxSQGxCyNloG87gXhwWIHc4J767lTZQw8ShFGSZbxRyaQmZJxd3NRUJ6ffwQNEi6PzG/L2tjdmvFCgcKqKL2F2Olu43MzggDka+IjPuOFI7Sbujn2fUglYKkkzFIi+R0I/QDrGS8UqDX5QkhiOHYfE84hkhSTkGNgOyDJFCzjhYLTq+vDtrG8r1LZtB6fcHtzB+uhD5VWzLx+lvF/8JV/XfAuwADsrJbMGG4l4AAAAABJRU5ErkJggg=="); +} +.annotationHTML.multiple { + /* images/multiple.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAOdpa+yJiuFYXOFYXeBYXONwded8f+NwdmhwkHB4iPr7/ezx+fP2+2h4kOzy+Wh4iPr8/gCBwTaczjaXyjaYyjaXyTaYyfr8/QCMzQCMzACHxzao2jal2Dak1zag03iAgI/Ckn64fZrHmX+4fZLCianPopPCiarOoqbLlafLlbnXq7nWq6fLlMTcsoCIeJCQcIiIeKCYaJiQcO16ee16evGVlfGWlfahn/ahoPWhn/WhoPe1tP///////wAAAAAAACH5BAEAAD0ALAAAAAAQABAAAAaRwJ5wSCwaj8WYcslcDmObaDTGq1Zjzw4mk+FQIRcFTzaUeTRoj4zHaI+HL0lkLnnxFgsH7zWEWSoTFBMwVlUwQy6JMDCJjYwuQx8tk5MfOzk4OjcfkSssKCkqHzY0MzQ1nEIJJSYkJCcJAQCzAQlDDyIjISMiCQYEAgMGD0MNIMfHDQUHBc3EQgjR0tPSSNY9QQA7"); +} +.annotationHTML.overlay { + /* images/plus.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEAQvB2JVdrAAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAD1JREFUCNdtjkESADAEAzemf69f66HMqGlOIhYiFRFRtSQBWAY7mzx+EDTL6sSgb1jTk7Q87rxyqe37fXsAa78gLyZnRgEAAAAASUVORK5CYII="); + background-position: right bottom; + position: relative; + top: -16px; +} +.annotationHTML.currentBracket { + /* images/currentBracket.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLEBULCGQmEKAAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAnklEQVQ4y7VTsRHDIBATJg1HCUzAHEzFBExAzwZsRMkE9gifKhc72ODYibr/+xcnoQdugq0LAujEwmbn0UxQh4OxpjX1XgshwFqLnPM5PQTQGlprWpbl3RhJ/CSQUm7qPYLp7i8cEpRSoJT6ju0lIaVEQgiKMQ4lHHpQayVjzHWCn5jIOcc8z9dMBADvPZxz3SC1tzCI8vgWdvL+VzwB8JSj2GFTyxIAAAAASUVORK5CYII="); +} +.annotationHTML.matchingBracket { + /* images/matchingBracket.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLEBUMAsuyb3kAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAoklEQVQ4y61TsQ3EIAw80DcI0USKGIApWIsB2IGGKbJPugxBR3VfvfRRCOSTvw7LPuPzGXgI8f0gwAsFu5rXIYMdDiEOIdnKW5YFzjnEGH+bhwA/KKVwmibu0BhRnpEZY1BrHTaVT7fQJZjnGeu63tOAJFNKVEox53yqQZfAWstt27oidgm01ve3UEqBaBjnspG89wgh3LiFgZXHt3Dh23/FGxKViehm0X85AAAAAElFTkSuQmCC"); +} +.annotationHTML.currentLine { + /* images/currentLine.gif */ + background-image: url("data:image/gif;base64,R0lGODlhEAAQAMQAALxe0bNWzbdZzrlb0KpPx61RybBTy6VLxadNxZGctIeUroyYsG92hHyMqIKRq2l9nmyAoHGDonaIpStXj6q80k1aXf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABYALAAAAAAQABAAAAVCoCWOZGmeKDql5ppOMGXBk/zOoltSNO6XrlXwxIPNYiMGq8SoLC2MaNPygEQkDYdikUg6LQcEoWAICAaA5HPNLoUAADs="); +} + +/* Styles for the overview ruler */ +.annotationOverview { + cursor: pointer; + border-radius: 2px; + left: 2px; + width: 8px; +} +.annotationOverview.task { + background-color: lightgreen; + border: 1px solid green; +} +.annotationOverview.breakpoint { + background-color: lightblue; + border: 1px solid blue; +} +.annotationOverview.bookmark { + background-color: yellow; + border: 1px solid orange; +} +.annotationOverview.error { + background-color: lightcoral; + border: 1px solid darkred; +} +.annotationOverview.warning { + background-color: Gold; + border: 1px solid black; +} +.annotationOverview.currentBracket { + background-color: lightgray; + border: 1px solid red; +} +.annotationOverview.matchingBracket { + background-color: lightgray; + border: 1px solid red; +} +.annotationOverview.currentLine { + background-color: #EAF2FE; + border: 1px solid black; +} + +/* Styles for text range */ +.annotationRange { + background-repeat: repeat-x; + background-position: left bottom; +} +.annotationRange.task { + /* images/squiggly_task.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLDhEoIrb7JmcAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAGUlEQVQI12NggIH/DGdhDCM45z/DfyiBAADgdQjGhI/4DAAAAABJRU5ErkJggg=="); +} +.annotationRange.breakpoint { + /* images/squiggly_breakpoint.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sLDhEqHTKradgAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAIklEQVQI11XJMQ0AMAzAMGMafwrFlD19+sUKIJTFo9k+B/kQ+Qr2bIVKOgAAAABJRU5ErkJggg=="); +} +.annotationRange.bookmark { + /* images/squiggly_bookmark.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); +} +.annotationRange.error { + /* images/squiggly_error.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg=="); +} +.annotationRange.warning { + /* images/squiggly_warning.png */ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); +} +.annotationRange.currentBracket { +} +.annotationRange.matchingBracket { + outline: 1px solid red; +} + +/* Styles for lines of text */ +.annotationLine { +} +.annotationLine.currentLine { + background-color: #EAF2FE; +} + +.token_singleline_comment { color: green; } @@ -67,15 +227,6 @@ font-weight: bold; } -.token_bracket_outline { - outline: 1px solid red; -} - -.token_bracket { - color: white; - background-color: grey; -} - .token_space { /* images/white_space.png */ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAABnRSTlMA/wAAAACkwsAdAAAAIUlEQVR4nGP4z8CAC+GUIEXuABhgkTuABEiRw2cmae4EAH05X7xDolNRAAAAAElFTkSuQmCC"); diff --git a/browser/devtools/sourceeditor/orion/orion.js b/browser/devtools/sourceeditor/orion/orion.js index 7b73fb1f02f..93d2681bfc6 100644 --- a/browser/devtools/sourceeditor/orion/orion.js +++ b/browser/devtools/sourceeditor/orion/orion.js @@ -1,4 +1,289 @@ /******************************************************************************* + * @license + * Copyright (c) 2010, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * Felipe Heidrich (IBM Corporation) - initial API and implementation + * Silenio Quarti (IBM Corporation) - initial API and implementation + * Mihai Sucan (Mozilla Foundation) - fix for Bug#364214 + */ + +/*global window */ + +/** + * Evaluates the definition function and mixes in the returned module with + * the module specified by moduleName. + *

+ * This function is intented to by used when RequireJS is not available. + *

+ * + * @param {String[]} deps The array of dependency names. + * @param {Function} callback The definition function. + * @param {String} moduleName The mixin module name. + */ +if (!window.define) { + window.define = function(deps, callback, moduleName) { + var module = this; + var split = (moduleName || "").split("/"), i, j; + for (i = 0; i < split.length; i++) { + module = module[split[i]] = (module[split[i]] || {}); + } + var depModules = [], depModule; + for (j = 0; j < deps.length; j++) { + depModule = this; + split = deps[j].split("/"); + for (i = 0; i < split.length - 1; i++) { + depModule = depModule[split[i]] = (depModule[split[i]] || {}); + } + depModules.push(depModule); + } + var newModule = callback.apply(this, depModules); + for (var p in newModule) { + if (newModule.hasOwnProperty(p)) { + module[p] = newModule[p]; + } + } + }; +} + +/** + * Require/get the defined modules. + *

+ * This function is intented to by used when RequireJS is not available. + *

+ * + * @param {String[]|String} deps The array of dependency names. This can also be + * a string, a single dependency name. + * @param {Function} [callback] Optional, the callback function to execute when + * multiple dependencies are required. The callback arguments will have + * references to each module in the same order as the deps array. + * @returns {Object|undefined} If the deps parameter is a string, then this + * function returns the required module definition, otherwise undefined is + * returned. + */ +if (!window.require) { + window.require = function(deps, callback) { + var depsArr = typeof deps === "string" ? [deps] : deps; + var depModules = [], depModule, split, i, j; + for (j = 0; j < depsArr.length; j++) { + depModule = this; + split = depsArr[j].split("/"); + for (i = 0; i < split.length - 1; i++) { + depModule = depModule[split[i]] = (depModule[split[i]] || {}); + } + depModules.push(depModule); + } + if (callback) { + callback.apply(this, depModules); + } + return typeof deps === "string" ? depModules[0] : undefined; + }; +}/******************************************************************************* + * Copyright (c) 2010, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * Felipe Heidrich (IBM Corporation) - initial API and implementation + * Silenio Quarti (IBM Corporation) - initial API and implementation + ******************************************************************************/ + +/*global define */ +define([], function() { + /** + * Constructs a new EventTarget object. + * + * @class + * @name orion.textview.EventTarget + */ + function EventTarget() { + } + /** + * Adds in the event target interface into the specified object. + * + * @param {Object} object The object to add in the event target interface. + */ + EventTarget.addMixin = function(object) { + var proto = EventTarget.prototype; + for (var p in proto) { + if (proto.hasOwnProperty(p)) { + object[p] = proto[p]; + } + } + }; + EventTarget.prototype = /** @lends orion.textview.EventTarget.prototype */ { + /** + * Adds an event listener to this event target. + * + * @param {String} type The event type. + * @param {Function|EventListener} listener The function or the EventListener that will be executed when the event happens. + * @param {Boolean} [useCapture=false] true if the listener should be trigged in the capture phase. + * + * @see #removeEventListener + */ + addEventListener: function(type, listener, useCapture) { + if (!this._eventTypes) { this._eventTypes = {}; } + var state = this._eventTypes[type]; + if (!state) { + state = this._eventTypes[type] = {level: 0, listeners: []}; + } + var listeners = state.listeners; + listeners.push({listener: listener, useCapture: useCapture}); + }, + /** + * Dispatches the given event to the listeners added to this event target. + * @param {Event} evt The event to dispatch. + */ + dispatchEvent: function(evt) { + if (!this._eventTypes) { return; } + var type = evt.type; + var state = this._eventTypes[type]; + if (state) { + var listeners = state.listeners; + try { + state.level++; + if (listeners) { + for (var i=0, len=listeners.length; i < len; i++) { + if (listeners[i]) { + var l = listeners[i].listener; + if (typeof l === "function") { + l.call(this, evt); + } else if (l.handleEvent && typeof l.handleEvent === "function") { + l.handleEvent(evt); + } + } + } + } + } finally { + state.level--; + if (state.compact && state.level === 0) { + for (var j=listeners.length - 1; j >= 0; j--) { + if (!listeners[j]) { + listeners.splice(j, 1); + } + } + if (listeners.length === 0) { + delete this._eventTypes[type]; + } + state.compact = false; + } + } + } + }, + /** + * Returns whether there is a listener for the specified event type. + * + * @param {String} type The event type + * + * @see #addEventListener + * @see #removeEventListener + */ + isListening: function(type) { + if (!this._eventTypes) { return false; } + return this._eventTypes[type] !== undefined; + }, + /** + * Removes an event listener from the event target. + *

+ * All the parameters must be the same ones used to add the listener. + *

+ * + * @param {String} type The event type + * @param {Function|EventListener} listener The function or the EventListener that will be executed when the event happens. + * @param {Boolean} [useCapture=false] true if the listener should be trigged in the capture phase. + * + * @see #addEventListener + */ + removeEventListener: function(type, listener, useCapture){ + if (!this._eventTypes) { return; } + var state = this._eventTypes[type]; + if (state) { + var listeners = state.listeners; + for (var i=0, len=listeners.length; i < len; i++) { + var l = listeners[i]; + if (l && l.listener === listener && l.useCapture === useCapture) { + if (state.level !== 0) { + listeners[i] = null; + state.compact = true; + } else { + listeners.splice(i, 1); + } + break; + } + } + if (listeners.length === 0) { + delete this._eventTypes[type]; + } + } + } + }; + return {EventTarget: EventTarget}; +}, "orion/textview"); +/******************************************************************************* + * @license + * Copyright (c) 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +/*global define */ +/*jslint browser:true regexp:false*/ +/** + * @name orion.editor.regex + * @class Utilities for dealing with regular expressions. + * @description Utilities for dealing with regular expressions. + */ +define([], function() { + /** + * @methodOf orion.editor.regex + * @static + * @description Escapes regex special characters in the input string. + * @param {String} str The string to escape. + * @returns {String} A copy of str with regex special characters escaped. + */ + function escape(str) { + return str.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&"); + } + + /** + * @methodOf orion.editor.regex + * @static + * @description Parses a pattern and flags out of a regex literal string. + * @param {String} str The string to parse. Should look something like "/ab+c/" or "/ab+c/i". + * @returns {Object} If str looks like a regex literal, returns an object with properties + *
+ *
pattern
{String}
+ *
flags
{String}
+ *
otherwise returns null. + */ + function parse(str) { + var regexp = /^\s*\/(.+)\/([gim]{0,3})\s*$/.exec(str); + if (regexp) { + return { + pattern : regexp[1], + flags : regexp[2] + }; + } + return null; + } + + return { + escape: escape, + parse: parse + }; +}, "orion/editor"); +/******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -12,38 +297,29 @@ /*global window define */ -/** - * @namespace The global container for Orion APIs. - */ -var orion = orion || {}; -/** - * @namespace The container for textview APIs. - */ -orion.textview = orion.textview || {}; - -/** - * Constructs a new key binding with the given key code and modifiers. - * - * @param {String|Number} keyCode the key code. - * @param {Boolean} mod1 the primary modifier (usually Command on Mac and Control on other platforms). - * @param {Boolean} mod2 the secondary modifier (usually Shift). - * @param {Boolean} mod3 the third modifier (usually Alt). - * @param {Boolean} mod4 the fourth modifier (usually Control on the Mac). - * - * @class A KeyBinding represents of a key code and a modifier state that can be triggered by the user using the keyboard. - * @name orion.textview.KeyBinding - * - * @property {String|Number} keyCode The key code. - * @property {Boolean} mod1 The primary modifier (usually Command on Mac and Control on other platforms). - * @property {Boolean} mod2 The secondary modifier (usually Shift). - * @property {Boolean} mod3 The third modifier (usually Alt). - * @property {Boolean} mod4 The fourth modifier (usually Control on the Mac). - * - * @see orion.textview.TextView#setKeyBinding - */ -orion.textview.KeyBinding = (function() { +define([], function() { var isMac = window.navigator.platform.indexOf("Mac") !== -1; - /** @private */ + + /** + * Constructs a new key binding with the given key code and modifiers. + * + * @param {String|Number} keyCode the key code. + * @param {Boolean} mod1 the primary modifier (usually Command on Mac and Control on other platforms). + * @param {Boolean} mod2 the secondary modifier (usually Shift). + * @param {Boolean} mod3 the third modifier (usually Alt). + * @param {Boolean} mod4 the fourth modifier (usually Control on the Mac). + * + * @class A KeyBinding represents of a key code and a modifier state that can be triggered by the user using the keyboard. + * @name orion.textview.KeyBinding + * + * @property {String|Number} keyCode The key code. + * @property {Boolean} mod1 The primary modifier (usually Command on Mac and Control on other platforms). + * @property {Boolean} mod2 The secondary modifier (usually Shift). + * @property {Boolean} mod3 The third modifier (usually Alt). + * @property {Boolean} mod4 The fourth modifier (usually Control on the Mac). + * + * @see orion.textview.TextView#setKeyBinding + */ function KeyBinding (keyCode, mod1, mod2, mod3, mod4) { if (typeof(keyCode) === "string") { this.keyCode = keyCode.toUpperCase().charCodeAt(0); @@ -89,16 +365,10 @@ orion.textview.KeyBinding = (function() { return true; } }; - return KeyBinding; -}()); - -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define([], function() { - return orion.textview; - }); -} - + return {KeyBinding: KeyBinding}; +}, "orion/textview"); /******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -108,49 +378,40 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { * Contributors: IBM Corporation - initial API and implementation ******************************************************************************/ -/*global window define setTimeout clearTimeout setInterval clearInterval Node */ +/*global define setTimeout clearTimeout setInterval clearInterval Node */ -/** - * @namespace The global container for Orion APIs. - */ -var orion = orion || {}; -/** - * @namespace The container for textview APIs. - */ -orion.textview = orion.textview || {}; +define(['orion/textview/tooltip'], function(mTooltip) { -/** - * Constructs a new ruler. - *

- * The default implementation does not implement all the methods in the interface - * and is useful only for objects implementing rulers. - *

- * - * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {String} [rulerOverview="page"] the overview for the ruler. - * @param {orion.textview.Style} [rulerStyle] the style for the ruler. - * - * @class This interface represents a ruler for the text view. - *

- * A Ruler is a graphical element that is placed either on the left or on the right side of - * the view. It can be used to provide the view with per line decoration such as line numbering, - * bookmarks, breakpoints, folding disclosures, etc. - *

- * There are two types of rulers: page and document. A page ruler only shows the content for the lines that are - * visible, while a document ruler always shows the whole content. - *

- * See:
- * {@link orion.textview.LineNumberRuler}
- * {@link orion.textview.AnnotationRuler}
- * {@link orion.textview.OverviewRuler}
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#addRuler} - *

- * @name orion.textview.Ruler - */ -orion.textview.Ruler = (function() { - /** @private */ + /** + * Constructs a new ruler. + *

+ * The default implementation does not implement all the methods in the interface + * and is useful only for objects implementing rulers. + *

+ * + * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. + * @param {String} [rulerLocation="left"] the location for the ruler. + * @param {String} [rulerOverview="page"] the overview for the ruler. + * @param {orion.textview.Style} [rulerStyle] the style for the ruler. + * + * @class This interface represents a ruler for the text view. + *

+ * A Ruler is a graphical element that is placed either on the left or on the right side of + * the view. It can be used to provide the view with per line decoration such as line numbering, + * bookmarks, breakpoints, folding disclosures, etc. + *

+ * There are two types of rulers: page and document. A page ruler only shows the content for the lines that are + * visible, while a document ruler always shows the whole content. + *

+ * See:
+ * {@link orion.textview.LineNumberRuler}
+ * {@link orion.textview.AnnotationRuler}
+ * {@link orion.textview.OverviewRuler}
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#addRuler} + *

+ * @name orion.textview.Ruler + */ function Ruler (annotationModel, rulerLocation, rulerOverview, rulerStyle) { this._location = rulerLocation || "left"; this._overview = rulerOverview || "page"; @@ -158,8 +419,11 @@ orion.textview.Ruler = (function() { this._types = []; this._view = null; var self = this; - this._annotationModelListener = { - onChanged: function(e) { + this._listener = { + onTextModelChanged: function(e) { + self._onTextModelChanged(e); + }, + onAnnotationModelChanged: function(e) { self._onAnnotationModelChanged(e); } }; @@ -210,7 +474,7 @@ orion.textview.Ruler = (function() { var annotation = annotations.next(); if (!this.isAnnotationTypeVisible(annotation.type)) { continue; } var annotationLineStart = baseModel.getLineAtOffset(annotation.start); - var annotationLineEnd = baseModel.getLineAtOffset(annotation.end - 1); + var annotationLineEnd = baseModel.getLineAtOffset(Math.max(annotation.start, annotation.end - 1)); for (var lineIndex = annotationLineStart; lineIndex<=annotationLineEnd; lineIndex++) { var visualLineIndex = lineIndex; if (model !== baseModel) { @@ -332,11 +596,11 @@ orion.textview.Ruler = (function() { */ setAnnotationModel: function (annotationModel) { if (this._annotationModel) { - this._annotationModel.removeListener(this._annotationModelListener); + this._annotationModel.removEventListener("Changed", this._listener.onAnnotationModelChanged); } this._annotationModel = annotationModel; if (this._annotationModel) { - this._annotationModel.addListener(this._annotationModelListener); + this._annotationModel.addEventListener("Changed", this._listener.onAnnotationModelChanged); } }, /** @@ -374,11 +638,11 @@ orion.textview.Ruler = (function() { */ setView: function (view) { if (this._onTextModelChanged && this._view) { - this._view.removeEventListener("ModelChanged", this, this._onTextModelChanged); + this._view.removeEventListener("ModelChanged", this._listener.onTextModelChanged); } this._view = view; if (this._onTextModelChanged && this._view) { - this._view.addEventListener("ModelChanged", this, this._onTextModelChanged); + this._view.addEventListener("ModelChanged", this._listener.onTextModelChanged); } }, /** @@ -407,7 +671,7 @@ orion.textview.Ruler = (function() { * @param {DOMEvent} e the mouse move event. */ onMouseMove: function(lineIndex, e) { - var tooltip = orion.textview.Tooltip.getTooltip(this._view); + var tooltip = mTooltip.Tooltip.getTooltip(this._view); if (!tooltip) { return; } if (tooltip.isVisible() && this._tooltipLineIndex === lineIndex) { return; } this._tooltipLineIndex = lineIndex; @@ -426,7 +690,9 @@ orion.textview.Ruler = (function() { * @param {Number} lineIndex the line index of the annotation under the pointer. * @param {DOMEvent} e the mouse over event. */ - onMouseOver: this._onMouseMove, + onMouseOver: function(lineIndex, e) { + this.onMouseMove(lineIndex, e); + }, /** * This event is sent when the mouse pointer exits a line annotation. * @@ -435,7 +701,7 @@ orion.textview.Ruler = (function() { * @param {DOMEvent} e the mouse out event. */ onMouseOut: function(lineIndex, e) { - var tooltip = orion.textview.Tooltip.getTooltip(this._view); + var tooltip = mTooltip.Tooltip.getTooltip(this._view); if (!tooltip) { return; } tooltip.setTarget(null); }, @@ -566,37 +832,34 @@ orion.textview.Ruler = (function() { return result; } }; - return Ruler; -}()); -/** - * Constructs a new line numbering ruler. - * - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * @param {orion.textview.Style} [oddStyle={style: {backgroundColor: "white"}] the style for lines with odd line index. - * @param {orion.textview.Style} [evenStyle={backgroundColor: "white"}] the style for lines with even line index. - * - * @augments orion.textview.Ruler - * @class This objects implements a line numbering ruler. - * - *

See:
- * {@link orion.textview.Ruler} - *

- * @name orion.textview.LineNumberRuler - */ -orion.textview.LineNumberRuler = (function() { - /** @private */ + /** + * Constructs a new line numbering ruler. + * + * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. + * @param {String} [rulerLocation="left"] the location for the ruler. + * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. + * @param {orion.textview.Style} [oddStyle={style: {backgroundColor: "white"}] the style for lines with odd line index. + * @param {orion.textview.Style} [evenStyle={backgroundColor: "white"}] the style for lines with even line index. + * + * @augments orion.textview.Ruler + * @class This objects implements a line numbering ruler. + * + *

See:
+ * {@link orion.textview.Ruler} + *

+ * @name orion.textview.LineNumberRuler + */ function LineNumberRuler (annotationModel, rulerLocation, rulerStyle, oddStyle, evenStyle) { - orion.textview.Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); + Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); this._oddStyle = oddStyle || {style: {backgroundColor: "white"}}; this._evenStyle = evenStyle || {style: {backgroundColor: "white"}}; this._numOfDigits = 0; } - LineNumberRuler.prototype = new orion.textview.Ruler(); + LineNumberRuler.prototype = new Ruler(); /** @ignore */ LineNumberRuler.prototype.getAnnotations = function(startLine, endLine) { - var result = orion.textview.Ruler.prototype.getAnnotations.call(this, startLine, endLine); + var result = Ruler.prototype.getAnnotations.call(this, startLine, endLine); var model = this._view.getModel(); for (var lineIndex = startLine; lineIndex < endLine; lineIndex++) { var style = lineIndex & 1 ? this._oddStyle : this._evenStyle; @@ -628,74 +891,67 @@ orion.textview.LineNumberRuler = (function() { this._view.redrawLines(startLine, model.getLineCount(), this); } }; - return LineNumberRuler; -}()); -/** - * @class This is class represents an annotation for the AnnotationRuler. - *

- * See:
- * {@link orion.textview.AnnotationRuler} - *

- * - * @name orion.textview.Annotation - * - * @property {String} [html=""] The html content for the annotation, typically contains an image. - * @property {orion.textview.Style} [style] the style for the annotation. - * @property {orion.textview.Style} [overviewStyle] the style for the annotation in the overview ruler. - */ -/** - * Contructs a new annotation ruler. - * - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * @param {orion.textview.Annotation} [defaultAnnotation] the default annotation. - * - * @augments orion.textview.Ruler - * @class This objects implements an annotation ruler. - * - *

See:
- * {@link orion.textview.Ruler}
- * {@link orion.textview.Annotation} - *

- * @name orion.textview.AnnotationRuler - */ -orion.textview.AnnotationRuler = (function() { - /** @private */ - function AnnotationRuler (annotationModel, rulerLocation, rulerStyle) { - orion.textview.Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); - } - AnnotationRuler.prototype = new orion.textview.Ruler(); - return AnnotationRuler; -}()); - -/** - * Contructs an overview ruler. - *

- * The overview ruler is used in conjunction with a AnnotationRuler, for each annotation in the - * AnnotationRuler this ruler displays a mark in the overview. Clicking on the mark causes the - * view to scroll to the annotated line. - *

- * - * @param {String} [rulerLocation="left"] the location for the ruler. - * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. - * @param {orion.textview.AnnotationRuler} [annotationRuler] the annotation ruler for the overview. - * - * @augments orion.textview.Ruler - * @class This objects implements an overview ruler. - * - *

See:
- * {@link orion.textview.AnnotationRuler}
- * {@link orion.textview.Ruler} - *

- * @name orion.textview.OverviewRuler - */ -orion.textview.OverviewRuler = (function() { - /** @private */ - function OverviewRuler (annotationModel, rulerLocation, rulerStyle) { - orion.textview.Ruler.call(this, annotationModel, rulerLocation, "document", rulerStyle); + /** + * @class This is class represents an annotation for the AnnotationRuler. + *

+ * See:
+ * {@link orion.textview.AnnotationRuler} + *

+ * + * @name orion.textview.Annotation + * + * @property {String} [html=""] The html content for the annotation, typically contains an image. + * @property {orion.textview.Style} [style] the style for the annotation. + * @property {orion.textview.Style} [overviewStyle] the style for the annotation in the overview ruler. + */ + /** + * Constructs a new annotation ruler. + * + * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. + * @param {String} [rulerLocation="left"] the location for the ruler. + * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. + * @param {orion.textview.Annotation} [defaultAnnotation] the default annotation. + * + * @augments orion.textview.Ruler + * @class This objects implements an annotation ruler. + * + *

See:
+ * {@link orion.textview.Ruler}
+ * {@link orion.textview.Annotation} + *

+ * @name orion.textview.AnnotationRuler + */ + function AnnotationRuler (annotationModel, rulerLocation, rulerStyle) { + Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); } - OverviewRuler.prototype = new orion.textview.Ruler(); + AnnotationRuler.prototype = new Ruler(); + + /** + * Constructs a new overview ruler. + *

+ * The overview ruler is used in conjunction with a AnnotationRuler, for each annotation in the + * AnnotationRuler this ruler displays a mark in the overview. Clicking on the mark causes the + * view to scroll to the annotated line. + *

+ * + * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. + * @param {String} [rulerLocation="left"] the location for the ruler. + * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. + * + * @augments orion.textview.Ruler + * @class This objects implements an overview ruler. + * + *

See:
+ * {@link orion.textview.AnnotationRuler}
+ * {@link orion.textview.Ruler} + *

+ * @name orion.textview.OverviewRuler + */ + function OverviewRuler (annotationModel, rulerLocation, rulerStyle) { + Ruler.call(this, annotationModel, rulerLocation, "document", rulerStyle); + } + OverviewRuler.prototype = new Ruler(); /** @ignore */ OverviewRuler.prototype.getRulerStyle = function() { @@ -719,7 +975,7 @@ orion.textview.OverviewRuler = (function() { } return "Line: " + (mapLine + 1); } - return orion.textview.Ruler.prototype._getTooltipContents.call(this, lineIndex, annotations); + return Ruler.prototype._getTooltipContents.call(this, lineIndex, annotations); }; /** @ignore */ OverviewRuler.prototype._mergeAnnotation = function(previousAnnotation, annotation, annotationLineIndex, annotationLineCount) { @@ -733,15 +989,27 @@ orion.textview.OverviewRuler = (function() { } return result; }; - return OverviewRuler; -}()); -orion.textview.FoldingRuler = (function() { - /** @private */ + /** + * Constructs a new folding ruler. + * + * @param {orion.textview.AnnotationModel} annotationModel the annotation model for the ruler. + * @param {String} [rulerLocation="left"] the location for the ruler. + * @param {orion.textview.Style} [rulerStyle=undefined] the style for the ruler. + * + * @augments orion.textview.Ruler + * @class This objects implements an overview ruler. + * + *

See:
+ * {@link orion.textview.AnnotationRuler}
+ * {@link orion.textview.Ruler} + *

+ * @name orion.textview.OverviewRuler + */ function FoldingRuler (annotationModel, rulerLocation, rulerStyle) { - orion.textview.AnnotationRuler.call(this, annotationModel, rulerLocation, rulerStyle); + AnnotationRuler.call(this, annotationModel, rulerLocation, rulerStyle); } - FoldingRuler.prototype = new orion.textview.AnnotationRuler(); + FoldingRuler.prototype = new AnnotationRuler(); /** @ignore */ FoldingRuler.prototype.onClick = function(lineIndex, e) { @@ -763,7 +1031,7 @@ orion.textview.FoldingRuler = (function() { annotation = a; } if (annotation) { - var tooltip = orion.textview.Tooltip.getTooltip(this._view); + var tooltip = mTooltip.Tooltip.getTooltip(this._view); if (tooltip) { tooltip.setTarget(null); } @@ -782,12 +1050,12 @@ orion.textview.FoldingRuler = (function() { return null; } } - return orion.textview.AnnotationRuler.prototype._getTooltipContents.call(this, lineIndex, annotations); + return AnnotationRuler.prototype._getTooltipContents.call(this, lineIndex, annotations); }; /** @ignore */ FoldingRuler.prototype._onAnnotationModelChanged = function(e) { if (e.textModelChangedEvent) { - orion.textview.AnnotationRuler.prototype._onAnnotationModelChanged.call(this, e); + AnnotationRuler.prototype._onAnnotationModelChanged.call(this, e); return; } var view = this._view; @@ -815,15 +1083,16 @@ orion.textview.FoldingRuler = (function() { } }; - return FoldingRuler; -}()); - -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define(['orion/textview/tooltip'], function() { - return orion.textview; - }); -} + return { + Ruler: Ruler, + AnnotationRuler: AnnotationRuler, + LineNumberRuler: LineNumberRuler, + OverviewRuler: OverviewRuler, + FoldingRuler: FoldingRuler + }; +}, "orion/textview"); /******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -833,33 +1102,10 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { * Contributors: IBM Corporation - initial API and implementation ******************************************************************************/ -/*global window define */ +/*global define */ -/** - * @namespace The global container for Orion APIs. - */ -var orion = orion || {}; -/** - * @namespace The container for textview APIs. - */ -orion.textview = orion.textview || {}; +define([], function() { -/** - * Constructs a new UndoStack on a text view. - * - * @param {orion.textview.TextView} view the text view for the undo stack. - * @param {Number} [size=100] the size for the undo stack. - * - * @name orion.textview.UndoStack - * @class The UndoStack is used to record the history of a text model associated to an view. Every - * change to the model is added to stack, allowing the application to undo and redo these changes. - * - *

- * See:
- * {@link orion.textview.TextView}
- *

- */ -orion.textview.UndoStack = (function() { /** * Constructs a new Change object. * @@ -867,53 +1113,50 @@ orion.textview.UndoStack = (function() { * @name orion.textview.Change * @private */ - var Change = (function() { - function Change(offset, text, previousText) { - this.offset = offset; - this.text = text; - this.previousText = previousText; - } - Change.prototype = { - /** @ignore */ - undo: function (view, select) { - this._doUndoRedo(this.offset, this.previousText, this.text, view, select); - }, - /** @ignore */ - redo: function (view, select) { - this._doUndoRedo(this.offset, this.text, this.previousText, view, select); - }, - _doUndoRedo: function(offset, text, previousText, view, select) { - var model = view.getModel(); - /* - * TODO UndoStack should be changing the text in the base model. - * This is code needs to change when modifications in the base - * model are supported properly by the projection model. - */ - if (model.mapOffset && view.annotationModel) { - var mapOffset = model.mapOffset(offset, true); - if (mapOffset < 0) { - var annotationModel = view.annotationModel; - var iter = annotationModel.getAnnotations(offset, offset + 1); - while (iter.hasNext()) { - var annotation = iter.next(); - if (annotation.type === "orion.annotation.folding") { - annotation.expand(); - mapOffset = model.mapOffset(offset, true); - break; - } + function Change(offset, text, previousText) { + this.offset = offset; + this.text = text; + this.previousText = previousText; + } + Change.prototype = { + /** @ignore */ + undo: function (view, select) { + this._doUndoRedo(this.offset, this.previousText, this.text, view, select); + }, + /** @ignore */ + redo: function (view, select) { + this._doUndoRedo(this.offset, this.text, this.previousText, view, select); + }, + _doUndoRedo: function(offset, text, previousText, view, select) { + var model = view.getModel(); + /* + * TODO UndoStack should be changing the text in the base model. + * This is code needs to change when modifications in the base + * model are supported properly by the projection model. + */ + if (model.mapOffset && view.annotationModel) { + var mapOffset = model.mapOffset(offset, true); + if (mapOffset < 0) { + var annotationModel = view.annotationModel; + var iter = annotationModel.getAnnotations(offset, offset + 1); + while (iter.hasNext()) { + var annotation = iter.next(); + if (annotation.type === "orion.annotation.folding") { + annotation.expand(); + mapOffset = model.mapOffset(offset, true); + break; } } - if (mapOffset < 0) { return; } - offset = mapOffset; - } - view.setText(text, offset, offset + previousText.length); - if (select) { - view.setSelection(offset, offset + text.length); } + if (mapOffset < 0) { return; } + offset = mapOffset; } - }; - return Change; - }()); + view.setText(text, offset, offset + previousText.length); + if (select) { + view.setSelection(offset, offset + text.length); + } + } + }; /** * Constructs a new CompoundChange object. @@ -922,44 +1165,63 @@ orion.textview.UndoStack = (function() { * @name orion.textview.CompoundChange * @private */ - var CompoundChange = (function() { - function CompoundChange (selection, caret) { - this.selection = selection; - this.caret = caret; - this.changes = []; - } - CompoundChange.prototype = { - /** @ignore */ - add: function (change) { - this.changes.push(change); - }, - /** @ignore */ - undo: function (view, select) { - for (var i=this.changes.length - 1; i >= 0; i--) { - this.changes[i].undo(view, false); - } - if (select) { - var start = this.selection.start; - var end = this.selection.end; - view.setSelection(this.caret ? start : end, this.caret ? end : start); - } - }, - /** @ignore */ - redo: function (view, select) { - for (var i = 0; i < this.changes.length; i++) { - this.changes[i].redo(view, false); - } - if (select) { - var start = this.selection.start; - var end = this.selection.end; - view.setSelection(this.caret ? start : end, this.caret ? end : start); - } + function CompoundChange () { + this.changes = []; + } + CompoundChange.prototype = { + /** @ignore */ + add: function (change) { + this.changes.push(change); + }, + /** @ignore */ + end: function (view) { + this.endSelection = view.getSelection(); + this.endCaret = view.getCaretOffset(); + }, + /** @ignore */ + undo: function (view, select) { + for (var i=this.changes.length - 1; i >= 0; i--) { + this.changes[i].undo(view, false); } - }; - return CompoundChange; - }()); + if (select) { + var start = this.startSelection.start; + var end = this.startSelection.end; + view.setSelection(this.startCaret ? start : end, this.startCaret ? end : start); + } + }, + /** @ignore */ + redo: function (view, select) { + for (var i = 0; i < this.changes.length; i++) { + this.changes[i].redo(view, false); + } + if (select) { + var start = this.endSelection.start; + var end = this.endSelection.end; + view.setSelection(this.endCaret ? start : end, this.endCaret ? end : start); + } + }, + /** @ignore */ + start: function (view) { + this.startSelection = view.getSelection(); + this.startCaret = view.getCaretOffset(); + } + }; - /** @private */ + /** + * Constructs a new UndoStack on a text view. + * + * @param {orion.textview.TextView} view the text view for the undo stack. + * @param {Number} [size=100] the size for the undo stack. + * + * @name orion.textview.UndoStack + * @class The UndoStack is used to record the history of a text model associated to an view. Every + * change to the model is added to stack, allowing the application to undo and redo these changes. + * + *

+ * See:
+ * {@link orion.textview.TextView}
+ *

+ */ function UndoStack (view, size) { this.view = view; this.size = size !== undefined ? size : 100; @@ -970,14 +1232,16 @@ orion.textview.UndoStack = (function() { } this.model = model; var self = this; - this._modelListener = { + this._listener = { onChanging: function(e) { - self._onModelChanging(e); + self._onChanging(e); + }, + onDestroy: function(e) { + self._onDestroy(e); } }; - model.addListener(this._modelListener); - view._undoStack = this; - view.addEventListener("Destroy", this, this._onDestroy); + model.addEventListener("Changing", this._listener.onChanging); + view.addEventListener("Destroy", this._listener.onDestroy); } UndoStack.prototype = /** @lends orion.textview.UndoStack.prototype */ { /** @@ -1064,6 +1328,9 @@ orion.textview.UndoStack = (function() { * @see #startCompoundChange */ endCompoundChange: function() { + if (this.compoundChange) { + this.compoundChange.end(this.view); + } this.compoundChange = undefined; }, /** @@ -1144,9 +1411,10 @@ orion.textview.UndoStack = (function() { */ startCompoundChange: function() { this._commitUndo(); - var change = new CompoundChange(this.view.getSelection(), this.view.getCaretOffset()); + var change = new CompoundChange(); this.add(change); this.compoundChange = change; + this.compoundChange.start(this.view); }, _commitUndo: function () { if (this._undoStart !== undefined) { @@ -1159,12 +1427,11 @@ orion.textview.UndoStack = (function() { this._undoText = ""; } }, - _onDestroy: function() { - this.model.removeListener(this._modelListener); - this.view.removeEventListener("Destroy", this, this._onDestroy); - this.view._undoStack = null; + _onDestroy: function(evt) { + this.model.removeEventListener("Changing", this._listener.onChanging); + this.view.removeEventListener("Destroy", this._listener.onDestroy); }, - _onModelChanging: function(e) { + _onChanging: function(e) { var newText = e.text; var start = e.start; var removedCharCount = e.removedCharCount; @@ -1199,15 +1466,13 @@ orion.textview.UndoStack = (function() { this.add(new Change(start, newText, this.model.getText(start, start + removedCharCount))); } }; - return UndoStack; -}()); - -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define([], function() { - return orion.textview; - }); -} + + return { + UndoStack: UndoStack + }; +}, "orion/textview"); /******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -1219,40 +1484,32 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { * Silenio Quarti (IBM Corporation) - initial API and implementation ******************************************************************************/ -/*global window define */ +/*global define window*/ -/** - * @namespace The global container for Orion APIs. - */ -var orion = orion || {}; -/** - * @namespace The container for textview APIs. - */ -orion.textview = orion.textview || {}; - -/** - * Constructs a new TextModel with the given text and default line delimiter. - * - * @param {String} [text=""] the text that the model will store - * @param {String} [lineDelimiter=platform delimiter] the line delimiter used when inserting new lines to the model. - * - * @name orion.textview.TextModel - * @class The TextModel is an interface that provides text for the view. Applications may - * implement the TextModel interface to provide a custom store for the view content. The - * view interacts with its text model in order to access and update the text that is being - * displayed and edited in the view. This is the default implementation. - *

- * See:
- * {@link orion.textview.TextView}
- * {@link orion.textview.TextView#setModel} - *

- */ -orion.textview.TextModel = (function() { +define(['orion/textview/eventTarget'], function(mEventTarget) { var isWindows = window.navigator.platform.indexOf("Win") !== -1; - /** @private */ + /** + * Constructs a new TextModel with the given text and default line delimiter. + * + * @param {String} [text=""] the text that the model will store + * @param {String} [lineDelimiter=platform delimiter] the line delimiter used when inserting new lines to the model. + * + * @name orion.textview.TextModel + * @class The TextModel is an interface that provides text for the view. Applications may + * implement the TextModel interface to provide a custom store for the view content. The + * view interacts with its text model in order to access and update the text that is being + * displayed and edited in the view. This is the default implementation. + *

+ * See:
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#setModel} + *

+ * @borrows orion.textview.EventTarget#addEventListener as #addEventListener + * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener + * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent + */ function TextModel(text, lineDelimiter) { - this._listeners = []; this._lastLineIndex = -1; this._text = [""]; this._lineOffsets = [0]; @@ -1261,33 +1518,6 @@ orion.textview.TextModel = (function() { } TextModel.prototype = /** @lends orion.textview.TextModel.prototype */ { - /** - * Adds a listener to the model. - * - * @param {Object} listener the listener to add. - * @param {Function} [listener.onChanged] see {@link #onChanged}. - * @param {Function} [listener.onChanging] see {@link #onChanging}. - * - * @see removeListener - */ - addListener: function(listener) { - this._listeners.push(listener); - }, - /** - * Removes a listener from the model. - * - * @param {Object} listener the listener to remove - * - * @see #addListener - */ - removeListener: function(listener) { - for (var i = 0; i < this._listeners.length; i++) { - if (this._listeners[i] === listener) { - this._listeners.splice(i, 1); - return; - } - } - }, /** * Returns the number of characters in the model. * @@ -1512,12 +1742,7 @@ orion.textview.TextModel = (function() { * @param {orion.textview.ModelChangingEvent} modelChangingEvent the changing event */ onChanging: function(modelChangingEvent) { - for (var i = 0; i < this._listeners.length; i++) { - var l = this._listeners[i]; - if (l && l.onChanging) { - l.onChanging(modelChangingEvent); - } - } + return this.dispatchEvent(modelChangingEvent); }, /** * Notifies all listeners that the text has changed. @@ -1534,12 +1759,7 @@ orion.textview.TextModel = (function() { * @param {orion.textview.ModelChangedEvent} modelChangedEvent the changed event */ onChanged: function(modelChangedEvent) { - for (var i = 0; i < this._listeners.length; i++) { - var l = this._listeners[i]; - if (l && l.onChanged) { - l.onChanged(modelChangedEvent); - } - } + return this.dispatchEvent(modelChangedEvent); }, /** * Sets the line delimiter that is used by the view @@ -1616,6 +1836,7 @@ orion.textview.TextModel = (function() { } var modelChangingEvent = { + type: "Changing", text: text, start: eventStart, removedCharCount: removedCharCount, @@ -1679,6 +1900,7 @@ orion.textview.TextModel = (function() { if (this._text.length === 0) { this._text = [""]; } var modelChangedEvent = { + type: "Changed", start: eventStart, removedCharCount: removedCharCount, addedCharCount: addedCharCount, @@ -1688,16 +1910,631 @@ orion.textview.TextModel = (function() { this.onChanged(modelChangedEvent); } }; + mEventTarget.EventTarget.addMixin(TextModel.prototype); - return TextModel; -}()); + return {TextModel: TextModel}; +}, "orion/textview");/******************************************************************************* + * @license + * Copyright (c) 2010, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of the Eclipse Public License v1.0 + * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution + * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). + * + * Contributors: + * Felipe Heidrich (IBM Corporation) - initial API and implementation + * Silenio Quarti (IBM Corporation) - initial API and implementation + ******************************************************************************/ -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define([], function() { - return orion.textview; - }); -} +/*global define */ + +define(['orion/textview/eventTarget'], function(mEventTarget) { + /** + * @class This object represents a decoration attached to a range of text. Annotations are added to a + * AnnotationModel which is attached to a TextModel. + *

+ * See:
+ * {@link orion.textview.AnnotationModel}
+ * {@link orion.textview.Ruler}
+ *

+ * @name orion.textview.Annotation + * + * @property {String} type The annotation type (for example, orion.annotation.error). + * @property {Number} start The start offset of the annotation in the text model. + * @property {Number} end The end offset of the annotation in the text model. + * @property {String} html The HTML displayed for the annotation. + * @property {String} title The text description for the annotation. + * @property {orion.textview.Style} style The style information for the annotation used in the annotations ruler and tooltips. + * @property {orion.textview.Style} overviewStyle The style information for the annotation used in the overview ruler. + * @property {orion.textview.Style} rangeStyle The style information for the annotation used in the text view to decorate a range of text. + * @property {orion.textview.Style} lineStyle The style information for the annotation used in the text view to decorate a line of text. + */ + /** + * Constructs a new folding annotation. + * + * @param {orion.textview.ProjectionTextModel} projectionModel The projection text model. + * @param {String} type The annotation type. + * @param {Number} start The start offset of the annotation in the text model. + * @param {Number} end The end offset of the annotation in the text model. + * @param {String} expandedHTML The HTML displayed for this annotation when it is expanded. + * @param {orion.textview.Style} expandedStyle The style information for the annotation when it is expanded. + * @param {String} collapsedHTML The HTML displayed for this annotation when it is collapsed. + * @param {orion.textview.Style} collapsedStyle The style information for the annotation when it is collapsed. + * + * @class This object represents a folding annotation. + * @name orion.textview.FoldingAnnotation + */ + function FoldingAnnotation (projectionModel, type, start, end, expandedHTML, expandedStyle, collapsedHTML, collapsedStyle) { + this.type = type; + this.start = start; + this.end = end; + this._projectionModel = projectionModel; + this._expandedHTML = this.html = expandedHTML; + this._expandedStyle = this.style = expandedStyle; + this._collapsedHTML = collapsedHTML; + this._collapsedStyle = collapsedStyle; + this.expanded = true; + } + + FoldingAnnotation.prototype = /** @lends orion.textview.FoldingAnnotation.prototype */ { + /** + * Collapses the annotation. + */ + collapse: function () { + if (!this.expanded) { return; } + this.expanded = false; + this.html = this._collapsedHTML; + this.style = this._collapsedStyle; + var projectionModel = this._projectionModel; + var baseModel = projectionModel.getBaseModel(); + this._projection = { + start: baseModel.getLineStart(baseModel.getLineAtOffset(this.start) + 1), + end: baseModel.getLineEnd(baseModel.getLineAtOffset(this.end), true) + }; + projectionModel.addProjection(this._projection); + }, + /** + * Expands the annotation. + */ + expand: function () { + if (this.expanded) { return; } + this.expanded = true; + this.html = this._expandedHTML; + this.style = this._expandedStyle; + this._projectionModel.removeProjection(this._projection); + } + }; + + /** + * Constructs an annotation model. + * + * @param {textModel} textModel The text model. + * + * @class This object manages annotations for a TextModel. + *

+ * See:
+ * {@link orion.textview.Annotation}
+ * {@link orion.textview.TextModel}
+ *

+ * @name orion.textview.AnnotationModel + * @borrows orion.textview.EventTarget#addEventListener as #addEventListener + * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener + * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent + */ + function AnnotationModel(textModel) { + this._annotations = []; + var self = this; + this._listener = { + onChanged: function(modelChangedEvent) { + self._onChanged(modelChangedEvent); + } + }; + this.setTextModel(textModel); + } + + AnnotationModel.prototype = /** @lends orion.textview.AnnotationModel.prototype */ { + /** + * Adds an annotation to the annotation model. + *

The annotation model listeners are notified of this change.

+ * + * @param {orion.textview.Annotation} annotation the annotation to be added. + * + * @see #removeAnnotation + */ + addAnnotation: function(annotation) { + if (!annotation) { return; } + var annotations = this._annotations; + var index = this._binarySearch(annotations, annotation.start); + annotations.splice(index, 0, annotation); + var e = { + type: "Changed", + added: [annotation], + removed: [], + changed: [] + }; + this.onChanged(e); + }, + /** + * Returns the text model. + * + * @return {orion.textview.TextModel} The text model. + * + * @see #setTextModel + */ + getTextModel: function() { + return this._model; + }, + /** + * @class This object represents an annotation iterator. + *

+ * See:
+ * {@link orion.textview.AnnotationModel#getAnnotations}
+ *

+ * @name orion.textview.AnnotationIterator + * + * @property {Function} hasNext Determines whether there are more annotations in the iterator. + * @property {Function} next Returns the next annotation in the iterator. + */ + /** + * Returns an iterator of annotations for the given range of text. + * + * @param {Number} start the start offset of the range. + * @param {Number} end the end offset of the range. + * @return {orion.textview.AnnotationIterator} an annotation iterartor. + */ + getAnnotations: function(start, end) { + var annotations = this._annotations, current; + //TODO binary search does not work for range intersection when there are overlaping ranges, need interval search tree for this + var i = 0; + var skip = function() { + while (i < annotations.length) { + var a = annotations[i++]; + if ((start === a.start) || (start > a.start ? start < a.end : a.start < end)) { + return a; + } + if (a.start >= end) { + break; + } + } + return null; + }; + current = skip(); + return { + next: function() { + var result = current; + if (result) { current = skip(); } + return result; + }, + hasNext: function() { + return current !== null; + } + }; + }, + /** + * Notifies the annotation model that the given annotation has been modified. + *

The annotation model listeners are notified of this change.

+ * + * @param {orion.textview.Annotation} annotation the modified annotation. + * + * @see #addAnnotation + */ + modifyAnnotation: function(annotation) { + if (!annotation) { return; } + var index = this._getAnnotationIndex(annotation); + if (index < 0) { return; } + var e = { + type: "Changed", + added: [], + removed: [], + changed: [annotation] + }; + this.onChanged(e); + }, + /** + * Notifies all listeners that the annotation model has changed. + * + * @param {orion.textview.Annotation[]} added The list of annotation being added to the model. + * @param {orion.textview.Annotation[]} changed The list of annotation modified in the model. + * @param {orion.textview.Annotation[]} removed The list of annotation being removed from the model. + * @param {ModelChangedEvent} textModelChangedEvent the text model changed event that trigger this change, can be null if the change was trigger by a method call (for example, {@link #addAnnotation}). + */ + onChanged: function(e) { + return this.dispatchEvent(e); + }, + /** + * Removes all annotations of the given type. All annotations + * are removed if the type is not specified. + *

The annotation model listeners are notified of this change. Only one changed event is generated.

+ * + * @param {Object} type the type of annotations to be removed. + * + * @see #removeAnnotation + */ + removeAnnotations: function(type) { + var annotations = this._annotations; + var removed, i; + if (type) { + removed = []; + for (i = annotations.length - 1; i >= 0; i--) { + var annotation = annotations[i]; + if (annotation.type === type) { + annotations.splice(i, 1); + } + removed.splice(0, 0, annotation); + } + } else { + removed = annotations; + annotations = []; + } + var e = { + type: "Changed", + removed: removed, + added: [], + changed: [] + }; + this.onChanged(e); + }, + /** + * Removes an annotation from the annotation model. + *

The annotation model listeners are notified of this change.

+ * + * @param {orion.textview.Annotation} annotation the annotation to be removed. + * + * @see #addAnnotation + */ + removeAnnotation: function(annotation) { + if (!annotation) { return; } + var index = this._getAnnotationIndex(annotation); + if (index < 0) { return; } + var e = { + type: "Changed", + removed: this._annotations.splice(index, 1), + added: [], + changed: [] + }; + this.onChanged(e); + }, + /** + * Removes and adds the specifed annotations to the annotation model. + *

The annotation model listeners are notified of this change. Only one changed event is generated.

+ * + * @param {orion.textview.Annotation} remove the annotations to be removed. + * @param {orion.textview.Annotation} add the annotations to be added. + * + * @see #addAnnotation + * @see #removeAnnotation + */ + replaceAnnotations: function(remove, add) { + var annotations = this._annotations, i, index, annotation, removed = []; + if (remove) { + for (i = remove.length - 1; i >= 0; i--) { + annotation = remove[i]; + index = this._getAnnotationIndex(annotation); + if (index < 0) { continue; } + annotations.splice(index, 1); + removed.splice(0, 0, annotation); + } + } + if (!add) { add = []; } + for (i = 0; i < add.length; i++) { + annotation = add[i]; + index = this._binarySearch(annotations, annotation.start); + annotations.splice(index, 0, annotation); + } + var e = { + type: "Changed", + removed: removed, + added: add, + changed: [] + }; + this.onChanged(e); + }, + /** + * Sets the text model of the annotation model. The annotation + * model listens for changes in the text model to update and remove + * annotations that are affected by the change. + * + * @param {orion.textview.TextModel} textModel the text model. + * + * @see #getTextModel + */ + setTextModel: function(textModel) { + if (this._model) { + this._model.removeEventListener("Changed", this._listener.onChanged); + } + this._model = textModel; + if (this._model) { + this._model.addEventListener("Changed", this._listener.onChanged); + } + }, + /** @ignore */ + _binarySearch: function (array, offset) { + var high = array.length, low = -1, index; + while (high - low > 1) { + index = Math.floor((high + low) / 2); + if (offset <= array[index].start) { + high = index; + } else { + low = index; + } + } + return high; + }, + /** @ignore */ + _getAnnotationIndex: function(annotation) { + var annotations = this._annotations; + var index = this._binarySearch(annotations, annotation.start); + while (index < annotations.length && annotations[index].start === annotation.start) { + if (annotations[index] === annotation) { + return index; + } + index++; + } + return -1; + }, + /** @ignore */ + _onChanged: function(modelChangedEvent) { + var start = modelChangedEvent.start; + var addedCharCount = modelChangedEvent.addedCharCount; + var removedCharCount = modelChangedEvent.removedCharCount; + var annotations = this._annotations, end = start + removedCharCount; + //TODO binary search does not work for range intersection when there are overlaping ranges, need interval search tree for this + var startIndex = 0; + if (!(0 <= startIndex && startIndex < annotations.length)) { return; } + var e = { + type: "Changed", + added: [], + removed: [], + changed: [], + textModelChangedEvent: modelChangedEvent + }; + var changeCount = addedCharCount - removedCharCount, i; + for (i = startIndex; i < annotations.length; i++) { + var annotation = annotations[i]; + if (annotation.start >= end) { + annotation.start += changeCount; + annotation.end += changeCount; + e.changed.push(annotation); + } else if (annotation.end <= start) { + //nothing + } else if (annotation.start < start && end < annotation.end) { + annotation.end += changeCount; + e.changed.push(annotation); + } else { + annotations.splice(i, 1); + e.removed.push(annotation); + i--; + } + } + if (e.added.length > 0 || e.removed.length > 0 || e.changed.length > 0) { + this.onChanged(e); + } + } + }; + mEventTarget.EventTarget.addMixin(AnnotationModel.prototype); + + /** + * Constructs a new styler for annotations. + * + * @param {orion.textview.TextView} view The styler view. + * @param {orion.textview.AnnotationModel} view The styler annotation model. + * + * @class This object represents a styler for annotation attached to a text view. + * @name orion.textview.AnnotationStyler + */ + function AnnotationStyler (view, annotationModel) { + this._view = view; + this._annotationModel = annotationModel; + this._types = []; + var self = this; + this._listener = { + onDestroy: function(e) { + self._onDestroy(e); + }, + onLineStyle: function(e) { + self._onLineStyle(e); + }, + onChanged: function(e) { + self._onAnnotationModelChanged(e); + } + }; + view.addEventListener("Destroy", this._listener.onDestroy); + view.addEventListener("LineStyle", this._listener.onLineStyle); + annotationModel.addEventListener("Changed", this._listener.onChanged); + } + AnnotationStyler.prototype = /** @lends orion.textview.AnnotationStyler.prototype */ { + /** + * Adds an annotation type to the receiver. + *

+ * Only annotations of the specified types will be shown by + * this receiver. + *

+ * + * @param type {Object} the annotation type to be shown + * + * @see #removeAnnotationType + * @see #isAnnotationTypeVisible + */ + addAnnotationType: function(type) { + this._types.push(type); + }, + /** + * Destroys the styler. + *

+ * Removes all listeners added by this styler. + *

+ */ + destroy: function() { + var view = this._view; + if (view) { + view.removeEventListener("Destroy", this._listener.onDestroy); + view.removeEventListener("LineStyle", this._listener.onLineStyle); + this.view = null; + } + var annotationModel = this._annotationModel; + if (annotationModel) { + annotationModel.removeEventListener("Changed", this._listener.onChanged); + annotationModel = null; + } + }, + /** + * Returns whether the receiver shows annotations of the specified type. + * + * @param {Object} type the annotation type + * @returns {Boolean} whether the specified annotation type is shown + * + * @see #addAnnotationType + * @see #removeAnnotationType + */ + isAnnotationTypeVisible: function(type) { + for (var i = 0; i < this._types.length; i++) { + if (this._types[i] === type) { + return true; + } + } + return false; + }, + /** + * Removes an annotation type from the receiver. + * + * @param {Object} type the annotation type to be removed + * + * @see #addAnnotationType + * @see #isAnnotationTypeVisible + */ + removeAnnotationType: function(type) { + for (var i = 0; i < this._types.length; i++) { + if (this._types[i] === type) { + this._types.splice(i, 1); + break; + } + } + }, + _mergeStyle: function(result, style) { + if (style) { + if (!result) { result = {}; } + if (result.styleClass && style.styleClass && result.styleClass !== style.styleClass) { + result.styleClass += " " + style.styleClass; + } else { + result.styleClass = style.styleClass; + } + var prop; + if (style.style) { + if (!result.style) { result.style = {}; } + for (prop in style.style) { + if (!result.style[prop]) { + result.style[prop] = style.style[prop]; + } + } + } + if (style.attributes) { + if (!result.attributes) { result.attributes = {}; } + for (prop in style.attributes) { + if (!result.attributes[prop]) { + result.attributes[prop] = style.attributes[prop]; + } + } + } + } + return result; + }, + _mergeStyleRanges: function(ranges, styleRange) { + if (!ranges) { return; } + for (var i=0; i= range.end) { continue; } + var mergedStyle = this._mergeStyle({}, range.style); + mergedStyle = this._mergeStyle(mergedStyle, styleRange.style); + if (styleRange.start <= range.start && styleRange.end >= range.end) { + ranges[i] = {start: range.start, end: range.end, style: mergedStyle}; + } else if (styleRange.start > range.start && styleRange.end < range.end) { + ranges.splice(i, 1, + {start: range.start, end: styleRange.start, style: range.style}, + {start: styleRange.start, end: styleRange.end, style: mergedStyle}, + {start: styleRange.end, end: range.end, style: range.style}); + i += 2; + } else if (styleRange.start > range.start) { + ranges.splice(i, 1, + {start: range.start, end: styleRange.start, style: range.style}, + {start: styleRange.start, end: range.end, style: mergedStyle}); + i += 1; + } else if (styleRange.end < range.end) { + ranges.splice(i, 1, + {start: range.start, end: styleRange.end, style: mergedStyle}, + {start: styleRange.end, end: range.end, style: range.style}); + i += 1; + } + } + }, + _onAnnotationModelChanged: function(e) { + if (e.textModelChangedEvent) { + return; + } + var view = this._view; + if (!view) { return; } + var self = this; + var model = view.getModel(); + function redraw(changes) { + for (var i = 0; i < changes.length; i++) { + if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; } + var start = changes[i].start; + var end = changes[i].end; + if (model.getBaseModel) { + start = model.mapOffset(start, true); + end = model.mapOffset(end, true); + } + if (start !== -1 && end !== -1) { + view.redrawRange(start, end); + } + } + } + redraw(e.added); + redraw(e.removed); + redraw(e.changed); + }, + _onDestroy: function(e) { + this.destroy(); + }, + _onLineStyle: function (e) { + var annotationModel = this._annotationModel; + var viewModel = this._view.getModel(); + var baseModel = annotationModel.getTextModel(); + var start = e.lineStart; + var end = e.lineStart + e.lineText.length; + if (baseModel !== viewModel) { + start = viewModel.mapOffset(start); + end = viewModel.mapOffset(end); + } + var annotations = annotationModel.getAnnotations(start, end); + while (annotations.hasNext()) { + var annotation = annotations.next(); + if (!this.isAnnotationTypeVisible(annotation.type)) { continue; } + if (annotation.rangeStyle) { + var annotationStart = annotation.start; + var annotationEnd = annotation.end; + if (baseModel !== viewModel) { + annotationStart = viewModel.mapOffset(annotationStart, true); + annotationEnd = viewModel.mapOffset(annotationEnd, true); + } + this._mergeStyleRanges(e.ranges, {start: annotationStart, end: annotationEnd, style: annotation.rangeStyle}); + } + if (annotation.lineStyle) { + e.style = this._mergeStyle({}, e.style); + e.style = this._mergeStyle(e.style, annotation.lineStyle); + } + } + } + }; + + return { + FoldingAnnotation: FoldingAnnotation, + AnnotationModel: AnnotationModel, + AnnotationStyler: AnnotationStyler + }; +}, "orion/textview"); /******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -1707,19 +2544,10 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { * Contributors: IBM Corporation - initial API and implementation ******************************************************************************/ -/*global window define setTimeout clearTimeout setInterval clearInterval Node */ +/*global define setTimeout clearTimeout setInterval clearInterval Node */ -/** - * @namespace The global container for Orion APIs. - */ -var orion = orion || {}; -/** - * @namespace The container for textview APIs. - */ -orion.textview = orion.textview || {}; +define(['orion/textview/textView', 'orion/textview/textModel', 'orion/textview/projectionTextModel'], function(mTextView, mTextModel, mProjectionTextModel) { -/** @ignore */ -orion.textview.Tooltip = (function() { /** @private */ function Tooltip (view) { this._view = view; @@ -1816,14 +2644,15 @@ orion.textview.Tooltip = (function() { (contentsDiv = this._htmlParent).innerHTML = contents; } else if (contents instanceof Node) { (contentsDiv = this._htmlParent).appendChild(contents); - } else if (contents instanceof orion.textview.ProjectionTextModel) { + } else if (contents instanceof mProjectionTextModel.ProjectionTextModel) { if (!this._contentsView) { - this._emptyModel = new orion.textview.TextModel(""); + this._emptyModel = new mTextModel.TextModel(""); //TODO need hook into setup.js (or editor.js) to create a text view (and styler) - var newView = this._contentsView = new orion.textview.TextView({ + var newView = this._contentsView = new mTextView.TextView({ model: this._emptyModel, parent: this._viewParent, tabSize: 4, + sync: true, stylesheet: ["/orion/textview/tooltip.css", "/orion/textview/rulers.css", "/examples/textview/textstyler.css", "/css/default-theme.css"] }); @@ -1831,7 +2660,12 @@ orion.textview.Tooltip = (function() { newView._clientDiv.contentEditable = false; //TODO need to find a better way of sharing the styler for multiple views var view = this._view; - newView.addEventListener("LineStyle", view, view.onLineStyle); + var listener = { + onLineStyle: function(e) { + view.onLineStyle(e); + } + }; + newView.addEventListener("LineStyle", listener.onLineStyle); } var contentsView = this._contentsView; contentsView.setModel(contents); @@ -1893,16 +2727,16 @@ orion.textview.Tooltip = (function() { annotation = annotations[0]; if (annotation.title) { title = annotation.title.replace(//g, ">"); - return annotation.html + " " + title; + return "
" + annotation.html + " " + title + "
"; } else { - var newModel = new orion.textview.ProjectionTextModel(baseModel); + var newModel = new mProjectionTextModel.ProjectionTextModel(baseModel); var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(annotation.start)); newModel.addProjection({start: annotation.end, end: newModel.getCharCount()}); newModel.addProjection({start: 0, end: lineStart}); return newModel; } } else { - var tooltipHTML = "Multiple annotations:
"; + var tooltipHTML = "
Multiple annotations:
"; for (var i = 0; i < annotations.length; i++) { annotation = annotations[i]; title = annotation.title; @@ -1910,7 +2744,7 @@ orion.textview.Tooltip = (function() { title = getText(annotation.start, annotation.end); } title = title.replace(//g, ">"); - tooltipHTML += annotation.html + " " + title + "
"; + tooltipHTML += "
" + annotation.html + " " + title + "
"; } return tooltipHTML; } @@ -1935,14 +2769,10 @@ orion.textview.Tooltip = (function() { return value || defaultValue; } }; - return Tooltip; -}()); - -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define([], function() { - return orion.textview; - }); -}/******************************************************************************* + return {Tooltip: Tooltip}; +}, "orion/textview"); +/******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -1952,35 +2782,12 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { * Contributors: * Felipe Heidrich (IBM Corporation) - initial API and implementation * Silenio Quarti (IBM Corporation) - initial API and implementation - * Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#358623 Bug#362286 Bug#362107 Bug#362428 Bug#362835 Bug#363508 + * Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#362835 Bug#362428 Bug#362286 Bug#354270 Bug#361474 Bug#363945 Bug#366312 ******************************************************************************/ /*global window document navigator setTimeout clearTimeout XMLHttpRequest define */ -/** - * @namespace The global container for Orion APIs. - */ -var orion = orion || {}; -/** - * @namespace The container for textview APIs. - */ -orion.textview = orion.textview || {}; - -/** - * Constructs a new text view. - * - * @param options the view options. - * @param {String|DOMElement} options.parent the parent element for the view, it can be either a DOM element or an ID for a DOM element. - * @param {orion.textview.TextModel} [options.model] the text model for the view. If this options is not set the view creates an empty {@link orion.textview.TextModel}. - * @param {Boolean} [options.readonly=false] whether or not the view is read-only. - * @param {Boolean} [options.fullSelection=true] whether or not the view is in full selection mode. - * @param {String|String[]} [options.stylesheet] one or more stylesheet URIs for the view. - * @param {Number} [options.tabSize] The number of spaces in a tab. - * - * @class A TextView is a user interface for editing text. - * @name orion.textview.TextView - */ -orion.textview.TextView = (function() { +define(['orion/textview/textModel', 'orion/textview/keyBinding', 'orion/textview/eventTarget'], function(mTextModel, mKeyBinding, mEventTarget) { /** @private */ function addHandler(node, type, handler, capture) { @@ -1998,19 +2805,22 @@ orion.textview.TextView = (function() { node.detachEvent("on" + type, handler); } } - var isIE = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent) ? document.documentMode : undefined; - var isFirefox = parseFloat(navigator.userAgent.split("Firefox/")[1] || navigator.userAgent.split("Minefield/")[1]) || undefined; - var isOpera = navigator.userAgent.indexOf("Opera") !== -1; - var isChrome = navigator.userAgent.indexOf("Chrome") !== -1; - var isSafari = navigator.userAgent.indexOf("Safari") !== -1; - var isWebkit = navigator.userAgent.indexOf("WebKit") !== -1; - var isPad = navigator.userAgent.indexOf("iPad") !== -1; + var userAgent = navigator.userAgent; + var isIE; + if (document.selection && window.ActiveXObject && /MSIE/.test(userAgent)) { + isIE = document.documentMode ? document.documentMode : 7; + } + var isFirefox = parseFloat(userAgent.split("Firefox/")[1] || userAgent.split("Minefield/")[1]) || undefined; + var isOpera = userAgent.indexOf("Opera") !== -1; + var isChrome = userAgent.indexOf("Chrome") !== -1; + var isSafari = userAgent.indexOf("Safari") !== -1 && !isChrome; + var isWebkit = userAgent.indexOf("WebKit") !== -1; + var isPad = userAgent.indexOf("iPad") !== -1; var isMac = navigator.platform.indexOf("Mac") !== -1; var isWindows = navigator.platform.indexOf("Win") !== -1; var isLinux = navigator.platform.indexOf("Linux") !== -1; var isW3CEvents = typeof window.document.documentElement.addEventListener === "function"; var isRangeRects = (!isIE || isIE >= 9) && typeof window.document.createRange().getBoundingClientRect === "function"; - var isDnD = isFirefox || isWebkit; // drag and drop support var platformDelimiter = isWindows ? "\r\n" : "\n"; /** @@ -2019,185 +2829,108 @@ orion.textview.TextView = (function() { * @class A Selection represents a range of selected text in the view. * @name orion.textview.Selection */ - var Selection = (function() { + function Selection (start, end, caret) { + /** + * The selection start offset. + * + * @name orion.textview.Selection#start + */ + this.start = start; + /** + * The selection end offset. + * + * @name orion.textview.Selection#end + */ + this.end = end; /** @private */ - function Selection (start, end, caret) { - /** - * The selection start offset. - * - * @name orion.textview.Selection#start - */ - this.start = start; - /** - * The selection end offset. - * - * @name orion.textview.Selection#end - */ - this.end = end; - /** @private */ - this.caret = caret; //true if the start, false if the caret is at end - } - Selection.prototype = /** @lends orion.textview.Selection.prototype */ { - /** @private */ - clone: function() { - return new Selection(this.start, this.end, this.caret); - }, - /** @private */ - collapse: function() { - if (this.caret) { - this.end = this.start; - } else { - this.start = this.end; - } - }, - /** @private */ - extend: function (offset) { - if (this.caret) { - this.start = offset; - } else { - this.end = offset; - } - if (this.start > this.end) { - var tmp = this.start; - this.start = this.end; - this.end = tmp; - this.caret = !this.caret; - } - }, - /** @private */ - setCaret: function(offset) { + this.caret = caret; //true if the start, false if the caret is at end + } + Selection.prototype = /** @lends orion.textview.Selection.prototype */ { + /** @private */ + clone: function() { + return new Selection(this.start, this.end, this.caret); + }, + /** @private */ + collapse: function() { + if (this.caret) { + this.end = this.start; + } else { + this.start = this.end; + } + }, + /** @private */ + extend: function (offset) { + if (this.caret) { this.start = offset; + } else { this.end = offset; - this.caret = false; - }, - /** @private */ - getCaret: function() { - return this.caret ? this.start : this.end; - }, - /** @private */ - toString: function() { - return "start=" + this.start + " end=" + this.end + (this.caret ? " caret is at start" : " caret is at end"); - }, - /** @private */ - isEmpty: function() { - return this.start === this.end; - }, - /** @private */ - equals: function(object) { - return this.caret === object.caret && this.start === object.start && this.end === object.end; } - }; - return Selection; - }()); - - /** - * Constructs a new EventTable object. - * - * @class - * @name orion.textview.EventTable - * @private - */ - var EventTable = (function() { + if (this.start > this.end) { + var tmp = this.start; + this.start = this.end; + this.end = tmp; + this.caret = !this.caret; + } + }, /** @private */ - function EventTable(){ - this._types = {}; + setCaret: function(offset) { + this.start = offset; + this.end = offset; + this.caret = false; + }, + /** @private */ + getCaret: function() { + return this.caret ? this.start : this.end; + }, + /** @private */ + toString: function() { + return "start=" + this.start + " end=" + this.end + (this.caret ? " caret is at start" : " caret is at end"); + }, + /** @private */ + isEmpty: function() { + return this.start === this.end; + }, + /** @private */ + equals: function(object) { + return this.caret === object.caret && this.start === object.start && this.end === object.end; } - EventTable.prototype = /** @lends EventTable.prototype */ { - /** @private */ - addEventListener: function(type, context, func, data) { - var state = this._types[type]; - if (!state) { - state = this._types[type] = {level: 0, listeners: []}; - } - var listener = { - context: context, - func: func, - data: data - }; - var listeners = state.listeners; - listeners.push(listener); - }, - /** @private */ - sendEvent: function(type, event) { - var state = this._types[type]; - if (state) { - var listeners = state.listeners; - try { - state.level++; - if (listeners) { - for (var i=0, len=listeners.length; i < len; i++) { - var l = listeners[i]; - if (l && l.context && l.func) { - l.func.call(l.context, event, l.data); - } - } - } - } finally { - state.level--; - if (state.compact && state.level === 0) { - for (var j=listeners.length - 1; j >= 0; j--) { - if (!listeners[j]) { - listeners.splice(j, 1); - } - } - state.compact = false; - } - } - } - }, - /** @private */ - removeEventListener: function(type, context, func, data){ - var state = this._types[type]; - if (state) { - var listeners = state.listeners; - for (var i=0, len=listeners.length; i < len; i++) { - var l = listeners[i]; - if (l && l.context === context && l.func === func && l.data === data) { - if (state.level !== 0) { - listeners[i] = null; - state.compact = true; - } else { - listeners.splice(i, 1); - } - break; - } - } - } - } - }; - return EventTable; - }()); - - /** @private */ + }; + /** + * @class This object describes the options for the text view. + *

+ * See:
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#setOptions} + * {@link orion.textview.TextView#getOptions} + *

+ * @name orion.textview.TextViewOptions + * + * @property {String|DOMElement} parent the parent element for the view, it can be either a DOM element or an ID for a DOM element. + * @property {orion.textview.TextModel} [model] the text model for the view. If it is not set the view creates an empty {@link orion.textview.TextModel}. + * @property {Boolean} [readonly=false] whether or not the view is read-only. + * @property {Boolean} [fullSelection=true] whether or not the view is in full selection mode. + * @property {Boolean} [sync=false] whether or not the view creation should be synchronous (if possible). + * @property {Boolean} [expandTab=false] whether or not the tab key inserts white spaces + * @property {String|String[]} [stylesheet] one or more stylesheet URIs for the view. + * @property {String} [themeClass] the CSS class for the view theming. + * @property {Number} [tabSize] The number of spaces in a tab. + */ + /** + * Constructs a new text view. + * + * @param {orion.textview.TextViewOptions} options the view options. + * + * @class A TextView is a user interface for editing text. + * @name orion.textview.TextView + * @borrows orion.textview.EventTarget#addEventListener as #addEventListener + * @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener + * @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent + */ function TextView (options) { this._init(options); } TextView.prototype = /** @lends orion.textview.TextView.prototype */ { - /** - * Adds an event listener to the text view. - * - * @param {String} type the event type. The supported events are: - *
    - *
  • "Modify" See {@link #onModify}
  • - *
  • "Selection" See {@link #onSelection}
  • - *
  • "Scroll" See {@link #onScroll}
  • - *
  • "Verify" See {@link #onVerify}
  • - *
  • "Destroy" See {@link #onDestroy}
  • - *
  • "LineStyle" See {@link #onLineStyle}
  • - *
  • "ModelChanging" See {@link #onModelChanging}
  • - *
  • "ModelChanged" See {@link #onModelChanged}
  • - *
- * @param {Object} context the context of the function. - * @param {Function} func the function that will be executed when the event happens. - * The function should take an event as the first parameter and optional data as the second parameter. - * @param {Object} [data] optional data passed to the function. - * - * @see #removeEventListener - */ - addEventListener: function(type, context, func, data) { - this._eventTable.addEventListener(type, context, func, data); - }, /** * Adds a ruler to the text view. * @@ -2212,6 +2945,7 @@ orion.textview.TextView = (function() { computeSize: function() { var w = 0, h = 0; var model = this._model, clientDiv = this._clientDiv; + if (!clientDiv) { return {width: w, height: h}; } var clientWidth = clientDiv.style.width; /* * Feature in WekKit. Webkit limits the width of the lines @@ -2228,7 +2962,7 @@ orion.textview.TextView = (function() { var document = this._frameDocument; for (var lineIndex=0; lineIndextrue if the text view has focus, otherwise false. + */ + hasFocus: function() { + return this._hasFocus; + }, /** * Returns all action names defined in the text view. *

@@ -2466,6 +3206,7 @@ orion.textview.TextView = (function() { * @see #setTopIndex */ getBottomIndex: function(fullyVisible) { + if (!this._clientDiv) { return 0; } return this._getBottomIndex(fullyVisible); }, /** @@ -2483,6 +3224,7 @@ orion.textview.TextView = (function() { * @see #convert */ getBottomPixel: function() { + if (!this._clientDiv) { return 0; } return this._getScroll().y + this._getClientHeight(); }, /** @@ -2513,6 +3255,7 @@ orion.textview.TextView = (function() { * @see #convert */ getClientArea: function() { + if (!this._clientDiv) { return {x: 0, y: 0, width: 0, height: 0}; } var scroll = this._getScroll(); return {x: scroll.x, y: scroll.y, width: this._getClientWidth(), height: this._getClientHeight()}; }, @@ -2530,6 +3273,7 @@ orion.textview.TextView = (function() { * @see #convert */ getHorizontalPixel: function() { + if (!this._clientDiv) { return 0; } return this._getScroll().x; }, /** @@ -2561,6 +3305,7 @@ orion.textview.TextView = (function() { * @see #getLinePixel */ getLineHeight: function(lineIndex) { + if (!this._clientDiv) { return 0; } return this._getLineHeight(); }, /** @@ -2577,6 +3322,7 @@ orion.textview.TextView = (function() { * @see #convert */ getLinePixel: function(lineIndex) { + if (!this._clientDiv) { return 0; } lineIndex = Math.min(Math.max(0, lineIndex), this._model.getLineCount()); var lineHeight = this._getLineHeight(); return lineHeight * lineIndex; @@ -2596,6 +3342,7 @@ orion.textview.TextView = (function() { * @see #convert */ getLocationAtOffset: function(offset) { + if (!this._clientDiv) { return {x: 0, y: 0}; } var model = this._model; offset = Math.min(Math.max(0, offset), model.getCharCount()); var lineIndex = model.getLineAtOffset(offset); @@ -2606,6 +3353,44 @@ orion.textview.TextView = (function() { var y = this.getLinePixel(lineIndex); return {x: x, y: y}; }, + /** + * Returns the specified view options. + *

+ * The returned value is either a orion.textview.TextViewOptions or an option value. An option value is returned when only one string paremeter + * is specified. A orion.textview.TextViewOptions is returned when there are no paremeters, or the parameters are a list of options names or a + * orion.textview.TextViewOptions. All view options are returned when there no paremeters. + *

+ * + * @param {String|orion.textview.TextViewOptions} [options] The options to return. + * @return {Object|orion.textview.TextViewOptions} The requested options or an option value. + * + * @see #setOptions + */ + getOptions: function() { + var options; + if (arguments.length === 0) { + options = this._defaultOptions(); + } else if (arguments.length === 1) { + var arg = arguments[0]; + if (typeof arg === "string") { + return this._clone(this["_" + arg]); + } + options = arg; + } else { + options = {}; + for (var index in arguments) { + if (arguments.hasOwnProperty(index)) { + options[arguments[index]] = undefined; + } + } + } + for (var option in options) { + if (options.hasOwnProperty(option)) { + options[option] = this._clone(this["_" + option]); + } + } + return options; + }, /** * Returns the text model of the text view. * @@ -2625,6 +3410,7 @@ orion.textview.TextView = (function() { * @see #getLocationAtOffset */ getOffsetAtLocation: function(x, y) { + if (!this._clientDiv) { return 0; } var scroll = this._getScroll(); var viewRect = this._viewDiv.getBoundingClientRect(); var viewPad = this._getViewPadding(); @@ -2689,6 +3475,7 @@ orion.textview.TextView = (function() { * @see #setTopIndex */ getTopIndex: function(fullyVisible) { + if (!this._clientDiv) { return 0; } return this._getTopIndex(fullyVisible); }, /** @@ -2706,6 +3493,7 @@ orion.textview.TextView = (function() { * @see #convert */ getTopPixel: function() { + if (!this._clientDiv) { return 0; } return this._getScroll().y; }, /** @@ -2727,6 +3515,7 @@ orion.textview.TextView = (function() { * @see #getActions */ invokeAction: function (name, defaultAction) { + if (!this._clientDiv) { return; } var actions = this._actions; for (var i = 0; i < actions.length; i++) { var a = actions[i]; @@ -2761,9 +3550,30 @@ orion.textview.TextView = (function() { * @event * @param {orion.textview.ContextMenuEvent} contextMenuEvent the event */ - onContextMenu: function(contextMenuEvent) { - this._eventTable.sendEvent("ContextMenu", contextMenuEvent); + onContextMenu: function(contextMenuEvent) { + return this.dispatchEvent(contextMenuEvent); }, + onDragStart: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, + onDrag: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, + onDragEnd: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, + onDragEnter: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, + onDragOver: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, + onDragLeave: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, + onDrop: function(dragEvent) { + return this.dispatchEvent(dragEvent); + }, /** * @class This is the event sent when the text view is destroyed. *

@@ -2782,7 +3592,7 @@ orion.textview.TextView = (function() { * @see #destroy */ onDestroy: function(destroyEvent) { - this._eventTable.sendEvent("Destroy", destroyEvent); + return this.dispatchEvent(destroyEvent); }, /** * @class This object is used to define style information for the text view. @@ -2834,7 +3644,25 @@ orion.textview.TextView = (function() { * @param {orion.textview.LineStyleEvent} lineStyleEvent the event */ onLineStyle: function(lineStyleEvent) { - this._eventTable.sendEvent("LineStyle", lineStyleEvent); + return this.dispatchEvent(lineStyleEvent); + }, + /** + * @class This is the event sent when the text view has loaded its contents. + *

+ * See:
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#event:onLoad} + *

+ * @name orion.textview.LoadEvent + */ + /** + * This event is sent when the text view has loaded its contents. + * + * @event + * @param {orion.textview.LoadEvent} loadEvent the event + */ + onLoad: function(loadEvent) { + return this.dispatchEvent(loadEvent); }, /** * @class This is the event sent when the text in the model has changed. @@ -2859,7 +3687,7 @@ orion.textview.TextView = (function() { * @param {orion.textview.ModelChangedEvent} modelChangedEvent the event */ onModelChanged: function(modelChangedEvent) { - this._eventTable.sendEvent("ModelChanged", modelChangedEvent); + return this.dispatchEvent(modelChangedEvent); }, /** * @class This is the event sent when the text in the model is about to change. @@ -2885,7 +3713,7 @@ orion.textview.TextView = (function() { * @param {orion.textview.ModelChangingEvent} modelChangingEvent the event */ onModelChanging: function(modelChangingEvent) { - this._eventTable.sendEvent("ModelChanging", modelChangingEvent); + return this.dispatchEvent(modelChangingEvent); }, /** * @class This is the event sent when the text is modified by the text view. @@ -2907,7 +3735,22 @@ orion.textview.TextView = (function() { * @param {orion.textview.ModifyEvent} modifyEvent the event */ onModify: function(modifyEvent) { - this._eventTable.sendEvent("Modify", modifyEvent); + return this.dispatchEvent(modifyEvent); + }, + onMouseDown: function(mouseEvent) { + return this.dispatchEvent(mouseEvent); + }, + onMouseUp: function(mouseEvent) { + return this.dispatchEvent(mouseEvent); + }, + onMouseMove: function(mouseEvent) { + return this.dispatchEvent(mouseEvent); + }, + onMouseOver: function(mouseEvent) { + return this.dispatchEvent(mouseEvent); + }, + onMouseOut: function(mouseEvent) { + return this.dispatchEvent(mouseEvent); }, /** * @class This is the event sent when the selection changes in the text view. @@ -2928,7 +3771,7 @@ orion.textview.TextView = (function() { * @param {orion.textview.SelectionEvent} selectionEvent the event */ onSelection: function(selectionEvent) { - this._eventTable.sendEvent("Selection", selectionEvent); + return this.dispatchEvent(selectionEvent); }, /** * @class This is the event sent when the text view scrolls. @@ -2949,7 +3792,7 @@ orion.textview.TextView = (function() { * @param {orion.textview.ScrollEvent} scrollEvent the event */ onScroll: function(scrollEvent) { - this._eventTable.sendEvent("Scroll", scrollEvent); + return this.dispatchEvent(scrollEvent); }, /** * @class This is the event sent when the text is about to be modified by the text view. @@ -2979,7 +3822,77 @@ orion.textview.TextView = (function() { * @param {orion.textview.VerifyEvent} verifyEvent the event */ onVerify: function(verifyEvent) { - this._eventTable.sendEvent("Verify", verifyEvent); + return this.dispatchEvent(verifyEvent); + }, + /** + * @class This is the event sent when the text view has unloaded its contents. + *

+ * See:
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#event:onLoad} + *

+ * @name orion.textview.UnloadEvent + */ + /** + * This event is sent when the text view has unloaded its contents. + * + * @event + * @param {orion.textview.UnloadEvent} unloadEvent the event + */ + onUnload: function(unloadEvent) { + return this.dispatchEvent(unloadEvent); + }, + /** + * @class This is the event sent when the text view is focused. + *

+ * See:
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#event:onFocus}
+ *

+ * @name orion.textview.FocusEvent + */ + /** + * This event is sent when the text view is focused. + * + * @event + * @param {orion.textview.FocusEvent} focusEvent the event + */ + onFocus: function(focusEvent) { + return this.dispatchEvent(focusEvent); + }, + /** + * @class This is the event sent when the text view goes out of focus. + *

+ * See:
+ * {@link orion.textview.TextView}
+ * {@link orion.textview.TextView#event:onBlur}
+ *

+ * @name orion.textview.BlurEvent + */ + /** + * This event is sent when the text view goes out of focus. + * + * @event + * @param {orion.textview.BlurEvent} blurEvent the event + */ + onBlur: function(blurEvent) { + return this.dispatchEvent(blurEvent); + }, + /** + * Redraws the entire view, including rulers. + * + * @see #redrawLines + * @see #redrawRange + * @see #setRedraw + */ + redraw: function() { + if (this._redrawCount > 0) { return; } + var lineCount = this._model.getLineCount(); + var rulers = this.getRulers(); + for (var i = 0; i < rulers.length; i++) { + this.redrawLines(0, lineCount, rulers[i]); + } + this.redrawLines(0, lineCount); }, /** * Redraws the text in the given line range. @@ -2989,6 +3902,10 @@ orion.textview.TextView = (function() { * * @param {Number} [startLine=0] the start line * @param {Number} [endLine=line count] the end line + * + * @see #redraw + * @see #redrawRange + * @see #setRedraw */ redrawLines: function(startLine, endLine, ruler) { if (this._redrawCount > 0) { return; } @@ -3038,32 +3955,20 @@ orion.textview.TextView = (function() { * * @param {Number} [start=0] the start offset of text range * @param {Number} [end=char count] the end offset of text range + * + * @see #redraw + * @see #redrawLines + * @see #setRedraw */ redrawRange: function(start, end) { + if (this._redrawCount > 0) { return; } var model = this._model; if (start === undefined) { start = 0; } if (end === undefined) { end = model.getCharCount(); } - if (start === end) { return; } var startLine = model.getLineAtOffset(start); - var endLine = model.getLineAtOffset(Math.max(0, end - 1)) + 1; + var endLine = model.getLineAtOffset(Math.max(start, end - 1)) + 1; this.redrawLines(startLine, endLine); }, - /** - * Removes an event listener from the text view. - *

- * All the parameters must be the same ones used to add the listener. - *

- * - * @param {String} type the event type. - * @param {Object} context the context of the function. - * @param {Function} func the function that will be executed when the event happens. - * @param {Object} [data] optional data passed to the function. - * - * @see #addEventListener - */ - removeEventListener: function(type, context, func, data) { - this._eventTable.removeEventListener(type, context, func, data); - }, /** * Removes a ruler from the text view. * @@ -3187,19 +4092,28 @@ orion.textview.TextView = (function() { * @see #convert */ setHorizontalPixel: function(pixel) { + if (!this._clientDiv) { return; } pixel = Math.max(0, pixel); this._scrollView(pixel - this._getScroll().x, 0); }, + /** + * Sets whether the view should update the DOM. + *

+ * This can be used to improve the performance. + *

+ * When the flag is set to true, + * the entire view is marked as needing to be redrawn. + * Nested calls to this method are stacked. + *

+ * + * @param {Boolean} redraw the new redraw state + * + * @see #redraw + */ setRedraw: function(redraw) { if (redraw) { if (--this._redrawCount === 0) { - var lineCount = this._model.getLineCount(); - var rulers = this.getRulers(); - for (var i = 0; i < rulers.length; i++) { - this.redrawLines(0, lineCount, rulers[i]); - } - this.redrawLines(0, lineCount); - this._queueUpdatePage(); + this.redraw(); } } else { this._redrawCount++; @@ -3213,13 +4127,15 @@ orion.textview.TextView = (function() { setModel: function(model) { if (!model) { return; } if (model === this._model) { return; } - this._model.removeListener(this._modelListener); + this._model.removeEventListener("Changing", this._modelListener.onChanging); + this._model.removeEventListener("Changed", this._modelListener.onChanged); var oldLineCount = this._model.getLineCount(); var oldCharCount = this._model.getCharCount(); var newLineCount = model.getLineCount(); var newCharCount = model.getCharCount(); var newText = model.getText(); var e = { + type: "ModelChanging", text: newText, start: 0, removedCharCount: oldCharCount, @@ -3230,6 +4146,7 @@ orion.textview.TextView = (function() { this.onModelChanging(e); this._model = model; e = { + type: "ModelChanged", start: 0, removedCharCount: oldCharCount, addedCharCount: newCharCount, @@ -3237,10 +4154,55 @@ orion.textview.TextView = (function() { addedLineCount: newLineCount }; this.onModelChanged(e); - this._model.addListener(this._modelListener); + this._model.addEventListener("Changing", this._modelListener.onChanging); + this._model.addEventListener("Changed", this._modelListener.onChanged); this._reset(); this._updatePage(); }, + /** + * Sets the view options for the view. + * + * @param {orion.textview.TextViewOptions} options the view options. + * + * @see #getOptions + */ + setOptions: function (options) { + var defaultOptions = this._defaultOptions(); + var recreate = false, option, created = this._clientDiv; + if (created) { + for (option in options) { + if (options.hasOwnProperty(option)) { + if (defaultOptions[option].recreate) { + recreate = true; + break; + } + } + } + } + var changed = false; + for (option in options) { + if (options.hasOwnProperty(option)) { + var newValue = options[option], oldValue = this["_" + option]; + if (this._compare(oldValue, newValue)) { continue; } + changed = true; + if (!recreate) { + var update = defaultOptions[option].update; + if (created && update) { + update.call(this, newValue); + continue; + } + } + this["_" + option] = this._clone(newValue); + } + } + if (changed) { + if (recreate) { + var oldParent = this._frame.parentNode; + oldParent.removeChild(this._frame); + this._parent.appendChild(this._frame); + } + } + }, /** * Sets the text view selection. *

@@ -3307,14 +4269,7 @@ orion.textview.TextView = (function() { * force the clientDiv to loose and receive focus if the it is focused. */ if (isFirefox) { - var clientDiv = this._clientDiv; - if (clientDiv) { - var hasFocus = this._hasFocus; - if (hasFocus) { clientDiv.blur(); } - clientDiv.contentEditable = false; - clientDiv.contentEditable = true; - if (hasFocus) { clientDiv.focus(); } - } + this._fixCaret(); } } }, @@ -3331,6 +4286,7 @@ orion.textview.TextView = (function() { * @see #getTopIndex */ setTopIndex: function(topIndex) { + if (!this._clientDiv) { return; } var model = this._model; if (model.getCharCount() === 0) { return; @@ -3361,6 +4317,7 @@ orion.textview.TextView = (function() { * @see #convert */ setTopPixel: function(pixel) { + if (!this._clientDiv) { return; } var lineHeight = this._getLineHeight(); var clientHeight = this._getClientHeight(); var lineCount = this._model.getLineCount(); @@ -3382,6 +4339,12 @@ orion.textview.TextView = (function() { /**************************************** Event handlers *********************************/ _handleBodyMouseDown: function (e) { if (!e) { e = window.event; } + if (isFirefox) { + this._clientDiv.contentEditable = false; + (this._overlayDiv || this._clientDiv).draggable = true; + this._ignoreBlur = true; + } + /* * Prevent clicks outside of the view from taking focus * away the view. Note that in Firefox and Opera clicking on the @@ -3389,7 +4352,7 @@ orion.textview.TextView = (function() { * do not have this problem and stopping the click over the * scrollbar for them causes mouse capture problems. */ - var topNode = isOpera ? this._clientDiv : this._overlayDiv || this._viewDiv; + var topNode = isOpera || (isFirefox && !this._overlayDiv) ? this._clientDiv : this._overlayDiv || this._viewDiv; var temp = e.target ? e.target : e.srcElement; while (temp) { @@ -3409,8 +4372,26 @@ orion.textview.TextView = (function() { setTimeout(function() { topNode.releaseCapture(); }, 0); } }, + _handleBodyMouseUp: function (e) { + if (!e) { e = window.event; } + if (isFirefox) { + this._clientDiv.contentEditable = true; + (this._overlayDiv || this._clientDiv).draggable = false; + + /* + * Bug in Firefox. For some reason, Firefox stops showing the caret + * in some cases. For example when the user cancels a drag operation + * by pressing ESC. The fix is to detect that the drag operation was + * cancelled, toggle the contentEditable state and force the clientDiv + * to loose and receive focus if the it is focused. + */ + this._fixCaret(); + this._ignoreBlur = false; + } + }, _handleBlur: function (e) { if (!e) { e = window.event; } + if (this._ignoreBlur) { return; } this._hasFocus = false; /* * Bug in IE 8 and earlier. For some reason when text is deselected @@ -3434,15 +4415,27 @@ orion.textview.TextView = (function() { this._selDiv3.style.background = color; } } + if (!this._ignoreFocus) { + this.onBlur({type: "Blur"}); + } }, _handleContextMenu: function (e) { if (!e) { e = window.event; } - var scroll = this._getScroll(); - var viewRect = this._viewDiv.getBoundingClientRect(); - var viewPad = this._getViewPadding(); - var x = e.clientX + scroll.x - viewRect.left - viewPad.left; - var y = e.clientY + scroll.y - viewRect.top - viewPad.top; - this.onContextMenu({x: x, y: y, screenX: e.screenX, screenY: e.screenY}); + if (isFirefox && this._lastMouseButton === 3) { + // We need to update the DOM selection, because on + // right-click the caret moves to the mouse location. + // See bug 366312. + var timeDiff = e.timeStamp - this._lastMouseTime; + if (timeDiff <= this._clickTime) { + this._updateDOMSelection(); + } + } + if (this.isListening("ContextMenu")) { + var evt = this._createMouseEvent("ContextMenu", e); + evt.screenX = e.screenX; + evt.screenY = e.screenY; + this.onContextMenu(evt); + } if (e.preventDefault) { e.preventDefault(); } return false; }, @@ -3494,129 +4487,98 @@ orion.textview.TextView = (function() { }, _handleDragStart: function (e) { if (!e) { e = window.event; } - if (isDnD) { - var sel = this._getSelection(); - var text = !sel.isEmpty() ? this._getBaseText(sel.start, sel.end) : ""; - if (text) { - e.dataTransfer.effectAllowed = "copyMove"; - e.dataTransfer.setData("text/plain", text); - // TODO: generate a drag image to be a better visual indicatator of the drag operation. - this._dragStartSelection = {start: sel.start, end: sel.end}; - this.focus(); - return; - } + if (isFirefox) { + var self = this; + setTimeout(function() { + self._clientDiv.contentEditable = true; + self._clientDiv.draggable = false; + self._ignoreBlur = false; + }, 0); + } + if (this.isListening("DragStart") && this._dragOffset !== -1) { + this._isMouseDown = false; + this.onDragStart(this._createMouseEvent("DragStart", e)); + this._dragOffset = -1; + } else { + if (e.preventDefault) { e.preventDefault(); } + return false; + } + }, + _handleDrag: function (e) { + if (!e) { e = window.event; } + if (this.isListening("Drag")) { + this.onDrag(this._createMouseEvent("Drag", e)); } - if (e.preventDefault) { e.preventDefault(); } - return false; }, _handleDragEnd: function (e) { if (!e) { e = window.event; } - if (e.preventDefault) { e.preventDefault(); } - var startSel = this._dragStartSelection; - var drop = this._dropDestination; - if (startSel && e.dataTransfer.dropEffect === "move") { - var offset = 0; - if (drop && drop.offset < Math.min(startSel.start, startSel.end)) { - offset = drop.length; - } - var change = { - text: "", - start: startSel.start + offset, - end: startSel.end + offset - }; - this._modifyContent(change, false); + this._dropTarget = false; + this._dragOffset = -1; + if (this.isListening("DragEnd")) { + this.onDragEnd(this._createMouseEvent("DragEnd", e)); } - if (this._undoStack && drop) { - this._undoStack.endCompoundChange(); + if (isFirefox) { + this._fixCaret(); } - this._dragNode.draggable = false; - this._dragStartSelection = null; - this._dropDestination = null; - return false; }, _handleDragEnter: function (e) { if (!e) { e = window.event; } - if (e.preventDefault) { e.preventDefault(); } - var types = e.dataTransfer.types; - var allowed = false; - var types = isDnD ? e.dataTransfer.types : null; - if (types) { - // Firefox gives a .types of type StringList, while Webkit gives us an actual string. - allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain"); + var prevent = true; + this._dropTarget = true; + if (this.isListening("DragEnter")) { + prevent = false; + this.onDragEnter(this._createMouseEvent("DragEnter", e)); } - if (allowed) { - e.dataTransfer.dropEffect = "copyMove"; - this.focus(); - return true; + /* + * Webkit will not send drop events if this event is not prevented, as spec in HTML5. + * Firefox and IE do not follow this spec for contentEditable. Note that preventing this + * event will result is loss of functionality (insertion mark, etc). + */ + if (isWebkit || prevent) { + if (e.preventDefault) { e.preventDefault(); } + return false; } - e.dataTransfer.dropEffect = "none"; - return false; }, _handleDragOver: function (e) { if (!e) { e = window.event; } - if (e.preventDefault) { e.preventDefault(); } - var allowed = false; - var types = isDnD ? e.dataTransfer.types : null; - if (types) { - allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain"); + var prevent = true; + if (this.isListening("DragOver")) { + prevent = false; + this.onDragOver(this._createMouseEvent("DragOver", e)); } - if (!allowed) { - e.dataTransfer.dropEffect = "none"; + /* + * Webkit will not send drop events if this event is not prevented, as spec in HTML5. + * Firefox and IE do not follow this spec for contentEditable. Note that preventing this + * event will result is loss of functionality (insertion mark, etc). + */ + if (isWebkit || prevent) { + if (prevent) { e.dataTransfer.dropEffect = "none"; } + if (e.preventDefault) { e.preventDefault(); } return false; } - - var destLine = this._getYToLine(e.clientY); - var destOffset = this._getXToOffset(destLine, e.clientX); - - var startSel = this._dragStartSelection; - if (startSel && startSel.start <= destOffset && destOffset <= startSel.end) { - e.dataTransfer.dropEffect = "none"; - return false; + }, + _handleDragLeave: function (e) { + if (!e) { e = window.event; } + this._dropTarget = false; + if (this.isListening("DragLeave")) { + this.onDragLeave(this._createMouseEvent("DragLeave", e)); } - - if (!startSel) { - // Hide the selection when the user drags something coming from the outside. - // TODO: make sure the cursor is actually visible. It's not visible in Firefox during drag, only in Chrome... - this.setSelection(destOffset, destOffset, true); - } - - return true; }, _handleDrop: function (e) { if (!e) { e = window.event; } + this._dropTarget = false; + if (this.isListening("Drop")) { + this.onDrop(this._createMouseEvent("Drop", e)); + } + /* + * This event must be prevented otherwise the user agent will modify + * the DOM. Note that preventing the event on some user agents (i.e. IE) + * indicates that the operation is cancelled. This causes the dropEffect to + * be set to none in the dragend event causing the implementor to not execute + * the code responsible by the move effect. + */ if (e.preventDefault) { e.preventDefault(); } - var allowed = false; - var types = isDnD ? e.dataTransfer.types : null; - if (types) { - allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain"); - } - if (!allowed) { - return false; - } - - var destLine = this._getYToLine(e.clientY); - var destOffset = this._getXToOffset(destLine, e.clientX); - var startSel = this._dragStartSelection; - - if (startSel && startSel.start <= destOffset && destOffset <= startSel.end) { - return false; - } - - var text = e.dataTransfer.getData("text/plain"); - this.setSelection(destOffset, destOffset, true); - - if (startSel) { - this._dropDestination = {offset: destOffset, length: text.length}; - if (this._undoStack) { - this._undoStack.startCompoundChange(); - } - } else { - this._dragNode.draggable = false; - } - - this._doContent(text); - this.focus(); - return true; + return false; }, _handleDocFocus: function (e) { if (!e) { e = window.event; } @@ -3642,6 +4604,9 @@ orion.textview.TextView = (function() { this._selDiv3.style.background = color; } } + if (!this._ignoreFocus) { + this.onFocus({type: "Focus"}); + } }, _handleKeyDown: function (e) { if (!e) { e = window.event; } @@ -3662,7 +4627,7 @@ orion.textview.TextView = (function() { this._setLinksVisible(false); } if (e.keyCode === 229) { - if (this.readonly) { + if (this._readonly) { if (e.preventDefault) { e.preventDefault(); } return false; } @@ -3775,21 +4740,26 @@ orion.textview.TextView = (function() { _handleLoad: function (e) { var state = this._getVisible(); if (state === "visible" || (state === "hidden" && isWebkit)) { - this._createView(!e); + this._createView(); } }, _handleMouse: function (e) { + var result = true; var target = this._frameWindow; - if (isIE) { target = this._clientDiv; } + if (isIE || (isFirefox && !this._overlayDiv)) { target = this._clientDiv; } if (this._overlayDiv) { + if (this._hasFocus) { + this._ignoreFocus = true; + } var self = this; setTimeout(function () { self.focus(); + self._ignoreFocus = false; }, 0); } if (this._clickCount === 1) { - this._setGrab(target); - this._setSelectionTo(e.clientX, e.clientY, e.shiftKey); + result = this._setSelectionTo(e.clientX, e.clientY, e.shiftKey, !isOpera && this.isListening("DragStart")); + if (result) { this._setGrab(target); } } else { /* * Feature in IE8 and older, the sequence of events in the IE8 event model @@ -3810,9 +4780,13 @@ orion.textview.TextView = (function() { this._setSelectionTo(e.clientX, e.clientY, e.shiftKey); this._doubleClickSelection = this._getSelection(); } + return result; }, _handleMouseDown: function (e) { if (!e) { e = window.event; } + if (this.isListening("MouseDown")) { + this.onMouseDown(this._createMouseEvent("MouseDown", e)); + } if (this._linksVisible) { var target = e.target || e.srcElement; if (target.tagName !== "A") { @@ -3821,59 +4795,82 @@ orion.textview.TextView = (function() { return; } } - var left = e.which ? e.button === 0 : e.button === 1; this._commitIME(); - if (left) { - var deltaX = Math.abs(this._lastMouseX - e.clientX); - var deltaY = Math.abs(this._lastMouseY - e.clientY); - var time = e.timeStamp ? e.timeStamp : new Date().getTime(); - if ((time - this._lastMouseTime) <= this._clickTime && deltaX <= this._clickDist && deltaY <= this._clickDist) { + + var button = e.which; // 1 - left, 2 - middle, 3 - right + if (!button) { + // if IE 8 or older + if (e.button === 4) { button = 2; } + if (e.button === 2) { button = 3; } + if (e.button === 1) { button = 1; } + } + + // For middle click we always need getTime(). See _getClipboardText(). + var time = button !== 2 && e.timeStamp ? e.timeStamp : new Date().getTime(); + var timeDiff = time - this._lastMouseTime; + var deltaX = Math.abs(this._lastMouseX - e.clientX); + var deltaY = Math.abs(this._lastMouseY - e.clientY); + var sameButton = this._lastMouseButton === button; + this._lastMouseX = e.clientX; + this._lastMouseY = e.clientY; + this._lastMouseTime = time; + this._lastMouseButton = button; + + if (button === 1) { + this._isMouseDown = true; + if (sameButton && timeDiff <= this._clickTime && deltaX <= this._clickDist && deltaY <= this._clickDist) { this._clickCount++; } else { this._clickCount = 1; } - this._lastMouseX = e.clientX; - this._lastMouseY = e.clientY; - this._lastMouseTime = time; - - // Selection drag support - if (isDnD && this._clickCount === 1) { - var inSelection = false; - var selection = this._getSelection(); - if (!selection.isEmpty()) { - var clickLine = this._getYToLine(e.clientY); - var clickOffset = this._getXToOffset(clickLine, e.clientX); - inSelection = selection.start < clickOffset && clickOffset < selection.end; - } - - // Webkit fails to allow dragging if .draggable is set to true during mousedown. - // But Firefox makes it a requirement to set .draggable to true. - this._dragNode.draggable = !isWebkit && inSelection; - - if (inSelection) { - return; // allow the dragstart event - } - } - if (this._dragNode && this._dragNode.draggable) { - this._dragNode.draggable = false; - } - - this._isMouseDown = true; - this._handleMouse(e); - if (isOpera || isChrome) { + if (this._handleMouse(e) && (isOpera || isChrome || (isFirefox && !this._overlayDiv))) { if (!this._hasFocus) { this.focus(); } - } - if (e.preventDefault) { e.preventDefault(); } } }, + _handleMouseOver: function (e) { + if (!e) { e = window.event; } + if (this.isListening("MouseOver")) { + this.onMouseOver(this._createMouseEvent("MouseOver", e)); + } + }, + _handleMouseOut: function (e) { + if (!e) { e = window.event; } + if (this.isListening("MouseOut")) { + this.onMouseOut(this._createMouseEvent("MouseOut", e)); + } + }, _handleMouseMove: function (e) { if (!e) { e = window.event; } - this._setLinksVisible(!this._isMouseDown && (isMac ? e.metaKey : e.ctrlKey)); - if (!this._isMouseDown) { + if (this.isListening("MouseMove")) { + var topNode = this._overlayDiv || this._clientDiv; + var temp = e.target ? e.target : e.srcElement; + while (temp) { + if (topNode === temp) { + this.onMouseMove(this._createMouseEvent("MouseMove", e)); + break; + } + temp = temp.parentNode; + } + } + if (this._dropTarget) { + return; + } + /* + * Bug in IE9. IE sends one mouse event when the user changes the text by + * pasting or undo. These operations usually happen with the Ctrl key + * down which causes the view to enter link mode. Link mode does not end + * because there are no further events. The fix is to only enter link + * mode when the coordinates of the mouse move event have changed. + */ + var changed = this._linksVisible || this._lastMouseMoveX !== e.clientX || this._lastMouseMoveY !== e.clientY; + this._lastMouseMoveX = e.clientX; + this._lastMouseMoveY = e.clientY; + this._setLinksVisible(changed && !this._isMouseDown && (isMac ? e.metaKey : e.ctrlKey)); + if (!this._isMouseDown || this._dragOffset !== -1) { return; } /* @@ -3946,21 +4943,36 @@ orion.textview.TextView = (function() { } } }, + _createMouseEvent: function(type, e) { + var scroll = this._getScroll(); + var viewRect = this._viewDiv.getBoundingClientRect(); + var viewPad = this._getViewPadding(); + var x = e.clientX + scroll.x - viewRect.left - viewPad.left; + var y = e.clientY + scroll.y - viewRect.top; + return { + type: type, + event: e, + x: x, + y: y + }; + }, _handleMouseUp: function (e) { if (!e) { e = window.event; } + if (this.isListening("MouseUp")) { + this.onMouseUp(this._createMouseEvent("MouseUp", e)); + } if (this._linksVisible) { return; } var left = e.which ? e.button === 0 : e.button === 1; if (left) { - if (this._dragNode && this._dragNode.draggable) { - this._dragNode.draggable = false; - if (!this._dragStartSelection) { - this._setSelectionTo(e.clientX, e.clientY, false); - } - this.focus(); + if (this._dragOffset !== -1) { + var selection = this._getSelection(); + selection.extend(this._dragOffset); + selection.collapse(); + this._setSelection(selection, true, true); + this._dragOffset = -1; } - this._isMouseDown = false; this._endAutoScroll(); @@ -3978,6 +4990,17 @@ orion.textview.TextView = (function() { * mouse down and ungrab on mouse move when the button 1 is not set. */ if (isW3CEvents) { this._setGrab(null); } + + /* + * Note that there cases when Firefox sets the DOM selection in mouse up. + * This happens for example after a cancelled drag operation. + * + * Note that on Chrome and IE, the caret stops blicking if mouse up is + * prevented. + */ + if (isFirefox) { + e.preventDefault(); + } } }, _handleMouseWheel: function (e) { @@ -4062,7 +5085,11 @@ orion.textview.TextView = (function() { * Bug in IE, */ var self = this; - setTimeout(function() {self._updateDOMSelection();}, 0); + this._ignoreFocus = true; + setTimeout(function() { + self._updateDOMSelection(); + this._ignoreFocus = false; + }, 0); } if (e.preventDefault) { e.preventDefault(); } return false; @@ -4130,8 +5157,9 @@ orion.textview.TextView = (function() { this._hScroll = scroll.x; this._vScroll = scroll.y; this._commitIME(); - this._updatePage(); + this._updatePage(oldY === scroll.y); var e = { + type: "Scroll", oldValue: {x: oldX, y: oldY}, newValue: scroll }; @@ -4320,7 +5348,7 @@ orion.textview.TextView = (function() { } }, 0); } -// e.preventDefault(); +// e.preventDefault(); }, /************************************ Actions ******************************************/ @@ -4360,12 +5388,22 @@ orion.textview.TextView = (function() { var model = this._model; var caret = selection.getCaret(); var lineIndex = model.getLineAtOffset(caret); - if (caret === model.getLineStart(lineIndex)) { + var lineStart = model.getLineStart(lineIndex); + if (caret === lineStart) { if (lineIndex > 0) { selection.extend(model.getLineEnd(lineIndex - 1)); } } else { - selection.extend(this._getOffset(caret, args.unit, -1)); + var removeTab = false; + if (this._expandTab && args.unit === "character" && (caret - lineStart) % this._tabSize === 0) { + var lineText = model.getText(lineStart, caret); + removeTab = !/[^ ]/.test(lineText); // Only spaces between line start and caret. + } + if (removeTab) { + selection.extend(caret - this._tabSize); + } else { + selection.extend(this._getOffset(caret, args.unit, -1)); + } } } this._modifyContent({text: "", start: selection.start, end: selection.end}, true); @@ -4491,7 +5529,7 @@ orion.textview.TextView = (function() { if (lineIndex + 1 < model.getLineCount()) { var scrollX = this._getScroll().x; var x = this._columnX; - if (x === -1 || args.select || args.wholeLine) { + if (x === -1 || args.wholeLine || (args.select && isIE)) { var offset = args.wholeLine ? model.getLineEnd(lineIndex + 1) : caret; x = this._getOffsetToX(offset) + scrollX; } @@ -4510,7 +5548,7 @@ orion.textview.TextView = (function() { if (lineIndex > 0) { var scrollX = this._getScroll().x; var x = this._columnX; - if (x === -1 || args.select || args.wholeLine) { + if (x === -1 || args.wholeLine || (args.select && isIE)) { var offset = args.wholeLine ? model.getLineStart(lineIndex - 1) : caret; x = this._getOffsetToX(offset) + scrollX; } @@ -4535,7 +5573,7 @@ orion.textview.TextView = (function() { var scrollLines = Math.min(lineCount - caretLine - 1, lines); scrollLines = Math.max(1, scrollLines); var x = this._columnX; - if (x === -1 || args.select) { + if (x === -1 || (args.select && isIE)) { x = this._getOffsetToX(caret) + scroll.x; } selection.extend(this._getXToOffset(caretLine + scrollLines, x - scroll.x)); @@ -4562,7 +5600,7 @@ orion.textview.TextView = (function() { var lines = Math.floor(clientHeight / lineHeight); var scrollLines = Math.max(1, Math.min(caretLine, lines)); var x = this._columnX; - if (x === -1 || args.select) { + if (x === -1 || (args.select && isIE)) { x = this._getOffsetToX(caret) + scroll.x; } selection.extend(this._getXToOffset(caretLine - scrollLines, x - scroll.x)); @@ -4574,11 +5612,19 @@ orion.textview.TextView = (function() { return true; }, _doPaste: function(e) { - var text = this._getClipboardText(e); - if (text) { - this._doContent(text); - } - return text !== null; + var self = this; + var result = this._getClipboardText(e, function(text) { + if (text) { + if (isLinux && self._lastMouseButton === 2) { + var timeDiff = new Date().getTime() - self._lastMouseTime; + if (timeDiff <= self._clickTime) { + self._setSelectionTo(self._lastMouseX, self._lastMouseY); + } + } + self._doContent(text); + } + }); + return result !== null; }, _doScroll: function (args) { var type = args.type; @@ -4616,12 +5662,27 @@ orion.textview.TextView = (function() { return true; }, _doTab: function (args) { - this._doContent("\t"); + var text = "\t"; + if (this._expandTab) { + var model = this._model; + var caret = this._getSelection().getCaret(); + var lineIndex = model.getLineAtOffset(caret); + var lineStart = model.getLineStart(lineIndex); + var spaces = this._tabSize - ((caret - lineStart) % this._tabSize); + text = (new Array(spaces + 1)).join(" "); + } + this._doContent(text); return true; }, /************************************ Internals ******************************************/ - _applyStyle: function(style, node) { + _applyStyle: function(style, node, reset) { + if (reset) { + var attrs = node.attributes; + for (var i= attrs.length; i-->0;) { + node.removeAttributeNode(attrs[i]); + } + } if (!style) { return; } @@ -4710,7 +5771,17 @@ orion.textview.TextView = (function() { if (h4 > h3) { fontStyle = 3; } - this._largestFontStyle = fontStyle; + var style; + if (fontStyle !== 0) { + style = {style: {}}; + if ((fontStyle & 1) !== 0) { + style.style.fontStyle = "italic"; + } + if ((fontStyle & 2) !== 0) { + style.style.fontWeight = "bold"; + } + } + this._largestFontStyle = style; parent.removeChild(line); return lineHeight; }, @@ -4754,6 +5825,40 @@ orion.textview.TextView = (function() { this._setSelection(selection, true); return true; }, + _clone: function (obj) { + /*Note that this code only works because of the limited types used in TextViewOptions */ + if (obj instanceof Array) { + return obj.slice(0); + } + return obj; + }, + _compare: function (s1, s2) { + if (s1 === s2) { return true; } + if (s1 && !s2 || !s1 && s2) { return false; } + if ((s1 && s1.constructor === String) || (s2 && s2.constructor === String)) { return false; } + if (s1 instanceof Array || s2 instanceof Array) { + if (!(s1 instanceof Array && s2 instanceof Array)) { return false; } + if (s1.length !== s2.length) { return false; } + for (var i = 0; i < s1.length; i++) { + if (!this._compare(s1[i], s2[i])) { + return false; + } + } + return true; + } + if (!(s1 instanceof Object) || !(s2 instanceof Object)) { return false; } + var p; + for (p in s1) { + if (s1.hasOwnProperty(p)) { + if (!s2.hasOwnProperty(p)) { return false; } + if (!this._compare(s1[p], s2[p])) {return false; } + } + } + for (p in s2) { + if (!s1.hasOwnProperty(p)) { return false; } + } + return true; + }, _commitIME: function () { if (this._imeOffset === -1) { return; } // make the state of the IME match the state the view expects it be in @@ -4805,7 +5910,7 @@ orion.textview.TextView = (function() { } }, _createActions: function () { - var KeyBinding = orion.textview.KeyBinding; + var KeyBinding = mKeyBinding.KeyBinding; //no duplicate keybindings var bindings = this._keyBindings = []; @@ -4827,20 +5932,27 @@ orion.textview.TextView = (function() { bindings.push({name: "scrollTextEnd", keyBinding: new KeyBinding(35), predefined: true}); bindings.push({name: "textStart", keyBinding: new KeyBinding(38, true), predefined: true}); bindings.push({name: "textEnd", keyBinding: new KeyBinding(40, true), predefined: true}); + bindings.push({name: "scrollPageUp", keyBinding: new KeyBinding(38, null, null, null, true), predefined: true}); + bindings.push({name: "scrollPageDown", keyBinding: new KeyBinding(40, null, null, null, true), predefined: true}); + bindings.push({name: "lineStart", keyBinding: new KeyBinding(37, null, null, null, true), predefined: true}); + bindings.push({name: "lineEnd", keyBinding: new KeyBinding(39, null, null, null, true), predefined: true}); + //TODO These two actions should be changed to paragraph start and paragraph end when word wrap is implemented + bindings.push({name: "lineStart", keyBinding: new KeyBinding(38, null, null, true), predefined: true}); + bindings.push({name: "lineEnd", keyBinding: new KeyBinding(40, null, null, true), predefined: true}); } else { bindings.push({name: "pageUp", keyBinding: new KeyBinding(33), predefined: true}); bindings.push({name: "pageDown", keyBinding: new KeyBinding(34), predefined: true}); bindings.push({name: "lineStart", keyBinding: new KeyBinding(36), predefined: true}); bindings.push({name: "lineEnd", keyBinding: new KeyBinding(35), predefined: true}); - if (isLinux) { - bindings.push({name: "lineStartUp", keyBinding: new KeyBinding(38, true), predefined: true}); - bindings.push({name: "lineEndDown", keyBinding: new KeyBinding(40, true), predefined: true}); - } bindings.push({name: "wordPrevious", keyBinding: new KeyBinding(37, true), predefined: true}); bindings.push({name: "wordNext", keyBinding: new KeyBinding(39, true), predefined: true}); bindings.push({name: "textStart", keyBinding: new KeyBinding(36, true), predefined: true}); bindings.push({name: "textEnd", keyBinding: new KeyBinding(35, true), predefined: true}); } + if (isFirefox && isLinux) { + bindings.push({name: "lineUp", keyBinding: new KeyBinding(38, true), predefined: true}); + bindings.push({name: "lineDown", keyBinding: new KeyBinding(40, true), predefined: true}); + } // Select Cursor Navigation bindings.push({name: "selectLineUp", keyBinding: new KeyBinding(38, null, true), predefined: true}); @@ -4858,6 +5970,11 @@ orion.textview.TextView = (function() { bindings.push({name: "selectTextEnd", keyBinding: new KeyBinding(35, null, true), predefined: true}); bindings.push({name: "selectTextStart", keyBinding: new KeyBinding(38, true, true), predefined: true}); bindings.push({name: "selectTextEnd", keyBinding: new KeyBinding(40, true, true), predefined: true}); + bindings.push({name: "selectLineStart", keyBinding: new KeyBinding(37, null, true, null, true), predefined: true}); + bindings.push({name: "selectLineEnd", keyBinding: new KeyBinding(39, null, true, null, true), predefined: true}); + //TODO These two actions should be changed to select paragraph start and select paragraph end when word wrap is implemented + bindings.push({name: "selectLineStart", keyBinding: new KeyBinding(38, null, true, true), predefined: true}); + bindings.push({name: "selectLineEnd", keyBinding: new KeyBinding(40, null, true, true), predefined: true}); } else { if (isLinux) { bindings.push({name: "selectWholeLineUp", keyBinding: new KeyBinding(38, true, true), predefined: true}); @@ -4936,8 +6053,6 @@ orion.textview.TextView = (function() { {name: "lineDown", defaultHandler: function() {return self._doLineDown({select: false});}}, {name: "lineStart", defaultHandler: function() {return self._doHome({select: false, ctrl:false});}}, {name: "lineEnd", defaultHandler: function() {return self._doEnd({select: false, ctrl:false});}}, - {name: "lineStartUp", defaultHandler: function() {return self._doLineUp({select: false, wholeLine:true});}}, - {name: "lineEndDown", defaultHandler: function() {return self._doLineDown({select: false, wholeLine:true});}}, {name: "charPrevious", defaultHandler: function() {return self._doCursorPrevious({select: false, unit:"character"});}}, {name: "charNext", defaultHandler: function() {return self._doCursorNext({select: false, unit:"character"});}}, {name: "pageUp", defaultHandler: function() {return self._doPageUp({select: false});}}, @@ -4982,49 +6097,20 @@ orion.textview.TextView = (function() { {name: "paste", defaultHandler: function() {return self._doPaste();}} ]; }, - _createLine: function(parent, sibling, document, lineIndex, model) { + _createLine: function(parent, div, document, lineIndex, model) { var lineText = model.getLine(lineIndex); var lineStart = model.getLineStart(lineIndex); - var e = {textView: this, lineIndex: lineIndex, lineText: lineText, lineStart: lineStart}; + var e = {type:"LineStyle", textView: this, lineIndex: lineIndex, lineText: lineText, lineStart: lineStart}; this.onLineStyle(e); - var child = document.createElement("DIV"); - child.lineIndex = lineIndex; - this._applyStyle(e.style, child); - if (lineText.length !== 0) { - var start = 0; - var tabSize = this._customTabSize; - if (tabSize && tabSize !== 8) { - var tabIndex = lineText.indexOf("\t"), ignoreChars = 0; - while (tabIndex !== -1) { - this._createRanges(child, document, e.ranges, start, tabIndex, lineText, lineStart); - var spacesCount = tabSize - ((tabIndex + ignoreChars) % tabSize); - var spaces = "\u00A0"; - for (var i = 1; i < spacesCount; i++) { - spaces += " "; - } - var tabSpan = document.createElement("SPAN"); - tabSpan.appendChild(document.createTextNode(spaces)); - tabSpan.ignoreChars = spacesCount - 1; - ignoreChars += tabSpan.ignoreChars; - if (e.ranges) { - for (var j = 0; j < e.ranges.length; j++) { - var range = e.ranges[j]; - var styleStart = range.start - lineStart; - var styleEnd = range.end - lineStart; - if (styleStart > tabIndex) { break; } - if (styleStart <= tabIndex && tabIndex < styleEnd) { - this._applyStyle(range.style, tabSpan); - break; - } - } - } - child.appendChild(tabSpan); - start = tabIndex + 1; - tabIndex = lineText.indexOf("\t", start); - } - } - this._createRanges(child, document, e.ranges, start, lineText.length, lineText, lineStart); + var lineDiv = div || document.createElement("DIV"); + if (!div || !this._compare(div.viewStyle, e.style)) { + this._applyStyle(e.style, lineDiv, div); + lineDiv.viewStyle = e.style; } + lineDiv.lineIndex = lineIndex; + var ranges = []; + var data = {tabOffset: 0, ranges: ranges}; + this._createRanges(e.ranges, lineText, 0, lineText.length, lineStart, data); /* * A trailing span with a whitespace is added for three different reasons: @@ -5034,14 +6120,6 @@ orion.textview.TextView = (function() { * selection at the end of the line when the line is fully selected. * 3. The height of a div with only an empty span is zero. */ - var span = document.createElement("SPAN"); - span.ignoreChars = 1; - if ((this._largestFontStyle & 1) !== 0) { - span.style.fontStyle = "italic"; - } - if ((this._largestFontStyle & 2) !== 0) { - span.style.fontWeight = "bold"; - } var c = " "; if (!this._fullSelection && isIE < 9) { /* @@ -5061,13 +6139,76 @@ orion.textview.TextView = (function() { */ c = "\u200C"; } - span.appendChild(document.createTextNode(c)); - child.appendChild(span); + ranges.push({text: c, style: this._largestFontStyle, ignoreChars: 1}); - parent.insertBefore(child, sibling); - return child; + var range, span, style, oldSpan, oldStyle, text, oldText, end = 0, oldEnd = 0, next; + var changeCount, changeStart; + if (div) { + var modelChangedEvent = div.modelChangedEvent; + if (modelChangedEvent) { + if (modelChangedEvent.removedLineCount === 0 && modelChangedEvent.addedLineCount === 0) { + changeStart = modelChangedEvent.start - lineStart; + changeCount = modelChangedEvent.addedCharCount - modelChangedEvent.removedCharCount; + } else { + changeStart = -1; + } + delete div.modelChangedEvent; + } + oldSpan = div.firstChild; + } + for (var i = 0; i < ranges.length; i++) { + range = ranges[i]; + text = range.text; + end += text.length; + style = range.style; + if (oldSpan) { + oldText = oldSpan.firstChild.data; + oldStyle = oldSpan.viewStyle; + if (oldText === text && this._compare(style, oldStyle)) { + oldEnd += oldText.length; + delete oldSpan._rectsCache; + span = oldSpan = oldSpan.nextSibling; + continue; + } else { + while (oldSpan) { + if (changeStart !== -1) { + var spanEnd = end; + if (spanEnd >= changeStart) { + spanEnd -= changeCount; + } + var length = oldSpan.firstChild.data.length; + if (oldEnd + length > spanEnd) { break; } + oldEnd += length; + } + next = oldSpan.nextSibling; + lineDiv.removeChild(oldSpan); + oldSpan = next; + } + } + } + span = this._createSpan(lineDiv, document, text, style, range.ignoreChars); + if (oldSpan) { + lineDiv.insertBefore(span, oldSpan); + } else { + lineDiv.appendChild(span); + } + if (div) { + div.lineWidth = undefined; + } + } + if (div) { + var tmp = span ? span.nextSibling : null; + while (tmp) { + next = tmp.nextSibling; + div.removeChild(tmp); + tmp = next; + } + } else { + parent.appendChild(lineDiv); + } + return lineDiv; }, - _createRanges: function(parent, document, ranges, start, end, text, lineStart) { + _createRanges: function(ranges, text, start, end, lineStart, data) { if (start >= end) { return; } if (ranges) { for (var i = 0; i < ranges.length; i++) { @@ -5080,18 +6221,55 @@ orion.textview.TextView = (function() { styleStart = Math.max(start, styleStart); styleEnd = Math.min(end, styleEnd); if (start < styleStart) { - parent.appendChild(this._createRange(parent, document, text.substring(start, styleStart), null)); + this._createRange(text, start, styleStart, null, data); } - parent.appendChild(this._createRange(parent, document, text.substring(styleStart, styleEnd), range.style)); + while (i + 1 < ranges.length && ranges[i + 1].start - lineStart === styleEnd && this._compare(range.style, ranges[i + 1].style)) { + range = ranges[i + 1]; + styleEnd = Math.min(lineStart + end, range.end) - lineStart; + i++; + } + this._createRange(text, styleStart, styleEnd, range.style, data); start = styleEnd; } } } if (start < end) { - parent.appendChild(this._createRange(parent, document, text.substring(start, end), null)); + this._createRange(text, start, end, null, data); } }, - _createRange: function(parent, document, text, style) { + _createRange: function(text, start, end, style, data) { + if (start >= end) { return; } + var tabSize = this._customTabSize, range; + if (tabSize && tabSize !== 8) { + var tabIndex = text.indexOf("\t", start); + while (tabIndex !== -1 && tabIndex < end) { + if (start < tabIndex) { + range = {text: text.substring(start, tabIndex), style: style}; + data.ranges.push(range); + data.tabOffset += range.text.length; + } + var spacesCount = tabSize - (data.tabOffset % tabSize); + if (spacesCount > 0) { + //TODO hack to preserve text length in getDOMText() + var spaces = "\u00A0"; + for (var i = 1; i < spacesCount; i++) { + spaces += " "; + } + range = {text: spaces, style: style, ignoreChars: spacesCount - 1}; + data.ranges.push(range); + data.tabOffset += range.text.length; + } + start = tabIndex + 1; + tabIndex = text.indexOf("\t", start); + } + } + if (start < end) { + range = {text: text.substring(start, end), style: style}; + data.ranges.push(range); + data.tabOffset += range.text.length; + } + }, + _createSpan: function(parent, document, text, style, ignoreChars) { var isLink = style && style.tagName === "A"; if (isLink) { parent.hasLink = true; } var tagName = isLink && this._linksVisible ? "A" : "SPAN"; @@ -5103,6 +6281,9 @@ orion.textview.TextView = (function() { addHandler(child, "click", function(e) { return self._handleLinkClick(e); }, false); } child.viewStyle = style; + if (ignoreChars) { + child.ignoreChars = ignoreChars; + } return child; }, _createRuler: function(ruler) { @@ -5172,7 +6353,10 @@ orion.textview.TextView = (function() { * parent is not connected to the document. Only create it when the load * event is trigged. */ - addHandler(frame, "load", this._loadHandler = function(e) { self._handleLoad(e); }); + this._loadHandler = function(e) { + self._handleLoad(e); + }; + addHandler(frame, "load", this._loadHandler, !!isFirefox); if (!isWebkit) { /* * Feature in IE and Firefox. It is not possible to get the style of an @@ -5188,9 +6372,11 @@ orion.textview.TextView = (function() { } parent.appendChild(frame); /* create synchronously if possible */ - this._handleLoad(); + if (this._sync) { + this._handleLoad(); + } }, - _getFrameHTML: function(sync) { + _getFrameHTML: function() { var html = []; html.push(""); html.push(""); @@ -5206,9 +6392,8 @@ orion.textview.TextView = (function() { if (this._stylesheet) { var stylesheet = typeof(this._stylesheet) === "string" ? [this._stylesheet] : this._stylesheet; for (var i = 0; i < stylesheet.length; i++) { - if (sync) { + if (this._sync) { try { - //Force CSS to be loaded synchronously so lineHeight can be calculated var objXml = new XMLHttpRequest(); if (objXml.overrideMimeType) { objXml.overrideMimeType("text/css"); @@ -5226,29 +6411,64 @@ orion.textview.TextView = (function() { html.push("'>"); } } + /* + * Feature in WebKit. In WebKit, window load will not wait for the style sheets + * to be loaded unless there is script element after the style sheet link elements. + */ + html.push(""); html.push(""); html.push(""); html.push(""); return html.join(""); }, - _createView: function(sync) { + _createView: function() { + if (this._frameDocument) { return; } + var frameWindow = this._frameWindow = this._frame.contentWindow; + var frameDocument = this._frameDocument = frameWindow.document; + var self = this; + function write() { + frameDocument.open(); + frameDocument.write(self._getFrameHTML()); + frameDocument.close(); + self._windowLoadHandler = function(e) { + self._createContent(); + }; + addHandler(frameWindow, "load", self._windowLoadHandler); + } + /* + * Bug in Firefox. Firefox does not send window load event if document.write + * is done inside of the frame load event handler. + */ + if (isFirefox && !this._sync) { + setTimeout(write, 0); + } else { + write(); + } + if (this._sync) { + this._createContent(); + } + }, + _createContent: function() { if (this._clientDiv) { return; } - if (this._ignoreCreate) { return; } - this._ignoreCreate = true; - - var frame = this._frame; var parent = this._parent; var parentDocument = this._parentDocument; - var frameWindow = frame.contentWindow; - this._frameWindow = frameWindow; - var frameDocument = frameWindow.document; - this._frameDocument = frameDocument; - frameDocument.open(); - frameDocument.write(this._getFrameHTML(true)); - frameDocument.close(); - + var frameDocument = this._frameDocument; + /* + * Bug in Safari. Safari sends the window load event before the + * style sheets are loaded. The fix is to defer creation of the + * contents until the document readyState changes to complete. + */ + var self = this; + if (!this._sync && frameDocument.readyState !== "complete") { + setTimeout(function() { + self._createContent(); + }, 10); + return; + } var body = frameDocument.body; - body.className = "viewContainer"; + this._setThemeClass(this._themeClass, true); body.style.margin = "0px"; body.style.borderWidth = "0px"; body.style.padding = "0px"; @@ -5316,93 +6536,47 @@ orion.textview.TextView = (function() { scrollDiv.style.borderWidth = "0px"; scrollDiv.style.padding = "0px"; viewDiv.appendChild(scrollDiv); - - if (isPad || (this._fullSelection && !isWebkit)) { - this._hightlightRGB = "Highlight"; - var selDiv1 = frameDocument.createElement("DIV"); - this._selDiv1 = selDiv1; - selDiv1.id = "selDiv1"; - selDiv1.style.position = "fixed"; - selDiv1.style.borderWidth = "0px"; - selDiv1.style.margin = "0px"; - selDiv1.style.padding = "0px"; - selDiv1.style.MozOutline = "none"; - selDiv1.style.outline = "none"; - selDiv1.style.background = this._hightlightRGB; - selDiv1.style.width="0px"; - selDiv1.style.height="0px"; - scrollDiv.appendChild(selDiv1); - var selDiv2 = frameDocument.createElement("DIV"); - this._selDiv2 = selDiv2; - selDiv2.id = "selDiv2"; - selDiv2.style.position = "fixed"; - selDiv2.style.borderWidth = "0px"; - selDiv2.style.margin = "0px"; - selDiv2.style.padding = "0px"; - selDiv2.style.MozOutline = "none"; - selDiv2.style.outline = "none"; - selDiv2.style.background = this._hightlightRGB; - selDiv2.style.width="0px"; - selDiv2.style.height="0px"; - scrollDiv.appendChild(selDiv2); - var selDiv3 = frameDocument.createElement("DIV"); - this._selDiv3 = selDiv3; - selDiv3.id = "selDiv3"; - selDiv3.style.position = "fixed"; - selDiv3.style.borderWidth = "0px"; - selDiv3.style.margin = "0px"; - selDiv3.style.padding = "0px"; - selDiv3.style.MozOutline = "none"; - selDiv3.style.outline = "none"; - selDiv3.style.background = this._hightlightRGB; - selDiv3.style.width="0px"; - selDiv3.style.height="0px"; - scrollDiv.appendChild(selDiv3); + + if (isFirefox) { + var clipDiv = frameDocument.createElement("DIV"); + this._clipDiv = clipDiv; + clipDiv.id = "clipDiv"; + clipDiv.style.position = "fixed"; + clipDiv.style.overflow = "hidden"; + clipDiv.style.margin = "0px"; + clipDiv.style.borderWidth = "0px"; + clipDiv.style.padding = "0px"; + scrollDiv.appendChild(clipDiv); - /* - * Bug in Firefox. The Highlight color is mapped to list selection - * background instead of the text selection background. The fix - * is to map known colors using a table or fallback to light blue. - */ - if (isFirefox && isMac) { - var style = this._frameWindow.getComputedStyle(selDiv3, null); - var rgb = style.getPropertyValue("background-color"); - switch (rgb) { - case "rgb(119, 141, 168)": rgb = "rgb(199, 208, 218)"; break; - case "rgb(127, 127, 127)": rgb = "rgb(198, 198, 198)"; break; - case "rgb(255, 193, 31)": rgb = "rgb(250, 236, 115)"; break; - case "rgb(243, 70, 72)": rgb = "rgb(255, 176, 139)"; break; - case "rgb(255, 138, 34)": rgb = "rgb(255, 209, 129)"; break; - case "rgb(102, 197, 71)": rgb = "rgb(194, 249, 144)"; break; - case "rgb(140, 78, 184)": rgb = "rgb(232, 184, 255)"; break; - default: rgb = "rgb(180, 213, 255)"; break; - } - this._hightlightRGB = rgb; - selDiv1.style.background = rgb; - selDiv2.style.background = rgb; - selDiv3.style.background = rgb; - var styleSheet = frameDocument.styleSheets[0]; - styleSheet.insertRule("::-moz-selection {background: " + rgb + "; }", 0); - } + var clipScrollDiv = frameDocument.createElement("DIV"); + this._clipScrollDiv = clipScrollDiv; + clipScrollDiv.id = "clipScrollDiv"; + clipScrollDiv.style.position = "absolute"; + clipScrollDiv.style.height = "1px"; + clipScrollDiv.style.top = "-1000px"; + clipDiv.appendChild(clipScrollDiv); } + + this._setFullSelection(this._fullSelection, true); var clientDiv = frameDocument.createElement("DIV"); clientDiv.className = "viewContent"; this._clientDiv = clientDiv; clientDiv.id = "clientDiv"; clientDiv.style.whiteSpace = "pre"; - clientDiv.style.position = "fixed"; + clientDiv.style.position = this._clipDiv ? "absolute" : "fixed"; clientDiv.style.borderWidth = "0px"; clientDiv.style.margin = "0px"; clientDiv.style.padding = "0px"; clientDiv.style.MozOutline = "none"; clientDiv.style.outline = "none"; + clientDiv.style.zIndex = "1"; if (isPad) { clientDiv.style.WebkitTapHighlightColor = "transparent"; } - scrollDiv.appendChild(clientDiv); + (this._clipDiv || scrollDiv).appendChild(clientDiv); - if (isFirefox) { + if (isFirefox && !clientDiv.setCapture) { var overlayDiv = frameDocument.createElement("DIV"); this._overlayDiv = overlayDiv; overlayDiv.id = "overlayDiv"; @@ -5411,54 +6585,50 @@ orion.textview.TextView = (function() { overlayDiv.style.margin = clientDiv.style.margin; overlayDiv.style.padding = clientDiv.style.padding; overlayDiv.style.cursor = "text"; - overlayDiv.style.zIndex = "1"; - scrollDiv.appendChild(overlayDiv); + overlayDiv.style.zIndex = "2"; + (this._clipDiv || scrollDiv).appendChild(overlayDiv); } if (!isPad) { clientDiv.contentEditable = "true"; } - if (isDnD) { - this._dragNode = this._overlayDiv || this._clientDiv; - } this._lineHeight = this._calculateLineHeight(); this._viewPadding = this._calculatePadding(); if (isIE) { body.style.lineHeight = this._lineHeight + "px"; } - if (this._tabSize) { - if (isOpera) { - clientDiv.style.OTabSize = this._tabSize+""; - } else if (isFirefox >= 4) { - clientDiv.style.MozTabSize = this._tabSize+""; - } else if (this._tabSize !== 8) { - this._customTabSize = this._tabSize; - } - } + this._setTabSize(this._tabSize, true); this._hookEvents(); var rulers = this._rulers; for (var i=0; i 0 || v > 0) { - var self = this; - setTimeout(function() { - self._scrollView(h, v); - }, 0); - } + this._updatePage(); + var h = this._hScroll, v = this._vScroll; + this._vScroll = this._hScroll = 0; + if (h > 0 || v > 0) { + viewDiv.scrollLeft = h; + viewDiv.scrollTop = v; } - this._ignoreCreate = false; + this.onLoad({type: "Load"}); + }, + _defaultOptions: function() { + return { + parent: {value: undefined, recreate: true, update: null}, + model: {value: undefined, recreate: false, update: this.setModel}, + readonly: {value: false, recreate: false, update: null}, + fullSelection: {value: true, recreate: false, update: this._setFullSelection}, + tabSize: {value: 8, recreate: false, update: this._setTabSize}, + expandTab: {value: false, recreate: false, update: null}, + stylesheet: {value: [], recreate: true, update: null}, + themeClass: {value: undefined, recreate: false, update: this._setThemeClass}, + sync: {value: false, recreate: false, update: null} + }; }, _destroyFrame: function() { var frame = this._frame; if (!frame) { return; } if (this._loadHandler) { - removeHandler(frame, "load", this._loadHandler); + removeHandler(frame, "load", this._loadHandler, !!isFirefox); this._loadHandler = null; } if (this._attrModifiedHandler) { @@ -5467,8 +6637,6 @@ orion.textview.TextView = (function() { } frame.parentNode.removeChild(frame); this._frame = null; - this._frameDocument = null; - this._frameWindow = null; }, _destroyRuler: function(ruler) { var side = ruler.getLocation(); @@ -5490,6 +6658,10 @@ orion.textview.TextView = (function() { if (!clientDiv) { return; } this._setGrab(null); this._unhookEvents(); + if (this._windowLoadHandler) { + removeHandler(this._frameWindow, "load", this._windowLoadHandler); + this._windowLoadHandler = null; + } /* Destroy timers */ if (this._autoScrollTimerID) { @@ -5511,15 +6683,20 @@ orion.textview.TextView = (function() { this._selDiv1 = null; this._selDiv2 = null; this._selDiv3 = null; + this._insertedSelRule = false; this._textArea = null; this._clipboardDiv = null; this._scrollDiv = null; this._viewDiv = null; + this._clipDiv = null; + this._clipScrollDiv = null; this._clientDiv = null; this._overlayDiv = null; - this._dragNode = null; this._leftDiv = null; this._rightDiv = null; + this._frameDocument = null; + this._frameWindow = null; + this.onUnload({type: "Unload"}); }, _doAutoScroll: function (direction, x, y) { this._autoScrollDir = direction; @@ -5534,6 +6711,18 @@ orion.textview.TextView = (function() { this._autoScrollDir = undefined; this._autoScrollTimerID = undefined; }, + _fixCaret: function() { + var clientDiv = this._clientDiv; + if (clientDiv) { + var hasFocus = this._hasFocus; + this._ignoreFocus = true; + if (hasFocus) { clientDiv.blur(); } + clientDiv.contentEditable = false; + clientDiv.contentEditable = true; + if (hasFocus) { clientDiv.focus(); } + this._ignoreFocus = false; + } + }, _getBaseText: function(start, end) { var model = this._model; /* This is the only case the view access the base model, alternatively the view could use a event to application to customize the text */ @@ -5646,7 +6835,7 @@ orion.textview.TextView = (function() { var viewPad = this._getViewPadding(); return Math.max(0, this._viewDiv.clientWidth - viewPad.left - viewPad.right); }, - _getClipboardText: function (event) { + _getClipboardText: function (event, handler) { var delimiter = this._model.getLineDelimiter(); var clipboadText, text; if (this._frameWindow.clipboardData) { @@ -5654,51 +6843,65 @@ orion.textview.TextView = (function() { clipboadText = []; text = this._frameWindow.clipboardData.getData("Text"); this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);}); - return clipboadText.join(""); + text = clipboadText.join(""); + if (handler) { handler(text); } + return text; } if (isFirefox) { + this._ignoreFocus = true; var document = this._frameDocument; var clipboardDiv = this._clipboardDiv; clipboardDiv.innerHTML = "

";
 				clipboardDiv.firstChild.focus();
 				var self = this;
 				var _getText = function() {
-					var text = self._getTextFromElement(clipboardDiv);
+					var noteText = self._getTextFromElement(clipboardDiv);
 					clipboardDiv.innerHTML = "";
 					clipboadText = [];
-					self._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
+					self._convertDelimiter(noteText, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
 					return clipboadText.join("");
 				};
 				
 				/* Try execCommand first. Works on firefox with clipboard permission. */
 				var result = false;
 				this._ignorePaste = true;
-				try {
-					result = document.execCommand("paste", false, null);
-				} catch (ex) {
-					// Firefox can throw even when execCommand() works, see bug 362835
-					result = clipboardDiv.childNodes.length > 1 || clipboardDiv.firstChild && clipboardDiv.firstChild.childNodes.length > 0;
+
+				/* Do not try execCommand if middle-click is used, because if we do, we get the clipboard text, not the primary selection text. */
+				if (!isLinux || this._lastMouseButton !== 2) {
+					try {
+						result = document.execCommand("paste", false, null);
+					} catch (ex) {
+						/* Firefox can throw even when execCommand() works, see bug 362835. */
+						result = clipboardDiv.childNodes.length > 1 || clipboardDiv.firstChild && clipboardDiv.firstChild.childNodes.length > 0;
+					}
 				}
 				this._ignorePaste = false;
 				if (!result) {
-					/*
-					* Try native paste in DOM, works for firefox during the paste event.
-					*/
+					/* Try native paste in DOM, works for firefox during the paste event. */
 					if (event) {
 						setTimeout(function() {
 							self.focus();
-							var text = _getText();
-							if (text) { self._doContent(text); }
+							text = _getText();
+							if (text && handler) {
+								handler(text);
+							}
+							self._ignoreFocus = false;
 						}, 0);
 						return null;
 					} else {
 						/* no event and no clipboard permission, paste can't be performed */
 						this.focus();
+						this._ignoreFocus = false;
 						return "";
 					}
 				}
 				this.focus();
-				return _getText();
+				this._ignoreFocus = false;
+				text = _getText();
+				if (text && handler) {
+					handler(text);
+				}
+				return text;
 			}
 			//webkit
 			if (event && event.clipboardData) {
@@ -5709,7 +6912,11 @@ orion.textview.TextView = (function() {
 				clipboadText = [];
 				text = event.clipboardData.getData("text/plain");
 				this._convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
-				return clipboadText.join("");
+				text = clipboadText.join("");
+				if (text && handler) {
+					handler(text);
+				}
+				return text;
 			} else {
 				//TODO try paste using extension (Chrome only)
 			}
@@ -5749,8 +6956,8 @@ orion.textview.TextView = (function() {
 			newRange.selectNode(element);
 
 			var selection = window.getSelection();
-			var oldRanges = [];
-			for (var i = 0; i < selection.rangeCount; i++) {
+			var oldRanges = [], i;
+			for (i = 0; i < selection.rangeCount; i++) {
 				oldRanges.push(selection.getRangeAt(i));
 			}
 
@@ -5761,7 +6968,7 @@ orion.textview.TextView = (function() {
 			var text = selection.toString();
 
 			selection.removeAllRanges();
-			for (var i = 0; i < oldRanges.length; i++) {
+			for (i = 0; i < oldRanges.length; i++) {
 				selection.addRange(oldRanges[i]);
 			}
 
@@ -6217,7 +7424,8 @@ orion.textview.TextView = (function() {
 					self._onModelChanged(modelChangedEvent);
 				}
 			};
-			this._model.addListener(this._modelListener);
+			this._model.addEventListener("Changing", this._modelListener.onChanging);
+			this._model.addEventListener("Changed", this._modelListener.onChanged);
 			
 			var clientDiv = this._clientDiv;
 			var viewDiv = this._viewDiv;
@@ -6242,7 +7450,6 @@ orion.textview.TextView = (function() {
 				handlers.push({target: touchDiv, type: "touchend", handler: function(e) { return self._handleTouchEnd(e); }});
 			} else {
 				var topNode = this._overlayDiv || this._clientDiv;
-				var dragNode = this._dragNode || topNode;
 				var grabNode = isIE ? clientDiv : this._frameWindow;
 				handlers.push({target: clientDiv, type: "keydown", handler: function(e) { return self._handleKeyDown(e);}});
 				handlers.push({target: clientDiv, type: "keypress", handler: function(e) { return self._handleKeyPress(e);}});
@@ -6253,16 +7460,19 @@ orion.textview.TextView = (function() {
 				handlers.push({target: clientDiv, type: "cut", handler: function(e) { return self._handleCut(e);}});
 				handlers.push({target: clientDiv, type: "paste", handler: function(e) { return self._handlePaste(e);}});
 				handlers.push({target: clientDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e);}});
+				handlers.push({target: clientDiv, type: "mouseover", handler: function(e) { return self._handleMouseOver(e);}});
+				handlers.push({target: clientDiv, type: "mouseout", handler: function(e) { return self._handleMouseOut(e);}});
 				handlers.push({target: grabNode, type: "mouseup", handler: function(e) { return self._handleMouseUp(e);}});
 				handlers.push({target: grabNode, type: "mousemove", handler: function(e) { return self._handleMouseMove(e);}});
 				handlers.push({target: body, type: "mousedown", handler: function(e) { return self._handleBodyMouseDown(e);}});
-				handlers.push({target: dragNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e);}});
-				if (isDnD) {
-					handlers.push({target: dragNode, type: "dragend", handler: function(e) { return self._handleDragEnd(e);}});
-					handlers.push({target: dragNode, type: "dragenter", handler: function(e) { return self._handleDragEnter(e);}});
-				}
-				handlers.push({target: dragNode, type: "dragover", handler: function(e) { return self._handleDragOver(e);}});
-				handlers.push({target: dragNode, type: "drop", handler: function(e) { return self._handleDrop(e);}});
+				handlers.push({target: body, type: "mouseup", handler: function(e) { return self._handleBodyMouseUp(e);}});
+				handlers.push({target: topNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e);}});
+				handlers.push({target: topNode, type: "drag", handler: function(e) { return self._handleDrag(e);}});
+				handlers.push({target: topNode, type: "dragend", handler: function(e) { return self._handleDragEnd(e);}});
+				handlers.push({target: topNode, type: "dragenter", handler: function(e) { return self._handleDragEnter(e);}});
+				handlers.push({target: topNode, type: "dragover", handler: function(e) { return self._handleDragOver(e);}});
+				handlers.push({target: topNode, type: "dragleave", handler: function(e) { return self._handleDragLeave(e);}});
+				handlers.push({target: topNode, type: "drop", handler: function(e) { return self._handleDrop(e);}});
 				if (isChrome) {
 					handlers.push({target: this._parentDocument, type: "mousemove", handler: function(e) { return self._handleMouseMove(e);}});
 					handlers.push({target: this._parentDocument, type: "mouseup", handler: function(e) { return self._handleMouseUp(e);}});
@@ -6282,6 +7492,8 @@ orion.textview.TextView = (function() {
 				}
 				if (this._overlayDiv) {
 					handlers.push({target: this._overlayDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e);}});
+					handlers.push({target: this._overlayDiv, type: "mouseover", handler: function(e) { return self._handleMouseOver(e);}});
+					handlers.push({target: this._overlayDiv, type: "mouseout", handler: function(e) { return self._handleMouseOut(e);}});
 					handlers.push({target: this._overlayDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e); }});
 				}
 				if (!isW3CEvents) {
@@ -6299,32 +7511,30 @@ orion.textview.TextView = (function() {
 				parent = window.document.getElementById(parent);
 			}
 			if (!parent) { throw "no parent"; }
-			this._parent = parent;
-			this._model = options.model ? options.model : new orion.textview.TextModel();
-			this.readonly = options.readonly === true;
-			this._fullSelection = options.fullSelection === undefined || options.fullSelection;
-			/* 
-			* Bug in IE 8. For some reason, during scrolling IE does not reflow the elements
-			* that are used to compute the location for the selection divs. This causes the
-			* divs to be placed at the wrong location. The fix is to disabled full selection for IE8.
-			*/
-			if (isIE < 9) {
-				this._fullSelection = false;
+			options.parent = parent;
+			options.model = options.model || new mTextModel.TextModel();
+			var defaultOptions = this._defaultOptions();
+			for (var option in defaultOptions) {
+				if (defaultOptions.hasOwnProperty(option)) {
+					var value;
+					if (options[option] !== undefined) {
+						value = options[option];
+					} else {
+						value = defaultOptions[option].value;
+					}
+					this["_" + option] = value;
+				}
 			}
-			this._stylesheet = options.stylesheet;
-			this._tabSize = options.tabSize;
 			this._rulers = [];
 			this._selection = new Selection (0, 0, false);
 			this._linksVisible = false;
-			this._eventTable = new EventTable();
 			this._redrawCount = 0;
 			this._maxLineWidth = 0;
 			this._maxLineIndex = -1;
 			this._ignoreSelect = true;
+			this._ignoreFocus = false;
 			this._columnX = -1;
-			
-			this._dragStartSelection = null;
-			this._dropDestination = null;
+			this._dragOffset = -1;
 
 			/* Auto scroll */
 			this._autoScrollX = null;
@@ -6357,10 +7567,10 @@ orion.textview.TextView = (function() {
 			this._createFrame();
 		},
 		_modifyContent: function(e, updateCaret) {
-			if (this.readonly && !e._code) {
+			if (this._readonly && !e._code) {
 				return;
 			}
-
+			e.type = "Verify";
 			this.onVerify(e);
 
 			if (e.text === null || e.text === undefined) { return; }
@@ -6378,10 +7588,12 @@ orion.textview.TextView = (function() {
 				selection.setCaret(e.start + e.text.length);
 				this._setSelection(selection, true);
 			}
-			this.onModify({});
+			this.onModify({type: "Modify"});
 		},
 		_onModelChanged: function(modelChangedEvent) {
+			modelChangedEvent.type = "ModelChanged";
 			this.onModelChanged(modelChangedEvent);
+			modelChangedEvent.type = "Changed";
 			var start = modelChangedEvent.start;
 			var addedCharCount = modelChangedEvent.addedCharCount;
 			var removedCharCount = modelChangedEvent.removedCharCount;
@@ -6406,7 +7618,14 @@ orion.textview.TextView = (function() {
 			while (child) {
 				var lineIndex = child.lineIndex;
 				if (startLine <= lineIndex && lineIndex <= startLine + removedLineCount) {
-					child.lineChanged = true;
+					if (startLine === lineIndex && !child.modelChangedEvent && !child.lineRemoved) {
+						child.modelChangedEvent = modelChangedEvent;
+						child.lineChanged = true;
+					} else {
+						child.lineRemoved = true;
+						child.lineChanged = false;
+						child.modelChangedEvent = null;
+					}
 				}
 				if (lineIndex > startLine + removedLineCount) {
 					child.lineIndex = lineIndex + addedLineCount - removedLineCount;
@@ -6421,7 +7640,9 @@ orion.textview.TextView = (function() {
 			this._updatePage();
 		},
 		_onModelChanging: function(modelChangingEvent) {
+			modelChangingEvent.type = "ModelChanging";
 			this.onModelChanging(modelChangingEvent);
+			modelChangingEvent.type = "Changing";
 		},
 		_queueUpdatePage: function() {
 			if (this._updateTimer) { return; }
@@ -6447,7 +7668,7 @@ orion.textview.TextView = (function() {
 			if (clientDiv) {
 				var child = clientDiv.firstChild;
 				while (child) {
-					child.lineChanged = true;
+					child.lineRemoved = true;
 					child = child.nextSibling;
 				}
 				/*
@@ -6456,11 +7677,13 @@ orion.textview.TextView = (function() {
 				* force the clientDiv to loose and receive focus if the it is focused.
 				*/
 				if (isFirefox) {
+					this._ignoreFocus = false;
 					var hasFocus = this._hasFocus;
 					if (hasFocus) { clientDiv.blur(); }
 					clientDiv.contentEditable = false;
 					clientDiv.contentEditable = true;
 					if (hasFocus) { clientDiv.focus(); }
+					this._ignoreFocus = false;
 				}
 			}
 		},
@@ -6505,7 +7728,7 @@ orion.textview.TextView = (function() {
 			/*
 			* Scrolling is done only by setting the scrollLeft and scrollTop fields in the
 			* view div. This causes an updatePage from the scroll event. In some browsers 
-			* this event is asynchromous and forcing update page to run synchronously
+			* this event is asynchronous and forcing update page to run synchronously
 			* leads to redraw problems. 
 			* On Chrome 11, the view redrawing at times when holding PageDown/PageUp key.
 			* On Firefox 4 for Linux, the view redraws the first page when holding 
@@ -6720,6 +7943,12 @@ orion.textview.TextView = (function() {
 					var right = clientRect.right;
 					var top = viewRect.top + viewPad.top;
 					var bottom = clientRect.bottom;
+					var hd = 0, vd = 0;
+					if (this._clipDiv) {
+						var clipRect = this._clipDiv.getBoundingClientRect();
+						hd = clipRect.left - this._clipDiv.scrollLeft;
+						vd = clipRect.top;
+					}
 					var r;
 					var endLineBounds = this._getLineBoundingClientRect(endNode);
 					if (endOffset === 0) {
@@ -6738,8 +7967,8 @@ orion.textview.TextView = (function() {
 					var sel1Top = Math.min(bottom, Math.max(top, startLineBounds.top));
 					var sel1Right = right;
 					var sel1Bottom = Math.min(bottom, Math.max(top, startLineBounds.bottom));
-					sel1Div.style.left = sel1Left + "px";
-					sel1Div.style.top = sel1Top + "px";
+					sel1Div.style.left = (sel1Left - hd) + "px";
+					sel1Div.style.top = (sel1Top - vd) + "px";
 					sel1Div.style.width = Math.max(0, sel1Right - sel1Left) + "px";
 					sel1Div.style.height = Math.max(0, sel1Bottom - sel1Top) + (isPad ? 1 : 0) + "px";
 					if (isPad) {
@@ -6758,8 +7987,8 @@ orion.textview.TextView = (function() {
 						var sel3Right = Math.min(right, Math.max(left, r));
 						var sel3Bottom = Math.min(bottom, Math.max(top, endLineBounds.bottom));
 						var sel3Div = this._selDiv3;
-						sel3Div.style.left = sel3Left + "px";
-						sel3Div.style.top = sel3Top + "px";
+						sel3Div.style.left = (sel3Left - hd) + "px";
+						sel3Div.style.top = (sel3Top - vd) + "px";
 						sel3Div.style.width = Math.max(0, sel3Right - sel3Left - handleWidth) + "px";
 						sel3Div.style.height = Math.max(0, sel3Bottom - sel3Top) + "px";
 						if (isPad) {
@@ -6767,8 +7996,8 @@ orion.textview.TextView = (function() {
 						}
 						if (sel3Top - sel1Bottom > 0) {
 							var sel2Div = this._selDiv2;
-							sel2Div.style.left = left + "px";
-							sel2Div.style.top = sel1Bottom + "px";
+							sel2Div.style.left = (left - hd)  + "px";
+							sel2Div.style.top = (sel1Bottom - vd) + "px";
 							sel2Div.style.width = Math.max(0, right - left) + "px";
 							sel2Div.style.height = Math.max(0, sel3Top - sel1Bottom) + (isPad ? 1 : 0) + "px";
 						}
@@ -6815,7 +8044,7 @@ orion.textview.TextView = (function() {
 						var next = lineChild.nextSibling;
 						var style = lineChild.viewStyle;
 						if (style && style.tagName === "A") {
-							line.replaceChild(this._createRange(line, document, lineChild.firstChild.data, style), lineChild);
+							line.replaceChild(this._createSpan(line, document, lineChild.firstChild.data, style), lineChild);
 						}
 						lineChild = next;
 					}
@@ -6831,6 +8060,7 @@ orion.textview.TextView = (function() {
 				if (!oldSelection.equals(selection)) {
 					this._selection = selection;
 					var e = {
+						type: "Selection",
 						oldValue: {start:oldSelection.start, end:oldSelection.end},
 						newValue: {start:selection.start, end:selection.end}
 					};
@@ -6853,12 +8083,18 @@ orion.textview.TextView = (function() {
 				if (update) { this._updateDOMSelection(); }
 			}
 		},
-		_setSelectionTo: function (x,y,extent) {
+		_setSelectionTo: function (x, y, extent, drag) {
 			var model = this._model, offset;
 			var selection = this._getSelection();
 			var lineIndex = this._getYToLine(y);
 			if (this._clickCount === 1) {
 				offset = this._getXToOffset(lineIndex, x);
+				if (drag && !extent) {
+					if (selection.start <= offset && offset < selection.end) {
+						this._dragOffset = offset;
+						return false;
+					}
+				}
 				selection.extend(offset);
 				if (!extent) { selection.collapse(); }
 			} else {
@@ -6897,6 +8133,155 @@ orion.textview.TextView = (function() {
 				selection.extend(end);
 			} 
 			this._setSelection(selection, true, true);
+			return true;
+		},
+		_setFullSelection: function(fullSelection, init) {
+			this._fullSelection = fullSelection;
+			
+			/* 
+			* Bug in IE 8. For some reason, during scrolling IE does not reflow the elements
+			* that are used to compute the location for the selection divs. This causes the
+			* divs to be placed at the wrong location. The fix is to disabled full selection for IE8.
+			*/
+			if (isIE < 9) {
+				this._fullSelection = false;
+			}
+			if (isWebkit) {
+				this._fullSelection = true;
+			}
+			var parent = this._clipDiv || this._scrollDiv;
+			if (!parent) {
+				return;
+			}
+			if (!isPad && !this._fullSelection) {
+				if (this._selDiv1) {
+					parent.removeChild(this._selDiv1);
+					this._selDiv1 = null;
+				}
+				if (this._selDiv2) {
+					parent.removeChild(this._selDiv2);
+					this._selDiv2 = null;
+				}
+				if (this._selDiv3) {
+					parent.removeChild(this._selDiv3);
+					this._selDiv3 = null;
+				}
+				return;
+			}
+			
+			if (!this._selDiv1 && (isPad || (this._fullSelection && !isWebkit))) {
+				var frameDocument = this._frameDocument;
+				this._hightlightRGB = "Highlight";
+				var selDiv1 = frameDocument.createElement("DIV");
+				this._selDiv1 = selDiv1;
+				selDiv1.id = "selDiv1";
+				selDiv1.style.position = this._clipDiv ? "absolute" : "fixed";
+				selDiv1.style.borderWidth = "0px";
+				selDiv1.style.margin = "0px";
+				selDiv1.style.padding = "0px";
+				selDiv1.style.MozOutline = "none";
+				selDiv1.style.outline = "none";
+				selDiv1.style.background = this._hightlightRGB;
+				selDiv1.style.width = "0px";
+				selDiv1.style.height = "0px";
+				selDiv1.style.zIndex = "0";
+				parent.appendChild(selDiv1);
+				var selDiv2 = frameDocument.createElement("DIV");
+				this._selDiv2 = selDiv2;
+				selDiv2.id = "selDiv2";
+				selDiv2.style.position = this._clipDiv ? "absolute" : "fixed";
+				selDiv2.style.borderWidth = "0px";
+				selDiv2.style.margin = "0px";
+				selDiv2.style.padding = "0px";
+				selDiv2.style.MozOutline = "none";
+				selDiv2.style.outline = "none";
+				selDiv2.style.background = this._hightlightRGB;
+				selDiv2.style.width = "0px";
+				selDiv2.style.height = "0px";
+				selDiv2.style.zIndex = "0";
+				parent.appendChild(selDiv2);
+				var selDiv3 = frameDocument.createElement("DIV");
+				this._selDiv3 = selDiv3;
+				selDiv3.id = "selDiv3";
+				selDiv3.style.position = this._clipDiv ? "absolute" : "fixed";
+				selDiv3.style.borderWidth = "0px";
+				selDiv3.style.margin = "0px";
+				selDiv3.style.padding = "0px";
+				selDiv3.style.MozOutline = "none";
+				selDiv3.style.outline = "none";
+				selDiv3.style.background = this._hightlightRGB;
+				selDiv3.style.width = "0px";
+				selDiv3.style.height = "0px";
+				selDiv3.style.zIndex = "0";
+				parent.appendChild(selDiv3);
+				
+				/*
+				* Bug in Firefox. The Highlight color is mapped to list selection
+				* background instead of the text selection background.  The fix
+				* is to map known colors using a table or fallback to light blue.
+				*/
+				if (isFirefox && isMac) {
+					var style = this._frameWindow.getComputedStyle(selDiv3, null);
+					var rgb = style.getPropertyValue("background-color");
+					switch (rgb) {
+						case "rgb(119, 141, 168)": rgb = "rgb(199, 208, 218)"; break;
+						case "rgb(127, 127, 127)": rgb = "rgb(198, 198, 198)"; break;
+						case "rgb(255, 193, 31)": rgb = "rgb(250, 236, 115)"; break;
+						case "rgb(243, 70, 72)": rgb = "rgb(255, 176, 139)"; break;
+						case "rgb(255, 138, 34)": rgb = "rgb(255, 209, 129)"; break;
+						case "rgb(102, 197, 71)": rgb = "rgb(194, 249, 144)"; break;
+						case "rgb(140, 78, 184)": rgb = "rgb(232, 184, 255)"; break;
+						default: rgb = "rgb(180, 213, 255)"; break;
+					}
+					this._hightlightRGB = rgb;
+					selDiv1.style.background = rgb;
+					selDiv2.style.background = rgb;
+					selDiv3.style.background = rgb;
+					if (!this._insertedSelRule) {
+						var styleSheet = frameDocument.styleSheets[0];
+						styleSheet.insertRule("::-moz-selection {background: " + rgb + "; }", 0);
+						this._insertedSelRule = true;
+					}
+				}
+				if (!init) {
+					this._updateDOMSelection();
+				}
+			}
+		},
+		_setTabSize: function (tabSize, init) {
+			this._tabSize = tabSize;
+			this._customTabSize = undefined;
+			var clientDiv = this._clientDiv;
+			if (isOpera) {
+				if (clientDiv) { clientDiv.style.OTabSize = this._tabSize+""; }
+			} else if (isFirefox >= 4) {
+				if (clientDiv) {  clientDiv.style.MozTabSize = this._tabSize+""; }
+			} else if (this._tabSize !== 8) {
+				this._customTabSize = this._tabSize;
+				if (!init) {
+					this.redrawLines();
+				}
+			}
+		},
+		_setThemeClass: function (themeClass, init) {
+			this._themeClass = themeClass;
+			var document = this._frameDocument;
+			if (document) {
+				var viewContainerClass = "viewContainer";
+				if (this._themeClass) { viewContainerClass += " " + this._themeClass; }
+				document.body.className = viewContainerClass;
+				if (!init) {
+					if (isIE) {
+						document.body.style.lineHeight = "normal";
+					}
+					this._lineHeight = this._calculateLineHeight();
+					this._viewPadding = this._calculatePadding();
+					if (isIE) {
+						document.body.style.lineHeight = this._lineHeight + "px";
+					}
+					this.redraw();
+				}
+			}
 		},
 		_showCaret: function (allSelection, pageScroll) {
 			if (!this._clientDiv) { return; }
@@ -6997,7 +8382,8 @@ orion.textview.TextView = (function() {
 			this._imeOffset = selection.start;
 		},
 		_unhookEvents: function() {
-			this._model.removeListener(this._modelListener);
+			this._model.removeEventListener("Changing", this._modelListener.onChanging);
+			this._model.removeEventListener("Changed", this._modelListener.onChanged);
 			this._modelListener = null;
 			for (var i=0; i 0) { return; }
-			if (this._updateTimer) { 
+			if (this._updateTimer) {
 				clearTimeout(this._updateTimer);
 				this._updateTimer = null;
+				hScrollOnly = false;
 			}
-			var document = this._frameDocument;
-			var viewDiv = this._viewDiv;
 			var clientDiv = this._clientDiv;
 			if (!clientDiv) { return; }
-			var frameWidth = this._getFrameWidth();
-			var frameHeight = this._getFrameHeight();
-			document.body.style.width = frameWidth + "px";
-			document.body.style.height = frameHeight + "px";
-			var viewPad = this._getViewPadding();
-			
-			/* Update view height in order to have client height computed */
-			viewDiv.style.height = Math.max(0, (frameHeight - viewPad.top - viewPad.bottom)) + "px";
-			
 			var model = this._model;
+			var scroll = this._getScroll();
+			var viewPad = this._getViewPadding();
+			var lineCount = model.getLineCount();
 			var lineHeight = this._getLineHeight();
-			var scrollY = this._getScroll().y;
-			var firstLine = Math.max(0, scrollY) / lineHeight;
+			var firstLine = Math.max(0, scroll.y) / lineHeight;
 			var topIndex = Math.floor(firstLine);
 			var lineStart = Math.max(0, topIndex - 1);
 			var top = Math.round((firstLine - lineStart) * lineHeight);
-			var lineCount = model.getLineCount();
-			var clientHeight = this._getClientHeight();
-			var partialY = Math.round((firstLine - topIndex) * lineHeight);
-			var linesPerPage = Math.floor((clientHeight + partialY) / lineHeight);
-			var bottomIndex = Math.min(topIndex + linesPerPage, lineCount - 1);
-			var lineEnd = Math.min(bottomIndex + 1, lineCount - 1);
-			this._partialY = partialY;
-			
-			var lineIndex, lineWidth;
-			var child = clientDiv.firstChild;
-			while (child) {
-				lineIndex = child.lineIndex;
-				var nextChild = child.nextSibling;
-				if (!(lineStart <= lineIndex && lineIndex <= lineEnd) || child.lineChanged || child.lineIndex === -1) {
-					if (this._mouseWheelLine === child) {
-						child.style.display = "none";
-						child.lineIndex = -1;
+			var partialY = this._partialY = Math.round((firstLine - topIndex) * lineHeight);
+			var scrollWidth, scrollHeight = lineCount * lineHeight;
+			var leftWidth, clientWidth, clientHeight;
+			if (hScrollOnly) {
+				clientWidth = this._getClientWidth();
+				clientHeight = this._getClientHeight();
+				leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
+				scrollWidth = Math.max(this._maxLineWidth, clientWidth);
+			} else {
+				var document = this._frameDocument;
+				var frameWidth = this._getFrameWidth();
+				var frameHeight = this._getFrameHeight();
+				document.body.style.width = frameWidth + "px";
+				document.body.style.height = frameHeight + "px";
+
+				/* Update view height in order to have client height computed */
+				var viewDiv = this._viewDiv;
+				viewDiv.style.height = Math.max(0, (frameHeight - viewPad.top - viewPad.bottom)) + "px";
+				clientHeight = this._getClientHeight();
+				var linesPerPage = Math.floor((clientHeight + partialY) / lineHeight);
+				var bottomIndex = Math.min(topIndex + linesPerPage, lineCount - 1);
+				var lineEnd = Math.min(bottomIndex + 1, lineCount - 1);
+				
+				var lineIndex, lineWidth;
+				var child = clientDiv.firstChild;
+				while (child) {
+					lineIndex = child.lineIndex;
+					var nextChild = child.nextSibling;
+					if (!(lineStart <= lineIndex && lineIndex <= lineEnd) || child.lineRemoved || child.lineIndex === -1) {
+						if (this._mouseWheelLine === child) {
+							child.style.display = "none";
+							child.lineIndex = -1;
+						} else {
+							clientDiv.removeChild(child);
+						}
+					}
+					child = nextChild;
+				}
+	
+				child = this._getLineNext();
+				var frag = document.createDocumentFragment();
+				for (lineIndex=lineStart; lineIndex<=lineEnd; lineIndex++) {
+					if (!child || child.lineIndex > lineIndex) {
+						this._createLine(frag, null, document, lineIndex, model);
 					} else {
-						clientDiv.removeChild(child);
+						if (frag.firstChild) {
+							clientDiv.insertBefore(frag, child);
+							frag = document.createDocumentFragment();
+						}
+						if (child && child.lineChanged) {
+							child = this._createLine(frag, child, document, lineIndex, model);
+							child.lineChanged = false;
+						}
+						child = this._getLineNext(child);
 					}
 				}
-				child = nextChild;
-			}
-
-			child = this._getLineNext();
-			var frag = document.createDocumentFragment();
-			for (lineIndex=lineStart; lineIndex<=lineEnd; lineIndex++) {
-				if (!child || child.lineIndex > lineIndex) {
-					this._createLine(frag, null, document, lineIndex, model);
-				} else {
-					if (frag.firstChild) {
-						clientDiv.insertBefore(frag, child);
-						frag = document.createDocumentFragment();
+				if (frag.firstChild) { clientDiv.insertBefore(frag, child); }
+	
+				/*
+				* Feature in WekKit. Webkit limits the width of the lines
+				* computed below to the width of the client div.  This causes
+				* the lines to be wrapped even though "pre" is set.  The fix
+				* is to set the width of the client div to a larger number
+				* before computing the lines width.  Note that this value is
+				* reset to the appropriate value further down.
+				*/ 
+				if (isWebkit) {
+					clientDiv.style.width = (0x7FFFF).toString() + "px";
+				}
+	
+				var rect;
+				child = this._getLineNext();
+				while (child) {
+					lineWidth = child.lineWidth;
+					if (lineWidth === undefined) {
+						rect = this._getLineBoundingClientRect(child);
+						lineWidth = child.lineWidth = rect.right - rect.left;
 					}
-					child = this._getLineNext(child);
-				}
-			}
-			if (frag.firstChild) { clientDiv.insertBefore(frag, child); }
-
-			/*
-			* Feature in WekKit. Webkit limits the width of the lines
-			* computed below to the width of the client div.  This causes
-			* the lines to be wrapped even though "pre" is set.  The fix
-			* is to set the width of the client div to a larger number
-			* before computing the lines width.  Note that this value is
-			* reset to the appropriate value further down.
-			*/ 
-			if (isWebkit) {
-				clientDiv.style.width = (0x7FFFF).toString() + "px";
-			}
-
-			var rect;
-			child = this._getLineNext();
-			while (child) {
-				lineWidth = child.lineWidth;
-				if (lineWidth === undefined) {
-					rect = this._getLineBoundingClientRect(child);
-					lineWidth = child.lineWidth = rect.right - rect.left;
-				}
-				if (lineWidth >= this._maxLineWidth) {
-					this._maxLineWidth = lineWidth;
-					this._maxLineIndex = child.lineIndex;
-				}
-				if (child.lineIndex === topIndex) { this._topChild = child; }
-				if (child.lineIndex === bottomIndex) { this._bottomChild = child; }
-				if (this._checkMaxLineIndex === child.lineIndex) { this._checkMaxLineIndex = -1; }
-				child = this._getLineNext(child);
-			}
-			if (this._checkMaxLineIndex !== -1) {
-				lineIndex = this._checkMaxLineIndex;
-				this._checkMaxLineIndex = -1;
-				if (0 <= lineIndex && lineIndex < lineCount) {
-					var dummy = this._createLine(clientDiv, null, document, lineIndex, model);
-					rect = this._getLineBoundingClientRect(dummy);
-					lineWidth = rect.right - rect.left;
 					if (lineWidth >= this._maxLineWidth) {
 						this._maxLineWidth = lineWidth;
-						this._maxLineIndex = lineIndex;
+						this._maxLineIndex = child.lineIndex;
 					}
-					clientDiv.removeChild(dummy);
+					if (child.lineIndex === topIndex) { this._topChild = child; }
+					if (child.lineIndex === bottomIndex) { this._bottomChild = child; }
+					if (this._checkMaxLineIndex === child.lineIndex) { this._checkMaxLineIndex = -1; }
+					child = this._getLineNext(child);
 				}
-			}
-
-			// Update rulers
-			this._updateRuler(this._leftDiv, topIndex, bottomIndex);
-			this._updateRuler(this._rightDiv, topIndex, bottomIndex);
-			
-			var leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
-			var rightWidth = this._rightDiv ? this._rightDiv.scrollWidth : 0;
-			viewDiv.style.left = leftWidth + "px";
-			viewDiv.style.width = Math.max(0, frameWidth - leftWidth - rightWidth - viewPad.left - viewPad.right) + "px";
-			if (this._rightDiv) {
-				this._rightDiv.style.left = (frameWidth - rightWidth) + "px"; 
-			}
-			
-			var scrollDiv = this._scrollDiv;
-			/* Need to set the height first in order for the width to consider the vertical scrollbar */
-			var scrollHeight = lineCount * lineHeight;
-			scrollDiv.style.height = scrollHeight + "px";
-			// TODO if frameHeightWithoutHScrollbar < scrollHeight  < frameHeightWithHScrollbar and the horizontal bar is visible, 
-			// then the clientWidth is wrong because the vertical scrollbar is showing. To correct code should hide both scrollbars 
-			// at this point.
-			var clientWidth = this._getClientWidth();
-			var width = Math.max(this._maxLineWidth, clientWidth);
-			/*
-			* Except by IE 8 and earlier, all other browsers are not allocating enough space for the right padding 
-			* in the scrollbar. It is possible this a bug since all other paddings are considered.
-			*/
-			var scrollWidth = width;
-			if (!isIE || isIE >= 9) { width += viewPad.right; }
-			scrollDiv.style.width = width + "px";
-
-			// Get the left scroll after setting the width of the scrollDiv as this can change the horizontal scroll offset.
-			var scroll = this._getScroll();
-			var left = scroll.x;
-			var clipLeft = left;
-			var clipTop = top;
-			var clipRight = left + clientWidth;
-			var clipBottom = top + clientHeight;
-			if (clipLeft === 0) { clipLeft -= viewPad.left; }
-			if (clipTop === 0) { clipTop -= viewPad.top; }
-			if (clipRight === scrollWidth) { clipRight += viewPad.right; }
-			if (scroll.y + clientHeight === scrollHeight) { clipBottom += viewPad.bottom; }
-			clientDiv.style.clip = "rect(" + clipTop + "px," + clipRight + "px," + clipBottom + "px," + clipLeft + "px)";
-			clientDiv.style.left = (-left + leftWidth + viewPad.left) + "px";
-			clientDiv.style.top = (-top + viewPad.top) + "px";
-			clientDiv.style.width = (isWebkit ? scrollWidth : clientWidth + left) + "px";
-			clientDiv.style.height = (clientHeight + top) + "px";
-			var overlayDiv = this._overlayDiv;
-			if (overlayDiv) {
-				overlayDiv.style.clip = clientDiv.style.clip;
-				overlayDiv.style.left = clientDiv.style.left;
-				overlayDiv.style.top = clientDiv.style.top;
-				overlayDiv.style.width = clientDiv.style.width;
-				overlayDiv.style.height = clientDiv.style.height;
-			}
-			function _updateRulerSize(divRuler) {
-				if (!divRuler) { return; }
+				if (this._checkMaxLineIndex !== -1) {
+					lineIndex = this._checkMaxLineIndex;
+					this._checkMaxLineIndex = -1;
+					if (0 <= lineIndex && lineIndex < lineCount) {
+						var dummy = this._createLine(clientDiv, null, document, lineIndex, model);
+						rect = this._getLineBoundingClientRect(dummy);
+						lineWidth = rect.right - rect.left;
+						if (lineWidth >= this._maxLineWidth) {
+							this._maxLineWidth = lineWidth;
+							this._maxLineIndex = lineIndex;
+						}
+						clientDiv.removeChild(dummy);
+					}
+				}
+	
+				// Update rulers
+				this._updateRuler(this._leftDiv, topIndex, bottomIndex);
+				this._updateRuler(this._rightDiv, topIndex, bottomIndex);
+				
+				leftWidth = this._leftDiv ? this._leftDiv.scrollWidth : 0;
+				var rightWidth = this._rightDiv ? this._rightDiv.scrollWidth : 0;
+				viewDiv.style.left = leftWidth + "px";
+				viewDiv.style.width = Math.max(0, frameWidth - leftWidth - rightWidth - viewPad.left - viewPad.right) + "px";
+				if (this._rightDiv) {
+					this._rightDiv.style.left = (frameWidth - rightWidth) + "px"; 
+				}
+				/* Need to set the height first in order for the width to consider the vertical scrollbar */
+				var scrollDiv = this._scrollDiv;
+				scrollDiv.style.height = scrollHeight + "px";
+				/*
+				* TODO if frameHeightWithoutHScrollbar < scrollHeight  < frameHeightWithHScrollbar and the horizontal bar is visible, 
+				* then the clientWidth is wrong because the vertical scrollbar is showing. To correct code should hide both scrollbars 
+				* at this point.
+				*/
+				clientWidth = this._getClientWidth();
+				var width = Math.max(this._maxLineWidth, clientWidth);
+				/*
+				* Except by IE 8 and earlier, all other browsers are not allocating enough space for the right padding 
+				* in the scrollbar. It is possible this a bug since all other paddings are considered.
+				*/
+				scrollWidth = width;
+				if (!isIE || isIE >= 9) { width += viewPad.right; }
+				scrollDiv.style.width = width + "px";
+				if (this._clipScrollDiv) {
+					this._clipScrollDiv.style.width = width + "px";
+				}
+				/* Get the left scroll after setting the width of the scrollDiv as this can change the horizontal scroll offset. */
+				scroll = this._getScroll();
 				var rulerHeight = clientHeight + viewPad.top + viewPad.bottom;
-				var cells = divRuler.firstChild.rows[0].cells;
-				for (var i = 0; i < cells.length; i++) {
-					var div = cells[i].firstChild;
-					var offset = lineHeight;
-					if (div._ruler.getOverview() === "page") { offset += partialY; }
-					div.style.top = -offset + "px";
-					div.style.height = (rulerHeight + offset) + "px";
-					div = div.nextSibling;
-				}
-				divRuler.style.height = rulerHeight + "px";
+				this._updateRulerSize(this._leftDiv, rulerHeight);
+				this._updateRulerSize(this._rightDiv, rulerHeight);
 			}
-			_updateRulerSize(this._leftDiv);
-			_updateRulerSize(this._rightDiv);
-			if (isPad) {
-				var self = this;
-				setTimeout(function() {self._resizeTouchDiv();}, 0);
+			var left = scroll.x;	
+			var clipDiv = this._clipDiv;
+			var overlayDiv = this._overlayDiv;
+			var clipLeft, clipTop;
+			if (clipDiv) {
+				clipDiv.scrollLeft = left;			
+				clipLeft = leftWidth + viewPad.left;
+				clipTop = viewPad.top;
+				var clipWidth = clientWidth;
+				var clipHeight = clientHeight;
+				var clientLeft = 0, clientTop = -top;
+				if (scroll.x === 0) {
+					clipLeft -= viewPad.left;
+					clipWidth += viewPad.left;
+					clientLeft = viewPad.left;
+				} 
+				if (scroll.x + clientWidth === scrollWidth) {
+					clipWidth += viewPad.right;
+				}
+				if (scroll.y === 0) {
+					clipTop -= viewPad.top;
+					clipHeight += viewPad.top;
+					clientTop += viewPad.top;
+				}
+				if (scroll.y + clientHeight === scrollHeight) { 
+					clipHeight += viewPad.bottom; 
+				}
+				clipDiv.style.left = clipLeft + "px";
+				clipDiv.style.top = clipTop + "px";
+				clipDiv.style.width = clipWidth + "px";
+				clipDiv.style.height = clipHeight + "px";
+				clientDiv.style.left = clientLeft + "px";
+				clientDiv.style.top = clientTop + "px";
+				clientDiv.style.width = scrollWidth + "px";
+				clientDiv.style.height = (clientHeight + top) + "px";
+				if (overlayDiv) {
+					overlayDiv.style.left = clientDiv.style.left;
+					overlayDiv.style.top = clientDiv.style.top;
+					overlayDiv.style.width = clientDiv.style.width;
+					overlayDiv.style.height = clientDiv.style.height;
+				}
+			} else {
+				clipLeft = left;
+				clipTop = top;
+				var clipRight = left + clientWidth;
+				var clipBottom = top + clientHeight;
+				if (clipLeft === 0) { clipLeft -= viewPad.left; }
+				if (clipTop === 0) { clipTop -= viewPad.top; }
+				if (clipRight === scrollWidth) { clipRight += viewPad.right; }
+				if (scroll.y + clientHeight === scrollHeight) { clipBottom += viewPad.bottom; }
+				clientDiv.style.clip = "rect(" + clipTop + "px," + clipRight + "px," + clipBottom + "px," + clipLeft + "px)";
+				clientDiv.style.left = (-left + leftWidth + viewPad.left) + "px";
+				clientDiv.style.width = (isWebkit ? scrollWidth : clientWidth + left) + "px";
+				if (!hScrollOnly) {
+					clientDiv.style.top = (-top + viewPad.top) + "px";
+					clientDiv.style.height = (clientHeight + top) + "px";
+				}
+				if (overlayDiv) {
+					overlayDiv.style.clip = clientDiv.style.clip;
+					overlayDiv.style.left = clientDiv.style.left;
+					overlayDiv.style.width = clientDiv.style.width;
+					if (!hScrollOnly) {
+						overlayDiv.style.top = clientDiv.style.top;
+						overlayDiv.style.height = clientDiv.style.height;
+					}
+				}
 			}
 			this._updateDOMSelection();
 
@@ -7243,6 +8671,25 @@ orion.textview.TextView = (function() {
 					this._showCaret();
 				}
 			}
+			if (isPad) {
+				var self = this;
+				setTimeout(function() {self._resizeTouchDiv();}, 0);
+			}
+		},
+		_updateRulerSize: function (divRuler, rulerHeight) {
+			if (!divRuler) { return; }
+			var partialY = this._partialY;
+			var lineHeight = this._getLineHeight();
+			var cells = divRuler.firstChild.rows[0].cells;
+			for (var i = 0; i < cells.length; i++) {
+				var div = cells[i].firstChild;
+				var offset = lineHeight;
+				if (div._ruler.getOverview() === "page") { offset += partialY; }
+				div.style.top = -offset + "px";
+				div.style.height = (rulerHeight + offset) + "px";
+				div = div.nextSibling;
+			}
+			divRuler.style.height = rulerHeight + "px";
 		},
 		_updateRuler: function (divRuler, topIndex, bottomIndex) {
 			if (!divRuler) { return; }
@@ -7371,16 +8818,151 @@ orion.textview.TextView = (function() {
 			}
 		}
 	};//end prototype
+	mEventTarget.EventTarget.addMixin(TextView.prototype);
 	
-	return TextView;
-}());
+	return {TextView: TextView};
+}, "orion/textview");
 
-if (typeof window !== "undefined" && typeof window.define !== "undefined") {
-	define(['orion/textview/textModel', 'orion/textview/keyBinding'], function() {
-		return orion.textview;
-	});
-}
-/******************************************************************************* 
+/*******************************************************************************
+ * @license
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials are made 
+ * available under the terms of the Eclipse Public License v1.0 
+ * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
+ * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
+ * 
+ * Contributors: 
+ *		Felipe Heidrich (IBM Corporation) - initial API and implementation
+ *		Silenio Quarti (IBM Corporation) - initial API and implementation
+ ******************************************************************************/
+ 
+/*global define */
+
+define([], function() {
+
+	function TextDND(view, undoStack) {
+		this._view = view;
+		this._undoStack = undoStack;
+		this._dragSelection = null;
+		this._dropOffset = -1;
+		this._dropText = null;
+		var self = this;
+		this._listener = {
+			onDragStart: function (evt) {
+				self._onDragStart(evt);
+			},
+			onDragEnd: function (evt) {
+				self._onDragEnd(evt);
+			},
+			onDragEnter: function (evt) {
+				self._onDragEnter(evt);
+			},
+			onDragOver: function (evt) {
+				self._onDragOver(evt);
+			},
+			onDrop: function (evt) {
+				self._onDrop(evt);
+			},
+			onDestroy: function (evt) {
+				self._onDestroy(evt);
+			}
+		};
+		view.addEventListener("DragStart", this._listener.onDragStart);
+		view.addEventListener("DragEnd", this._listener.onDragEnd);
+		view.addEventListener("DragEnter", this._listener.onDragEnter);
+		view.addEventListener("DragOver", this._listener.onDragOver);
+		view.addEventListener("Drop", this._listener.onDrop);
+		view.addEventListener("Destroy", this._listener.onDestroy);
+	}
+	TextDND.prototype = {
+		destroy: function() {
+			var view = this._view;
+			if (!view) { return; }
+			view.removeEventListener("DragStart", this._listener.onDragStart);
+			view.removeEventListener("DragEnd", this._listener.onDragEnd);
+			view.removeEventListener("DragEnter", this._listener.onDragEnter);
+			view.removeEventListener("DragOver", this._listener.onDragOver);
+			view.removeEventListener("Drop", this._listener.onDrop);
+			view.removeEventListener("Destroy", this._listener.onDestroy);
+			this._view = null;
+		},
+		_onDestroy: function(e) {
+			this.destroy();
+		},
+		_onDragStart: function(e) {
+			var view = this._view;
+			var selection = view.getSelection();
+			var model = view.getModel();
+			if (model.getBaseModel) {
+				selection.start = model.mapOffset(selection.start);
+				selection.end = model.mapOffset(selection.end);
+				model = model.getBaseModel();
+			}
+			var text = model.getText(selection.start, selection.end);
+			if (text) {
+				this._dragSelection = selection;
+				e.event.dataTransfer.effectAllowed = "copyMove";
+				e.event.dataTransfer.setData("Text", text);
+			}
+		},
+		_onDragEnd: function(e) {
+			var view = this._view;
+			if (this._dragSelection) {
+				if (this._undoStack) { this._undoStack.startCompoundChange(); }
+				var move = e.event.dataTransfer.dropEffect === "move";
+				if (move) {
+					view.setText("", this._dragSelection.start, this._dragSelection.end);
+				}
+				if (this._dropText) {
+					var text = this._dropText;
+					var offset = this._dropOffset;
+					if (move) {
+						if (offset >= this._dragSelection.end) {
+							offset -= this._dragSelection.end - this._dragSelection.start;
+						} else if (offset >= this._dragSelection.start) {
+							offset = this._dragSelection.start;
+						}
+					}
+					view.setText(text, offset, offset);
+					view.setSelection(offset, offset + text.length);
+					this._dropText = null;
+					this._dropOffset = -1;
+				}
+				if (this._undoStack) { this._undoStack.endCompoundChange(); }
+			}
+			this._dragSelection = null;
+		},
+		_onDragEnter: function(e) {
+			this._onDragOver(e);
+		},
+		_onDragOver: function(e) {
+			var types = e.event.dataTransfer.types;
+			if (types) {
+				var allowed = types.contains ? types.contains("text/plain") : types.indexOf("text/plain") !== -1;
+				if (!allowed) {
+					e.event.dataTransfer.dropEffect = "none";
+				}
+			}
+		},
+		_onDrop: function(e) {
+			var view = this._view;
+			var text = e.event.dataTransfer.getData("Text");
+			if (text) {
+				var offset = view.getOffsetAtLocation(e.x, e.y);
+				if (this._dragSelection) {
+					this._dropOffset = offset;
+					this._dropText = text;
+				} else {
+					view.setText(text, offset, offset);
+					view.setSelection(offset, offset + text.length);
+				}
+			}
+		}
+	};
+
+	return {TextDND: TextDND};
+}, "orion/textview");/******************************************************************************* 
+ * @license
  * Copyright (c) 2011 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
@@ -7391,104 +8973,100 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") {
  ******************************************************************************/
 
 /*jslint */
-/*global define orion:true window */
+/*global define */
 
-var orion = orion || {};
+define([], function() {
 
-orion.editor = orion.editor || {};
-
-/**
- * Provides a grammar that can do some very rough syntax highlighting for HTML.
- * @class orion.syntax.HtmlGrammar
- */
-orion.editor.HtmlGrammar = (function() {
-	return {
-		/**
-		 * What kind of highlight provider we are.
-		 * @public
-		 * @type String
-		 */
-		type: "grammar",
-		
-		/**
-		 * The file extensions that we provide rules for.
-		 * @public
-		 * @type String[]
-		 */
-		fileTypes: [ "html", "htm" ],
-		
-		/**
-		 * Object containing the grammar rules.
-		 * @public
-		 * @type Object
-		 */
-		grammar: {
-			"name": "HTML",
-			"scopeName": "source.html",
-			"uuid": "3B5C76FB-EBB5-D930-F40C-047D082CE99B",
-			"patterns": [
-				// TODO unicode?
-				{
-					"match": "]+>",
-					"name": "entity.name.tag.doctype.html"
-				},
-				{
-					"begin": "",
-					"beginCaptures": {
-						"0": { "name": "punctuation.definition.comment.html" }
+	/**
+	 * Provides a grammar that can do some very rough syntax highlighting for HTML.
+	 * @class orion.syntax.HtmlGrammar
+	 */
+	function HtmlGrammar() {
+		return {
+			/**
+			 * What kind of highlight provider we are.
+			 * @public
+			 * @type String
+			 */
+			type: "grammar",
+			
+			/**
+			 * The file extensions that we provide rules for.
+			 * @public
+			 * @type String[]
+			 */
+			fileTypes: [ "html", "htm" ],
+			
+			/**
+			 * Object containing the grammar rules.
+			 * @public
+			 * @type Object
+			 */
+			grammar: {
+				"name": "HTML",
+				"scopeName": "source.html",
+				"uuid": "3B5C76FB-EBB5-D930-F40C-047D082CE99B",
+				"patterns": [
+					// TODO unicode?
+					{
+						"match": "]+>",
+						"name": "entity.name.tag.doctype.html"
 					},
-					"endCaptures": {
-						"0": { "name": "punctuation.definition.comment.html" }
+					{
+						"begin": "",
+						"beginCaptures": {
+							"0": { "name": "punctuation.definition.comment.html" }
+						},
+						"endCaptures": {
+							"0": { "name": "punctuation.definition.comment.html" }
+						},
+						"patterns": [
+							{
+								"match": "--",
+								"name": "invalid.illegal.badcomment.html"
+							}
+						],
+						"contentName": "comment.block.html"
 					},
-					"patterns": [
-						{
-							"match": "--",
-							"name": "invalid.illegal.badcomment.html"
-						}
-					],
-					"contentName": "comment.block.html"
-				},
-				{ // startDelimiter + tagName
-					"match": "<[A-Za-z0-9_\\-:]+(?= ?)",
-					"name": "entity.name.tag.html"
-				},
-				{ "include": "#attrName" },
-				{ "include": "#qString" },
-				{ "include": "#qqString" },
-				// TODO attrName, qString, qqString should be applied first while inside a tag
-				{ // startDelimiter + slash + tagName + endDelimiter
-					"match": "",
-					"name": "entity.name.tag.html"
-				},
-				{ // end delimiter of open tag
-					"match": ">", 
-					"name": "entity.name.tag.html"
-				} ],
-			"repository": {
-				"attrName": { // attribute name
-					"match": "[A-Za-z\\-:]+(?=\\s*=\\s*['\"])",
-					"name": "entity.other.attribute.name.html"
-				},
-				"qqString": { // double quoted string
-					"match": "(\")[^\"]+(\")",
-					"name": "string.quoted.double.html"
-				},
-				"qString": { // single quoted string
-					"match": "(')[^']+(\')",
-					"name": "string.quoted.single.html"
+					{ // startDelimiter + tagName
+						"match": "<[A-Za-z0-9_\\-:]+(?= ?)",
+						"name": "entity.name.tag.html"
+					},
+					{ "include": "#attrName" },
+					{ "include": "#qString" },
+					{ "include": "#qqString" },
+					// TODO attrName, qString, qqString should be applied first while inside a tag
+					{ // startDelimiter + slash + tagName + endDelimiter
+						"match": "",
+						"name": "entity.name.tag.html"
+					},
+					{ // end delimiter of open tag
+						"match": ">", 
+						"name": "entity.name.tag.html"
+					} ],
+				"repository": {
+					"attrName": { // attribute name
+						"match": "[A-Za-z\\-:]+(?=\\s*=\\s*['\"])",
+						"name": "entity.other.attribute.name.html"
+					},
+					"qqString": { // double quoted string
+						"match": "(\")[^\"]+(\")",
+						"name": "string.quoted.double.html"
+					},
+					"qString": { // single quoted string
+						"match": "(')[^']+(\')",
+						"name": "string.quoted.single.html"
+					}
 				}
 			}
-		}
-	};
-}());
+		};
+	}
 
-if (typeof window !== "undefined" && typeof window.define !== "undefined") {
-	define([], function() {
-		return orion.editor;
-	});
-}
+	return {HtmlGrammar: HtmlGrammar};
+}, "orion/editor");
 /******************************************************************************* 
+ * @license
  * Copyright (c) 2011 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
@@ -7499,85 +9077,11 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") {
  ******************************************************************************/
 
 /*jslint regexp:false laxbreak:true*/
-/*global define window*/
+/*global define */
 
-var orion = orion || {};
-orion.editor = orion.editor || {};
+define(['orion/editor/regex'], function(mRegex) {
 
-/**
- * @class A styler that does nothing, but can be extended by concrete stylers. To extend, call 
- * {@link orion.editor.AbstractStyler.extend} and provide implementations of one or more of
- * the {@link #_onSelection}, {@link #_onModelChanged}, {@link #_onDestroy} and {@link #_onLineStyle} methods.
- * @name orion.editor.AbstractStyler
- */
-orion.editor.AbstractStyler = (function() {
-	/** @inner */
-	function AbstractStyler() {
-	}
-	AbstractStyler.prototype = /** @lends orion.editor.AbstractStyler.prototype */ {
-		/**
-		 * Initializes this styler with a TextView. If you are extending AbstractStyler,
-		 * you must call this from your subclass's constructor function.
-		 * @param {orion.textview.TextView} textView The TextView to provide styling for.
-		 */
-		initialize: function(textView) {
-			this.textView = textView;
-			
-			textView.addEventListener("Selection", this, this._onSelection);
-			textView.addEventListener("ModelChanged", this, this._onModelChanged);
-			textView.addEventListener("Destroy", this, this._onDestroy);
-			textView.addEventListener("LineStyle", this, this._onLineStyle);
-			textView.redrawLines();
-		},
-		/** Destroys this styler and removes all listeners. Called by the editor. */
-		destroy: function() {
-			if (this.textView) {
-				this.textView.removeEventListener("Selection", this, this._onSelection);
-				this.textView.removeEventListener("ModelChanged", this, this._onModelChanged);
-				this.textView.removeEventListener("Destroy", this, this._onDestroy);
-				this.textView.removeEventListener("LineStyle", this, this._onLineStyle);
-				this.textView = null;
-			}
-		},
-		/** To be overridden by subclass.
-		 * @public
-		 */
-		_onSelection: function(/**eclipse.SelectionEvent*/ selectionEvent) {},
-		/** To be overridden by subclass.
-		 * @public
-		 */
-		_onModelChanged: function(/**eclipse.ModelChangedEvent*/ modelChangedEvent) {},
-		/** To be overridden by subclass.
-		 * @public
-		 */
-		_onDestroy: function(/**eclipse.DestroyEvent*/ destroyEvent) {},
-		/** To be overridden by subclass.
-		 * @public
-		 */
-		_onLineStyle: function(/**eclipse.LineStyleEvent*/ lineStyleEvent) {}
-	};
-	
-	return AbstractStyler;
-}());
-
-/**
- * Helper for extending {@link orion.editor.AbstractStyler}.
- * @methodOf orion.editor.AbstractStyler
- * @static
- * @param {Function} subCtor The constructor function for the subclass.
- * @param {Object} [proto] Object to be mixed into the subclass's prototype. This object should 
- * contain your implementation of _onSelection, _onModelChanged, etc.
- */
-orion.editor.AbstractStyler.extend = function(subCtor, proto) {
-	if (typeof(subCtor) !== "function") { throw new Error("Function expected"); }
-	subCtor.prototype = new orion.editor.AbstractStyler();
-	subCtor.constructor = subCtor;
-	for (var p in proto) {
-		if (proto.hasOwnProperty(p)) { subCtor.prototype[p] = proto[p]; }
-	}
-};
-
-orion.editor.RegexUtil = {
+var RegexUtil = {
 	// Rules to detect some unsupported Oniguruma features
 	unsupported: [
 		{regex: /\(\?[ims\-]:/, func: function(match) { return "option on/off for subexp"; }},
@@ -7639,12 +9143,12 @@ orion.editor.RegexUtil = {
 		var i;
 		
 		// Handle global "x" flag (whitespace/comments)
-		str = orion.editor.RegexUtil.processGlobalFlag("x", str, function(subexp) {
+		str = RegexUtil.processGlobalFlag("x", str, function(subexp) {
 				return normalize(subexp);
 			});
 		
 		// Handle global "i" flag (case-insensitive)
-		str = orion.editor.RegexUtil.processGlobalFlag("i", str, function(subexp) {
+		str = RegexUtil.processGlobalFlag("i", str, function(subexp) {
 				flags += "i";
 				return subexp;
 			});
@@ -7716,7 +9220,7 @@ orion.editor.RegexUtil = {
 			var backrefMatch = /\\(\d+)/.exec(term);
 			if (backrefMatch) {
 				var text = sub[backrefMatch[1]] || "";
-				array.push(escape ? orion.editor.RegexUtil.escapeRegex(text) : text);
+				array.push(escape ? mRegex.escape(text) : text);
 			} else {
 				array.push(term);
 			}
@@ -7724,11 +9228,6 @@ orion.editor.RegexUtil = {
 		return new RegExp(array.join(""));
 	},
 	
-	/** @returns {String} The input string with regex special characters escaped. */
-	escapeRegex: function(/**String*/ str) {
-		return str.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&");
-	},
-	
 	/**
 	 * Builds a version of regex with every non-capturing term converted into a capturing group. This is a workaround
 	 * for JavaScript's lack of API to get the index at which a matched group begins in the input string.

@@ -7904,73 +9403,71 @@ orion.editor.RegexUtil = { } }; -/** - * @name orion.editor.TextMateStyler - * @class A styler that knows how to apply a subset of the TextMate grammar format to style a line. - * - *

Styling from a grammar:

- *

Each scope name given in the grammar is converted to an array of CSS class names. For example - * a region of text with scope keyword.control.php will be assigned the CSS classes
- * keyword, keyword-control, keyword-control-php

- * - *

A CSS file can give rules matching any of these class names to provide generic or more specific styling. - * For example,

- *

.keyword { font-color: blue; }

- *

colors all keywords blue, while

- *

.keyword-control-php { font-weight: bold; }

- *

bolds only PHP control keywords.

- * - *

This is useful when using grammars that adhere to TextMate's - * scope name conventions, - * as a single CSS rule can provide consistent styling to similar constructs across different languages.

- * - *

Top-level grammar constructs:

- *
  • patterns, repository (with limitations, see "Other Features") are supported.
  • - *
  • scopeName, firstLineMatch, foldingStartMarker, foldingStopMarker are not supported.
  • - *
  • fileTypes is not supported. When using the Orion service registry, the "orion.edit.highlighter" - * service serves a similar purpose.
  • - *
- * - *

Regular expression constructs:

- *
    - *
  • match patterns are supported.
  • - *
  • begin .. end patterns are supported.
  • - *
  • The "extended" regex forms (?x) and (?x:...) are supported, but only when they - * apply to the entire regex pattern.
  • - *
  • Matching is done using native JavaScript RegExps. As a result, many features of the Oniguruma regex - * engine used by TextMate are not supported. - * Unsupported features include: - *
    • Named captures
    • - *
    • Setting flags inside subgroups (eg. (?i:a)b)
    • - *
    • Lookbehind and negative lookbehind
    • - *
    • Subexpression call
    • - *
    • etc.
    • - *
    - *
  • - *
- * - *

Scope-assignment constructs:

- *
    - *
  • captures, beginCaptures, endCaptures are supported.
  • - *
  • name and contentName are supported.
  • - *
- * - *

Other features:

- *
    - *
  • applyEndPatternLast is supported.
  • - *
  • include is supported, but only when it references a rule in the current grammar's repository. - * Including $self, $base, or rule.from.another.grammar is not supported.
  • - *
- * - * @description Creates a new TextMateStyler. - * @extends orion.editor.AbstractStyler - * @param {orion.textview.TextView} textView The TextView to provide styling for. - * @param {Object} grammar The TextMate grammar to use for styling the TextView, as a JavaScript object. You can - * produce this object by running a PList-to-JavaScript conversion tool on a TextMate .tmLanguage file. - * @param {Object[]} [externalGrammars] Additional grammar objects that will be used to resolve named rule references. - */ -orion.editor.TextMateStyler = (function() { - /** @inner */ + /** + * @name orion.editor.TextMateStyler + * @class A styler that knows how to apply a subset of the TextMate grammar format to style a line. + * + *

Styling from a grammar:

+ *

Each scope name given in the grammar is converted to an array of CSS class names. For example + * a region of text with scope keyword.control.php will be assigned the CSS classes
+ * keyword, keyword-control, keyword-control-php

+ * + *

A CSS file can give rules matching any of these class names to provide generic or more specific styling. + * For example,

+ *

.keyword { font-color: blue; }

+ *

colors all keywords blue, while

+ *

.keyword-control-php { font-weight: bold; }

+ *

bolds only PHP control keywords.

+ * + *

This is useful when using grammars that adhere to TextMate's + * scope name conventions, + * as a single CSS rule can provide consistent styling to similar constructs across different languages.

+ * + *

Top-level grammar constructs:

+ *
  • patterns, repository (with limitations, see "Other Features") are supported.
  • + *
  • scopeName, firstLineMatch, foldingStartMarker, foldingStopMarker are not supported.
  • + *
  • fileTypes is not supported. When using the Orion service registry, the "orion.edit.highlighter" + * service serves a similar purpose.
  • + *
+ * + *

Regular expression constructs:

+ *
    + *
  • match patterns are supported.
  • + *
  • begin .. end patterns are supported.
  • + *
  • The "extended" regex forms (?x) and (?x:...) are supported, but only when they + * apply to the entire regex pattern.
  • + *
  • Matching is done using native JavaScript RegExps. As a result, many features of the Oniguruma regex + * engine used by TextMate are not supported. + * Unsupported features include: + *
    • Named captures
    • + *
    • Setting flags inside subgroups (eg. (?i:a)b)
    • + *
    • Lookbehind and negative lookbehind
    • + *
    • Subexpression call
    • + *
    • etc.
    • + *
    + *
  • + *
+ * + *

Scope-assignment constructs:

+ *
    + *
  • captures, beginCaptures, endCaptures are supported.
  • + *
  • name and contentName are supported.
  • + *
+ * + *

Other features:

+ *
    + *
  • applyEndPatternLast is supported.
  • + *
  • include is supported, but only when it references a rule in the current grammar's repository. + * Including $self, $base, or rule.from.another.grammar is not supported.
  • + *
+ * + * @description Creates a new TextMateStyler. + * @extends orion.editor.AbstractStyler + * @param {orion.textview.TextView} textView The TextView to provide styling for. + * @param {Object} grammar The TextMate grammar to use for styling the TextView, as a JavaScript object. You can + * produce this object by running a PList-to-JavaScript conversion tool on a TextMate .tmLanguage file. + * @param {Object[]} [externalGrammars] Additional grammar objects that will be used to resolve named rule references. + */ function TextMateStyler(textView, grammar, externalGrammars) { this.initialize(textView); // Copy grammar object(s) since we will mutate them @@ -7982,7 +9479,41 @@ orion.editor.TextMateStyler = (function() { this._allGrammars = {}; /* key: {String} scopeName of grammar, value: {Object} grammar */ this.preprocess(this.grammar); } - orion.editor.AbstractStyler.extend(TextMateStyler, /** @lends orion.editor.TextMateStyler.prototype */ { + TextMateStyler.prototype = /** @lends orion.editor.TextMateStyler.prototype */ { + initialize: function(textView) { + this.textView = textView; + var self = this; + this._listener = { + onModelChanged: function(e) { + self.onModelChanged(e); + }, + onDestroy: function(e) { + self.onDestroy(e); + }, + onLineStyle: function(e) { + self.onLineStyle(e); + } + }; + textView.addEventListener("ModelChanged", this._listener.onModelChanged); + textView.addEventListener("Destroy", this._listener.onDestroy); + textView.addEventListener("LineStyle", this._listener.onLineStyle); + textView.redrawLines(); + }, + onDestroy: function(/**eclipse.DestroyEvent*/ e) { + this.destroy(); + }, + destroy: function() { + if (this.textView) { + this.textView.removeEventListener("ModelChanged", this._listener.onModelChanged); + this.textView.removeEventListener("Destroy", this._listener.onDestroy); + this.textView.removeEventListener("LineStyle", this._listener.onLineStyle); + this.textView = null; + } + this.grammar = null; + this._styles = null; + this._tree = null; + this._listener = null; + }, /** @private */ copy: function(obj) { return JSON.parse(JSON.stringify(obj)); @@ -7995,7 +9526,7 @@ orion.editor.TextMateStyler = (function() { if (rule._resolvedRule && rule._typedRule) { continue; } -// console.debug("Process " + (rule.include || rule.name)); +// console.debug("Process " + (rule.include || rule.name)); // Look up include'd rule, create typed *Rule instance rule._resolvedRule = this._resolve(rule); @@ -8066,23 +9597,23 @@ orion.editor.TextMateStyler = (function() { function BeginEndRule(/**Object*/ rule) { this.rule = rule; // TODO: the TextMate blog claims that "end" is optional. - this.beginRegex = orion.editor.RegexUtil.toRegExp(rule.begin); - this.endRegex = orion.editor.RegexUtil.toRegExp(rule.end); + this.beginRegex = RegexUtil.toRegExp(rule.begin); + this.endRegex = RegexUtil.toRegExp(rule.end); this.subrules = rule.patterns || []; - this.endRegexHasBackRef = orion.editor.RegexUtil.hasBackReference(this.endRegex); + this.endRegexHasBackRef = RegexUtil.hasBackReference(this.endRegex); // Deal with non-0 captures - var complexCaptures = orion.editor.RegexUtil.complexCaptures(rule.captures); - var complexBeginEnd = orion.editor.RegexUtil.complexCaptures(rule.beginCaptures) || orion.editor.RegexUtil.complexCaptures(rule.endCaptures); + var complexCaptures = RegexUtil.complexCaptures(rule.captures); + var complexBeginEnd = RegexUtil.complexCaptures(rule.beginCaptures) || RegexUtil.complexCaptures(rule.endCaptures); this.isComplex = complexCaptures || complexBeginEnd; if (this.isComplex) { - var bg = orion.editor.RegexUtil.groupify(this.beginRegex); + var bg = RegexUtil.groupify(this.beginRegex); this.beginRegex = bg[0]; this.beginOld2New = bg[1]; this.beginConsuming = bg[2]; - var eg = orion.editor.RegexUtil.groupify(this.endRegex, this.beginOld2New /*Update end's backrefs to begin's new group #s*/); + var eg = RegexUtil.groupify(this.endRegex, this.beginOld2New /*Update end's backrefs to begin's new group #s*/); this.endRegex = eg[0]; this.endOld2New = eg[1]; this.endConsuming = eg[2]; @@ -8098,10 +9629,10 @@ orion.editor.TextMateStyler = (function() { MatchRule: (function() { function MatchRule(/**Object*/ rule) { this.rule = rule; - this.matchRegex = orion.editor.RegexUtil.toRegExp(rule.match); - this.isComplex = orion.editor.RegexUtil.complexCaptures(rule.captures); + this.matchRegex = RegexUtil.toRegExp(rule.match); + this.isComplex = RegexUtil.complexCaptures(rule.captures); if (this.isComplex) { - var mg = orion.editor.RegexUtil.groupify(this.matchRegex); + var mg = RegexUtil.groupify(this.matchRegex); this.matchRegex = mg[0]; this.matchOld2New = mg[1]; this.matchConsuming = mg[2]; @@ -8192,7 +9723,7 @@ orion.editor.TextMateStyler = (function() { // Build a new regex if the "end" regex has backrefs since they refer to matched groups of beginMatch if (rule.endRegexHasBackRef) { - this.endRegexSubstituted = orion.editor.RegexUtil.getSubstitutedRegex(rule.endRegex, beginMatch); + this.endRegexSubstituted = RegexUtil.getSubstitutedRegex(rule.endRegex, beginMatch); } else { this.endRegexSubstituted = null; } @@ -8284,7 +9815,7 @@ orion.editor.TextMateStyler = (function() { this._tree = root; this.parse(this._tree, false, 0); }, - _onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) { + onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) { var addedCharCount = e.addedCharCount, addedLineCount = e.addedLineCount, removedCharCount = e.removedCharCount, @@ -8684,7 +10215,7 @@ orion.editor.TextMateStyler = (function() { node.parent.children.length = node.getIndexInParent() + 1; } }, - _onLineStyle: function(/**eclipse.LineStyleEvent*/ e) { + onLineStyle: function(/**eclipse.LineStyleEvent*/ e) { function byStart(r1, r2) { return r1.start - r2.start; } @@ -8702,7 +10233,7 @@ orion.editor.TextMateStyler = (function() { var scopes = this.getLineScope(model, node, lineStart, lineEnd); e.ranges = this.toStyleRanges(scopes); -// // Editor requires StyleRanges must be in ascending order by 'start', or else some will be ignored + // Editor requires StyleRanges must be in ascending order by 'start', or else some will be ignored e.ranges.sort(byStart); }, /** Runs parse algorithm on [start, end] in the context of node, assigning scope as we find matches. @@ -8866,13 +10397,6 @@ orion.editor.TextMateStyler = (function() { } return result.reverse(); }, - _onSelection: function(e) { - }, - _onDestroy: function(/**eclipse.DestroyEvent*/ e) { - this.grammar = null; - this._styles = null; - this._tree = null; - }, /** * Applies the grammar to obtain the {@link eclipse.StyleRange[]} for the given line. * @returns eclipse.StyleRange[] @@ -8890,17 +10414,15 @@ orion.editor.TextMateStyler = (function() { } return styleRanges; } - }); - return TextMateStyler; -}()); - -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define([], function() { - return orion.editor; - }); -} - + }; + + return { + RegexUtil: RegexUtil, + TextMateStyler: TextMateStyler + }; +}, "orion/editor"); /******************************************************************************* + * @license * Copyright (c) 2010, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 @@ -8910,12 +10432,9 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { * Contributors: IBM Corporation - initial API and implementation ******************************************************************************/ -/*global document window navigator define orion */ +/*global document window navigator define */ -var examples = examples || {}; -examples.textview = examples.textview || {}; - -examples.textview.TextStyler = (function() { +define(['orion/textview/annotations'], function(mAnnotations) { var JS_KEYWORDS = ["break", @@ -8988,7 +10507,6 @@ examples.textview.TextStyler = (function() { var TASK_TAG = 12; // Styles - var isIE = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent) ? document.documentMode : undefined; var singleCommentStyle = {styleClass: "token_singleline_comment"}; var multiCommentStyle = {styleClass: "token_multiline_comment"}; var docCommentStyle = {styleClass: "token_doc_comment"}; @@ -8999,88 +10517,87 @@ examples.textview.TextStyler = (function() { var keywordStyle = {styleClass: "token_keyword"}; var spaceStyle = {styleClass: "token_space"}; var tabStyle = {styleClass: "token_tab"}; - var bracketStyle = {styleClass: isIE < 9 ? "token_bracket" : "token_bracket_outline"}; var caretLineStyle = {styleClass: "line_caret"}; - var Scanner = (function() { - function Scanner (keywords, whitespacesVisible) { - this.keywords = keywords; - this.whitespacesVisible = whitespacesVisible; - this.setText(""); - } - - Scanner.prototype = { - getOffset: function() { - return this.offset; - }, - getStartOffset: function() { - return this.startOffset; - }, - getData: function() { - return this.text.substring(this.startOffset, this.offset); - }, - getDataLength: function() { - return this.offset - this.startOffset; - }, - _default: function(c) { - var keywords = this.keywords; - switch (c) { - case 32: // SPACE - case 9: // TAB - if (this.whitespacesVisible) { - return c === 32 ? WHITE_SPACE : WHITE_TAB; - } + function Scanner (keywords, whitespacesVisible) { + this.keywords = keywords; + this.whitespacesVisible = whitespacesVisible; + this.setText(""); + } + + Scanner.prototype = { + getOffset: function() { + return this.offset; + }, + getStartOffset: function() { + return this.startOffset; + }, + getData: function() { + return this.text.substring(this.startOffset, this.offset); + }, + getDataLength: function() { + return this.offset - this.startOffset; + }, + _default: function(c) { + var keywords = this.keywords; + switch (c) { + case 32: // SPACE + case 9: // TAB + if (this.whitespacesVisible) { + return c === 32 ? WHITE_SPACE : WHITE_TAB; + } + do { + c = this._read(); + } while(c === 32 || c === 9); + this._unread(c); + return WHITE; + case 123: // { + case 125: // } + case 40: // ( + case 41: // ) + case 91: // [ + case 93: // ] + case 60: // < + case 62: // > + // BRACKETS + return c; + default: + var isCSS = this.isCSS; + if ((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)) { //LETTER OR UNDERSCORE OR NUMBER + var off = this.offset - 1; do { c = this._read(); - } while(c === 32 || c === 9); + } while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)); //LETTER OR UNDERSCORE OR NUMBER this._unread(c); - return WHITE; - case 123: // { - case 125: // } - case 40: // ( - case 41: // ) - case 91: // [ - case 93: // ] - case 60: // < - case 62: // > - // BRACKETS - return c; - default: - var isCSS = this.isCSS; - if ((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)) { //LETTER OR UNDERSCORE OR NUMBER - var off = this.offset - 1; - do { - c = this._read(); - } while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57) || (0x2d === c && isCSS)); //LETTER OR UNDERSCORE OR NUMBER - this._unread(c); - if (keywords.length > 0) { - var word = this.text.substring(off, this.offset); - //TODO slow - for (var i=0; i 0) { + var word = this.text.substring(off, this.offset); + //TODO slow + for (var i=0; i comment - c = this._read(); + } + return UNKOWN; + } + }, + _read: function() { + if (this.offset < this.text.length) { + return this.text.charCodeAt(this.offset++); + } + return -1; + }, + _unread: function(c) { + if (c !== -1) { this.offset--; } + }, + nextToken: function() { + this.startOffset = this.offset; + while (true) { + var c = this._read(); + switch (c) { + case -1: return null; + case 47: // SLASH -> comment + c = this._read(); + if (!this.isCSS) { if (c === 47) { // SLASH -> single line while (true) { c = this._read(); @@ -9090,201 +10607,188 @@ examples.textview.TextStyler = (function() { } } } - if (c === 42) { // STAR -> multi line - c = this._read(); - var token = MULTILINE_COMMENT; - if (c === 42) { - token = DOC_COMMENT; - } - while (true) { - while (c === 42) { - c = this._read(); - if (c === 47) { - return token; - } - } - if (c === -1) { - this._unread(c); + } + if (c === 42) { // STAR -> multi line + c = this._read(); + var token = MULTILINE_COMMENT; + if (c === 42) { + token = DOC_COMMENT; + } + while (true) { + while (c === 42) { + c = this._read(); + if (c === 47) { return token; } + } + if (c === -1) { + this._unread(c); + return token; + } + c = this._read(); + } + } + this._unread(c); + return UNKOWN; + case 39: // SINGLE QUOTE -> char const + while(true) { + c = this._read(); + switch (c) { + case 39: + return STRING; + case 13: + case 10: + case -1: + this._unread(c); + return STRING; + case 92: // BACKSLASH c = this._read(); - } + break; } - this._unread(c); - return UNKOWN; - case 39: // SINGLE QUOTE -> char const - while(true) { - c = this._read(); - switch (c) { - case 39: - return STRING; - case 13: - case 10: - case -1: - this._unread(c); - return STRING; - case 92: // BACKSLASH - c = this._read(); - break; - } + } + break; + case 34: // DOUBLE QUOTE -> string + while(true) { + c = this._read(); + switch (c) { + case 34: // DOUBLE QUOTE + return STRING; + case 13: + case 10: + case -1: + this._unread(c); + return STRING; + case 92: // BACKSLASH + c = this._read(); + break; } - break; - case 34: // DOUBLE QUOTE -> string - while(true) { - c = this._read(); - switch (c) { - case 34: // DOUBLE QUOTE - return STRING; - case 13: - case 10: - case -1: - this._unread(c); - return STRING; - case 92: // BACKSLASH - c = this._read(); - break; - } - } - break; - default: - return this._default(c); - } - } - }, - setText: function(text) { - this.text = text; - this.offset = 0; - this.startOffset = 0; - } - }; - return Scanner; - }()); - - var WhitespaceScanner = (function() { - function WhitespaceScanner () { - Scanner.call(this, null, true); - } - WhitespaceScanner.prototype = new Scanner(null); - WhitespaceScanner.prototype.nextToken = function() { - this.startOffset = this.offset; - while (true) { - var c = this._read(); - switch (c) { - case -1: return null; - case 32: // SPACE - return WHITE_SPACE; - case 9: // TAB - return WHITE_TAB; + } + break; default: - do { - c = this._read(); - } while(!(c === 32 || c === 9 || c === -1)); - this._unread(c); - return UNKOWN; + return this._default(c); } } - }; - - return WhitespaceScanner; - }()); - - var CommentScanner = (function() { - function CommentScanner (whitespacesVisible) { - Scanner.call(this, null, whitespacesVisible); + }, + setText: function(text) { + this.text = text; + this.offset = 0; + this.startOffset = 0; } - CommentScanner.prototype = new Scanner(null); - CommentScanner.prototype.setType = function(type) { - this._type = type; - }; - CommentScanner.prototype.nextToken = function() { - this.startOffset = this.offset; - while (true) { - var c = this._read(); - switch (c) { - case -1: return null; - case 32: // SPACE - case 9: // TAB - if (this.whitespacesVisible) { - return c === 32 ? WHITE_SPACE : WHITE_TAB; - } + }; + + function WhitespaceScanner () { + Scanner.call(this, null, true); + } + WhitespaceScanner.prototype = new Scanner(null); + WhitespaceScanner.prototype.nextToken = function() { + this.startOffset = this.offset; + while (true) { + var c = this._read(); + switch (c) { + case -1: return null; + case 32: // SPACE + return WHITE_SPACE; + case 9: // TAB + return WHITE_TAB; + default: + do { + c = this._read(); + } while(!(c === 32 || c === 9 || c === -1)); + this._unread(c); + return UNKOWN; + } + } + }; + + function CommentScanner (whitespacesVisible) { + Scanner.call(this, null, whitespacesVisible); + } + CommentScanner.prototype = new Scanner(null); + CommentScanner.prototype.setType = function(type) { + this._type = type; + }; + CommentScanner.prototype.nextToken = function() { + this.startOffset = this.offset; + while (true) { + var c = this._read(); + switch (c) { + case -1: return null; + case 32: // SPACE + case 9: // TAB + if (this.whitespacesVisible) { + return c === 32 ? WHITE_SPACE : WHITE_TAB; + } + do { + c = this._read(); + } while(c === 32 || c === 9); + this._unread(c); + return WHITE; + case 60: // < + if (this._type === DOC_COMMENT) { do { c = this._read(); - } while(c === 32 || c === 9); + } while(!(c === 62 || c === -1)); // > + if (c === 62) { + return HTML_MARKUP; + } + } + return UNKOWN; + case 64: // @ + if (this._type === DOC_COMMENT) { + do { + c = this._read(); + } while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57)); //LETTER OR UNDERSCORE OR NUMBER this._unread(c); - return WHITE; - case 60: // < - if (this._type === DOC_COMMENT) { - do { + return DOC_TAG; + } + return UNKOWN; + case 84: // T + if ((c = this._read()) === 79) { // O + if ((c = this._read()) === 68) { // D + if ((c = this._read()) === 79) { // O c = this._read(); - } while(!(c === 62 || c === -1)); // > - if (c === 62) { - return HTML_MARKUP; - } - } - return UNKOWN; - case 64: // @ - if (this._type === DOC_COMMENT) { - do { - c = this._read(); - } while((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57)); //LETTER OR UNDERSCORE OR NUMBER - this._unread(c); - return DOC_TAG; - } - return UNKOWN; - case 84: // T - if ((c = this._read()) === 79) { // O - if ((c = this._read()) === 68) { // D - if ((c = this._read()) === 79) { // O - c = this._read(); - if (!((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57))) { - this._unread(c); - return TASK_TAG; - } - this._unread(c); - } else { + if (!((97 <= c && c <= 122) || (65 <= c && c <= 90) || c === 95 || (48 <= c && c <= 57))) { this._unread(c); + return TASK_TAG; } + this._unread(c); } else { this._unread(c); } } else { this._unread(c); } - //FALL THROUGH - default: - do { - c = this._read(); - } while(!(c === 32 || c === 9 || c === -1 || c === 60 || c === 64 || c === 84)); + } else { this._unread(c); - return UNKOWN; - } + } + //FALL THROUGH + default: + do { + c = this._read(); + } while(!(c === 32 || c === 9 || c === -1 || c === 60 || c === 64 || c === 84)); + this._unread(c); + return UNKOWN; } - }; - - return CommentScanner; - }()); - - var FirstScanner = (function() { - function FirstScanner () { - Scanner.call(this, null, false); } - FirstScanner.prototype = new Scanner(null); - FirstScanner.prototype._default = function(c) { - while(true) { - c = this._read(); - switch (c) { - case 47: // SLASH - case 34: // DOUBLE QUOTE - case 39: // SINGLE QUOTE - case -1: - this._unread(c); - return UNKOWN; - } + }; + + function FirstScanner () { + Scanner.call(this, null, false); + } + FirstScanner.prototype = new Scanner(null); + FirstScanner.prototype._default = function(c) { + while(true) { + c = this._read(); + switch (c) { + case 47: // SLASH + case 34: // DOUBLE QUOTE + case 39: // SINGLE QUOTE + case -1: + this._unread(c); + return UNKOWN; } - }; - - return FirstScanner; - }()); + } + }; function TextStyler (view, lang, annotationModel) { this.commentStart = "/*"; @@ -9297,38 +10801,47 @@ examples.textview.TextStyler = (function() { } this.whitespacesVisible = false; this.detectHyperlinks = true; - this.highlightCaretLine = true; + this.highlightCaretLine = false; this.foldingEnabled = true; this.detectTasks = true; this._scanner = new Scanner(keywords, this.whitespacesVisible); - //TODO this scanner is not the best/correct way to parse CSS - if (lang === "css") { - this._scanner.isCSS = true; - } this._firstScanner = new FirstScanner(); this._commentScanner = new CommentScanner(this.whitespacesVisible); this._whitespaceScanner = new WhitespaceScanner(); + //TODO these scanners are not the best/correct way to parse CSS + if (lang === "css") { + this._scanner.isCSS = true; + this._firstScanner.isCSS = true; + } this.view = view; this.annotationModel = annotationModel; - this._currentBracket = undefined; - this._matchingBracket = undefined; + this._bracketAnnotations = undefined; - view.addEventListener("Selection", this, this._onSelection); + var self = this; + this._listener = { + onChanged: function(e) { + self._onModelChanged(e); + }, + onDestroy: function(e) { + self._onDestroy(e); + }, + onLineStyle: function(e) { + self._onLineStyle(e); + }, + onSelection: function(e) { + self._onSelection(e); + } + }; var model = view.getModel(); if (model.getBaseModel) { - var self = this; - this._baseModelListener = { - onChanged: function(modelChangedEvent) { - self._onModelChanged(modelChangedEvent); - } - }; - model.getBaseModel().addListener(this._baseModelListener); + model.getBaseModel().addEventListener("Changed", this._listener.onChanged); } else { //TODO still needed to keep the event order correct (styler before view) - view.addEventListener("ModelChanged", this, this._onModelChanged); + view.addEventListener("ModelChanged", this._listener.onChanged); } - view.addEventListener("Destroy", this, this._onDestroy); - view.addEventListener("LineStyle", this, this._onLineStyle); + view.addEventListener("Selection", this._listener.onSelection); + view.addEventListener("Destroy", this._listener.onDestroy); + view.addEventListener("LineStyle", this._listener.onLineStyle); this._computeComments (); this._computeFolding(); view.redrawLines(); @@ -9340,13 +10853,13 @@ examples.textview.TextStyler = (function() { if (view) { var model = view.getModel(); if (model.getBaseModel) { - model.getBaseModel().removeListener(this._baseModelListener); + model.getBaseModel().removeEventListener("Changed", this._listener.onChanged); } else { - view.removeEventListener("ModelChanged", this, this._onModelChanged); + view.removeEventListener("ModelChanged", this._listener.onChanged); } - view.removeEventListener("Selection", this, this._onSelection); - view.removeEventListener("Destroy", this, this._onDestroy); - view.removeEventListener("LineStyle", this, this._onLineStyle); + view.removeEventListener("Selection", this._listener.onSelection); + view.removeEventListener("Destroy", this._listener.onDestroy); + view.removeEventListener("LineStyle", this._listener.onLineStyle); this.view = null; } }, @@ -9415,7 +10928,7 @@ examples.textview.TextStyler = (function() { if (startLine === endLine) { return null; } - return new orion.textview.FoldingAnnotation(viewModel, "orion.annotation.folding", start, end, + return new mAnnotations.FoldingAnnotation(viewModel, "orion.annotation.folding", start, end, "
", {styleClass: "annotation expanded"}, "", {styleClass: "annotation collapsed"}); }, @@ -9428,8 +10941,12 @@ examples.textview.TextStyler = (function() { if (viewModel.getBaseModel) { baseModel = viewModel.getBaseModel(); } var annotations = annotationModel.getAnnotations(commentStart, commentEnd); var remove = []; + var annotationType = "orion.annotation.task"; while (annotations.hasNext()) { - remove.push(annotations.next()); + var annotation = annotations.next(); + if (annotation.type === annotationType) { + remove.push(annotation); + } } var add = []; var scanner = this._commentScanner; @@ -9445,11 +10962,12 @@ examples.textview.TextStyler = (function() { add.push({ start: tokenStart, end: end, - type: "orion.annotation.task", + type: annotationType, title: baseModel.getText(tokenStart, end), style: {styleClass: "annotation task"}, html: "
", - overviewStyle: {styleClass: "annotationOverview task"} + overviewStyle: {styleClass: "annotationOverview task"}, + rangeStyle: {styleClass: "annotationRange task"} }); } } @@ -9513,39 +11031,35 @@ examples.textview.TextStyler = (function() { while ((token = scanner.nextToken())) { var tokenStart = scanner.getStartOffset() + offset; var style = null; - if (tokenStart === this._matchingBracket) { - style = bracketStyle; - } else { - switch (token) { - case KEYWORD: style = keywordStyle; break; - case STRING: - if (this.whitespacesVisible) { - this._parseString(scanner.getData(), tokenStart, styles, stringStyle); - continue; - } else { - style = stringStyle; - } - break; - case DOC_COMMENT: - this._parseComment(scanner.getData(), tokenStart, styles, docCommentStyle, token); + switch (token) { + case KEYWORD: style = keywordStyle; break; + case STRING: + if (this.whitespacesVisible) { + this._parseString(scanner.getData(), tokenStart, styles, stringStyle); continue; - case SINGLELINE_COMMENT: - this._parseComment(scanner.getData(), tokenStart, styles, singleCommentStyle, token); - continue; - case MULTILINE_COMMENT: - this._parseComment(scanner.getData(), tokenStart, styles, multiCommentStyle, token); - continue; - case WHITE_TAB: - if (this.whitespacesVisible) { - style = tabStyle; - } - break; - case WHITE_SPACE: - if (this.whitespacesVisible) { - style = spaceStyle; - } - break; - } + } else { + style = stringStyle; + } + break; + case DOC_COMMENT: + this._parseComment(scanner.getData(), tokenStart, styles, docCommentStyle, token); + continue; + case SINGLELINE_COMMENT: + this._parseComment(scanner.getData(), tokenStart, styles, singleCommentStyle, token); + continue; + case MULTILINE_COMMENT: + this._parseComment(scanner.getData(), tokenStart, styles, multiCommentStyle, token); + continue; + case WHITE_TAB: + if (this.whitespacesVisible) { + style = tabStyle; + } + break; + case WHITE_SPACE: + if (this.whitespacesVisible) { + style = spaceStyle; + } + break; } styles.push({start: tokenStart, end: scanner.getOffset() + offset, style: style}); } @@ -9684,7 +11198,6 @@ examples.textview.TextStyler = (function() { return result; }, _findMatchingBracket: function(model, offset) { - if (model.getBaseModel) { model = model.getBaseModel(); } var brackets = "{}()[]<>"; var bracket = model.getText(offset, offset + 1); var bracketIndex = brackets.indexOf(bracket, 0); @@ -9809,13 +11322,6 @@ examples.textview.TextStyler = (function() { var view = this.view; var model = view.getModel(); var lineIndex; - var bracket = this._matchingBracket; - if (bracket !== undefined) { - if (model.getBaseModel) { bracket = model.mapOffset(bracket, true); } - lineIndex = model.getLineAtOffset(bracket); - view.redrawLines(lineIndex, lineIndex + 1); - this._matchingBracket = this._currentBracket = undefined; - } if (this.highlightCaretLine) { var oldLineIndex = model.getLineAtOffset(oldSelection.start); lineIndex = model.getLineAtOffset(newSelection.start); @@ -9830,31 +11336,44 @@ examples.textview.TextStyler = (function() { } } } - if (newSelection.start !== newSelection.end || newSelection.start === 0) { - return; - } - var caret = view.getCaretOffset() - 1; - if (caret < 0) { return; } - var mapCaret = caret; - if (model.getBaseModel) { - mapCaret = model.mapOffset(caret); - } - bracket = this._findMatchingBracket(model, mapCaret); - if (bracket !== -1) { - this._currentBracket = mapCaret; - this._matchingBracket = bracket; - if (model.getBaseModel) { bracket = model.mapOffset(bracket, true); } - lineIndex = model.getLineAtOffset(bracket); - view.redrawLines(lineIndex, lineIndex + 1); + if (!this.annotationModel) { return; } + var remove = this._bracketAnnotations, add, caret; + if (newSelection.start === newSelection.end && (caret = view.getCaretOffset()) > 0) { + var mapCaret = caret - 1; + if (model.getBaseModel) { + mapCaret = model.mapOffset(mapCaret); + model = model.getBaseModel(); + } + var bracket = this._findMatchingBracket(model, mapCaret); + if (bracket !== -1) { + add = [{ + start: bracket, + end: bracket + 1, + type: "orion.annotation.matchingBracket", + title: "Matching Bracket", + html: "
", + overviewStyle: {styleClass: "annotationOverview matchingBracket"}, + rangeStyle: {styleClass: "annotationRange matchingBracket"} + }, + { + start: mapCaret, + end: mapCaret + 1, + type: "orion.annotation.currentBracket", + title: "Current Bracket", + html: "
", + overviewStyle: {styleClass: "annotationOverview currentBracket"}, + rangeStyle: {styleClass: "annotationRange currentBracket"} + }]; + } } + this._bracketAnnotations = add; + this.annotationModel.replaceAnnotations(remove, add); }, _onModelChanged: function(e) { var start = e.start; var removedCharCount = e.removedCharCount; var addedCharCount = e.addedCharCount; var changeCount = addedCharCount - removedCharCount; - if (this._matchingBracket && start < this._matchingBracket) { this._matchingBracket += changeCount; } - if (this._currentBracket && start < this._currentBracket) { this._currentBracket += changeCount; } var view = this.view; var viewModel = view.getModel(); var baseModel = viewModel.getBaseModel ? viewModel.getBaseModel() : viewModel; @@ -9870,7 +11389,11 @@ examples.textview.TextStyler = (function() { ts = this.comments[commentStart].start; if (ts > start) { ts += changeCount; } } else { - ts = lineStart; + if (commentStart === commentCount && commentCount > 0 && charCount - changeCount === this.comments[commentCount - 1].end) { + ts = this.comments[commentCount - 1].start; + } else { + ts = lineStart; + } } var te; if (commentEnd < commentCount) { @@ -9971,11 +11494,6 @@ examples.textview.TextStyler = (function() { } } }; - return TextStyler; -}()); - -if (typeof window !== "undefined" && typeof window.define !== "undefined") { - define([], function() { - return examples.textview; - }); -} + + return {TextStyler: TextStyler}; +}, "examples/textview"); diff --git a/browser/devtools/sourceeditor/source-editor-orion.jsm b/browser/devtools/sourceeditor/source-editor-orion.jsm index b78b1c04e78..8bc8f6856c2 100644 --- a/browser/devtools/sourceeditor/source-editor-orion.jsm +++ b/browser/devtools/sourceeditor/source-editor-orion.jsm @@ -42,6 +42,11 @@ const Cu = Components.utils; const Ci = Components.interfaces; Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", + "@mozilla.org/widget/clipboardhelper;1", + "nsIClipboardHelper"); const ORION_SCRIPT = "chrome://browser/content/orion.js"; const ORION_IFRAME = "data:text/html;charset=utf8," + @@ -57,8 +62,7 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; * SourceEditor.THEMES to Orion CSS files. */ const ORION_THEMES = { - textmate: ["chrome://browser/content/orion.css", - "chrome://browser/content/orion-mozilla.css"], + mozilla: ["chrome://browser/content/orion-mozilla.css"], }; /** @@ -71,6 +75,13 @@ const ORION_EVENTS = { Selection: "Selection", }; +/** + * Known Orion annotation types. + */ +const ORION_ANNOTATION_TYPES = { + currentBracket: "orion.annotation.currentBracket", + matchingBracket: "orion.annotation.matchingBracket", +}; var EXPORTED_SYMBOLS = ["SourceEditor"]; @@ -90,17 +101,24 @@ function SourceEditor() { Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE); SourceEditor.DEFAULTS.EXPAND_TAB = Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB); + + this._onOrionSelection = this._onOrionSelection.bind(this); } SourceEditor.prototype = { _view: null, _iframe: null, + _model: null, _undoStack: null, - _lines_ruler: null, + _linesRuler: null, _styler: null, + _annotationStyler: null, + _annotationModel: null, + _dragAndDrop: null, _mode: null, _expandTab: null, _tabSize: null, + _iframeWindow: null, /** * The editor container element. @@ -152,11 +170,7 @@ SourceEditor.prototype = { let onIframeLoad = (function() { this._iframe.removeEventListener("load", onIframeLoad, true); - - Services.scriptloader.loadSubScript(ORION_SCRIPT, - this._iframe.contentWindow.wrappedJSObject, "utf8"); - - this._onLoad(aCallback); + this._onIframeLoad(); }).bind(this); this._iframe.addEventListener("load", onIframeLoad, true); @@ -166,20 +180,23 @@ SourceEditor.prototype = { aElement.appendChild(this._iframe); this.parentElement = aElement; this._config = aConfig; + this._onReadyCallback = aCallback; }, /** * The editor iframe load event handler. - * * @private - * @param function [aCallback] - * Optional function invoked when the editor completes loading. */ - _onLoad: function SE__onLoad(aCallback) + _onIframeLoad: function SE__onIframeLoad() { + this._iframeWindow = this._iframe.contentWindow.wrappedJSObject; + let window = this._iframeWindow; let config = this._config; - let window = this._iframe.contentWindow.wrappedJSObject; - let textview = window.orion.textview; + + Services.scriptloader.loadSubScript(ORION_SCRIPT, window, "utf8"); + + let TextModel = window.require("orion/textview/textModel").TextModel; + let TextView = window.require("orion/textview/textView").TextView; this._expandTab = typeof config.expandTab != "undefined" ? config.expandTab : SourceEditor.DEFAULTS.EXPAND_TAB; @@ -188,73 +205,77 @@ SourceEditor.prototype = { let theme = config.theme || SourceEditor.DEFAULTS.THEME; let stylesheet = theme in ORION_THEMES ? ORION_THEMES[theme] : theme; - this._view = new textview.TextView({ - model: new textview.TextModel(config.placeholderText), + this._model = new TextModel(config.placeholderText); + this._view = new TextView({ + model: this._model, parent: "editor", stylesheet: stylesheet, tabSize: this._tabSize, + expandTab: this._expandTab, readonly: config.readOnly, + themeClass: "mozilla" + (config.readOnly ? " readonly" : ""), }); + let onOrionLoad = function() { + this._view.removeEventListener("Load", onOrionLoad); + this._onOrionLoad(); + }.bind(this); + + this._view.addEventListener("Load", onOrionLoad); + if (Services.appinfo.OS == "Linux") { + this._view.addEventListener("Selection", this._onOrionSelection); + } + + let KeyBinding = window.require("orion/textview/keyBinding").KeyBinding; + let TextDND = window.require("orion/textview/textDND").TextDND; + let LineNumberRuler = window.require("orion/textview/rulers").LineNumberRuler; + let UndoStack = window.require("orion/textview/undoStack").UndoStack; + let AnnotationModel = window.require("orion/textview/annotations").AnnotationModel; + + this._annotationModel = new AnnotationModel(this._model); + if (config.showLineNumbers) { - this._lines_ruler = new textview.LineNumberRuler(null, "left", + this._linesRuler = new LineNumberRuler(this._annotationModel, "left", {styleClass: "rulerLines"}, {styleClass: "rulerLine odd"}, {styleClass: "rulerLine even"}); - this._view.addRuler(this._lines_ruler); + this._view.addRuler(this._linesRuler); } this.setMode(config.mode || SourceEditor.DEFAULTS.MODE); - this._undoStack = new textview.UndoStack(this._view, + this._undoStack = new UndoStack(this._view, config.undoLimit || SourceEditor.DEFAULTS.UNDO_LIMIT); - this._initEditorFeatures(); + this._dragAndDrop = new TextDND(this._view, this._undoStack); + + this._view.setAction("tab", this._doTab.bind(this)); + + let shiftTabKey = new KeyBinding(Ci.nsIDOMKeyEvent.DOM_VK_TAB, false, true); + this._view.setAction("Unindent Lines", this._doUnindentLines.bind(this)); + this._view.setKeyBinding(shiftTabKey, "Unindent Lines"); + this._view.setAction("enter", this._doEnter.bind(this)); (config.keys || []).forEach(function(aKey) { - let binding = new textview.KeyBinding(aKey.code, aKey.accel, aKey.shift, - aKey.alt); + let binding = new KeyBinding(aKey.code, aKey.accel, aKey.shift, aKey.alt); this._view.setKeyBinding(binding, aKey.action); if (aKey.callback) { this._view.setAction(aKey.action, aKey.callback); } }, this); - - if (aCallback) { - let iframe = this._view._frame; - let document = iframe.contentDocument; - if (!document || document.readyState != "complete") { - let onIframeLoad = function () { - iframe.contentWindow.removeEventListener("load", onIframeLoad, false); - aCallback(this); - }.bind(this); - iframe.contentWindow.addEventListener("load", onIframeLoad, false); - } else { - aCallback(this); - } - } }, /** - * Initialize the custom Orion editor features. + * The Orion "Load" event handler. This is called when the Orion editor + * completes the initialization. * @private */ - _initEditorFeatures: function SE__initEditorFeatures() + _onOrionLoad: function SE__onOrionLoad() { - let window = this._iframe.contentWindow.wrappedJSObject; - let textview = window.orion.textview; - - this._view.setAction("tab", this._doTab.bind(this)); - - let shiftTabKey = new textview.KeyBinding(Ci.nsIDOMKeyEvent.DOM_VK_TAB, - false, true); - this._view.setAction("Unindent Lines", this._doUnindentLines.bind(this)); - this._view.setKeyBinding(shiftTabKey, "Unindent Lines"); - this._view.setAction("enter", this._doEnter.bind(this)); - - if (this._expandTab) { - this._view.setAction("deletePrevious", this._doDeletePrevious.bind(this)); + if (this._onReadyCallback) { + this._onReadyCallback(this); + this._onReadyCallback = null; } }, @@ -302,34 +323,9 @@ SourceEditor.prototype = { this._view.setSelection(newSelectionStart, newSelectionEnd); this.endCompoundChange(); - } else { - this.setText(indent, selection.start, selection.end); + return true; } - return true; - }, - - /** - * The "deletePrevious" editor action implementation. This adds unindentation - * support to the Backspace key implementation. - * @private - */ - _doDeletePrevious: function SE__doDeletePrevious() - { - let selection = this.getSelection(); - if (selection.start == selection.end && this._expandTab) { - let model = this._model; - let lineIndex = model.getLineAtOffset(selection.start); - let lineStart = model.getLineStart(lineIndex); - let offset = selection.start - lineStart; - if (offset >= this._tabSize && (offset % this._tabSize) == 0) { - let text = this.getText(lineStart, selection.start); - if (!/[^ ]/.test(text)) { - this.setText("", selection.start - this._tabSize, selection.end); - return true; - } - } - } return false; }, @@ -357,7 +353,7 @@ SourceEditor.prototype = { for (let line, i = firstLine; i <= lastLine; i++) { line = model.getLine(i, true); if (line.indexOf(indent) != 0) { - return false; + return true; } lines.push(line.substring(indent.length)); } @@ -426,12 +422,22 @@ SourceEditor.prototype = { }, /** - * Get the Orion Model, the TextModel object instance we use. + * Orion Selection event handler for the X Window System users. This allows + * one to select text and have it copied into the X11 PRIMARY. + * * @private - * @type object + * @param object aEvent + * The Orion Selection event object. */ - get _model() { - return this._view.getModel(); + _onOrionSelection: function SE__onOrionSelection(aEvent) + { + let text = this.getText(aEvent.newValue.start, aEvent.newValue.end); + if (!text) { + return; + } + + clipboardHelper.copyStringToClipboard(text, + Ci.nsIClipboard.kSelectionClipboard); }, /** @@ -453,15 +459,12 @@ SourceEditor.prototype = { * The event type you want to listen for. * @param function aCallback * The function you want executed when the event is triggered. - * @param mixed [aData] - * Optional data to pass to the callback when the event is triggered. */ addEventListener: - function SE_addEventListener(aEventType, aCallback, aData) + function SE_addEventListener(aEventType, aCallback) { if (aEventType in ORION_EVENTS) { - this._view.addEventListener(ORION_EVENTS[aEventType], true, - aCallback, aData); + this._view.addEventListener(ORION_EVENTS[aEventType], aCallback); } else { throw new Error("SourceEditor.addEventListener() unknown event " + "type " + aEventType); @@ -478,15 +481,12 @@ SourceEditor.prototype = { * The event type you have a listener for. * @param function aCallback * The function you have as the event handler. - * @param mixed [aData] - * The optional data passed to the callback. */ removeEventListener: - function SE_removeEventListener(aEventType, aCallback, aData) + function SE_removeEventListener(aEventType, aCallback) { if (aEventType in ORION_EVENTS) { - this._view.removeEventListener(ORION_EVENTS[aEventType], true, - aCallback, aData); + this._view.removeEventListener(ORION_EVENTS[aEventType], aCallback); } else { throw new Error("SourceEditor.removeEventListener() unknown event " + "type " + aEventType); @@ -587,7 +587,7 @@ SourceEditor.prototype = { */ hasFocus: function SE_hasFocus() { - return this._iframe.ownerDocument.activeElement === this._iframe; + return this._view.hasFocus(); }, /** @@ -703,6 +703,47 @@ SourceEditor.prototype = { this._view.setCaretOffset(aOffset, true); }, + /** + * Get the caret position. + * + * @return object + * An object that holds two properties: + * - line: the line number, counting from 0. + * - col: the column number, counting from 0. + */ + getCaretPosition: function SE_getCaretPosition() + { + let offset = this.getCaretOffset(); + let line = this._model.getLineAtOffset(offset); + let lineStart = this._model.getLineStart(line); + let column = offset - lineStart; + return {line: line, col: column}; + }, + + /** + * Set the caret position: line and column. + * + * @param number aLine + * The new caret line location. Line numbers start from 0. + * @param number [aColumn=0] + * Optional. The new caret column location. Columns start from 0. + */ + setCaretPosition: function SE_setCaretPosition(aLine, aColumn) + { + this.setCaretOffset(this._model.getLineStart(aLine) + (aColumn || 0)); + }, + + /** + * Get the line count. + * + * @return number + * The number of lines in the document being edited. + */ + getLineCount: function SE_getLineCount() + { + return this._model.getLineCount(); + }, + /** * Get the line delimiter used in the document being edited. * @@ -726,21 +767,41 @@ SourceEditor.prototype = { this._styler.destroy(); this._styler = null; } + if (this._annotationStyler) { + this._annotationStyler.destroy(); + this._annotationStyler = null; + } - let window = this._iframe.contentWindow.wrappedJSObject; - let TextStyler = window.examples.textview.TextStyler; - let TextMateStyler = window.orion.editor.TextMateStyler; - let HtmlGrammar = window.orion.editor.HtmlGrammar; + let window = this._iframeWindow; switch (aMode) { case SourceEditor.MODES.JAVASCRIPT: case SourceEditor.MODES.CSS: - this._styler = new TextStyler(this._view, aMode); + let TextStyler = + window.require("examples/textview/textStyler").TextStyler; + + this._styler = new TextStyler(this._view, aMode, this._annotationModel); + this._styler.setFoldingEnabled(false); + this._styler.setHighlightCaretLine(true); + + let AnnotationStyler = + window.require("orion/textview/annotations").AnnotationStyler; + + this._annotationStyler = + new AnnotationStyler(this._view, this._annotationModel); + this._annotationStyler. + addAnnotationType(ORION_ANNOTATION_TYPES.matchingBracket); + this._annotationStyler. + addAnnotationType(ORION_ANNOTATION_TYPES.currentBracket); break; case SourceEditor.MODES.HTML: case SourceEditor.MODES.XML: - this._styler = new TextMateStyler(this._view, HtmlGrammar.grammar); + let TextMateStyler = + window.require("orion/editor/textMateStyler").TextMateStyler; + let HtmlGrammar = + window.require("orion/editor/htmlGrammar").HtmlGrammar; + this._styler = new TextMateStyler(this._view, new HtmlGrammar().grammar); break; } @@ -765,7 +826,10 @@ SourceEditor.prototype = { */ set readOnly(aValue) { - this._view.readonly = aValue; + this._view.setOptions({ + readonly: aValue, + themeClass: "mozilla" + (aValue ? " readonly" : ""), + }); }, /** @@ -774,7 +838,7 @@ SourceEditor.prototype = { */ get readOnly() { - return this._view.readonly; + return this._view.getOptions("readonly"); }, /** @@ -782,14 +846,24 @@ SourceEditor.prototype = { */ destroy: function SE_destroy() { + if (Services.appinfo.OS == "Linux") { + this._view.removeEventListener("Selection", this._onOrionSelection); + } + this._onOrionSelection = null; + this._view.destroy(); this.parentElement.removeChild(this._iframe); this.parentElement = null; + this._iframeWindow = null; this._iframe = null; this._undoStack = null; this._styler = null; - this._lines_ruler = null; + this._linesRuler = null; + this._dragAndDrop = null; + this._annotationModel = null; + this._annotationStyler = null; this._view = null; + this._model = null; this._config = null; }, }; diff --git a/browser/devtools/sourceeditor/source-editor-textarea.jsm b/browser/devtools/sourceeditor/source-editor-textarea.jsm index 07417df8fe1..2907fe812b0 100644 --- a/browser/devtools/sourceeditor/source-editor-textarea.jsm +++ b/browser/devtools/sourceeditor/source-editor-textarea.jsm @@ -231,7 +231,7 @@ SourceEditor.prototype = { let listeners = this._listeners[SourceEditor.EVENTS.SELECTION] || []; listeners.forEach(function(aListener) { - aListener.callback.call(null, sendEvent, aListener.data); + aListener.callback.call(null, sendEvent); }, this); this._lastSelection = selection; @@ -256,7 +256,7 @@ SourceEditor.prototype = { { let listeners = this._listeners[SourceEditor.EVENTS.TEXT_CHANGED] || []; listeners.forEach(function(aListener) { - aListener.callback.call(null, aEvent, aListener.data); + aListener.callback.call(null, aEvent); }, this); }, @@ -279,16 +279,13 @@ SourceEditor.prototype = { * The event type you want to listen for. * @param function aCallback * The function you want executed when the event is triggered. - * @param mixed [aData] - * Optional data to pass to the callback when the event is triggered. */ addEventListener: - function SE_addEventListener(aEventType, aCallback, aData) + function SE_addEventListener(aEventType, aCallback) { const EVENTS = SourceEditor.EVENTS; let listener = { type: aEventType, - data: aData, callback: aCallback, }; @@ -316,24 +313,20 @@ SourceEditor.prototype = { * The event type you have a listener for. * @param function aCallback * The function you have as the event handler. - * @param mixed [aData] - * The optional data passed to the callback. */ removeEventListener: - function SE_removeEventListener(aEventType, aCallback, aData) + function SE_removeEventListener(aEventType, aCallback) { let listeners = this._listeners[aEventType]; if (!listeners) { - throw new Error("SourceEditor.removeEventListener() called for an " + - "unknown event."); + return; } const EVENTS = SourceEditor.EVENTS; this._listeners[aEventType] = listeners.filter(function(aListener) { let isSameListener = aListener.type == aEventType && - aListener.callback === aCallback && - aListener.data === aData; + aListener.callback === aCallback; if (isSameListener && aListener.domType) { aListener.target.removeEventListener(aListener.domType, aListener.handler, false); @@ -366,7 +359,7 @@ SourceEditor.prototype = { }; aDOMEvent.preventDefault(); - aListener.callback.call(null, sendEvent, aListener.data); + aListener.callback.call(null, sendEvent); }, /** @@ -601,6 +594,41 @@ SourceEditor.prototype = { this.setSelection(aOffset, aOffset); }, + /** + * Set the caret position: line and column. + * + * @param number aLine + * The new caret line location. Line numbers start from 0. + * @param number [aColumn=0] + * Optional. The new caret column location. Columns start from 0. + */ + setCaretPosition: function SE_setCaretPosition(aLine, aColumn) + { + aColumn = aColumn || 0; + + let text = this._textbox.value; + let i = 0, n = text.length, c0, c1; + let line = 0, col = 0; + while (i < n) { + c1 = text.charAt(i++); + if (line < aLine && (c1 == "\r" || (c0 != "\r" && c1 == "\n"))) { + // Count lines and reset the column only until we reach the desired line + // such that if the desired column is out of boundaries we will stop + // after the given number of characters from the line start. + line++; + col = 0; + } else { + col++; + } + + if (line == aLine && col == aColumn) { + this.setCaretOffset(i); + return; + } + c0 = c1; + } + }, + /** * Get the line delimiter used in the document being edited. * diff --git a/browser/devtools/sourceeditor/source-editor.jsm b/browser/devtools/sourceeditor/source-editor.jsm index c1eb05363b7..37cbfdc098e 100644 --- a/browser/devtools/sourceeditor/source-editor.jsm +++ b/browser/devtools/sourceeditor/source-editor.jsm @@ -90,7 +90,7 @@ SourceEditor.MODES = { * Predefined themes for syntax highlighting. */ SourceEditor.THEMES = { - TEXTMATE: "textmate", + MOZILLA: "mozilla", }; /** @@ -98,7 +98,7 @@ SourceEditor.THEMES = { */ SourceEditor.DEFAULTS = { MODE: SourceEditor.MODES.TEXT, - THEME: SourceEditor.THEMES.TEXTMATE, + THEME: SourceEditor.THEMES.MOZILLA, UNDO_LIMIT: 200, TAB_SIZE: 4, // overriden by pref EXPAND_TAB: true, // overriden by pref diff --git a/browser/devtools/sourceeditor/test/Makefile.in b/browser/devtools/sourceeditor/test/Makefile.in index f72df271098..36e4bd68e36 100644 --- a/browser/devtools/sourceeditor/test/Makefile.in +++ b/browser/devtools/sourceeditor/test/Makefile.in @@ -50,6 +50,9 @@ _BROWSER_TEST_FILES = \ browser_bug687573_vscroll.js \ browser_bug687568_pagescroll.js \ browser_bug687580_drag_and_drop.js \ + browser_bug695035_middle_click_paste.js \ + browser_bug687160_line_api.js \ + head.js \ libs:: $(_BROWSER_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) diff --git a/browser/devtools/sourceeditor/test/browser_bug687160_line_api.js b/browser/devtools/sourceeditor/test/browser_bug687160_line_api.js new file mode 100644 index 00000000000..c81f29995a5 --- /dev/null +++ b/browser/devtools/sourceeditor/test/browser_bug687160_line_api.js @@ -0,0 +1,79 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource:///modules/source-editor.jsm"); + +let testWin; +let editor; + +function test() +{ + waitForExplicitFinish(); + + const windowUrl = "data:text/xml," + + ""; + const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; + + testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null); + testWin.addEventListener("load", function onWindowLoad() { + testWin.removeEventListener("load", onWindowLoad, false); + waitForFocus(initEditor, testWin); + }, false); +} + +function initEditor() +{ + let hbox = testWin.document.querySelector("hbox"); + editor = new SourceEditor(); + editor.init(hbox, {}, editorLoaded); +} + +function editorLoaded() +{ + let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT); + + editor.focus(); + + editor.setText("line1\nline2\nline3"); + + if (component != "textarea") { + is(editor.getLineCount(), 3, "getLineCount() works"); + } + + editor.setCaretPosition(1); + is(editor.getCaretOffset(), 6, "setCaretPosition(line) works"); + + let pos; + if (component != "textarea") { + pos = editor.getCaretPosition(); + ok(pos.line == 1 && pos.col == 0, "getCaretPosition() works"); + } + + editor.setCaretPosition(1, 3); + is(editor.getCaretOffset(), 9, "setCaretPosition(line, column) works"); + + if (component != "textarea") { + pos = editor.getCaretPosition(); + ok(pos.line == 1 && pos.col == 3, "getCaretPosition() works"); + } + + editor.setCaretPosition(2); + is(editor.getCaretOffset(), 12, "setCaretLine() works, confirmed"); + + if (component != "textarea") { + pos = editor.getCaretPosition(); + ok(pos.line == 2 && pos.col == 0, "setCaretPosition(line) works, again"); + } + + editor.destroy(); + + testWin.close(); + testWin = editor = null; + + waitForFocus(finish, window); +} + diff --git a/browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js b/browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js index e3c0370230c..1bb9518282e 100644 --- a/browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js +++ b/browser/devtools/sourceeditor/test/browser_bug687580_drag_and_drop.js @@ -16,7 +16,6 @@ function test() ok(true, "skip test for bug 687580: only applicable for Orion"); return; // Testing for the fix requires direct Orion API access. } - waitForExplicitFinish(); const windowUrl = "data:application/vnd.mozilla.xul+xml," + @@ -71,7 +70,7 @@ function editorLoaded() let ds = Cc["@mozilla.org/widget/dragservice;1"]. getService(Ci.nsIDragService); - let target = view._dragNode; + let target = view._clientDiv; let targetWin = target.ownerDocument.defaultView; let dataTransfer = null; @@ -93,7 +92,9 @@ function editorLoaded() target.removeEventListener("drop", onDrop, false); let selection = editor.getSelection(); - is(selection.start, selection.end, "selection is collapsed"); + is(selection.end - selection.start, + initialSelection.end - initialSelection.start, + "selection is correct"); is(editor.getText(0, 2), "l3", "drag and drop worked"); let offset = editor.getCaretOffset(); diff --git a/browser/devtools/sourceeditor/test/browser_bug695035_middle_click_paste.js b/browser/devtools/sourceeditor/test/browser_bug695035_middle_click_paste.js new file mode 100644 index 00000000000..afe69fc22a5 --- /dev/null +++ b/browser/devtools/sourceeditor/test/browser_bug695035_middle_click_paste.js @@ -0,0 +1,97 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource:///modules/source-editor.jsm"); + +let testWin; +let editor; + +function test() +{ + if (Services.appinfo.OS != "Linux") { + ok(true, "this test only applies to Linux, skipping.") + return; + } + + waitForExplicitFinish(); + + const windowUrl = "data:text/xml," + + ""; + const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; + + testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null); + testWin.addEventListener("load", function onWindowLoad() { + testWin.removeEventListener("load", onWindowLoad, false); + waitForFocus(initEditor, testWin); + }, false); +} + +function initEditor() +{ + let hbox = testWin.document.querySelector("hbox"); + + editor = new SourceEditor(); + editor.init(hbox, {}, editorLoaded); +} + +function editorLoaded() +{ + editor.focus(); + + let initialText = "initial text!"; + + editor.setText(initialText); + + let expectedString = "foobarBug695035-" + Date.now(); + + let doCopy = function() { + let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]. + getService(Ci.nsIClipboardHelper); + clipboardHelper.copyStringToClipboard(expectedString, + Ci.nsIClipboard.kSelectionClipboard); + }; + + let onCopy = function() { + editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste); + + EventUtils.synthesizeMouse(editor.editorElement, 10, 10, {}, testWin); + EventUtils.synthesizeMouse(editor.editorElement, 11, 11, {button: 1}, testWin); + }; + + let onPaste = function() { + editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste); + + let text = editor.getText(); + isnot(text.indexOf(expectedString), -1, "middle-click paste works"); + isnot(text, initialText, "middle-click paste works (confirmed)"); + + executeSoon(doTestBug695032); + }; + + let doTestBug695032 = function() { + info("test for bug 695032 - editor selection should be placed in the X11 primary selection buffer"); + + let text = "foobarBug695032 test me, test me!"; + editor.setText(text); + + waitForSelection(text, function() { + EventUtils.synthesizeKey("a", {accelKey: true}, testWin); + }, testEnd, testEnd); + }; + + waitForSelection(expectedString, doCopy, onCopy, testEnd); +} + +function testEnd() +{ + editor.destroy(); + testWin.close(); + + testWin = editor = null; + + waitForFocus(finish, window); +} diff --git a/browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js b/browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js index db05396075c..7ffc964f2d8 100644 --- a/browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js +++ b/browser/devtools/sourceeditor/test/browser_sourceeditor_initialization.js @@ -412,16 +412,16 @@ function testEclipseBug362107() editor.setCaretOffset(16); EventUtils.synthesizeKey("VK_UP", {ctrlKey: true}, testWin); - is(editor.getCaretOffset(), 7, "Ctrl-Up works"); + is(editor.getCaretOffset(), 9, "Ctrl-Up works"); EventUtils.synthesizeKey("VK_UP", {ctrlKey: true}, testWin); - is(editor.getCaretOffset(), 0, "Ctrl-Up works twice"); + is(editor.getCaretOffset(), 2, "Ctrl-Up works twice"); EventUtils.synthesizeKey("VK_DOWN", {ctrlKey: true}, testWin); - is(editor.getCaretOffset(), 13, "Ctrl-Down works"); + is(editor.getCaretOffset(), 9, "Ctrl-Down works"); EventUtils.synthesizeKey("VK_DOWN", {ctrlKey: true}, testWin); - is(editor.getCaretOffset(), 20, "Ctrl-Down works twice"); + is(editor.getCaretOffset(), 16, "Ctrl-Down works twice"); } function testBug687577() diff --git a/browser/devtools/sourceeditor/test/head.js b/browser/devtools/sourceeditor/test/head.js new file mode 100644 index 00000000000..635bbaa69b6 --- /dev/null +++ b/browser/devtools/sourceeditor/test/head.js @@ -0,0 +1,107 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* + * Polls the X11 primary selection buffer waiting for the expected value. A known + * value different than the expected value is put on the clipboard first (and + * also polled for) so we can be sure the value we get isn't just the expected + * value because it was already in the buffer. + * + * @param aExpectedStringOrValidatorFn + * The string value that is expected to be in the X11 primary selection buffer + * or a validator function getting clipboard data and returning a bool. + * @param aSetupFn + * A function responsible for setting the primary selection buffer to the + * expected value, called after the known value setting succeeds. + * @param aSuccessFn + * A function called when the expected value is found in the primary + * selection buffer. + * @param aFailureFn + * A function called if the expected value isn't found in the primary + * selection buffer within 5s. It can also be called if the known value + * can't be found. + * @param aFlavor [optional] The flavor to look for. Defaults to "text/unicode". + */ +function waitForSelection(aExpectedStringOrValidatorFn, aSetupFn, + aSuccessFn, aFailureFn, aFlavor) { + let requestedFlavor = aFlavor || "text/unicode"; + + // Build a default validator function for common string input. + var inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string" + ? function(aData) aData == aExpectedStringOrValidatorFn + : aExpectedStringOrValidatorFn; + + let clipboard = Cc["@mozilla.org/widget/clipboard;1"]. + getService(Ci.nsIClipboard); + + // reset for the next use + function reset() { + waitForSelection._polls = 0; + } + + function wait(validatorFn, successFn, failureFn, flavor) { + if (++waitForSelection._polls > 50) { + // Log the failure. + ok(false, "Timed out while polling the X11 primary selection buffer."); + reset(); + failureFn(); + return; + } + + let transferable = Cc["@mozilla.org/widget/transferable;1"]. + createInstance(Ci.nsITransferable); + transferable.addDataFlavor(requestedFlavor); + + clipboard.getData(transferable, clipboard.kSelectionClipboard); + + let str = {}; + let strLength = {}; + + transferable.getTransferData(requestedFlavor, str, strLength); + + let data = null; + if (str.value) { + let strValue = str.value.QueryInterface(Ci.nsISupportsString); + data = strValue.data.substring(0, strLength.value / 2); + } + + if (validatorFn(data)) { + // Don't show the success message when waiting for preExpectedVal + if (preExpectedVal) { + preExpectedVal = null; + } else { + ok(true, "The X11 primary selection buffer has the correct value"); + } + reset(); + successFn(); + } else { + setTimeout(function() wait(validatorFn, successFn, failureFn, flavor), 100); + } + } + + // First we wait for a known value different from the expected one. + var preExpectedVal = waitForSelection._monotonicCounter + + "-waitForSelection-known-value"; + + let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]. + getService(Ci.nsIClipboardHelper); + clipboardHelper.copyStringToClipboard(preExpectedVal, + Ci.nsIClipboard.kSelectionClipboard); + + wait(function(aData) aData == preExpectedVal, + function() { + // Call the original setup fn + aSetupFn(); + wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor); + }, aFailureFn, "text/unicode"); +} + +waitForSelection._polls = 0; +waitForSelection.__monotonicCounter = 0; +waitForSelection.__defineGetter__("_monotonicCounter", function () { + return waitForSelection.__monotonicCounter++; +}); + diff --git a/browser/devtools/webconsole/AutocompletePopup.jsm b/browser/devtools/webconsole/AutocompletePopup.jsm index af5fd40fbbf..49058651dbc 100644 --- a/browser/devtools/webconsole/AutocompletePopup.jsm +++ b/browser/devtools/webconsole/AutocompletePopup.jsm @@ -221,6 +221,9 @@ AutocompletePopup.prototype = { */ clearItems: function AP_clearItems() { + // Reset the selectedIndex to -1 before clearing the list + this.selectedIndex = -1; + while (this._list.hasChildNodes()) { this._list.removeChild(this._list.firstChild); } diff --git a/browser/devtools/webconsole/HUDService.jsm b/browser/devtools/webconsole/HUDService.jsm index 6768f67a641..df56248dabd 100644 --- a/browser/devtools/webconsole/HUDService.jsm +++ b/browser/devtools/webconsole/HUDService.jsm @@ -5223,7 +5223,7 @@ JSTerm.prototype = { break; case Ci.nsIDOMKeyEvent.DOM_VK_RETURN: - if (this.autocompletePopup.isOpen) { + if (this.autocompletePopup.isOpen && this.autocompletePopup.selectedIndex > -1) { this.acceptProposedCompletion(); } else { @@ -5439,13 +5439,10 @@ JSTerm.prototype = { popup.hidePopup(); } - if (items.length > 0) { - popup.selectedIndex = 0; - if (items.length == 1) { - // onSelect is not fired when the popup is not open. - this.onAutocompleteSelect(); - } + if (items.length == 1) { + popup.selectedIndex = 0; } + this.onAutocompleteSelect(); } let accepted = false; diff --git a/browser/devtools/webconsole/test/browser/Makefile.in b/browser/devtools/webconsole/test/browser/Makefile.in index db93eb8788b..a6cd6ca7f8f 100644 --- a/browser/devtools/webconsole/test/browser/Makefile.in +++ b/browser/devtools/webconsole/test/browser/Makefile.in @@ -142,6 +142,7 @@ _BROWSER_TEST_FILES = \ browser_webconsole_bug_659907_console_dir.js \ browser_webconsole_bug_678816.js \ browser_webconsole_bug_664131_console_group.js \ + browser_webconsole_bug_704295.js \ browser_gcli_inspect.js \ browser_gcli_integrate.js \ browser_gcli_require.js \ diff --git a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_585991_autocomplete_keys.js b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_585991_autocomplete_keys.js index acdedc3892c..ed161904836 100644 --- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_585991_autocomplete_keys.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_585991_autocomplete_keys.js @@ -74,6 +74,9 @@ function tabLoaded() { return aItem.label == "item" + aIndex; }), true, "getItems returns back the same items"); + is(popup.selectedIndex, -1, "no index is selected"); + EventUtils.synthesizeKey("VK_DOWN", {}); + let prefix = jsterm.inputNode.value.replace(/[\S]/g, " "); is(popup.selectedIndex, 0, "index 0 is selected"); @@ -124,6 +127,9 @@ function autocompletePopupHidden() is(popup.itemCount, 4, "popup.itemCount is correct"); + is(popup.selectedIndex, -1, "no index is selected"); + EventUtils.synthesizeKey("VK_DOWN", {}); + let prefix = jsterm.inputNode.value.replace(/[\S]/g, " "); is(popup.selectedIndex, 0, "index 0 is selected"); @@ -167,6 +173,9 @@ function testReturnKey() ok(popup.isOpen, "popup is open"); is(popup.itemCount, 4, "popup.itemCount is correct"); + + is(popup.selectedIndex, -1, "no index is selected"); + EventUtils.synthesizeKey("VK_DOWN", {}); let prefix = jsterm.inputNode.value.replace(/[\S]/g, " "); diff --git a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_704295.js b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_704295.js new file mode 100644 index 00000000000..98751b8575f --- /dev/null +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_704295.js @@ -0,0 +1,76 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is DevTools test code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mihai Sucan + * Brijesh Patel + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Tests for bug 704295 + +const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-console.html"; + +function test() { + addTab(TEST_URI); + browser.addEventListener("DOMContentLoaded", testCompletion, false); +} + +function testCompletion() { + browser.removeEventListener("DOMContentLoaded", testCompletion, false); + + openConsole(); + + var jsterm = HUDService.getHudByWindow(content).jsterm; + var input = jsterm.inputNode; + + // Test typing 'var d = 5;' and press RETURN + jsterm.setInputValue("var d = "); + EventUtils.synthesizeKey("5", {}); + EventUtils.synthesizeKey(";", {}); + is(input.value, "var d = 5;", "var d = 5;"); + is(jsterm.completeNode.value, "", "no completion"); + EventUtils.synthesizeKey("VK_ENTER", {}); + is(jsterm.completeNode.value, "", "clear completion on execute()"); + + // Test typing 'var a = d' and press RETURN + jsterm.setInputValue("var a = "); + EventUtils.synthesizeKey("d", {}); + is(input.value, "var a = d", "var a = d"); + is(jsterm.completeNode.value, "", "no completion"); + EventUtils.synthesizeKey("VK_ENTER", {}); + is(jsterm.completeNode.value, "", "clear completion on execute()"); + + jsterm = input = null; + finishTest(); +} + diff --git a/browser/devtools/webconsole/test/browser/browser_webconsole_completion.js b/browser/devtools/webconsole/test/browser/browser_webconsole_completion.js index cd279379484..2126d66efb0 100644 --- a/browser/devtools/webconsole/test/browser/browser_webconsole_completion.js +++ b/browser/devtools/webconsole/test/browser/browser_webconsole_completion.js @@ -74,7 +74,7 @@ function testCompletion() { // Test typing 'document.getElem'. input.value = "document.getElem"; input.setSelectionRange(16, 16); - jsterm.complete(jsterm.COMPLETE_HINT_ONLY); + jsterm.complete(jsterm.COMPLETE_FORWARD); is(input.value, "document.getElem", "'document.getElem' completion"); is(jsterm.completeNode.value, " entById", "'document.getElem' completion"); diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 715a35f533f..7a5ba918e48 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -51,9 +51,6 @@ #else @BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@ #endif -#ifdef XP_WIN -@BINPATH@/components/@DLL_PREFIX@uconvd@DLL_SUFFIX@ -#endif #ifdef XP_MACOSX @BINPATH@/@MOZ_CHILD_PROCESS_NAME@.app/ @BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@ diff --git a/browser/installer/removed-files.in b/browser/installer/removed-files.in index 6b7d4e5b8f5..c69cb50c821 100644 --- a/browser/installer/removed-files.in +++ b/browser/installer/removed-files.in @@ -72,6 +72,7 @@ components/sidebar.xpt components/dom_telephony.xpt components/dom_system_b2g.xpt #endif +components/uconvd.dll components/WeaveCrypto.js components/WeaveCrypto.manifest components/xmlextras.xpt diff --git a/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties b/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties index ef8eceee75a..59a6b77f676 100644 --- a/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties +++ b/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties @@ -32,16 +32,6 @@ rule.sourceElement=element # e.g "Inherited from body#bodyID (styles.css:20)" rule.inheritedSource=Inherited from %S (%S) -# LOCALIZATION NOTE (group): Style properties are displayed in categories and -# these are the category names. -group.Text_Fonts_and_Color=Text, Fonts & Color -group.Background=Background -group.Dimensions=Dimensions -group.Positioning_and_Page_Flow=Positioning and Page Flow -group.Borders=Borders -group.Lists=Lists -group.Effects_and_Other=Effects and Other - # LOCALIZATION NOTE (style.highlighter.button): These strings are used inside # sidebar of the Highlighter for the style inspector button style.highlighter.button.label1=Properties diff --git a/browser/locales/en-US/chrome/browser/places/places.properties b/browser/locales/en-US/chrome/browser/places/places.properties index 3bdcb6005e3..46f9097e8de 100644 --- a/browser/locales/en-US/chrome/browser/places/places.properties +++ b/browser/locales/en-US/chrome/browser/places/places.properties @@ -59,8 +59,11 @@ saveSearch.inputLabel=Name: saveSearch.inputDefaultText=New Search detailsPane.noItems=No items -detailsPane.oneItem=One item -detailsPane.multipleItems=%S items +# LOCALIZATION NOTE (detailsPane.itemsCountLabel): Semi-colon list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 number of items +# example: 111 items +detailsPane.itemsCountLabel=One item;#1 items mostVisitedTitle=Most Visited recentlyBookmarkedTitle=Recently Bookmarked diff --git a/browser/themes/winstripe/browser.css b/browser/themes/winstripe/browser.css index d68ebf1ac0c..4be31d3f45c 100644 --- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -548,7 +548,7 @@ toolbarbutton.bookmark-item[open="true"] { -moz-padding-end: 2px; } -.bookmark-item > .toolbarbutton-icon { +.bookmark-item:not(#bookmarks-menu-button) > .toolbarbutton-icon { width: 16px; height: 16px; } diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index d8a401e264b..2d20cbbcfa8 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -478,6 +478,7 @@ MOZ_GRAPHITE_LIBS = @MOZ_GRAPHITE_LIBS@ MOZ_GRAPHITE = @MOZ_GRAPHITE@ MOZ_OTS_LIBS = @MOZ_OTS_LIBS@ MOZ_SKIA_LIBS = @MOZ_SKIA_LIBS@ +MOZ_ENABLE_SKIA = @MOZ_ENABLE_SKIA@ MOZ_NATIVE_SQLITE = @MOZ_NATIVE_SQLITE@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ diff --git a/configure.in b/configure.in index b597b34eb72..7e7ecab1faa 100644 --- a/configure.in +++ b/configure.in @@ -8018,6 +8018,21 @@ AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) AC_SUBST(GLIB_GMODULE_LIBS) +dnl ======================================================== +dnl Graphics checks. +dnl ======================================================== + +if test "${OS_ARCH}" = "Darwin" -o "${OS_TARGET}" = "Android"; then +MOZ_ENABLE_SKIA=1 +else +MOZ_ENABLE_SKIA= +fi + +MOZ_ARG_ENABLE_BOOL(skia, +[ --enable-skia Enable use of Skia], +MOZ_ENABLE_SKIA=1, +MOZ_ENABLE_SKIA=) + dnl ======================================================== dnl Check for cairo dnl ======================================================== @@ -8198,7 +8213,13 @@ AC_SUBST(MOZ_OTS_LIBS) dnl ======================================================== dnl Skia dnl ======================================================== -MOZ_SKIA_LIBS='$(DEPTH)/gfx/skia/$(LIB_PREFIX)skia.$(LIB_SUFFIX)' +if test "$MOZ_ENABLE_SKIA"; then + MOZ_SKIA_LIBS='$(DEPTH)/gfx/skia/$(LIB_PREFIX)skia.$(LIB_SUFFIX)' + AC_DEFINE(MOZ_ENABLE_SKIA) +else + MOZ_SKIA_LIBS= +fi +AC_SUBST(MOZ_ENABLE_SKIA) AC_SUBST(MOZ_SKIA_LIBS) dnl ======================================================== diff --git a/content/html/document/src/nsHTMLDocument.cpp b/content/html/document/src/nsHTMLDocument.cpp index e9df8264ec2..7e75a3c390e 100644 --- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -162,7 +162,6 @@ const PRInt32 kBackward = 1; //#define DEBUG_charset -#define NS_USE_NEW_VIEW_SOURCE 1 #define NS_USE_NEW_PLAIN_TEXT 1 static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); @@ -657,8 +656,7 @@ nsHTMLDocument::StartDocumentLoad(const char* aCommand, nsCAutoString contentType; aChannel->GetContentType(contentType); - bool viewSource = aCommand && !nsCRT::strcmp(aCommand, "view-source") && - NS_USE_NEW_VIEW_SOURCE; + bool viewSource = aCommand && !nsCRT::strcmp(aCommand, "view-source"); bool plainText = (contentType.EqualsLiteral(TEXT_PLAIN) || contentType.EqualsLiteral(TEXT_CSS) || contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) || diff --git a/dom/battery/BatteryManager.cpp b/dom/battery/BatteryManager.cpp index 9dfeefdd982..c0795592238 100644 --- a/dom/battery/BatteryManager.cpp +++ b/dom/battery/BatteryManager.cpp @@ -100,12 +100,10 @@ BatteryManager::Init(nsPIDOMWindow *aWindow, nsIScriptContext* aScriptContext) hal::RegisterBatteryObserver(this); - hal::BatteryInformation* batteryInfo = new hal::BatteryInformation(); - hal::GetCurrentBatteryInformation(batteryInfo); + hal::BatteryInformation batteryInfo; + hal::GetCurrentBatteryInformation(&batteryInfo); - UpdateFromBatteryInfo(*batteryInfo); - - delete batteryInfo; + UpdateFromBatteryInfo(batteryInfo); } void diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index aa7f0625bc4..a44c99b9f96 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -2001,19 +2001,6 @@ _releasevariantvalue(NPVariant* variant) VOID_TO_NPVARIANT(*variant); } -bool NP_CALLBACK -_tostring(NPObject* npobj, NPVariant *result) -{ - NS_ERROR("Write me!"); - - if (!NS_IsMainThread()) { - NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_tostring called from the wrong thread\n")); - return false; - } - - return false; -} - void NP_CALLBACK _setexception(NPObject* npobj, const NPUTF8 *message) { diff --git a/gfx/2d/Makefile.in b/gfx/2d/Makefile.in index bcb74559a7a..ff03120e72a 100644 --- a/gfx/2d/Makefile.in +++ b/gfx/2d/Makefile.in @@ -75,25 +75,29 @@ CPPSRCS = \ DEFINES += -DMOZ_GFX -DUSE_CAIRO -ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +ifdef MOZ_ENABLE_SKIA CPPSRCS += \ - SourceSurfaceSkia.cpp \ - DrawTargetSkia.cpp \ - PathSkia.cpp \ + SourceSurfaceSkia.cpp \ + DrawTargetSkia.cpp \ + PathSkia.cpp \ ScaledFontSkia.cpp \ - ScaledFontMac.cpp \ $(NULL) + DEFINES += -DUSE_SKIA + +endif + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +ifdef MOZ_ENABLE_SKIA +CPPSRCS += \ + ScaledFontMac.cpp \ + $(NULL) + +endif endif ifeq (android,$(MOZ_WIDGET_TOOLKIT)) -CPPSRCS += \ - SourceSurfaceSkia.cpp \ - DrawTargetSkia.cpp \ - ScaledFontSkia.cpp \ - PathSkia.cpp \ - $(NULL) -DEFINES += -DUSE_SKIA -DSK_BUILD_FOR_ANDROID_NDK +DEFINES += -DSK_BUILD_FOR_ANDROID_NDK endif DEFINES += -DSK_A32_SHIFT=24 -DSK_R32_SHIFT=16 -DSK_G32_SHIFT=8 -DSK_B32_SHIFT=0 @@ -109,13 +113,14 @@ CPPSRCS += \ SourceSurfaceD2DTarget.cpp \ PathD2D.cpp \ ScaledFontDWrite.cpp \ - SourceSurfaceSkia.cpp \ - DrawTargetSkia.cpp \ - PathSkia.cpp \ - ScaledFontSkia.cpp \ + $(NULL) +DEFINES += -DWIN32 + +ifdef MOZ_ENABLE_SKIA +CPPSRCS += \ ScaledFontWin.cpp \ $(NULL) -DEFINES += -DWIN32 -DUSE_SKIA +endif endif #ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) diff --git a/gfx/Makefile.in b/gfx/Makefile.in index d1d57023f7c..4cef7588af9 100644 --- a/gfx/Makefile.in +++ b/gfx/Makefile.in @@ -54,7 +54,7 @@ endif DIRS += 2d ycbcr angle src qcms gl layers harfbuzz/src ots/src thebes ipc -ifeq (,$(filter-out cocoa android windows,$(MOZ_WIDGET_TOOLKIT))) +ifdef MOZ_ENABLE_SKIA DIRS += skia endif diff --git a/intl/uconv/datamodule/Makefile.in b/intl/uconv/datamodule/Makefile.in deleted file mode 100644 index 018b7c486ed..00000000000 --- a/intl/uconv/datamodule/Makefile.in +++ /dev/null @@ -1,96 +0,0 @@ -# vim:set noet ts=8: -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# -# Alternatively, the contents of this file may be used under the terms of -# either of the GNU General Public License Version 2 or later (the "GPL"), -# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - -DEPTH = ../../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = uconvd -LIBRARY_NAME = uconvd -EXPORT_LIBRARY = 1 -IS_COMPONENT = 1 -MODULE_NAME = UConvData -ifeq (WINNT,$(OS_TARGET)) -FORCE_SHARED_LIB = 1 -else -LIBXUL_LIBRARY = 1 -endif -# To avoid conflict with OS/2 system uconv.dll -SHORT_LIBNAME = mzuconvd - -CPPSRCS = \ - nsUConvDataModule.cpp \ - $(NULL) - -LOCAL_INCLUDES = -I$(srcdir)/../util \ - -I$(srcdir)/../ucvlatin \ - -I$(srcdir)/../ucvibm \ - -I$(srcdir)/../ucvja \ - -I$(srcdir)/../ucvtw2 \ - -I$(srcdir)/../ucvtw \ - -I$(srcdir)/../ucvko \ - -I$(srcdir)/../ucvcn \ - $(NULL) - -EXTRA_DSO_LDOPTS += \ - $(XPCOM_GLUE_LDOPTS) \ - $(XPCOM_LIBS) \ - $(MOZ_JS_LIBS) \ - $(NSPR_LIBS) \ - $(MOZALLOC_LIB) \ - $(NULL) - -SHARED_LIBRARY_LIBS += \ - ../ucvlatin/$(LIB_PREFIX)ucvlatin_s.$(LIB_SUFFIX) \ - ../ucvibm/$(LIB_PREFIX)ucvibm_s.$(LIB_SUFFIX) \ - ../ucvja/$(LIB_PREFIX)ucvja_s.$(LIB_SUFFIX) \ - ../ucvtw2/$(LIB_PREFIX)ucvtw2_s.$(LIB_SUFFIX) \ - ../ucvtw/$(LIB_PREFIX)ucvtw_s.$(LIB_SUFFIX) \ - ../ucvko/$(LIB_PREFIX)ucvko_s.$(LIB_SUFFIX) \ - ../ucvcn/$(LIB_PREFIX)ucvcn_s.$(LIB_SUFFIX) \ - $(NULL) - -ifdef FORCE_SHARED_LIB -SHARED_LIBRARY_LIBS += \ - ../util/external/$(LIB_PREFIX)ucvxutil_s.$(LIB_SUFFIX) \ - $(NULL) -endif - -include $(topsrcdir)/config/rules.mk diff --git a/intl/uconv/datamodule/nsUConvDataModule.cpp b/intl/uconv/datamodule/nsUConvDataModule.cpp deleted file mode 100644 index 117dfcd18f5..00000000000 --- a/intl/uconv/datamodule/nsUConvDataModule.cpp +++ /dev/null @@ -1,1089 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Pierre Phaneuf - * IBM Corporation - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ -#include "nsCOMPtr.h" -#include "nsCRT.h" -#include "mozilla/ModuleUtils.h" -#include "nsIComponentManager.h" -#include "nsICategoryManager.h" -#include "nsICharsetConverterManager.h" -#include "nsEncoderDecoderUtils.h" -#include "nsIUnicodeDecoder.h" -#include "nsIUnicodeEncoder.h" -#include "nsIServiceManager.h" - -#include "mozilla/BlockingResourceBase.h" - -#include "nsUConvCID.h" - -#include "nsIFile.h" - -#include "nsCRT.h" - -// ucvlatin -#include "nsUCvLatinCID.h" -#include "nsUCvLatinDll.h" -#include "nsAsciiToUnicode.h" -#include "nsISO88592ToUnicode.h" -#include "nsISO88593ToUnicode.h" -#include "nsISO88594ToUnicode.h" -#include "nsISO88595ToUnicode.h" -#include "nsISO88596ToUnicode.h" -#include "nsISO88596EToUnicode.h" -#include "nsISO88596IToUnicode.h" -#include "nsISO88597ToUnicode.h" -#include "nsISO88598ToUnicode.h" -#include "nsISO88598EToUnicode.h" -#include "nsISO88598IToUnicode.h" -#include "nsISO88599ToUnicode.h" -#include "nsISO885910ToUnicode.h" -#include "nsISO885913ToUnicode.h" -#include "nsISO885914ToUnicode.h" -#include "nsISO885915ToUnicode.h" -#include "nsISO885916ToUnicode.h" -#include "nsISOIR111ToUnicode.h" -#include "nsCP1250ToUnicode.h" -#include "nsCP1251ToUnicode.h" -#include "nsCP1253ToUnicode.h" -#include "nsCP1254ToUnicode.h" -#include "nsCP1255ToUnicode.h" -#include "nsCP1256ToUnicode.h" -#include "nsCP1257ToUnicode.h" -#include "nsCP1258ToUnicode.h" -#include "nsCP874ToUnicode.h" -#include "nsISO885911ToUnicode.h" -#include "nsTIS620ToUnicode.h" -#include "nsCP866ToUnicode.h" -#include "nsKOI8RToUnicode.h" -#include "nsKOI8UToUnicode.h" -#include "nsMacCEToUnicode.h" -#include "nsMacGreekToUnicode.h" -#include "nsMacTurkishToUnicode.h" -#include "nsMacCroatianToUnicode.h" -#include "nsMacRomanianToUnicode.h" -#include "nsMacCyrillicToUnicode.h" -#include "nsMacIcelandicToUnicode.h" -#include "nsGEOSTD8ToUnicode.h" -#include "nsARMSCII8ToUnicode.h" -#include "nsTCVN5712ToUnicode.h" -#include "nsVISCIIToUnicode.h" -#include "nsVPSToUnicode.h" -#include "nsUTF7ToUnicode.h" -#include "nsMUTF7ToUnicode.h" -#include "nsUCS2BEToUnicode.h" -#include "nsT61ToUnicode.h" -#include "nsUserDefinedToUnicode.h" -#include "nsUnicodeToAscii.h" -#include "nsUnicodeToISO88592.h" -#include "nsUnicodeToISO88593.h" -#include "nsUnicodeToISO88594.h" -#include "nsUnicodeToISO88595.h" -#include "nsUnicodeToISO88596.h" -#include "nsUnicodeToISO88596E.h" -#include "nsUnicodeToISO88596I.h" -#include "nsUnicodeToISO88597.h" -#include "nsUnicodeToISO88598.h" -#include "nsUnicodeToISO88598E.h" -#include "nsUnicodeToISO88598I.h" -#include "nsUnicodeToISO88599.h" -#include "nsUnicodeToISO885910.h" -#include "nsUnicodeToISO885913.h" -#include "nsUnicodeToISO885914.h" -#include "nsUnicodeToISO885915.h" -#include "nsUnicodeToISO885916.h" -#include "nsUnicodeToISOIR111.h" -#include "nsUnicodeToCP1250.h" -#include "nsUnicodeToCP1251.h" -#include "nsUnicodeToCP1253.h" -#include "nsUnicodeToCP1254.h" -#include "nsUnicodeToCP1255.h" -#include "nsUnicodeToCP1256.h" -#include "nsUnicodeToCP1257.h" -#include "nsUnicodeToCP1258.h" -#include "nsUnicodeToCP874.h" -#include "nsUnicodeToISO885911.h" -#include "nsUnicodeToTIS620.h" -#include "nsUnicodeToCP866.h" -#include "nsUnicodeToKOI8R.h" -#include "nsUnicodeToKOI8U.h" -#include "nsUnicodeToMacCE.h" -#include "nsUnicodeToMacGreek.h" -#include "nsUnicodeToMacTurkish.h" -#include "nsUnicodeToMacCroatian.h" -#include "nsUnicodeToMacRomanian.h" -#include "nsUnicodeToMacCyrillic.h" -#include "nsUnicodeToMacIcelandic.h" -#include "nsUnicodeToGEOSTD8.h" -#include "nsUnicodeToARMSCII8.h" -#include "nsUnicodeToTCVN5712.h" -#include "nsUnicodeToVISCII.h" -#include "nsUnicodeToVPS.h" -#include "nsUnicodeToUTF7.h" -#include "nsUnicodeToMUTF7.h" -#include "nsUnicodeToUCS2BE.h" -#include "nsUnicodeToT61.h" -#include "nsUnicodeToUserDefined.h" -#include "nsUnicodeToSymbol.h" -#include "nsUnicodeToZapfDingbat.h" -#include "nsUnicodeToAdobeEuro.h" -#include "nsMacArabicToUnicode.h" -#include "nsMacDevanagariToUnicode.h" -#include "nsMacFarsiToUnicode.h" -#include "nsMacGujaratiToUnicode.h" -#include "nsMacGurmukhiToUnicode.h" -#include "nsMacHebrewToUnicode.h" -#include "nsUnicodeToMacArabic.h" -#include "nsUnicodeToMacDevanagari.h" -#include "nsUnicodeToMacFarsi.h" -#include "nsUnicodeToMacGujarati.h" -#include "nsUnicodeToMacGurmukhi.h" -#include "nsUnicodeToMacHebrew.h" -#include "nsUnicodeToTSCII.h" - -// ucvibm -#include "nsUCvIBMCID.h" -#include "nsUCvIBMDll.h" -#include "nsCP850ToUnicode.h" -#include "nsCP852ToUnicode.h" -#include "nsCP855ToUnicode.h" -#include "nsCP857ToUnicode.h" -#include "nsCP862ToUnicode.h" -#include "nsCP864ToUnicode.h" -#include "nsCP864iToUnicode.h" -#ifdef XP_OS2 -#include "nsCP869ToUnicode.h" -#include "nsCP1125ToUnicode.h" -#include "nsCP1131ToUnicode.h" -#endif -#include "nsUnicodeToCP850.h" -#include "nsUnicodeToCP852.h" -#include "nsUnicodeToCP855.h" -#include "nsUnicodeToCP857.h" -#include "nsUnicodeToCP862.h" -#include "nsUnicodeToCP864.h" -#include "nsUnicodeToCP864i.h" -#ifdef XP_OS2 -#include "nsUnicodeToCP869.h" -#include "nsUnicodeToCP1125.h" -#include "nsUnicodeToCP1131.h" -#endif - -// ucvja -#include "nsUCVJACID.h" -#include "nsUCVJA2CID.h" -#include "nsUCVJADll.h" -#include "nsJapaneseToUnicode.h" -#include "nsUnicodeToSJIS.h" -#include "nsUnicodeToEUCJP.h" -#include "nsUnicodeToISO2022JP.h" -#include "nsUnicodeToJISx0201.h" - -// ucvtw2 -#include "nsUCvTW2CID.h" -#include "nsUCvTW2Dll.h" -#include "nsEUCTWToUnicode.h" -#include "nsUnicodeToEUCTW.h" - -// ucvtw -#include "nsUCvTWCID.h" -#include "nsUCvTWDll.h" -#include "nsBIG5ToUnicode.h" -#include "nsUnicodeToBIG5.h" -#include "nsBIG5HKSCSToUnicode.h" -#include "nsUnicodeToBIG5HKSCS.h" -#include "nsUnicodeToHKSCS.h" - -// ucvko -#include "nsUCvKOCID.h" -#include "nsUCvKODll.h" -#include "nsEUCKRToUnicode.h" -#include "nsUnicodeToEUCKR.h" -#include "nsJohabToUnicode.h" -#include "nsUnicodeToJohab.h" -#include "nsCP949ToUnicode.h" -#include "nsUnicodeToCP949.h" -#include "nsISO2022KRToUnicode.h" - -// ucvcn -#include "nsUCvCnCID.h" -#include "nsUCvCnDll.h" -#include "nsHZToUnicode.h" -#include "nsUnicodeToHZ.h" -#include "nsGBKToUnicode.h" -#include "nsUnicodeToGBK.h" -#include "nsGB2312ToUnicodeV2.h" -#include "nsUnicodeToGB2312V2.h" -#include "nsISO2022CNToUnicode.h" -#include "nsUnicodeToISO2022CN.h" -#include "gbku.h" - -NS_CONVERTER_REGISTRY_START -NS_UCONV_REG_UNREG("ISO-8859-1", NS_ISO88591TOUNICODE_CID, NS_UNICODETOISO88591_CID) -NS_UCONV_REG_UNREG("windows-1252", NS_CP1252TOUNICODE_CID, NS_UNICODETOCP1252_CID) -NS_UCONV_REG_UNREG("x-mac-roman", NS_MACROMANTOUNICODE_CID, NS_UNICODETOMACROMAN_CID) -NS_UCONV_REG_UNREG("UTF-8", NS_UTF8TOUNICODE_CID, NS_UNICODETOUTF8_CID) - - // ucvlatin -NS_UCONV_REG_UNREG("us-ascii", NS_ASCIITOUNICODE_CID, NS_UNICODETOASCII_CID) -NS_UCONV_REG_UNREG("ISO-8859-2", NS_ISO88592TOUNICODE_CID, NS_UNICODETOISO88592_CID) -NS_UCONV_REG_UNREG("ISO-8859-3", NS_ISO88593TOUNICODE_CID, NS_UNICODETOISO88593_CID) -NS_UCONV_REG_UNREG("ISO-8859-4", NS_ISO88594TOUNICODE_CID, NS_UNICODETOISO88594_CID) -NS_UCONV_REG_UNREG("ISO-8859-5", NS_ISO88595TOUNICODE_CID, NS_UNICODETOISO88595_CID) -NS_UCONV_REG_UNREG("ISO-8859-6", NS_ISO88596TOUNICODE_CID, NS_UNICODETOISO88596_CID) -NS_UCONV_REG_UNREG("ISO-8859-6-I", NS_ISO88596ITOUNICODE_CID, NS_UNICODETOISO88596I_CID) -NS_UCONV_REG_UNREG("ISO-8859-6-E", NS_ISO88596ETOUNICODE_CID, NS_UNICODETOISO88596E_CID) -NS_UCONV_REG_UNREG("ISO-8859-7", NS_ISO88597TOUNICODE_CID, NS_UNICODETOISO88597_CID) -NS_UCONV_REG_UNREG("ISO-8859-8", NS_ISO88598TOUNICODE_CID, NS_UNICODETOISO88598_CID) -NS_UCONV_REG_UNREG("ISO-8859-8-I", NS_ISO88598ITOUNICODE_CID, NS_UNICODETOISO88598I_CID) -NS_UCONV_REG_UNREG("ISO-8859-8-E", NS_ISO88598ETOUNICODE_CID, NS_UNICODETOISO88598E_CID) -NS_UCONV_REG_UNREG("ISO-8859-9", NS_ISO88599TOUNICODE_CID, NS_UNICODETOISO88599_CID) -NS_UCONV_REG_UNREG("ISO-8859-10", NS_ISO885910TOUNICODE_CID, NS_UNICODETOISO885910_CID) -NS_UCONV_REG_UNREG("ISO-8859-13", NS_ISO885913TOUNICODE_CID, NS_UNICODETOISO885913_CID) -NS_UCONV_REG_UNREG("ISO-8859-14", NS_ISO885914TOUNICODE_CID, NS_UNICODETOISO885914_CID) -NS_UCONV_REG_UNREG("ISO-8859-15", NS_ISO885915TOUNICODE_CID, NS_UNICODETOISO885915_CID) -NS_UCONV_REG_UNREG("ISO-8859-16", NS_ISO885916TOUNICODE_CID, NS_UNICODETOISO885916_CID) -NS_UCONV_REG_UNREG("ISO-IR-111", NS_ISOIR111TOUNICODE_CID, NS_UNICODETOISOIR111_CID) -NS_UCONV_REG_UNREG("windows-1250", NS_CP1250TOUNICODE_CID, NS_UNICODETOCP1250_CID) -NS_UCONV_REG_UNREG("windows-1251", NS_CP1251TOUNICODE_CID, NS_UNICODETOCP1251_CID) -NS_UCONV_REG_UNREG("windows-1253", NS_CP1253TOUNICODE_CID, NS_UNICODETOCP1253_CID) -NS_UCONV_REG_UNREG("windows-1254", NS_CP1254TOUNICODE_CID, NS_UNICODETOCP1254_CID) -NS_UCONV_REG_UNREG("windows-1255", NS_CP1255TOUNICODE_CID, NS_UNICODETOCP1255_CID) -NS_UCONV_REG_UNREG("windows-1256", NS_CP1256TOUNICODE_CID, NS_UNICODETOCP1256_CID) -NS_UCONV_REG_UNREG("windows-1257", NS_CP1257TOUNICODE_CID, NS_UNICODETOCP1257_CID) -NS_UCONV_REG_UNREG("windows-1258", NS_CP1258TOUNICODE_CID, NS_UNICODETOCP1258_CID) -NS_UCONV_REG_UNREG("TIS-620", NS_TIS620TOUNICODE_CID, NS_UNICODETOTIS620_CID) -NS_UCONV_REG_UNREG("windows-874", NS_CP874TOUNICODE_CID, NS_UNICODETOCP874_CID) -NS_UCONV_REG_UNREG("ISO-8859-11", NS_ISO885911TOUNICODE_CID, NS_UNICODETOISO885911_CID) -NS_UCONV_REG_UNREG("IBM866", NS_CP866TOUNICODE_CID, NS_UNICODETOCP866_CID) -NS_UCONV_REG_UNREG("KOI8-R", NS_KOI8RTOUNICODE_CID, NS_UNICODETOKOI8R_CID) -NS_UCONV_REG_UNREG("KOI8-U", NS_KOI8UTOUNICODE_CID, NS_UNICODETOKOI8U_CID) -NS_UCONV_REG_UNREG("x-mac-ce", NS_MACCETOUNICODE_CID, NS_UNICODETOMACCE_CID) -NS_UCONV_REG_UNREG("x-mac-greek", NS_MACGREEKTOUNICODE_CID, NS_UNICODETOMACGREEK_CID) -NS_UCONV_REG_UNREG("x-mac-turkish", NS_MACTURKISHTOUNICODE_CID, NS_UNICODETOMACTURKISH_CID) -NS_UCONV_REG_UNREG("x-mac-croatian", NS_MACCROATIANTOUNICODE_CID, NS_UNICODETOMACCROATIAN_CID) -NS_UCONV_REG_UNREG("x-mac-romanian", NS_MACROMANIANTOUNICODE_CID, NS_UNICODETOMACROMANIAN_CID) -NS_UCONV_REG_UNREG("x-mac-cyrillic", NS_MACCYRILLICTOUNICODE_CID, NS_UNICODETOMACCYRILLIC_CID) -NS_UCONV_REG_UNREG("x-mac-icelandic", NS_MACICELANDICTOUNICODE_CID, NS_UNICODETOMACICELANDIC_CID) -NS_UCONV_REG_UNREG("GEOSTD8", NS_GEOSTD8TOUNICODE_CID, NS_UNICODETOGEOSTD8_CID) -NS_UCONV_REG_UNREG("armscii-8", NS_ARMSCII8TOUNICODE_CID, NS_UNICODETOARMSCII8_CID) -NS_UCONV_REG_UNREG("x-viet-tcvn5712", NS_TCVN5712TOUNICODE_CID, NS_UNICODETOTCVN5712_CID) -NS_UCONV_REG_UNREG("VISCII", NS_VISCIITOUNICODE_CID, NS_UNICODETOVISCII_CID) -NS_UCONV_REG_UNREG("x-viet-vps", NS_VPSTOUNICODE_CID, NS_UNICODETOVPS_CID) -NS_UCONV_REG_UNREG("UTF-7", NS_UTF7TOUNICODE_CID, NS_UNICODETOUTF7_CID) -NS_UCONV_REG_UNREG("x-imap4-modified-utf7", NS_MUTF7TOUNICODE_CID, NS_UNICODETOMUTF7_CID) -NS_UCONV_REG_UNREG("UTF-16", NS_UTF16TOUNICODE_CID, NS_UNICODETOUTF16_CID) -NS_UCONV_REG_UNREG("UTF-16BE", NS_UTF16BETOUNICODE_CID, NS_UNICODETOUTF16BE_CID) -NS_UCONV_REG_UNREG("UTF-16LE", NS_UTF16LETOUNICODE_CID, NS_UNICODETOUTF16LE_CID) -NS_UCONV_REG_UNREG("T.61-8bit", NS_T61TOUNICODE_CID, NS_UNICODETOT61_CID) -NS_UCONV_REG_UNREG("x-user-defined", NS_USERDEFINEDTOUNICODE_CID, NS_UNICODETOUSERDEFINED_CID) -NS_UCONV_REG_UNREG("x-mac-arabic" , NS_MACARABICTOUNICODE_CID, NS_UNICODETOMACARABIC_CID) -NS_UCONV_REG_UNREG("x-mac-devanagari" , NS_MACDEVANAGARITOUNICODE_CID, NS_UNICODETOMACDEVANAGARI_CID) -NS_UCONV_REG_UNREG("x-mac-farsi" , NS_MACFARSITOUNICODE_CID, NS_UNICODETOMACFARSI_CID) -NS_UCONV_REG_UNREG("x-mac-gurmukhi" , NS_MACGURMUKHITOUNICODE_CID, NS_UNICODETOMACGURMUKHI_CID) -NS_UCONV_REG_UNREG("x-mac-gujarati" , NS_MACGUJARATITOUNICODE_CID, NS_UNICODETOMACGUJARATI_CID) -NS_UCONV_REG_UNREG("x-mac-hebrew" , NS_MACHEBREWTOUNICODE_CID, NS_UNICODETOMACHEBREW_CID) - -NS_UCONV_REG_UNREG_ENCODER("Adobe-Symbol-Encoding" , NS_UNICODETOSYMBOL_CID) -NS_UCONV_REG_UNREG_ENCODER("x-zapf-dingbats" , NS_UNICODETOZAPFDINGBATS_CID) -NS_UCONV_REG_UNREG_ENCODER("x-tscii", NS_UNICODETOTSCII_CID) -NS_UCONV_REG_UNREG_ENCODER("x-tamilttf-0", NS_UNICODETOTAMILTTF_CID) - - // ucvibm -NS_UCONV_REG_UNREG("IBM850", NS_CP850TOUNICODE_CID, NS_UNICODETOCP850_CID) -NS_UCONV_REG_UNREG("IBM852", NS_CP852TOUNICODE_CID, NS_UNICODETOCP852_CID) -NS_UCONV_REG_UNREG("IBM855", NS_CP855TOUNICODE_CID, NS_UNICODETOCP855_CID) -NS_UCONV_REG_UNREG("IBM857", NS_CP857TOUNICODE_CID, NS_UNICODETOCP857_CID) -NS_UCONV_REG_UNREG("IBM862", NS_CP862TOUNICODE_CID, NS_UNICODETOCP862_CID) -NS_UCONV_REG_UNREG("IBM864", NS_CP864TOUNICODE_CID, NS_UNICODETOCP864_CID) -NS_UCONV_REG_UNREG("IBM864i", NS_CP864ITOUNICODE_CID, NS_UNICODETOCP864I_CID) -#ifdef XP_OS2 -NS_UCONV_REG_UNREG("IBM869", NS_CP869TOUNICODE_CID, NS_UNICODETOCP869_CID) -NS_UCONV_REG_UNREG("IBM1125", NS_CP1125TOUNICODE_CID, NS_UNICODETOCP1125_CID) -NS_UCONV_REG_UNREG("IBM1131", NS_CP1131TOUNICODE_CID, NS_UNICODETOCP1131_CID) -#endif - - // ucvja -NS_UCONV_REG_UNREG("Shift_JIS", NS_SJISTOUNICODE_CID, NS_UNICODETOSJIS_CID) -NS_UCONV_REG_UNREG("ISO-2022-JP", NS_ISO2022JPTOUNICODE_CID, NS_UNICODETOISO2022JP_CID) -NS_UCONV_REG_UNREG("EUC-JP", NS_EUCJPTOUNICODE_CID, NS_UNICODETOEUCJP_CID) - -NS_UCONV_REG_UNREG_ENCODER("jis_0201" , NS_UNICODETOJISX0201_CID) - - // ucvtw2 -NS_UCONV_REG_UNREG("x-euc-tw", NS_EUCTWTOUNICODE_CID, NS_UNICODETOEUCTW_CID) - - // ucvtw -NS_UCONV_REG_UNREG("Big5", NS_BIG5TOUNICODE_CID, NS_UNICODETOBIG5_CID) -NS_UCONV_REG_UNREG("Big5-HKSCS", NS_BIG5HKSCSTOUNICODE_CID, NS_UNICODETOBIG5HKSCS_CID) - -NS_UCONV_REG_UNREG_ENCODER("hkscs-1" , NS_UNICODETOHKSCS_CID) - - // ucvko -NS_UCONV_REG_UNREG("EUC-KR", NS_EUCKRTOUNICODE_CID, NS_UNICODETOEUCKR_CID) -NS_UCONV_REG_UNREG("x-johab", NS_JOHABTOUNICODE_CID, NS_UNICODETOJOHAB_CID) -NS_UCONV_REG_UNREG("x-windows-949", NS_CP949TOUNICODE_CID, NS_UNICODETOCP949_CID) -NS_UCONV_REG_UNREG_DECODER("ISO-2022-KR", NS_ISO2022KRTOUNICODE_CID) - -// ucvcn -NS_UCONV_REG_UNREG("GB2312", NS_GB2312TOUNICODE_CID, NS_UNICODETOGB2312_CID) -NS_UCONV_REG_UNREG("gbk", NS_GBKTOUNICODE_CID, NS_UNICODETOGBK_CID) -NS_UCONV_REG_UNREG("HZ-GB-2312", NS_HZTOUNICODE_CID, NS_UNICODETOHZ_CID) -NS_UCONV_REG_UNREG("gb18030", NS_GB18030TOUNICODE_CID, NS_UNICODETOGB18030_CID) -NS_UCONV_REG_UNREG_DECODER("ISO-2022-CN", NS_ISO2022CNTOUNICODE_CID) - -NS_CONVERTER_REGISTRY_END - -// ucvlatin -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF7ToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsMUTF7ToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF16ToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF16BEToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF16LEToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF7) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToMUTF7) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF16BE) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF16LE) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF16) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToTSCII) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToTamilTTF) - -// ucvibm - -// ucvja -NS_GENERIC_FACTORY_CONSTRUCTOR(nsShiftJISToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsEUCJPToUnicodeV2) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsISO2022JPToUnicodeV2) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToISO2022JP) - -// ucvtw2 - -// ucvtw - -// ucvko -NS_GENERIC_FACTORY_CONSTRUCTOR(nsISO2022KRToUnicode) - -// ucvcn -NS_GENERIC_FACTORY_CONSTRUCTOR(nsGB2312ToUnicodeV2) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToGB2312V2) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsGBKToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToGBK) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsHZToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToHZ) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsGB18030ToUnicode) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToGB18030) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsISO2022CNToUnicode) - - -//---------------------------------------------------------------------------- -// Global functions and data [declaration] - -// ucvja -const PRUint16 g_uf0201Mapping[] = { -#include "jis0201.uf" -}; - -const PRUint16 g_uf0201GLMapping[] = { -#include "jis0201gl.uf" -}; - -const PRUint16 g_uf0208Mapping[] = { -#include "jis0208.uf" -}; - -const PRUint16 g_uf0208extMapping[] = { -#include "jis0208ext.uf" -}; - -// ucvtw2 -const PRUint16 g_ufCNS1MappingTable[] = { -#include "cns_1.uf" -}; - -const PRUint16 g_ufCNS2MappingTable[] = { -#include "cns_2.uf" -}; - -const PRUint16 g_ufCNS3MappingTable[] = { -#include "cns3.uf" -}; - -const PRUint16 g_ufCNS4MappingTable[] = { -#include "cns4.uf" -}; - -const PRUint16 g_ufCNS5MappingTable[] = { -#include "cns5.uf" -}; - -const PRUint16 g_ufCNS6MappingTable[] = { -#include "cns6.uf" -}; - -const PRUint16 g_ufCNS7MappingTable[] = { -#include "cns7.uf" -}; - -const PRUint16 g_utCNS1MappingTable[] = { -#include "cns_1.ut" -}; - -const PRUint16 g_utCNS2MappingTable[] = { -#include "cns_2.ut" -}; - -const PRUint16 g_utCNS3MappingTable[] = { -#include "cns3.ut" -}; - -const PRUint16 g_utCNS4MappingTable[] = { -#include "cns4.ut" -}; - -const PRUint16 g_utCNS5MappingTable[] = { -#include "cns5.ut" -}; - -const PRUint16 g_utCNS6MappingTable[] = { -#include "cns6.ut" -}; - -const PRUint16 g_utCNS7MappingTable[] = { -#include "cns7.ut" -}; - -const PRUint16 g_ASCIIMappingTable[] = { - 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0x0000, 0x007F, 0x0000 -}; - -// ucvtw -const PRUint16 g_ufBig5Mapping[] = { -#include "big5.uf" -}; - -const PRUint16 g_utBIG5Mapping[] = { -#include "big5.ut" -}; - -const PRUint16 g_ufBig5HKSCSMapping[] = { -#include "hkscs.uf" -}; - -const PRUint16 g_ASCIIMapping[] = { - 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0x0000, 0x007F, 0x0000 -}; - -const PRUint16 g_utBig5HKSCSMapping[] = { -#include "hkscs.ut" -}; - -// ucvko -const PRUint16 g_utKSC5601Mapping[] = { -#include "u20kscgl.ut" -}; - -const PRUint16 g_ufKSC5601Mapping[] = { -#include "u20kscgl.uf" -}; - -const PRUint16 g_ucvko_AsciiMapping[] = { - 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0x0000, 0x007F, 0x0000 -}; - -const PRUint16 g_HangulNullMapping[] ={ - 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0xAC00, 0xD7A3, 0xAC00 -}; - -const PRUint16 g_ufJohabJamoMapping[] ={ -#include "johabjamo.uf" -}; - -NS_DEFINE_NAMED_CID(NS_ASCIITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88592TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88593TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88594TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88595TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88596TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88596ITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88596ETOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88597TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88598TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88598ITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88598ETOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO88599TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO885910TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO885913TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO885914TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO885915TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO885916TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISOIR111TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1250TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1251TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1253TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1254TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1255TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1256TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1257TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1258TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_TIS620TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ISO885911TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP874TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP866TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_KOI8RTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_KOI8UTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACCETOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACGREEKTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACTURKISHTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACCROATIANTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACROMANIANTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACCYRILLICTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACICELANDICTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_GEOSTD8TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_ARMSCII8TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_TCVN5712TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_VISCIITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_VPSTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UTF7TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MUTF7TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UTF16TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UTF16BETOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UTF16LETOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_T61TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_USERDEFINEDTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACARABICTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACDEVANAGARITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACFARSITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACGURMUKHITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACGUJARATITOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_MACHEBREWTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOASCII_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88592_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88593_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88594_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88595_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88596_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88596I_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88596E_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88597_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88598_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88598I_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88598E_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO88599_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO885910_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO885913_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO885914_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO885915_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO885916_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISOIR111_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1250_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1251_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1253_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1254_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1255_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1256_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1257_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1258_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOTIS620_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO885911_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP874_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP866_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOKOI8R_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOKOI8U_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACCE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACGREEK_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACTURKISH_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACCROATIAN_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACROMANIAN_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACCYRILLIC_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACICELANDIC_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOGEOSTD8_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOARMSCII8_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOTCVN5712_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOVISCII_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOVPS_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOUTF7_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMUTF7_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOUTF16BE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOUTF16LE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOUTF16_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOT61_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOUSERDEFINED_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOSYMBOL_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOZAPFDINGBATS_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOADOBEEURO_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACARABIC_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACDEVANAGARI_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACFARSI_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACGURMUKHI_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACGUJARATI_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOMACHEBREW_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOTSCII_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOTAMILTTF_CID); -NS_DEFINE_NAMED_CID(NS_CP850TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP852TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP855TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP857TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP862TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP864TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP864ITOUNICODE_CID); -#ifdef XP_OS2 -NS_DEFINE_NAMED_CID(NS_CP869TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1125TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_CP1131TOUNICODE_CID); -#endif -NS_DEFINE_NAMED_CID(NS_UNICODETOCP850_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP852_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP855_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP857_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP862_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP864_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP864I_CID); -#ifdef XP_OS2 -NS_DEFINE_NAMED_CID(NS_UNICODETOCP869_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1125_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP1131_CID); -#endif -NS_DEFINE_NAMED_CID(NS_SJISTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_EUCJPTOUNICODE_CID); -NS_DEFINE_NAMED_CID( NS_ISO2022JPTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOSJIS_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOEUCJP_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOISO2022JP_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOJISX0201_CID); -NS_DEFINE_NAMED_CID(NS_EUCTWTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOEUCTW_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOBIG5_CID); -NS_DEFINE_NAMED_CID(NS_BIG5TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOBIG5HKSCS_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOHKSCS_CID); -NS_DEFINE_NAMED_CID(NS_BIG5HKSCSTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_EUCKRTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOEUCKR_CID); -NS_DEFINE_NAMED_CID(NS_JOHABTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOJOHAB_CID); -NS_DEFINE_NAMED_CID(NS_CP949TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOCP949_CID); -NS_DEFINE_NAMED_CID(NS_ISO2022KRTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_GB2312TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOGB2312_CID); -NS_DEFINE_NAMED_CID(NS_GBKTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOGBK_CID); -NS_DEFINE_NAMED_CID(NS_HZTOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOHZ_CID); -NS_DEFINE_NAMED_CID(NS_GB18030TOUNICODE_CID); -NS_DEFINE_NAMED_CID(NS_UNICODETOGB18030_CID); -NS_DEFINE_NAMED_CID(NS_ISO2022CNTOUNICODE_CID); - -static const mozilla::Module::CIDEntry kUConvCIDs[] = { - { &kNS_ASCIITOUNICODE_CID, false, NULL, nsAsciiToUnicodeConstructor }, - { &kNS_ISO88592TOUNICODE_CID, false, NULL, nsISO88592ToUnicodeConstructor }, - { &kNS_ISO88593TOUNICODE_CID, false, NULL, nsISO88593ToUnicodeConstructor }, - { &kNS_ISO88594TOUNICODE_CID, false, NULL, nsISO88594ToUnicodeConstructor }, - { &kNS_ISO88595TOUNICODE_CID, false, NULL, nsISO88595ToUnicodeConstructor }, - { &kNS_ISO88596TOUNICODE_CID, false, NULL, nsISO88596ToUnicodeConstructor }, - { &kNS_ISO88596ITOUNICODE_CID, false, NULL, nsISO88596IToUnicodeConstructor }, - { &kNS_ISO88596ETOUNICODE_CID, false, NULL, nsISO88596EToUnicodeConstructor }, - { &kNS_ISO88597TOUNICODE_CID, false, NULL, nsISO88597ToUnicodeConstructor }, - { &kNS_ISO88598TOUNICODE_CID, false, NULL, nsISO88598ToUnicodeConstructor }, - { &kNS_ISO88598ITOUNICODE_CID, false, NULL, nsISO88598IToUnicodeConstructor }, - { &kNS_ISO88598ETOUNICODE_CID, false, NULL, nsISO88598EToUnicodeConstructor }, - { &kNS_ISO88599TOUNICODE_CID, false, NULL, nsISO88599ToUnicodeConstructor }, - { &kNS_ISO885910TOUNICODE_CID, false, NULL, nsISO885910ToUnicodeConstructor }, - { &kNS_ISO885913TOUNICODE_CID, false, NULL, nsISO885913ToUnicodeConstructor }, - { &kNS_ISO885914TOUNICODE_CID, false, NULL, nsISO885914ToUnicodeConstructor }, - { &kNS_ISO885915TOUNICODE_CID, false, NULL, nsISO885915ToUnicodeConstructor }, - { &kNS_ISO885916TOUNICODE_CID, false, NULL, nsISO885916ToUnicodeConstructor }, - { &kNS_ISOIR111TOUNICODE_CID, false, NULL, nsISOIR111ToUnicodeConstructor }, - { &kNS_CP1250TOUNICODE_CID, false, NULL, nsCP1250ToUnicodeConstructor }, - { &kNS_CP1251TOUNICODE_CID, false, NULL, nsCP1251ToUnicodeConstructor }, - { &kNS_CP1253TOUNICODE_CID, false, NULL, nsCP1253ToUnicodeConstructor }, - { &kNS_CP1254TOUNICODE_CID, false, NULL, nsCP1254ToUnicodeConstructor }, - { &kNS_CP1255TOUNICODE_CID, false, NULL, nsCP1255ToUnicodeConstructor }, - { &kNS_CP1256TOUNICODE_CID, false, NULL, nsCP1256ToUnicodeConstructor }, - { &kNS_CP1257TOUNICODE_CID, false, NULL, nsCP1257ToUnicodeConstructor }, - { &kNS_CP1258TOUNICODE_CID, false, NULL, nsCP1258ToUnicodeConstructor }, - { &kNS_TIS620TOUNICODE_CID, false, NULL, nsTIS620ToUnicodeConstructor }, - { &kNS_ISO885911TOUNICODE_CID, false, NULL, nsISO885911ToUnicodeConstructor }, - { &kNS_CP874TOUNICODE_CID, false, NULL, nsCP874ToUnicodeConstructor }, - { &kNS_CP866TOUNICODE_CID, false, NULL, nsCP866ToUnicodeConstructor }, - { &kNS_KOI8RTOUNICODE_CID, false, NULL, nsKOI8RToUnicodeConstructor }, - { &kNS_KOI8UTOUNICODE_CID, false, NULL, nsKOI8UToUnicodeConstructor }, - { &kNS_MACCETOUNICODE_CID, false, NULL, nsMacCEToUnicodeConstructor }, - { &kNS_MACGREEKTOUNICODE_CID, false, NULL, nsMacGreekToUnicodeConstructor }, - { &kNS_MACTURKISHTOUNICODE_CID, false, NULL, nsMacTurkishToUnicodeConstructor }, - { &kNS_MACCROATIANTOUNICODE_CID, false, NULL, nsMacCroatianToUnicodeConstructor }, - { &kNS_MACROMANIANTOUNICODE_CID, false, NULL, nsMacRomanianToUnicodeConstructor }, - { &kNS_MACCYRILLICTOUNICODE_CID, false, NULL, nsMacCyrillicToUnicodeConstructor }, - { &kNS_MACICELANDICTOUNICODE_CID, false, NULL, nsMacIcelandicToUnicodeConstructor }, - { &kNS_GEOSTD8TOUNICODE_CID, false, NULL, nsGEOSTD8ToUnicodeConstructor }, - { &kNS_ARMSCII8TOUNICODE_CID, false, NULL, nsARMSCII8ToUnicodeConstructor }, - { &kNS_TCVN5712TOUNICODE_CID, false, NULL, nsTCVN5712ToUnicodeConstructor }, - { &kNS_VISCIITOUNICODE_CID, false, NULL, nsVISCIIToUnicodeConstructor }, - { &kNS_VPSTOUNICODE_CID, false, NULL, nsVPSToUnicodeConstructor }, - { &kNS_UTF7TOUNICODE_CID, false, NULL, nsUTF7ToUnicodeConstructor }, - { &kNS_MUTF7TOUNICODE_CID, false, NULL, nsMUTF7ToUnicodeConstructor }, - { &kNS_UTF16TOUNICODE_CID, false, NULL, nsUTF16ToUnicodeConstructor }, - { &kNS_UTF16BETOUNICODE_CID, false, NULL, nsUTF16BEToUnicodeConstructor }, - { &kNS_UTF16LETOUNICODE_CID, false, NULL, nsUTF16LEToUnicodeConstructor }, - { &kNS_T61TOUNICODE_CID, false, NULL, nsT61ToUnicodeConstructor }, - { &kNS_USERDEFINEDTOUNICODE_CID, false, NULL, nsUserDefinedToUnicodeConstructor }, - { &kNS_MACARABICTOUNICODE_CID, false, NULL, nsMacArabicToUnicodeConstructor }, - { &kNS_MACDEVANAGARITOUNICODE_CID, false, NULL, nsMacDevanagariToUnicodeConstructor }, - { &kNS_MACFARSITOUNICODE_CID, false, NULL, nsMacFarsiToUnicodeConstructor }, - { &kNS_MACGURMUKHITOUNICODE_CID, false, NULL, nsMacGurmukhiToUnicodeConstructor }, - { &kNS_MACGUJARATITOUNICODE_CID, false, NULL, nsMacGujaratiToUnicodeConstructor }, - { &kNS_MACHEBREWTOUNICODE_CID, false, NULL, nsMacHebrewToUnicodeConstructor }, - { &kNS_UNICODETOASCII_CID, false, NULL, nsUnicodeToAsciiConstructor }, - { &kNS_UNICODETOISO88592_CID, false, NULL, nsUnicodeToISO88592Constructor }, - { &kNS_UNICODETOISO88593_CID, false, NULL, nsUnicodeToISO88593Constructor }, - { &kNS_UNICODETOISO88594_CID, false, NULL, nsUnicodeToISO88594Constructor }, - { &kNS_UNICODETOISO88595_CID, false, NULL, nsUnicodeToISO88595Constructor }, - { &kNS_UNICODETOISO88596_CID, false, NULL, nsUnicodeToISO88596Constructor }, - { &kNS_UNICODETOISO88596I_CID, false, NULL, nsUnicodeToISO88596IConstructor }, - { &kNS_UNICODETOISO88596E_CID, false, NULL, nsUnicodeToISO88596EConstructor }, - { &kNS_UNICODETOISO88597_CID, false, NULL, nsUnicodeToISO88597Constructor }, - { &kNS_UNICODETOISO88598_CID, false, NULL, nsUnicodeToISO88598Constructor }, - { &kNS_UNICODETOISO88598I_CID, false, NULL, nsUnicodeToISO88598IConstructor }, - { &kNS_UNICODETOISO88598E_CID, false, NULL, nsUnicodeToISO88598EConstructor }, - { &kNS_UNICODETOISO88599_CID, false, NULL, nsUnicodeToISO88599Constructor }, - { &kNS_UNICODETOISO885910_CID, false, NULL, nsUnicodeToISO885910Constructor }, - { &kNS_UNICODETOISO885913_CID, false, NULL, nsUnicodeToISO885913Constructor }, - { &kNS_UNICODETOISO885914_CID, false, NULL, nsUnicodeToISO885914Constructor }, - { &kNS_UNICODETOISO885915_CID, false, NULL, nsUnicodeToISO885915Constructor }, - { &kNS_UNICODETOISO885916_CID, false, NULL, nsUnicodeToISO885916Constructor }, - { &kNS_UNICODETOISOIR111_CID, false, NULL, nsUnicodeToISOIR111Constructor }, - { &kNS_UNICODETOCP1250_CID, false, NULL, nsUnicodeToCP1250Constructor }, - { &kNS_UNICODETOCP1251_CID, false, NULL, nsUnicodeToCP1251Constructor }, - { &kNS_UNICODETOCP1253_CID, false, NULL, nsUnicodeToCP1253Constructor }, - { &kNS_UNICODETOCP1254_CID, false, NULL, nsUnicodeToCP1254Constructor }, - { &kNS_UNICODETOCP1255_CID, false, NULL, nsUnicodeToCP1255Constructor }, - { &kNS_UNICODETOCP1256_CID, false, NULL, nsUnicodeToCP1256Constructor }, - { &kNS_UNICODETOCP1257_CID, false, NULL, nsUnicodeToCP1257Constructor }, - { &kNS_UNICODETOCP1258_CID, false, NULL, nsUnicodeToCP1258Constructor }, - { &kNS_UNICODETOTIS620_CID, false, NULL, nsUnicodeToTIS620Constructor }, - { &kNS_UNICODETOISO885911_CID, false, NULL, nsUnicodeToISO885911Constructor }, - { &kNS_UNICODETOCP874_CID, false, NULL, nsUnicodeToCP874Constructor }, - { &kNS_UNICODETOCP866_CID, false, NULL, nsUnicodeToCP866Constructor }, - { &kNS_UNICODETOKOI8R_CID, false, NULL, nsUnicodeToKOI8RConstructor }, - { &kNS_UNICODETOKOI8U_CID, false, NULL, nsUnicodeToKOI8UConstructor }, - { &kNS_UNICODETOMACCE_CID, false, NULL, nsUnicodeToMacCEConstructor }, - { &kNS_UNICODETOMACGREEK_CID, false, NULL, nsUnicodeToMacGreekConstructor }, - { &kNS_UNICODETOMACTURKISH_CID, false, NULL, nsUnicodeToMacTurkishConstructor }, - { &kNS_UNICODETOMACCROATIAN_CID, false, NULL, nsUnicodeToMacCroatianConstructor }, - { &kNS_UNICODETOMACROMANIAN_CID, false, NULL, nsUnicodeToMacRomanianConstructor }, - { &kNS_UNICODETOMACCYRILLIC_CID, false, NULL, nsUnicodeToMacCyrillicConstructor }, - { &kNS_UNICODETOMACICELANDIC_CID, false, NULL, nsUnicodeToMacIcelandicConstructor }, - { &kNS_UNICODETOGEOSTD8_CID, false, NULL, nsUnicodeToGEOSTD8Constructor }, - { &kNS_UNICODETOARMSCII8_CID, false, NULL, nsUnicodeToARMSCII8Constructor }, - { &kNS_UNICODETOTCVN5712_CID, false, NULL, nsUnicodeToTCVN5712Constructor }, - { &kNS_UNICODETOVISCII_CID, false, NULL, nsUnicodeToVISCIIConstructor }, - { &kNS_UNICODETOVPS_CID, false, NULL, nsUnicodeToVPSConstructor }, - { &kNS_UNICODETOUTF7_CID, false, NULL, nsUnicodeToUTF7Constructor }, - { &kNS_UNICODETOMUTF7_CID, false, NULL, nsUnicodeToMUTF7Constructor }, - { &kNS_UNICODETOUTF16BE_CID, false, NULL, nsUnicodeToUTF16BEConstructor }, - { &kNS_UNICODETOUTF16LE_CID, false, NULL, nsUnicodeToUTF16LEConstructor }, - { &kNS_UNICODETOUTF16_CID, false, NULL, nsUnicodeToUTF16Constructor }, - { &kNS_UNICODETOT61_CID, false, NULL, nsUnicodeToT61Constructor }, - { &kNS_UNICODETOUSERDEFINED_CID, false, NULL, nsUnicodeToUserDefinedConstructor }, - { &kNS_UNICODETOSYMBOL_CID, false, NULL, nsUnicodeToSymbolConstructor }, - { &kNS_UNICODETOZAPFDINGBATS_CID, false, NULL, nsUnicodeToZapfDingbatConstructor }, - { &kNS_UNICODETOADOBEEURO_CID, false, NULL, nsUnicodeToAdobeEuroConstructor }, - { &kNS_UNICODETOMACARABIC_CID, false, NULL, nsUnicodeToMacArabicConstructor }, - { &kNS_UNICODETOMACDEVANAGARI_CID, false, NULL, nsUnicodeToMacDevanagariConstructor }, - { &kNS_UNICODETOMACFARSI_CID, false, NULL, nsUnicodeToMacFarsiConstructor }, - { &kNS_UNICODETOMACGURMUKHI_CID, false, NULL, nsUnicodeToMacGurmukhiConstructor }, - { &kNS_UNICODETOMACGUJARATI_CID, false, NULL, nsUnicodeToMacGujaratiConstructor }, - { &kNS_UNICODETOMACHEBREW_CID, false, NULL, nsUnicodeToMacHebrewConstructor }, - { &kNS_UNICODETOTSCII_CID, false, NULL, nsUnicodeToTSCIIConstructor }, - { &kNS_UNICODETOTAMILTTF_CID, false, NULL, nsUnicodeToTamilTTFConstructor }, - { &kNS_CP850TOUNICODE_CID, false, NULL, nsCP850ToUnicodeConstructor }, - { &kNS_CP852TOUNICODE_CID, false, NULL, nsCP852ToUnicodeConstructor }, - { &kNS_CP855TOUNICODE_CID, false, NULL, nsCP855ToUnicodeConstructor }, - { &kNS_CP857TOUNICODE_CID, false, NULL, nsCP857ToUnicodeConstructor }, - { &kNS_CP862TOUNICODE_CID, false, NULL, nsCP862ToUnicodeConstructor }, - { &kNS_CP864TOUNICODE_CID, false, NULL, nsCP864ToUnicodeConstructor }, - { &kNS_CP864ITOUNICODE_CID, false, NULL, nsCP864iToUnicodeConstructor }, -#ifdef XP_OS2 - { &kNS_CP869TOUNICODE_CID, false, NULL, nsCP869ToUnicodeConstructor }, - { &kNS_CP1125TOUNICODE_CID, false, NULL, nsCP1125ToUnicodeConstructor }, - { &kNS_CP1131TOUNICODE_CID, false, NULL, nsCP1131ToUnicodeConstructor }, -#endif - { &kNS_UNICODETOCP850_CID, false, NULL, nsUnicodeToCP850Constructor }, - { &kNS_UNICODETOCP852_CID, false, NULL, nsUnicodeToCP852Constructor }, - { &kNS_UNICODETOCP855_CID, false, NULL, nsUnicodeToCP855Constructor }, - { &kNS_UNICODETOCP857_CID, false, NULL, nsUnicodeToCP857Constructor }, - { &kNS_UNICODETOCP862_CID, false, NULL, nsUnicodeToCP862Constructor }, - { &kNS_UNICODETOCP864_CID, false, NULL, nsUnicodeToCP864Constructor }, - { &kNS_UNICODETOCP864I_CID, false, NULL, nsUnicodeToCP864iConstructor }, -#ifdef XP_OS2 - { &kNS_UNICODETOCP869_CID, false, NULL, nsUnicodeToCP869Constructor }, - { &kNS_UNICODETOCP1125_CID, false, NULL, nsUnicodeToCP1125Constructor }, - { &kNS_UNICODETOCP1131_CID, false, NULL, nsUnicodeToCP1131Constructor }, -#endif - { &kNS_SJISTOUNICODE_CID, false, NULL, nsShiftJISToUnicodeConstructor }, - { &kNS_EUCJPTOUNICODE_CID, false, NULL, nsEUCJPToUnicodeV2Constructor }, - { &kNS_ISO2022JPTOUNICODE_CID, false, NULL, nsISO2022JPToUnicodeV2Constructor }, - { &kNS_UNICODETOSJIS_CID, false, NULL, nsUnicodeToSJISConstructor }, - { &kNS_UNICODETOEUCJP_CID, false, NULL, nsUnicodeToEUCJPConstructor }, - { &kNS_UNICODETOISO2022JP_CID, false, NULL, nsUnicodeToISO2022JPConstructor }, - { &kNS_UNICODETOJISX0201_CID, false, NULL, nsUnicodeToJISx0201Constructor }, - { &kNS_EUCTWTOUNICODE_CID, false, NULL, nsEUCTWToUnicodeConstructor }, - { &kNS_UNICODETOEUCTW_CID, false, NULL, nsUnicodeToEUCTWConstructor }, - { &kNS_UNICODETOBIG5_CID, false, NULL, nsUnicodeToBIG5Constructor }, - { &kNS_BIG5TOUNICODE_CID, false, NULL, nsBIG5ToUnicodeConstructor }, - { &kNS_UNICODETOBIG5HKSCS_CID, false, NULL, nsUnicodeToBIG5HKSCSConstructor }, - { &kNS_UNICODETOHKSCS_CID, false, NULL, nsUnicodeToHKSCSConstructor }, - { &kNS_BIG5HKSCSTOUNICODE_CID, false, NULL, nsBIG5HKSCSToUnicodeConstructor }, - { &kNS_EUCKRTOUNICODE_CID, false, NULL, nsEUCKRToUnicodeConstructor }, - { &kNS_UNICODETOEUCKR_CID, false, NULL, nsUnicodeToEUCKRConstructor }, - { &kNS_JOHABTOUNICODE_CID, false, NULL, nsJohabToUnicodeConstructor }, - { &kNS_UNICODETOJOHAB_CID, false, NULL, nsUnicodeToJohabConstructor }, - { &kNS_CP949TOUNICODE_CID, false, NULL, nsCP949ToUnicodeConstructor }, - { &kNS_UNICODETOCP949_CID, false, NULL, nsUnicodeToCP949Constructor }, - { &kNS_ISO2022KRTOUNICODE_CID, false, NULL, nsISO2022KRToUnicodeConstructor }, - { &kNS_GB2312TOUNICODE_CID, false, NULL, nsGB2312ToUnicodeV2Constructor }, - { &kNS_UNICODETOGB2312_CID, false, NULL, nsUnicodeToGB2312V2Constructor }, - { &kNS_GBKTOUNICODE_CID, false, NULL, nsGBKToUnicodeConstructor }, - { &kNS_UNICODETOGBK_CID, false, NULL, nsUnicodeToGBKConstructor }, - { &kNS_HZTOUNICODE_CID, false, NULL, nsHZToUnicodeConstructor }, - { &kNS_UNICODETOHZ_CID, false, NULL, nsUnicodeToHZConstructor }, - { &kNS_GB18030TOUNICODE_CID, false, NULL, nsGB18030ToUnicodeConstructor }, - { &kNS_UNICODETOGB18030_CID, false, NULL, nsUnicodeToGB18030Constructor }, - { &kNS_ISO2022CNTOUNICODE_CID, false, NULL, nsISO2022CNToUnicodeConstructor }, - { NULL }, -}; - -static const mozilla::Module::ContractIDEntry kUConvContracts[] = { - { NS_UNICODEDECODER_CONTRACTID_BASE "us-ascii", &kNS_ASCIITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-2", &kNS_ISO88592TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-3", &kNS_ISO88593TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-4", &kNS_ISO88594TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-5", &kNS_ISO88595TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-6", &kNS_ISO88596TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-6-I", &kNS_ISO88596ITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-6-E", &kNS_ISO88596ETOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-7", &kNS_ISO88597TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-8", &kNS_ISO88598TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-8-I", &kNS_ISO88598ITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-8-E", &kNS_ISO88598ETOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-9", &kNS_ISO88599TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-10", &kNS_ISO885910TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-13", &kNS_ISO885913TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-14", &kNS_ISO885914TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-15", &kNS_ISO885915TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-16", &kNS_ISO885916TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-IR-111", &kNS_ISOIR111TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1250", &kNS_CP1250TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1251", &kNS_CP1251TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1253", &kNS_CP1253TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1254", &kNS_CP1254TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1255", &kNS_CP1255TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1256", &kNS_CP1256TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1257", &kNS_CP1257TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1258", &kNS_CP1258TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "TIS-620", &kNS_TIS620TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-11", &kNS_ISO885911TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "windows-874", &kNS_CP874TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM866", &kNS_CP866TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "KOI8-R", &kNS_KOI8RTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "KOI8-U", &kNS_KOI8UTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-ce", &kNS_MACCETOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-greek", &kNS_MACGREEKTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-turkish", &kNS_MACTURKISHTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-croatian", &kNS_MACCROATIANTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-romanian", &kNS_MACROMANIANTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-cyrillic", &kNS_MACCYRILLICTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-icelandic", &kNS_MACICELANDICTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "GEOSTD8", &kNS_GEOSTD8TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "armscii-8", &kNS_ARMSCII8TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-viet-tcvn5712", &kNS_TCVN5712TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "VISCII", &kNS_VISCIITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-viet-vps", &kNS_VPSTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-7", &kNS_UTF7TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-imap4-modified-utf7", &kNS_MUTF7TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-16", &kNS_UTF16TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-16BE", &kNS_UTF16BETOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-16LE", &kNS_UTF16LETOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "T.61-8bit", &kNS_T61TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-user-defined", &kNS_USERDEFINEDTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-arabic", &kNS_MACARABICTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-devanagari", &kNS_MACDEVANAGARITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-farsi", &kNS_MACFARSITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-gurmukhi", &kNS_MACGURMUKHITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-gujarati", &kNS_MACGUJARATITOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-hebrew", &kNS_MACHEBREWTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "us-ascii", &kNS_UNICODETOASCII_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-2", &kNS_UNICODETOISO88592_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-3", &kNS_UNICODETOISO88593_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-4", &kNS_UNICODETOISO88594_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-5", &kNS_UNICODETOISO88595_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-6", &kNS_UNICODETOISO88596_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-6-I", &kNS_UNICODETOISO88596I_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-6-E", &kNS_UNICODETOISO88596E_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-7", &kNS_UNICODETOISO88597_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-8", &kNS_UNICODETOISO88598_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-8-I", &kNS_UNICODETOISO88598I_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-8-E", &kNS_UNICODETOISO88598E_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-9", &kNS_UNICODETOISO88599_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-10", &kNS_UNICODETOISO885910_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-13", &kNS_UNICODETOISO885913_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-14", &kNS_UNICODETOISO885914_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-15", &kNS_UNICODETOISO885915_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-16", &kNS_UNICODETOISO885916_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-IR-111", &kNS_UNICODETOISOIR111_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1250", &kNS_UNICODETOCP1250_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1251", &kNS_UNICODETOCP1251_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1253", &kNS_UNICODETOCP1253_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1254", &kNS_UNICODETOCP1254_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1255", &kNS_UNICODETOCP1255_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1256", &kNS_UNICODETOCP1256_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1257", &kNS_UNICODETOCP1257_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1258", &kNS_UNICODETOCP1258_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "TIS-620", &kNS_UNICODETOTIS620_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-11", &kNS_UNICODETOISO885911_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "windows-874", &kNS_UNICODETOCP874_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM866", &kNS_UNICODETOCP866_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "KOI8-R", &kNS_UNICODETOKOI8R_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "KOI8-U", &kNS_UNICODETOKOI8U_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-ce", &kNS_UNICODETOMACCE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-greek", &kNS_UNICODETOMACGREEK_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-turkish", &kNS_UNICODETOMACTURKISH_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-croatian", &kNS_UNICODETOMACCROATIAN_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-romanian", &kNS_UNICODETOMACROMANIAN_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-cyrillic", &kNS_UNICODETOMACCYRILLIC_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-icelandic", &kNS_UNICODETOMACICELANDIC_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "GEOSTD8", &kNS_UNICODETOGEOSTD8_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "armscii-8", &kNS_UNICODETOARMSCII8_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-viet-tcvn5712", &kNS_UNICODETOTCVN5712_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "VISCII", &kNS_UNICODETOVISCII_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-viet-vps", &kNS_UNICODETOVPS_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-7", &kNS_UNICODETOUTF7_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-imap4-modified-utf7", &kNS_UNICODETOMUTF7_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-16BE", &kNS_UNICODETOUTF16BE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-16LE", &kNS_UNICODETOUTF16LE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-16", &kNS_UNICODETOUTF16_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "T.61-8bit", &kNS_UNICODETOT61_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-user-defined", &kNS_UNICODETOUSERDEFINED_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "Adobe-Symbol-Encoding", &kNS_UNICODETOSYMBOL_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-zapf-dingbats", &kNS_UNICODETOZAPFDINGBATS_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-adobe-euro", &kNS_UNICODETOADOBEEURO_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-arabic", &kNS_UNICODETOMACARABIC_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-devanagari", &kNS_UNICODETOMACDEVANAGARI_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-farsi", &kNS_UNICODETOMACFARSI_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-gurmukhi", &kNS_UNICODETOMACGURMUKHI_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-gujarati", &kNS_UNICODETOMACGUJARATI_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-hebrew", &kNS_UNICODETOMACHEBREW_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-tscii", &kNS_UNICODETOTSCII_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-tamilttf-0", &kNS_UNICODETOTAMILTTF_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM850", &kNS_CP850TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM852", &kNS_CP852TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM855", &kNS_CP855TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM857", &kNS_CP857TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM862", &kNS_CP862TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM864", &kNS_CP864TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM864i", &kNS_CP864ITOUNICODE_CID }, -#ifdef XP_OS2 - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM869", &kNS_CP869TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM1125", &kNS_CP1125TOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "IBM1131", &kNS_CP1131TOUNICODE_CID }, -#endif - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM850", &kNS_UNICODETOCP850_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM852", &kNS_UNICODETOCP852_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM855", &kNS_UNICODETOCP855_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM857", &kNS_UNICODETOCP857_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM862", &kNS_UNICODETOCP862_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM864", &kNS_UNICODETOCP864_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM864i", &kNS_UNICODETOCP864I_CID }, -#ifdef XP_OS2 - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM869", &kNS_UNICODETOCP869_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM1125", &kNS_UNICODETOCP1125_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "IBM1131", &kNS_UNICODETOCP1131_CID }, -#endif - { NS_UNICODEDECODER_CONTRACTID_BASE "Shift_JIS", &kNS_SJISTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "EUC-JP", &kNS_EUCJPTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-2022-JP", &kNS_ISO2022JPTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "Shift_JIS", &kNS_UNICODETOSJIS_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "EUC-JP", &kNS_UNICODETOEUCJP_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-2022-JP", &kNS_UNICODETOISO2022JP_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "jis_0201", &kNS_UNICODETOJISX0201_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-euc-tw", &kNS_EUCTWTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-euc-tw", &kNS_UNICODETOEUCTW_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "Big5", &kNS_UNICODETOBIG5_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "Big5", &kNS_BIG5TOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "Big5-HKSCS", &kNS_UNICODETOBIG5HKSCS_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "hkscs-1", &kNS_UNICODETOHKSCS_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "Big5-HKSCS", &kNS_BIG5HKSCSTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "EUC-KR", &kNS_EUCKRTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "EUC-KR", &kNS_UNICODETOEUCKR_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-johab", &kNS_JOHABTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-johab", &kNS_UNICODETOJOHAB_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "x-windows-949", &kNS_CP949TOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "x-windows-949", &kNS_UNICODETOCP949_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-2022-KR", &kNS_ISO2022KRTOUNICODE_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "GB2312", &kNS_GB2312TOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "GB2312", &kNS_UNICODETOGB2312_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "gbk", &kNS_GBKTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "gbk", &kNS_UNICODETOGBK_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "HZ-GB-2312", &kNS_HZTOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "HZ-GB-2312", &kNS_UNICODETOHZ_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "gb18030", &kNS_GB18030TOUNICODE_CID }, - { NS_UNICODEENCODER_CONTRACTID_BASE "gb18030", &kNS_UNICODETOGB18030_CID }, - { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-2022-CN", &kNS_ISO2022CNTOUNICODE_CID }, - { NULL } -}; - -static void -UConvDataModuleDtor() -{ -#ifdef DEBUG -#ifdef XP_WIN - mozilla::BlockingResourceBase::Shutdown(); -#endif -#endif -} - -static const mozilla::Module kUConvDataModule = { - mozilla::Module::kVersion, - kUConvCIDs, - kUConvContracts, - kUConvCategories, - nsnull, - nsnull, - UConvDataModuleDtor -}; - -NSMODULE_DEFN(nsUConvDataModule) = &kUConvDataModule; diff --git a/intl/uconv/src/Makefile.in b/intl/uconv/src/Makefile.in index cbe574455f2..bbabcdf677e 100644 --- a/intl/uconv/src/Makefile.in +++ b/intl/uconv/src/Makefile.in @@ -76,7 +76,25 @@ ifneq (,$(INTEL_ARCHITECTURE)) CPPSRCS += nsUTF8ToUnicodeSSE2.cpp endif -LOCAL_INCLUDES = -I$(srcdir)/../util +LOCAL_INCLUDES = -I$(srcdir)/../util \ + -I$(srcdir)/../ucvlatin \ + -I$(srcdir)/../ucvibm \ + -I$(srcdir)/../ucvja \ + -I$(srcdir)/../ucvtw2 \ + -I$(srcdir)/../ucvtw \ + -I$(srcdir)/../ucvko \ + -I$(srcdir)/../ucvcn \ + $(NULL) + +SHARED_LIBRARY_LIBS += \ + ../ucvlatin/$(LIB_PREFIX)ucvlatin_s.$(LIB_SUFFIX) \ + ../ucvibm/$(LIB_PREFIX)ucvibm_s.$(LIB_SUFFIX) \ + ../ucvja/$(LIB_PREFIX)ucvja_s.$(LIB_SUFFIX) \ + ../ucvtw2/$(LIB_PREFIX)ucvtw2_s.$(LIB_SUFFIX) \ + ../ucvtw/$(LIB_PREFIX)ucvtw_s.$(LIB_SUFFIX) \ + ../ucvko/$(LIB_PREFIX)ucvko_s.$(LIB_SUFFIX) \ + ../ucvcn/$(LIB_PREFIX)ucvcn_s.$(LIB_SUFFIX) \ + $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/intl/uconv/src/nsUConvModule.cpp b/intl/uconv/src/nsUConvModule.cpp index d61a91633d5..b7de659f3bf 100644 --- a/intl/uconv/src/nsUConvModule.cpp +++ b/intl/uconv/src/nsUConvModule.cpp @@ -72,12 +72,317 @@ #include "nsUnicodeToMacRoman.h" #include "nsUnicodeToUTF8.h" +// ucvlatin +#include "nsUCvLatinCID.h" +#include "nsUCvLatinDll.h" +#include "nsAsciiToUnicode.h" +#include "nsISO88592ToUnicode.h" +#include "nsISO88593ToUnicode.h" +#include "nsISO88594ToUnicode.h" +#include "nsISO88595ToUnicode.h" +#include "nsISO88596ToUnicode.h" +#include "nsISO88596EToUnicode.h" +#include "nsISO88596IToUnicode.h" +#include "nsISO88597ToUnicode.h" +#include "nsISO88598ToUnicode.h" +#include "nsISO88598EToUnicode.h" +#include "nsISO88598IToUnicode.h" +#include "nsISO88599ToUnicode.h" +#include "nsISO885910ToUnicode.h" +#include "nsISO885913ToUnicode.h" +#include "nsISO885914ToUnicode.h" +#include "nsISO885915ToUnicode.h" +#include "nsISO885916ToUnicode.h" +#include "nsISOIR111ToUnicode.h" +#include "nsCP1250ToUnicode.h" +#include "nsCP1251ToUnicode.h" +#include "nsCP1253ToUnicode.h" +#include "nsCP1254ToUnicode.h" +#include "nsCP1255ToUnicode.h" +#include "nsCP1256ToUnicode.h" +#include "nsCP1257ToUnicode.h" +#include "nsCP1258ToUnicode.h" +#include "nsCP874ToUnicode.h" +#include "nsISO885911ToUnicode.h" +#include "nsTIS620ToUnicode.h" +#include "nsCP866ToUnicode.h" +#include "nsKOI8RToUnicode.h" +#include "nsKOI8UToUnicode.h" +#include "nsMacCEToUnicode.h" +#include "nsMacGreekToUnicode.h" +#include "nsMacTurkishToUnicode.h" +#include "nsMacCroatianToUnicode.h" +#include "nsMacRomanianToUnicode.h" +#include "nsMacCyrillicToUnicode.h" +#include "nsMacIcelandicToUnicode.h" +#include "nsGEOSTD8ToUnicode.h" +#include "nsARMSCII8ToUnicode.h" +#include "nsTCVN5712ToUnicode.h" +#include "nsVISCIIToUnicode.h" +#include "nsVPSToUnicode.h" +#include "nsUTF7ToUnicode.h" +#include "nsMUTF7ToUnicode.h" +#include "nsUCS2BEToUnicode.h" +#include "nsT61ToUnicode.h" +#include "nsUserDefinedToUnicode.h" +#include "nsUnicodeToAscii.h" +#include "nsUnicodeToISO88592.h" +#include "nsUnicodeToISO88593.h" +#include "nsUnicodeToISO88594.h" +#include "nsUnicodeToISO88595.h" +#include "nsUnicodeToISO88596.h" +#include "nsUnicodeToISO88596E.h" +#include "nsUnicodeToISO88596I.h" +#include "nsUnicodeToISO88597.h" +#include "nsUnicodeToISO88598.h" +#include "nsUnicodeToISO88598E.h" +#include "nsUnicodeToISO88598I.h" +#include "nsUnicodeToISO88599.h" +#include "nsUnicodeToISO885910.h" +#include "nsUnicodeToISO885913.h" +#include "nsUnicodeToISO885914.h" +#include "nsUnicodeToISO885915.h" +#include "nsUnicodeToISO885916.h" +#include "nsUnicodeToISOIR111.h" +#include "nsUnicodeToCP1250.h" +#include "nsUnicodeToCP1251.h" +#include "nsUnicodeToCP1253.h" +#include "nsUnicodeToCP1254.h" +#include "nsUnicodeToCP1255.h" +#include "nsUnicodeToCP1256.h" +#include "nsUnicodeToCP1257.h" +#include "nsUnicodeToCP1258.h" +#include "nsUnicodeToCP874.h" +#include "nsUnicodeToISO885911.h" +#include "nsUnicodeToTIS620.h" +#include "nsUnicodeToCP866.h" +#include "nsUnicodeToKOI8R.h" +#include "nsUnicodeToKOI8U.h" +#include "nsUnicodeToMacCE.h" +#include "nsUnicodeToMacGreek.h" +#include "nsUnicodeToMacTurkish.h" +#include "nsUnicodeToMacCroatian.h" +#include "nsUnicodeToMacRomanian.h" +#include "nsUnicodeToMacCyrillic.h" +#include "nsUnicodeToMacIcelandic.h" +#include "nsUnicodeToGEOSTD8.h" +#include "nsUnicodeToARMSCII8.h" +#include "nsUnicodeToTCVN5712.h" +#include "nsUnicodeToVISCII.h" +#include "nsUnicodeToVPS.h" +#include "nsUnicodeToUTF7.h" +#include "nsUnicodeToMUTF7.h" +#include "nsUnicodeToUCS2BE.h" +#include "nsUnicodeToT61.h" +#include "nsUnicodeToUserDefined.h" +#include "nsUnicodeToSymbol.h" +#include "nsUnicodeToZapfDingbat.h" +#include "nsUnicodeToAdobeEuro.h" +#include "nsMacArabicToUnicode.h" +#include "nsMacDevanagariToUnicode.h" +#include "nsMacFarsiToUnicode.h" +#include "nsMacGujaratiToUnicode.h" +#include "nsMacGurmukhiToUnicode.h" +#include "nsMacHebrewToUnicode.h" +#include "nsUnicodeToMacArabic.h" +#include "nsUnicodeToMacDevanagari.h" +#include "nsUnicodeToMacFarsi.h" +#include "nsUnicodeToMacGujarati.h" +#include "nsUnicodeToMacGurmukhi.h" +#include "nsUnicodeToMacHebrew.h" +#include "nsUnicodeToTSCII.h" + +// ucvibm +#include "nsUCvIBMCID.h" +#include "nsUCvIBMDll.h" +#include "nsCP850ToUnicode.h" +#include "nsCP852ToUnicode.h" +#include "nsCP855ToUnicode.h" +#include "nsCP857ToUnicode.h" +#include "nsCP862ToUnicode.h" +#include "nsCP864ToUnicode.h" +#include "nsCP864iToUnicode.h" +#ifdef XP_OS2 +#include "nsCP869ToUnicode.h" +#include "nsCP1125ToUnicode.h" +#include "nsCP1131ToUnicode.h" +#endif +#include "nsUnicodeToCP850.h" +#include "nsUnicodeToCP852.h" +#include "nsUnicodeToCP855.h" +#include "nsUnicodeToCP857.h" +#include "nsUnicodeToCP862.h" +#include "nsUnicodeToCP864.h" +#include "nsUnicodeToCP864i.h" +#ifdef XP_OS2 +#include "nsUnicodeToCP869.h" +#include "nsUnicodeToCP1125.h" +#include "nsUnicodeToCP1131.h" +#endif + +// ucvja +#include "nsUCVJACID.h" +#include "nsUCVJA2CID.h" +#include "nsUCVJADll.h" +#include "nsJapaneseToUnicode.h" +#include "nsUnicodeToSJIS.h" +#include "nsUnicodeToEUCJP.h" +#include "nsUnicodeToISO2022JP.h" +#include "nsUnicodeToJISx0201.h" + +// ucvtw2 +#include "nsUCvTW2CID.h" +#include "nsUCvTW2Dll.h" +#include "nsEUCTWToUnicode.h" +#include "nsUnicodeToEUCTW.h" + +// ucvtw +#include "nsUCvTWCID.h" +#include "nsUCvTWDll.h" +#include "nsBIG5ToUnicode.h" +#include "nsUnicodeToBIG5.h" +#include "nsBIG5HKSCSToUnicode.h" +#include "nsUnicodeToBIG5HKSCS.h" +#include "nsUnicodeToHKSCS.h" + +// ucvko +#include "nsUCvKOCID.h" +#include "nsUCvKODll.h" +#include "nsEUCKRToUnicode.h" +#include "nsUnicodeToEUCKR.h" +#include "nsJohabToUnicode.h" +#include "nsUnicodeToJohab.h" +#include "nsCP949ToUnicode.h" +#include "nsUnicodeToCP949.h" +#include "nsISO2022KRToUnicode.h" + +// ucvcn +#include "nsUCvCnCID.h" +#include "nsUCvCnDll.h" +#include "nsHZToUnicode.h" +#include "nsUnicodeToHZ.h" +#include "nsGBKToUnicode.h" +#include "nsUnicodeToGBK.h" +#include "nsGB2312ToUnicodeV2.h" +#include "nsUnicodeToGB2312V2.h" +#include "nsISO2022CNToUnicode.h" +#include "nsUnicodeToISO2022CN.h" +#include "gbku.h" + NS_CONVERTER_REGISTRY_START NS_UCONV_REG_UNREG("ISO-8859-1", NS_ISO88591TOUNICODE_CID, NS_UNICODETOISO88591_CID) NS_UCONV_REG_UNREG("windows-1252", NS_CP1252TOUNICODE_CID, NS_UNICODETOCP1252_CID) NS_UCONV_REG_UNREG("x-mac-roman", NS_MACROMANTOUNICODE_CID, NS_UNICODETOMACROMAN_CID) NS_UCONV_REG_UNREG("UTF-8", NS_UTF8TOUNICODE_CID, NS_UNICODETOUTF8_CID) + // ucvlatin +NS_UCONV_REG_UNREG("us-ascii", NS_ASCIITOUNICODE_CID, NS_UNICODETOASCII_CID) +NS_UCONV_REG_UNREG("ISO-8859-2", NS_ISO88592TOUNICODE_CID, NS_UNICODETOISO88592_CID) +NS_UCONV_REG_UNREG("ISO-8859-3", NS_ISO88593TOUNICODE_CID, NS_UNICODETOISO88593_CID) +NS_UCONV_REG_UNREG("ISO-8859-4", NS_ISO88594TOUNICODE_CID, NS_UNICODETOISO88594_CID) +NS_UCONV_REG_UNREG("ISO-8859-5", NS_ISO88595TOUNICODE_CID, NS_UNICODETOISO88595_CID) +NS_UCONV_REG_UNREG("ISO-8859-6", NS_ISO88596TOUNICODE_CID, NS_UNICODETOISO88596_CID) +NS_UCONV_REG_UNREG("ISO-8859-6-I", NS_ISO88596ITOUNICODE_CID, NS_UNICODETOISO88596I_CID) +NS_UCONV_REG_UNREG("ISO-8859-6-E", NS_ISO88596ETOUNICODE_CID, NS_UNICODETOISO88596E_CID) +NS_UCONV_REG_UNREG("ISO-8859-7", NS_ISO88597TOUNICODE_CID, NS_UNICODETOISO88597_CID) +NS_UCONV_REG_UNREG("ISO-8859-8", NS_ISO88598TOUNICODE_CID, NS_UNICODETOISO88598_CID) +NS_UCONV_REG_UNREG("ISO-8859-8-I", NS_ISO88598ITOUNICODE_CID, NS_UNICODETOISO88598I_CID) +NS_UCONV_REG_UNREG("ISO-8859-8-E", NS_ISO88598ETOUNICODE_CID, NS_UNICODETOISO88598E_CID) +NS_UCONV_REG_UNREG("ISO-8859-9", NS_ISO88599TOUNICODE_CID, NS_UNICODETOISO88599_CID) +NS_UCONV_REG_UNREG("ISO-8859-10", NS_ISO885910TOUNICODE_CID, NS_UNICODETOISO885910_CID) +NS_UCONV_REG_UNREG("ISO-8859-13", NS_ISO885913TOUNICODE_CID, NS_UNICODETOISO885913_CID) +NS_UCONV_REG_UNREG("ISO-8859-14", NS_ISO885914TOUNICODE_CID, NS_UNICODETOISO885914_CID) +NS_UCONV_REG_UNREG("ISO-8859-15", NS_ISO885915TOUNICODE_CID, NS_UNICODETOISO885915_CID) +NS_UCONV_REG_UNREG("ISO-8859-16", NS_ISO885916TOUNICODE_CID, NS_UNICODETOISO885916_CID) +NS_UCONV_REG_UNREG("ISO-IR-111", NS_ISOIR111TOUNICODE_CID, NS_UNICODETOISOIR111_CID) +NS_UCONV_REG_UNREG("windows-1250", NS_CP1250TOUNICODE_CID, NS_UNICODETOCP1250_CID) +NS_UCONV_REG_UNREG("windows-1251", NS_CP1251TOUNICODE_CID, NS_UNICODETOCP1251_CID) +NS_UCONV_REG_UNREG("windows-1253", NS_CP1253TOUNICODE_CID, NS_UNICODETOCP1253_CID) +NS_UCONV_REG_UNREG("windows-1254", NS_CP1254TOUNICODE_CID, NS_UNICODETOCP1254_CID) +NS_UCONV_REG_UNREG("windows-1255", NS_CP1255TOUNICODE_CID, NS_UNICODETOCP1255_CID) +NS_UCONV_REG_UNREG("windows-1256", NS_CP1256TOUNICODE_CID, NS_UNICODETOCP1256_CID) +NS_UCONV_REG_UNREG("windows-1257", NS_CP1257TOUNICODE_CID, NS_UNICODETOCP1257_CID) +NS_UCONV_REG_UNREG("windows-1258", NS_CP1258TOUNICODE_CID, NS_UNICODETOCP1258_CID) +NS_UCONV_REG_UNREG("TIS-620", NS_TIS620TOUNICODE_CID, NS_UNICODETOTIS620_CID) +NS_UCONV_REG_UNREG("windows-874", NS_CP874TOUNICODE_CID, NS_UNICODETOCP874_CID) +NS_UCONV_REG_UNREG("ISO-8859-11", NS_ISO885911TOUNICODE_CID, NS_UNICODETOISO885911_CID) +NS_UCONV_REG_UNREG("IBM866", NS_CP866TOUNICODE_CID, NS_UNICODETOCP866_CID) +NS_UCONV_REG_UNREG("KOI8-R", NS_KOI8RTOUNICODE_CID, NS_UNICODETOKOI8R_CID) +NS_UCONV_REG_UNREG("KOI8-U", NS_KOI8UTOUNICODE_CID, NS_UNICODETOKOI8U_CID) +NS_UCONV_REG_UNREG("x-mac-ce", NS_MACCETOUNICODE_CID, NS_UNICODETOMACCE_CID) +NS_UCONV_REG_UNREG("x-mac-greek", NS_MACGREEKTOUNICODE_CID, NS_UNICODETOMACGREEK_CID) +NS_UCONV_REG_UNREG("x-mac-turkish", NS_MACTURKISHTOUNICODE_CID, NS_UNICODETOMACTURKISH_CID) +NS_UCONV_REG_UNREG("x-mac-croatian", NS_MACCROATIANTOUNICODE_CID, NS_UNICODETOMACCROATIAN_CID) +NS_UCONV_REG_UNREG("x-mac-romanian", NS_MACROMANIANTOUNICODE_CID, NS_UNICODETOMACROMANIAN_CID) +NS_UCONV_REG_UNREG("x-mac-cyrillic", NS_MACCYRILLICTOUNICODE_CID, NS_UNICODETOMACCYRILLIC_CID) +NS_UCONV_REG_UNREG("x-mac-icelandic", NS_MACICELANDICTOUNICODE_CID, NS_UNICODETOMACICELANDIC_CID) +NS_UCONV_REG_UNREG("GEOSTD8", NS_GEOSTD8TOUNICODE_CID, NS_UNICODETOGEOSTD8_CID) +NS_UCONV_REG_UNREG("armscii-8", NS_ARMSCII8TOUNICODE_CID, NS_UNICODETOARMSCII8_CID) +NS_UCONV_REG_UNREG("x-viet-tcvn5712", NS_TCVN5712TOUNICODE_CID, NS_UNICODETOTCVN5712_CID) +NS_UCONV_REG_UNREG("VISCII", NS_VISCIITOUNICODE_CID, NS_UNICODETOVISCII_CID) +NS_UCONV_REG_UNREG("x-viet-vps", NS_VPSTOUNICODE_CID, NS_UNICODETOVPS_CID) +NS_UCONV_REG_UNREG("UTF-7", NS_UTF7TOUNICODE_CID, NS_UNICODETOUTF7_CID) +NS_UCONV_REG_UNREG("x-imap4-modified-utf7", NS_MUTF7TOUNICODE_CID, NS_UNICODETOMUTF7_CID) +NS_UCONV_REG_UNREG("UTF-16", NS_UTF16TOUNICODE_CID, NS_UNICODETOUTF16_CID) +NS_UCONV_REG_UNREG("UTF-16BE", NS_UTF16BETOUNICODE_CID, NS_UNICODETOUTF16BE_CID) +NS_UCONV_REG_UNREG("UTF-16LE", NS_UTF16LETOUNICODE_CID, NS_UNICODETOUTF16LE_CID) +NS_UCONV_REG_UNREG("T.61-8bit", NS_T61TOUNICODE_CID, NS_UNICODETOT61_CID) +NS_UCONV_REG_UNREG("x-user-defined", NS_USERDEFINEDTOUNICODE_CID, NS_UNICODETOUSERDEFINED_CID) +NS_UCONV_REG_UNREG("x-mac-arabic" , NS_MACARABICTOUNICODE_CID, NS_UNICODETOMACARABIC_CID) +NS_UCONV_REG_UNREG("x-mac-devanagari" , NS_MACDEVANAGARITOUNICODE_CID, NS_UNICODETOMACDEVANAGARI_CID) +NS_UCONV_REG_UNREG("x-mac-farsi" , NS_MACFARSITOUNICODE_CID, NS_UNICODETOMACFARSI_CID) +NS_UCONV_REG_UNREG("x-mac-gurmukhi" , NS_MACGURMUKHITOUNICODE_CID, NS_UNICODETOMACGURMUKHI_CID) +NS_UCONV_REG_UNREG("x-mac-gujarati" , NS_MACGUJARATITOUNICODE_CID, NS_UNICODETOMACGUJARATI_CID) +NS_UCONV_REG_UNREG("x-mac-hebrew" , NS_MACHEBREWTOUNICODE_CID, NS_UNICODETOMACHEBREW_CID) + +NS_UCONV_REG_UNREG_ENCODER("Adobe-Symbol-Encoding" , NS_UNICODETOSYMBOL_CID) +NS_UCONV_REG_UNREG_ENCODER("x-zapf-dingbats" , NS_UNICODETOZAPFDINGBATS_CID) +NS_UCONV_REG_UNREG_ENCODER("x-tscii", NS_UNICODETOTSCII_CID) +NS_UCONV_REG_UNREG_ENCODER("x-tamilttf-0", NS_UNICODETOTAMILTTF_CID) + + // ucvibm +NS_UCONV_REG_UNREG("IBM850", NS_CP850TOUNICODE_CID, NS_UNICODETOCP850_CID) +NS_UCONV_REG_UNREG("IBM852", NS_CP852TOUNICODE_CID, NS_UNICODETOCP852_CID) +NS_UCONV_REG_UNREG("IBM855", NS_CP855TOUNICODE_CID, NS_UNICODETOCP855_CID) +NS_UCONV_REG_UNREG("IBM857", NS_CP857TOUNICODE_CID, NS_UNICODETOCP857_CID) +NS_UCONV_REG_UNREG("IBM862", NS_CP862TOUNICODE_CID, NS_UNICODETOCP862_CID) +NS_UCONV_REG_UNREG("IBM864", NS_CP864TOUNICODE_CID, NS_UNICODETOCP864_CID) +NS_UCONV_REG_UNREG("IBM864i", NS_CP864ITOUNICODE_CID, NS_UNICODETOCP864I_CID) +#ifdef XP_OS2 +NS_UCONV_REG_UNREG("IBM869", NS_CP869TOUNICODE_CID, NS_UNICODETOCP869_CID) +NS_UCONV_REG_UNREG("IBM1125", NS_CP1125TOUNICODE_CID, NS_UNICODETOCP1125_CID) +NS_UCONV_REG_UNREG("IBM1131", NS_CP1131TOUNICODE_CID, NS_UNICODETOCP1131_CID) +#endif + + // ucvja +NS_UCONV_REG_UNREG("Shift_JIS", NS_SJISTOUNICODE_CID, NS_UNICODETOSJIS_CID) +NS_UCONV_REG_UNREG("ISO-2022-JP", NS_ISO2022JPTOUNICODE_CID, NS_UNICODETOISO2022JP_CID) +NS_UCONV_REG_UNREG("EUC-JP", NS_EUCJPTOUNICODE_CID, NS_UNICODETOEUCJP_CID) + +NS_UCONV_REG_UNREG_ENCODER("jis_0201" , NS_UNICODETOJISX0201_CID) + + // ucvtw2 +NS_UCONV_REG_UNREG("x-euc-tw", NS_EUCTWTOUNICODE_CID, NS_UNICODETOEUCTW_CID) + + // ucvtw +NS_UCONV_REG_UNREG("Big5", NS_BIG5TOUNICODE_CID, NS_UNICODETOBIG5_CID) +NS_UCONV_REG_UNREG("Big5-HKSCS", NS_BIG5HKSCSTOUNICODE_CID, NS_UNICODETOBIG5HKSCS_CID) + +NS_UCONV_REG_UNREG_ENCODER("hkscs-1" , NS_UNICODETOHKSCS_CID) + + // ucvko +NS_UCONV_REG_UNREG("EUC-KR", NS_EUCKRTOUNICODE_CID, NS_UNICODETOEUCKR_CID) +NS_UCONV_REG_UNREG("x-johab", NS_JOHABTOUNICODE_CID, NS_UNICODETOJOHAB_CID) +NS_UCONV_REG_UNREG("x-windows-949", NS_CP949TOUNICODE_CID, NS_UNICODETOCP949_CID) +NS_UCONV_REG_UNREG_DECODER("ISO-2022-KR", NS_ISO2022KRTOUNICODE_CID) + +// ucvcn +NS_UCONV_REG_UNREG("GB2312", NS_GB2312TOUNICODE_CID, NS_UNICODETOGB2312_CID) +NS_UCONV_REG_UNREG("gbk", NS_GBKTOUNICODE_CID, NS_UNICODETOGBK_CID) +NS_UCONV_REG_UNREG("HZ-GB-2312", NS_HZTOUNICODE_CID, NS_UNICODETOHZ_CID) +NS_UCONV_REG_UNREG("gb18030", NS_GB18030TOUNICODE_CID, NS_UNICODETOGB18030_CID) +NS_UCONV_REG_UNREG_DECODER("ISO-2022-CN", NS_ISO2022CNTOUNICODE_CID) + { NS_TITLE_BUNDLE_CATEGORY, "chrome://global/locale/charsetTitles.properties", "" }, { NS_DATA_BUNDLE_CATEGORY, "resource://gre-resources/charsetData.properties", "" }, @@ -86,10 +391,170 @@ NS_CONVERTER_REGISTRY_END NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF8) NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF8ToUnicode) +// ucvlatin +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF7ToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMUTF7ToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF16ToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF16BEToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF16LEToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF7) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToMUTF7) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF16BE) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF16LE) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToUTF16) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToTSCII) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToTamilTTF) + +// ucvibm + +// ucvja +NS_GENERIC_FACTORY_CONSTRUCTOR(nsShiftJISToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsEUCJPToUnicodeV2) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsISO2022JPToUnicodeV2) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToISO2022JP) + +// ucvtw2 + +// ucvtw + +// ucvko +NS_GENERIC_FACTORY_CONSTRUCTOR(nsISO2022KRToUnicode) + +// ucvcn +NS_GENERIC_FACTORY_CONSTRUCTOR(nsGB2312ToUnicodeV2) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToGB2312V2) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsGBKToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToGBK) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsHZToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToHZ) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsGB18030ToUnicode) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsUnicodeToGB18030) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsISO2022CNToUnicode) + //---------------------------------------------------------------------------- // Global functions and data [declaration] +// ucvja +const PRUint16 g_uf0201Mapping[] = { +#include "jis0201.uf" +}; + +const PRUint16 g_uf0201GLMapping[] = { +#include "jis0201gl.uf" +}; + +const PRUint16 g_uf0208Mapping[] = { +#include "jis0208.uf" +}; + +const PRUint16 g_uf0208extMapping[] = { +#include "jis0208ext.uf" +}; + +// ucvtw2 +const PRUint16 g_ufCNS1MappingTable[] = { +#include "cns_1.uf" +}; + +const PRUint16 g_ufCNS2MappingTable[] = { +#include "cns_2.uf" +}; + +const PRUint16 g_ufCNS3MappingTable[] = { +#include "cns3.uf" +}; + +const PRUint16 g_ufCNS4MappingTable[] = { +#include "cns4.uf" +}; + +const PRUint16 g_ufCNS5MappingTable[] = { +#include "cns5.uf" +}; + +const PRUint16 g_ufCNS6MappingTable[] = { +#include "cns6.uf" +}; + +const PRUint16 g_ufCNS7MappingTable[] = { +#include "cns7.uf" +}; + +const PRUint16 g_utCNS1MappingTable[] = { +#include "cns_1.ut" +}; + +const PRUint16 g_utCNS2MappingTable[] = { +#include "cns_2.ut" +}; + +const PRUint16 g_utCNS3MappingTable[] = { +#include "cns3.ut" +}; + +const PRUint16 g_utCNS4MappingTable[] = { +#include "cns4.ut" +}; + +const PRUint16 g_utCNS5MappingTable[] = { +#include "cns5.ut" +}; + +const PRUint16 g_utCNS6MappingTable[] = { +#include "cns6.ut" +}; + +const PRUint16 g_utCNS7MappingTable[] = { +#include "cns7.ut" +}; + +const PRUint16 g_ASCIIMappingTable[] = { + 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0x0000, 0x007F, 0x0000 +}; + +// ucvtw +const PRUint16 g_ufBig5Mapping[] = { +#include "big5.uf" +}; + +const PRUint16 g_utBIG5Mapping[] = { +#include "big5.ut" +}; + +const PRUint16 g_ufBig5HKSCSMapping[] = { +#include "hkscs.uf" +}; + +const PRUint16 g_ASCIIMapping[] = { + 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0x0000, 0x007F, 0x0000 +}; + +const PRUint16 g_utBig5HKSCSMapping[] = { +#include "hkscs.ut" +}; + +// ucvko +const PRUint16 g_utKSC5601Mapping[] = { +#include "u20kscgl.ut" +}; + +const PRUint16 g_ufKSC5601Mapping[] = { +#include "u20kscgl.uf" +}; + +const PRUint16 g_ucvko_AsciiMapping[] = { + 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0x0000, 0x007F, 0x0000 +}; + +const PRUint16 g_HangulNullMapping[] ={ + 0x0001, 0x0004, 0x0005, 0x0008, 0x0000, 0xAC00, 0xD7A3, 0xAC00 +}; + +const PRUint16 g_ufJohabJamoMapping[] ={ +#include "johabjamo.uf" +}; + NS_GENERIC_FACTORY_CONSTRUCTOR(nsCharsetConverterManager) NS_GENERIC_FACTORY_CONSTRUCTOR(nsTextToSubURI) NS_GENERIC_FACTORY_CONSTRUCTOR(nsUTF8ConverterService) @@ -111,6 +576,181 @@ NS_DEFINE_NAMED_CID(NS_UNICODETOISO88591_CID); NS_DEFINE_NAMED_CID(NS_UNICODETOCP1252_CID); NS_DEFINE_NAMED_CID(NS_UNICODETOMACROMAN_CID); NS_DEFINE_NAMED_CID(NS_UNICODETOUTF8_CID); +NS_DEFINE_NAMED_CID(NS_ASCIITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88592TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88593TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88594TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88595TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88596TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88596ITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88596ETOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88597TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88598TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88598ITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88598ETOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO88599TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO885910TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO885913TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO885914TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO885915TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO885916TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISOIR111TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1250TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1251TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1253TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1254TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1255TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1256TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1257TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1258TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_TIS620TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ISO885911TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP874TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP866TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_KOI8RTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_KOI8UTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACCETOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACGREEKTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACTURKISHTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACCROATIANTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACROMANIANTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACCYRILLICTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACICELANDICTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_GEOSTD8TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_ARMSCII8TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_TCVN5712TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_VISCIITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_VPSTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UTF7TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MUTF7TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UTF16TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UTF16BETOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UTF16LETOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_T61TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_USERDEFINEDTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACARABICTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACDEVANAGARITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACFARSITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACGURMUKHITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACGUJARATITOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_MACHEBREWTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOASCII_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88592_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88593_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88594_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88595_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88596_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88596I_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88596E_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88597_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88598_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88598I_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88598E_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO88599_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO885910_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO885913_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO885914_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO885915_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO885916_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISOIR111_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1250_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1251_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1253_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1254_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1255_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1256_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1257_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1258_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOTIS620_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO885911_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP874_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP866_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOKOI8R_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOKOI8U_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACCE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACGREEK_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACTURKISH_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACCROATIAN_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACROMANIAN_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACCYRILLIC_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACICELANDIC_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOGEOSTD8_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOARMSCII8_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOTCVN5712_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOVISCII_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOVPS_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOUTF7_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMUTF7_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOUTF16BE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOUTF16LE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOUTF16_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOT61_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOUSERDEFINED_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOSYMBOL_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOZAPFDINGBATS_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOADOBEEURO_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACARABIC_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACDEVANAGARI_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACFARSI_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACGURMUKHI_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACGUJARATI_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOMACHEBREW_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOTSCII_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOTAMILTTF_CID); +NS_DEFINE_NAMED_CID(NS_CP850TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP852TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP855TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP857TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP862TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP864TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP864ITOUNICODE_CID); +#ifdef XP_OS2 +NS_DEFINE_NAMED_CID(NS_CP869TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1125TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_CP1131TOUNICODE_CID); +#endif +NS_DEFINE_NAMED_CID(NS_UNICODETOCP850_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP852_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP855_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP857_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP862_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP864_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP864I_CID); +#ifdef XP_OS2 +NS_DEFINE_NAMED_CID(NS_UNICODETOCP869_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1125_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP1131_CID); +#endif +NS_DEFINE_NAMED_CID(NS_SJISTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_EUCJPTOUNICODE_CID); +NS_DEFINE_NAMED_CID( NS_ISO2022JPTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOSJIS_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOEUCJP_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOISO2022JP_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOJISX0201_CID); +NS_DEFINE_NAMED_CID(NS_EUCTWTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOEUCTW_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOBIG5_CID); +NS_DEFINE_NAMED_CID(NS_BIG5TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOBIG5HKSCS_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOHKSCS_CID); +NS_DEFINE_NAMED_CID(NS_BIG5HKSCSTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_EUCKRTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOEUCKR_CID); +NS_DEFINE_NAMED_CID(NS_JOHABTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOJOHAB_CID); +NS_DEFINE_NAMED_CID(NS_CP949TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOCP949_CID); +NS_DEFINE_NAMED_CID(NS_ISO2022KRTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_GB2312TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOGB2312_CID); +NS_DEFINE_NAMED_CID(NS_GBKTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOGBK_CID); +NS_DEFINE_NAMED_CID(NS_HZTOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOHZ_CID); +NS_DEFINE_NAMED_CID(NS_GB18030TOUNICODE_CID); +NS_DEFINE_NAMED_CID(NS_UNICODETOGB18030_CID); +NS_DEFINE_NAMED_CID(NS_ISO2022CNTOUNICODE_CID); static const mozilla::Module::CIDEntry kUConvCIDs[] = { { &kNS_ICHARSETCONVERTERMANAGER_CID, false, NULL, nsCharsetConverterManagerConstructor }, @@ -127,6 +767,181 @@ static const mozilla::Module::CIDEntry kUConvCIDs[] = { { &kNS_UNICODETOCP1252_CID, false, NULL, nsUnicodeToCP1252Constructor }, { &kNS_UNICODETOMACROMAN_CID, false, NULL, nsUnicodeToMacRomanConstructor }, { &kNS_UNICODETOUTF8_CID, false, NULL, nsUnicodeToUTF8Constructor }, + { &kNS_ASCIITOUNICODE_CID, false, NULL, nsAsciiToUnicodeConstructor }, + { &kNS_ISO88592TOUNICODE_CID, false, NULL, nsISO88592ToUnicodeConstructor }, + { &kNS_ISO88593TOUNICODE_CID, false, NULL, nsISO88593ToUnicodeConstructor }, + { &kNS_ISO88594TOUNICODE_CID, false, NULL, nsISO88594ToUnicodeConstructor }, + { &kNS_ISO88595TOUNICODE_CID, false, NULL, nsISO88595ToUnicodeConstructor }, + { &kNS_ISO88596TOUNICODE_CID, false, NULL, nsISO88596ToUnicodeConstructor }, + { &kNS_ISO88596ITOUNICODE_CID, false, NULL, nsISO88596IToUnicodeConstructor }, + { &kNS_ISO88596ETOUNICODE_CID, false, NULL, nsISO88596EToUnicodeConstructor }, + { &kNS_ISO88597TOUNICODE_CID, false, NULL, nsISO88597ToUnicodeConstructor }, + { &kNS_ISO88598TOUNICODE_CID, false, NULL, nsISO88598ToUnicodeConstructor }, + { &kNS_ISO88598ITOUNICODE_CID, false, NULL, nsISO88598IToUnicodeConstructor }, + { &kNS_ISO88598ETOUNICODE_CID, false, NULL, nsISO88598EToUnicodeConstructor }, + { &kNS_ISO88599TOUNICODE_CID, false, NULL, nsISO88599ToUnicodeConstructor }, + { &kNS_ISO885910TOUNICODE_CID, false, NULL, nsISO885910ToUnicodeConstructor }, + { &kNS_ISO885913TOUNICODE_CID, false, NULL, nsISO885913ToUnicodeConstructor }, + { &kNS_ISO885914TOUNICODE_CID, false, NULL, nsISO885914ToUnicodeConstructor }, + { &kNS_ISO885915TOUNICODE_CID, false, NULL, nsISO885915ToUnicodeConstructor }, + { &kNS_ISO885916TOUNICODE_CID, false, NULL, nsISO885916ToUnicodeConstructor }, + { &kNS_ISOIR111TOUNICODE_CID, false, NULL, nsISOIR111ToUnicodeConstructor }, + { &kNS_CP1250TOUNICODE_CID, false, NULL, nsCP1250ToUnicodeConstructor }, + { &kNS_CP1251TOUNICODE_CID, false, NULL, nsCP1251ToUnicodeConstructor }, + { &kNS_CP1253TOUNICODE_CID, false, NULL, nsCP1253ToUnicodeConstructor }, + { &kNS_CP1254TOUNICODE_CID, false, NULL, nsCP1254ToUnicodeConstructor }, + { &kNS_CP1255TOUNICODE_CID, false, NULL, nsCP1255ToUnicodeConstructor }, + { &kNS_CP1256TOUNICODE_CID, false, NULL, nsCP1256ToUnicodeConstructor }, + { &kNS_CP1257TOUNICODE_CID, false, NULL, nsCP1257ToUnicodeConstructor }, + { &kNS_CP1258TOUNICODE_CID, false, NULL, nsCP1258ToUnicodeConstructor }, + { &kNS_TIS620TOUNICODE_CID, false, NULL, nsTIS620ToUnicodeConstructor }, + { &kNS_ISO885911TOUNICODE_CID, false, NULL, nsISO885911ToUnicodeConstructor }, + { &kNS_CP874TOUNICODE_CID, false, NULL, nsCP874ToUnicodeConstructor }, + { &kNS_CP866TOUNICODE_CID, false, NULL, nsCP866ToUnicodeConstructor }, + { &kNS_KOI8RTOUNICODE_CID, false, NULL, nsKOI8RToUnicodeConstructor }, + { &kNS_KOI8UTOUNICODE_CID, false, NULL, nsKOI8UToUnicodeConstructor }, + { &kNS_MACCETOUNICODE_CID, false, NULL, nsMacCEToUnicodeConstructor }, + { &kNS_MACGREEKTOUNICODE_CID, false, NULL, nsMacGreekToUnicodeConstructor }, + { &kNS_MACTURKISHTOUNICODE_CID, false, NULL, nsMacTurkishToUnicodeConstructor }, + { &kNS_MACCROATIANTOUNICODE_CID, false, NULL, nsMacCroatianToUnicodeConstructor }, + { &kNS_MACROMANIANTOUNICODE_CID, false, NULL, nsMacRomanianToUnicodeConstructor }, + { &kNS_MACCYRILLICTOUNICODE_CID, false, NULL, nsMacCyrillicToUnicodeConstructor }, + { &kNS_MACICELANDICTOUNICODE_CID, false, NULL, nsMacIcelandicToUnicodeConstructor }, + { &kNS_GEOSTD8TOUNICODE_CID, false, NULL, nsGEOSTD8ToUnicodeConstructor }, + { &kNS_ARMSCII8TOUNICODE_CID, false, NULL, nsARMSCII8ToUnicodeConstructor }, + { &kNS_TCVN5712TOUNICODE_CID, false, NULL, nsTCVN5712ToUnicodeConstructor }, + { &kNS_VISCIITOUNICODE_CID, false, NULL, nsVISCIIToUnicodeConstructor }, + { &kNS_VPSTOUNICODE_CID, false, NULL, nsVPSToUnicodeConstructor }, + { &kNS_UTF7TOUNICODE_CID, false, NULL, nsUTF7ToUnicodeConstructor }, + { &kNS_MUTF7TOUNICODE_CID, false, NULL, nsMUTF7ToUnicodeConstructor }, + { &kNS_UTF16TOUNICODE_CID, false, NULL, nsUTF16ToUnicodeConstructor }, + { &kNS_UTF16BETOUNICODE_CID, false, NULL, nsUTF16BEToUnicodeConstructor }, + { &kNS_UTF16LETOUNICODE_CID, false, NULL, nsUTF16LEToUnicodeConstructor }, + { &kNS_T61TOUNICODE_CID, false, NULL, nsT61ToUnicodeConstructor }, + { &kNS_USERDEFINEDTOUNICODE_CID, false, NULL, nsUserDefinedToUnicodeConstructor }, + { &kNS_MACARABICTOUNICODE_CID, false, NULL, nsMacArabicToUnicodeConstructor }, + { &kNS_MACDEVANAGARITOUNICODE_CID, false, NULL, nsMacDevanagariToUnicodeConstructor }, + { &kNS_MACFARSITOUNICODE_CID, false, NULL, nsMacFarsiToUnicodeConstructor }, + { &kNS_MACGURMUKHITOUNICODE_CID, false, NULL, nsMacGurmukhiToUnicodeConstructor }, + { &kNS_MACGUJARATITOUNICODE_CID, false, NULL, nsMacGujaratiToUnicodeConstructor }, + { &kNS_MACHEBREWTOUNICODE_CID, false, NULL, nsMacHebrewToUnicodeConstructor }, + { &kNS_UNICODETOASCII_CID, false, NULL, nsUnicodeToAsciiConstructor }, + { &kNS_UNICODETOISO88592_CID, false, NULL, nsUnicodeToISO88592Constructor }, + { &kNS_UNICODETOISO88593_CID, false, NULL, nsUnicodeToISO88593Constructor }, + { &kNS_UNICODETOISO88594_CID, false, NULL, nsUnicodeToISO88594Constructor }, + { &kNS_UNICODETOISO88595_CID, false, NULL, nsUnicodeToISO88595Constructor }, + { &kNS_UNICODETOISO88596_CID, false, NULL, nsUnicodeToISO88596Constructor }, + { &kNS_UNICODETOISO88596I_CID, false, NULL, nsUnicodeToISO88596IConstructor }, + { &kNS_UNICODETOISO88596E_CID, false, NULL, nsUnicodeToISO88596EConstructor }, + { &kNS_UNICODETOISO88597_CID, false, NULL, nsUnicodeToISO88597Constructor }, + { &kNS_UNICODETOISO88598_CID, false, NULL, nsUnicodeToISO88598Constructor }, + { &kNS_UNICODETOISO88598I_CID, false, NULL, nsUnicodeToISO88598IConstructor }, + { &kNS_UNICODETOISO88598E_CID, false, NULL, nsUnicodeToISO88598EConstructor }, + { &kNS_UNICODETOISO88599_CID, false, NULL, nsUnicodeToISO88599Constructor }, + { &kNS_UNICODETOISO885910_CID, false, NULL, nsUnicodeToISO885910Constructor }, + { &kNS_UNICODETOISO885913_CID, false, NULL, nsUnicodeToISO885913Constructor }, + { &kNS_UNICODETOISO885914_CID, false, NULL, nsUnicodeToISO885914Constructor }, + { &kNS_UNICODETOISO885915_CID, false, NULL, nsUnicodeToISO885915Constructor }, + { &kNS_UNICODETOISO885916_CID, false, NULL, nsUnicodeToISO885916Constructor }, + { &kNS_UNICODETOISOIR111_CID, false, NULL, nsUnicodeToISOIR111Constructor }, + { &kNS_UNICODETOCP1250_CID, false, NULL, nsUnicodeToCP1250Constructor }, + { &kNS_UNICODETOCP1251_CID, false, NULL, nsUnicodeToCP1251Constructor }, + { &kNS_UNICODETOCP1253_CID, false, NULL, nsUnicodeToCP1253Constructor }, + { &kNS_UNICODETOCP1254_CID, false, NULL, nsUnicodeToCP1254Constructor }, + { &kNS_UNICODETOCP1255_CID, false, NULL, nsUnicodeToCP1255Constructor }, + { &kNS_UNICODETOCP1256_CID, false, NULL, nsUnicodeToCP1256Constructor }, + { &kNS_UNICODETOCP1257_CID, false, NULL, nsUnicodeToCP1257Constructor }, + { &kNS_UNICODETOCP1258_CID, false, NULL, nsUnicodeToCP1258Constructor }, + { &kNS_UNICODETOTIS620_CID, false, NULL, nsUnicodeToTIS620Constructor }, + { &kNS_UNICODETOISO885911_CID, false, NULL, nsUnicodeToISO885911Constructor }, + { &kNS_UNICODETOCP874_CID, false, NULL, nsUnicodeToCP874Constructor }, + { &kNS_UNICODETOCP866_CID, false, NULL, nsUnicodeToCP866Constructor }, + { &kNS_UNICODETOKOI8R_CID, false, NULL, nsUnicodeToKOI8RConstructor }, + { &kNS_UNICODETOKOI8U_CID, false, NULL, nsUnicodeToKOI8UConstructor }, + { &kNS_UNICODETOMACCE_CID, false, NULL, nsUnicodeToMacCEConstructor }, + { &kNS_UNICODETOMACGREEK_CID, false, NULL, nsUnicodeToMacGreekConstructor }, + { &kNS_UNICODETOMACTURKISH_CID, false, NULL, nsUnicodeToMacTurkishConstructor }, + { &kNS_UNICODETOMACCROATIAN_CID, false, NULL, nsUnicodeToMacCroatianConstructor }, + { &kNS_UNICODETOMACROMANIAN_CID, false, NULL, nsUnicodeToMacRomanianConstructor }, + { &kNS_UNICODETOMACCYRILLIC_CID, false, NULL, nsUnicodeToMacCyrillicConstructor }, + { &kNS_UNICODETOMACICELANDIC_CID, false, NULL, nsUnicodeToMacIcelandicConstructor }, + { &kNS_UNICODETOGEOSTD8_CID, false, NULL, nsUnicodeToGEOSTD8Constructor }, + { &kNS_UNICODETOARMSCII8_CID, false, NULL, nsUnicodeToARMSCII8Constructor }, + { &kNS_UNICODETOTCVN5712_CID, false, NULL, nsUnicodeToTCVN5712Constructor }, + { &kNS_UNICODETOVISCII_CID, false, NULL, nsUnicodeToVISCIIConstructor }, + { &kNS_UNICODETOVPS_CID, false, NULL, nsUnicodeToVPSConstructor }, + { &kNS_UNICODETOUTF7_CID, false, NULL, nsUnicodeToUTF7Constructor }, + { &kNS_UNICODETOMUTF7_CID, false, NULL, nsUnicodeToMUTF7Constructor }, + { &kNS_UNICODETOUTF16BE_CID, false, NULL, nsUnicodeToUTF16BEConstructor }, + { &kNS_UNICODETOUTF16LE_CID, false, NULL, nsUnicodeToUTF16LEConstructor }, + { &kNS_UNICODETOUTF16_CID, false, NULL, nsUnicodeToUTF16Constructor }, + { &kNS_UNICODETOT61_CID, false, NULL, nsUnicodeToT61Constructor }, + { &kNS_UNICODETOUSERDEFINED_CID, false, NULL, nsUnicodeToUserDefinedConstructor }, + { &kNS_UNICODETOSYMBOL_CID, false, NULL, nsUnicodeToSymbolConstructor }, + { &kNS_UNICODETOZAPFDINGBATS_CID, false, NULL, nsUnicodeToZapfDingbatConstructor }, + { &kNS_UNICODETOADOBEEURO_CID, false, NULL, nsUnicodeToAdobeEuroConstructor }, + { &kNS_UNICODETOMACARABIC_CID, false, NULL, nsUnicodeToMacArabicConstructor }, + { &kNS_UNICODETOMACDEVANAGARI_CID, false, NULL, nsUnicodeToMacDevanagariConstructor }, + { &kNS_UNICODETOMACFARSI_CID, false, NULL, nsUnicodeToMacFarsiConstructor }, + { &kNS_UNICODETOMACGURMUKHI_CID, false, NULL, nsUnicodeToMacGurmukhiConstructor }, + { &kNS_UNICODETOMACGUJARATI_CID, false, NULL, nsUnicodeToMacGujaratiConstructor }, + { &kNS_UNICODETOMACHEBREW_CID, false, NULL, nsUnicodeToMacHebrewConstructor }, + { &kNS_UNICODETOTSCII_CID, false, NULL, nsUnicodeToTSCIIConstructor }, + { &kNS_UNICODETOTAMILTTF_CID, false, NULL, nsUnicodeToTamilTTFConstructor }, + { &kNS_CP850TOUNICODE_CID, false, NULL, nsCP850ToUnicodeConstructor }, + { &kNS_CP852TOUNICODE_CID, false, NULL, nsCP852ToUnicodeConstructor }, + { &kNS_CP855TOUNICODE_CID, false, NULL, nsCP855ToUnicodeConstructor }, + { &kNS_CP857TOUNICODE_CID, false, NULL, nsCP857ToUnicodeConstructor }, + { &kNS_CP862TOUNICODE_CID, false, NULL, nsCP862ToUnicodeConstructor }, + { &kNS_CP864TOUNICODE_CID, false, NULL, nsCP864ToUnicodeConstructor }, + { &kNS_CP864ITOUNICODE_CID, false, NULL, nsCP864iToUnicodeConstructor }, +#ifdef XP_OS2 + { &kNS_CP869TOUNICODE_CID, false, NULL, nsCP869ToUnicodeConstructor }, + { &kNS_CP1125TOUNICODE_CID, false, NULL, nsCP1125ToUnicodeConstructor }, + { &kNS_CP1131TOUNICODE_CID, false, NULL, nsCP1131ToUnicodeConstructor }, +#endif + { &kNS_UNICODETOCP850_CID, false, NULL, nsUnicodeToCP850Constructor }, + { &kNS_UNICODETOCP852_CID, false, NULL, nsUnicodeToCP852Constructor }, + { &kNS_UNICODETOCP855_CID, false, NULL, nsUnicodeToCP855Constructor }, + { &kNS_UNICODETOCP857_CID, false, NULL, nsUnicodeToCP857Constructor }, + { &kNS_UNICODETOCP862_CID, false, NULL, nsUnicodeToCP862Constructor }, + { &kNS_UNICODETOCP864_CID, false, NULL, nsUnicodeToCP864Constructor }, + { &kNS_UNICODETOCP864I_CID, false, NULL, nsUnicodeToCP864iConstructor }, +#ifdef XP_OS2 + { &kNS_UNICODETOCP869_CID, false, NULL, nsUnicodeToCP869Constructor }, + { &kNS_UNICODETOCP1125_CID, false, NULL, nsUnicodeToCP1125Constructor }, + { &kNS_UNICODETOCP1131_CID, false, NULL, nsUnicodeToCP1131Constructor }, +#endif + { &kNS_SJISTOUNICODE_CID, false, NULL, nsShiftJISToUnicodeConstructor }, + { &kNS_EUCJPTOUNICODE_CID, false, NULL, nsEUCJPToUnicodeV2Constructor }, + { &kNS_ISO2022JPTOUNICODE_CID, false, NULL, nsISO2022JPToUnicodeV2Constructor }, + { &kNS_UNICODETOSJIS_CID, false, NULL, nsUnicodeToSJISConstructor }, + { &kNS_UNICODETOEUCJP_CID, false, NULL, nsUnicodeToEUCJPConstructor }, + { &kNS_UNICODETOISO2022JP_CID, false, NULL, nsUnicodeToISO2022JPConstructor }, + { &kNS_UNICODETOJISX0201_CID, false, NULL, nsUnicodeToJISx0201Constructor }, + { &kNS_EUCTWTOUNICODE_CID, false, NULL, nsEUCTWToUnicodeConstructor }, + { &kNS_UNICODETOEUCTW_CID, false, NULL, nsUnicodeToEUCTWConstructor }, + { &kNS_UNICODETOBIG5_CID, false, NULL, nsUnicodeToBIG5Constructor }, + { &kNS_BIG5TOUNICODE_CID, false, NULL, nsBIG5ToUnicodeConstructor }, + { &kNS_UNICODETOBIG5HKSCS_CID, false, NULL, nsUnicodeToBIG5HKSCSConstructor }, + { &kNS_UNICODETOHKSCS_CID, false, NULL, nsUnicodeToHKSCSConstructor }, + { &kNS_BIG5HKSCSTOUNICODE_CID, false, NULL, nsBIG5HKSCSToUnicodeConstructor }, + { &kNS_EUCKRTOUNICODE_CID, false, NULL, nsEUCKRToUnicodeConstructor }, + { &kNS_UNICODETOEUCKR_CID, false, NULL, nsUnicodeToEUCKRConstructor }, + { &kNS_JOHABTOUNICODE_CID, false, NULL, nsJohabToUnicodeConstructor }, + { &kNS_UNICODETOJOHAB_CID, false, NULL, nsUnicodeToJohabConstructor }, + { &kNS_CP949TOUNICODE_CID, false, NULL, nsCP949ToUnicodeConstructor }, + { &kNS_UNICODETOCP949_CID, false, NULL, nsUnicodeToCP949Constructor }, + { &kNS_ISO2022KRTOUNICODE_CID, false, NULL, nsISO2022KRToUnicodeConstructor }, + { &kNS_GB2312TOUNICODE_CID, false, NULL, nsGB2312ToUnicodeV2Constructor }, + { &kNS_UNICODETOGB2312_CID, false, NULL, nsUnicodeToGB2312V2Constructor }, + { &kNS_GBKTOUNICODE_CID, false, NULL, nsGBKToUnicodeConstructor }, + { &kNS_UNICODETOGBK_CID, false, NULL, nsUnicodeToGBKConstructor }, + { &kNS_HZTOUNICODE_CID, false, NULL, nsHZToUnicodeConstructor }, + { &kNS_UNICODETOHZ_CID, false, NULL, nsUnicodeToHZConstructor }, + { &kNS_GB18030TOUNICODE_CID, false, NULL, nsGB18030ToUnicodeConstructor }, + { &kNS_UNICODETOGB18030_CID, false, NULL, nsUnicodeToGB18030Constructor }, + { &kNS_ISO2022CNTOUNICODE_CID, false, NULL, nsISO2022CNToUnicodeConstructor }, { NULL }, }; @@ -145,6 +960,181 @@ static const mozilla::Module::ContractIDEntry kUConvContracts[] = { { NS_UNICODETOCP1252_CONTRACTID, &kNS_UNICODETOCP1252_CID }, { NS_UNICODETOMACROMAN_CONTRACTID, &kNS_UNICODETOMACROMAN_CID }, { NS_UNICODETOUTF8_CONTRACTID, &kNS_UNICODETOUTF8_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "us-ascii", &kNS_ASCIITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-2", &kNS_ISO88592TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-3", &kNS_ISO88593TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-4", &kNS_ISO88594TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-5", &kNS_ISO88595TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-6", &kNS_ISO88596TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-6-I", &kNS_ISO88596ITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-6-E", &kNS_ISO88596ETOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-7", &kNS_ISO88597TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-8", &kNS_ISO88598TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-8-I", &kNS_ISO88598ITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-8-E", &kNS_ISO88598ETOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-9", &kNS_ISO88599TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-10", &kNS_ISO885910TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-13", &kNS_ISO885913TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-14", &kNS_ISO885914TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-15", &kNS_ISO885915TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-16", &kNS_ISO885916TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-IR-111", &kNS_ISOIR111TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1250", &kNS_CP1250TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1251", &kNS_CP1251TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1253", &kNS_CP1253TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1254", &kNS_CP1254TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1255", &kNS_CP1255TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1256", &kNS_CP1256TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1257", &kNS_CP1257TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-1258", &kNS_CP1258TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "TIS-620", &kNS_TIS620TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-8859-11", &kNS_ISO885911TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "windows-874", &kNS_CP874TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM866", &kNS_CP866TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "KOI8-R", &kNS_KOI8RTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "KOI8-U", &kNS_KOI8UTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-ce", &kNS_MACCETOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-greek", &kNS_MACGREEKTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-turkish", &kNS_MACTURKISHTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-croatian", &kNS_MACCROATIANTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-romanian", &kNS_MACROMANIANTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-cyrillic", &kNS_MACCYRILLICTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-icelandic", &kNS_MACICELANDICTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "GEOSTD8", &kNS_GEOSTD8TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "armscii-8", &kNS_ARMSCII8TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-viet-tcvn5712", &kNS_TCVN5712TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "VISCII", &kNS_VISCIITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-viet-vps", &kNS_VPSTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-7", &kNS_UTF7TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-imap4-modified-utf7", &kNS_MUTF7TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-16", &kNS_UTF16TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-16BE", &kNS_UTF16BETOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "UTF-16LE", &kNS_UTF16LETOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "T.61-8bit", &kNS_T61TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-user-defined", &kNS_USERDEFINEDTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-arabic", &kNS_MACARABICTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-devanagari", &kNS_MACDEVANAGARITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-farsi", &kNS_MACFARSITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-gurmukhi", &kNS_MACGURMUKHITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-gujarati", &kNS_MACGUJARATITOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-mac-hebrew", &kNS_MACHEBREWTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "us-ascii", &kNS_UNICODETOASCII_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-2", &kNS_UNICODETOISO88592_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-3", &kNS_UNICODETOISO88593_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-4", &kNS_UNICODETOISO88594_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-5", &kNS_UNICODETOISO88595_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-6", &kNS_UNICODETOISO88596_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-6-I", &kNS_UNICODETOISO88596I_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-6-E", &kNS_UNICODETOISO88596E_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-7", &kNS_UNICODETOISO88597_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-8", &kNS_UNICODETOISO88598_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-8-I", &kNS_UNICODETOISO88598I_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-8-E", &kNS_UNICODETOISO88598E_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-9", &kNS_UNICODETOISO88599_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-10", &kNS_UNICODETOISO885910_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-13", &kNS_UNICODETOISO885913_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-14", &kNS_UNICODETOISO885914_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-15", &kNS_UNICODETOISO885915_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-16", &kNS_UNICODETOISO885916_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-IR-111", &kNS_UNICODETOISOIR111_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1250", &kNS_UNICODETOCP1250_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1251", &kNS_UNICODETOCP1251_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1253", &kNS_UNICODETOCP1253_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1254", &kNS_UNICODETOCP1254_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1255", &kNS_UNICODETOCP1255_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1256", &kNS_UNICODETOCP1256_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1257", &kNS_UNICODETOCP1257_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-1258", &kNS_UNICODETOCP1258_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "TIS-620", &kNS_UNICODETOTIS620_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-8859-11", &kNS_UNICODETOISO885911_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "windows-874", &kNS_UNICODETOCP874_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM866", &kNS_UNICODETOCP866_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "KOI8-R", &kNS_UNICODETOKOI8R_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "KOI8-U", &kNS_UNICODETOKOI8U_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-ce", &kNS_UNICODETOMACCE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-greek", &kNS_UNICODETOMACGREEK_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-turkish", &kNS_UNICODETOMACTURKISH_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-croatian", &kNS_UNICODETOMACCROATIAN_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-romanian", &kNS_UNICODETOMACROMANIAN_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-cyrillic", &kNS_UNICODETOMACCYRILLIC_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-icelandic", &kNS_UNICODETOMACICELANDIC_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "GEOSTD8", &kNS_UNICODETOGEOSTD8_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "armscii-8", &kNS_UNICODETOARMSCII8_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-viet-tcvn5712", &kNS_UNICODETOTCVN5712_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "VISCII", &kNS_UNICODETOVISCII_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-viet-vps", &kNS_UNICODETOVPS_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-7", &kNS_UNICODETOUTF7_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-imap4-modified-utf7", &kNS_UNICODETOMUTF7_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-16BE", &kNS_UNICODETOUTF16BE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-16LE", &kNS_UNICODETOUTF16LE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "UTF-16", &kNS_UNICODETOUTF16_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "T.61-8bit", &kNS_UNICODETOT61_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-user-defined", &kNS_UNICODETOUSERDEFINED_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "Adobe-Symbol-Encoding", &kNS_UNICODETOSYMBOL_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-zapf-dingbats", &kNS_UNICODETOZAPFDINGBATS_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-adobe-euro", &kNS_UNICODETOADOBEEURO_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-arabic", &kNS_UNICODETOMACARABIC_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-devanagari", &kNS_UNICODETOMACDEVANAGARI_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-farsi", &kNS_UNICODETOMACFARSI_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-gurmukhi", &kNS_UNICODETOMACGURMUKHI_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-gujarati", &kNS_UNICODETOMACGUJARATI_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-mac-hebrew", &kNS_UNICODETOMACHEBREW_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-tscii", &kNS_UNICODETOTSCII_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-tamilttf-0", &kNS_UNICODETOTAMILTTF_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM850", &kNS_CP850TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM852", &kNS_CP852TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM855", &kNS_CP855TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM857", &kNS_CP857TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM862", &kNS_CP862TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM864", &kNS_CP864TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM864i", &kNS_CP864ITOUNICODE_CID }, +#ifdef XP_OS2 + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM869", &kNS_CP869TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM1125", &kNS_CP1125TOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "IBM1131", &kNS_CP1131TOUNICODE_CID }, +#endif + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM850", &kNS_UNICODETOCP850_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM852", &kNS_UNICODETOCP852_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM855", &kNS_UNICODETOCP855_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM857", &kNS_UNICODETOCP857_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM862", &kNS_UNICODETOCP862_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM864", &kNS_UNICODETOCP864_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM864i", &kNS_UNICODETOCP864I_CID }, +#ifdef XP_OS2 + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM869", &kNS_UNICODETOCP869_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM1125", &kNS_UNICODETOCP1125_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "IBM1131", &kNS_UNICODETOCP1131_CID }, +#endif + { NS_UNICODEDECODER_CONTRACTID_BASE "Shift_JIS", &kNS_SJISTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "EUC-JP", &kNS_EUCJPTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-2022-JP", &kNS_ISO2022JPTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "Shift_JIS", &kNS_UNICODETOSJIS_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "EUC-JP", &kNS_UNICODETOEUCJP_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "ISO-2022-JP", &kNS_UNICODETOISO2022JP_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "jis_0201", &kNS_UNICODETOJISX0201_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-euc-tw", &kNS_EUCTWTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-euc-tw", &kNS_UNICODETOEUCTW_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "Big5", &kNS_UNICODETOBIG5_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "Big5", &kNS_BIG5TOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "Big5-HKSCS", &kNS_UNICODETOBIG5HKSCS_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "hkscs-1", &kNS_UNICODETOHKSCS_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "Big5-HKSCS", &kNS_BIG5HKSCSTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "EUC-KR", &kNS_EUCKRTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "EUC-KR", &kNS_UNICODETOEUCKR_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-johab", &kNS_JOHABTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-johab", &kNS_UNICODETOJOHAB_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "x-windows-949", &kNS_CP949TOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "x-windows-949", &kNS_UNICODETOCP949_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-2022-KR", &kNS_ISO2022KRTOUNICODE_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "GB2312", &kNS_GB2312TOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "GB2312", &kNS_UNICODETOGB2312_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "gbk", &kNS_GBKTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "gbk", &kNS_UNICODETOGBK_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "HZ-GB-2312", &kNS_HZTOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "HZ-GB-2312", &kNS_UNICODETOHZ_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "gb18030", &kNS_GB18030TOUNICODE_CID }, + { NS_UNICODEENCODER_CONTRACTID_BASE "gb18030", &kNS_UNICODETOGB18030_CID }, + { NS_UNICODEDECODER_CONTRACTID_BASE "ISO-2022-CN", &kNS_ISO2022CNTOUNICODE_CID }, { NULL } }; diff --git a/intl/uconv/ucvcn/Makefile.in b/intl/uconv/ucvcn/Makefile.in index 7f726519d03..b5765ee9733 100644 --- a/intl/uconv/ucvcn/Makefile.in +++ b/intl/uconv/ucvcn/Makefile.in @@ -45,9 +45,8 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvcn LIBRARY_NAME = ucvcn_s FORCE_STATIC_LIB=1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif + CPPSRCS = \ nsGB2312ToUnicodeV2.cpp \ diff --git a/intl/uconv/ucvcn/nsISO2022CNToUnicode.cpp b/intl/uconv/ucvcn/nsISO2022CNToUnicode.cpp index 42259188d5e..04a33af63a2 100644 --- a/intl/uconv/ucvcn/nsISO2022CNToUnicode.cpp +++ b/intl/uconv/ucvcn/nsISO2022CNToUnicode.cpp @@ -39,7 +39,6 @@ #include "nsUCSupport.h" #include "nsICharsetConverterManager.h" #include "nsIServiceManager.h" -#include "nsServiceManagerUtils.h" static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID); diff --git a/intl/uconv/ucvibm/Makefile.in b/intl/uconv/ucvibm/Makefile.in index 3234b43e218..46ec0247926 100644 --- a/intl/uconv/ucvibm/Makefile.in +++ b/intl/uconv/ucvibm/Makefile.in @@ -46,9 +46,7 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvibm LIBRARY_NAME = ucvibm_s FORCE_STATIC_LIB = 1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif CPPSRCS = \ diff --git a/intl/uconv/ucvja/Makefile.in b/intl/uconv/ucvja/Makefile.in index c4a140aee5d..74f6b0955a3 100644 --- a/intl/uconv/ucvja/Makefile.in +++ b/intl/uconv/ucvja/Makefile.in @@ -46,9 +46,8 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvja LIBRARY_NAME = ucvja_s FORCE_STATIC_LIB = 1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif + CPPSRCS = \ nsJapaneseToUnicode.cpp \ diff --git a/intl/uconv/ucvja/nsJapaneseToUnicode.cpp b/intl/uconv/ucvja/nsJapaneseToUnicode.cpp index 33c78d175cf..0b11b0a8925 100644 --- a/intl/uconv/ucvja/nsJapaneseToUnicode.cpp +++ b/intl/uconv/ucvja/nsJapaneseToUnicode.cpp @@ -42,7 +42,6 @@ #include "nsICharsetConverterManager.h" #include "nsIServiceManager.h" -#include "nsServiceManagerUtils.h" static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID); #ifdef XP_OS2 diff --git a/intl/uconv/ucvko/Makefile.in b/intl/uconv/ucvko/Makefile.in index 65594f25b33..c3a720e6360 100644 --- a/intl/uconv/ucvko/Makefile.in +++ b/intl/uconv/ucvko/Makefile.in @@ -45,9 +45,8 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvko LIBRARY_NAME = ucvko_s FORCE_STATIC_LIB=1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif + CPPSRCS = \ nsEUCKRToUnicode.cpp \ diff --git a/intl/uconv/ucvko/nsISO2022KRToUnicode.cpp b/intl/uconv/ucvko/nsISO2022KRToUnicode.cpp index c99f0e88661..e383a13654f 100644 --- a/intl/uconv/ucvko/nsISO2022KRToUnicode.cpp +++ b/intl/uconv/ucvko/nsISO2022KRToUnicode.cpp @@ -39,7 +39,6 @@ #include "nsUCSupport.h" #include "nsICharsetConverterManager.h" #include "nsIServiceManager.h" -#include "nsServiceManagerUtils.h" static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID); diff --git a/intl/uconv/ucvlatin/Makefile.in b/intl/uconv/ucvlatin/Makefile.in index 8230d45a202..db6e08c6e7f 100644 --- a/intl/uconv/ucvlatin/Makefile.in +++ b/intl/uconv/ucvlatin/Makefile.in @@ -45,9 +45,8 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvlatin LIBRARY_NAME = ucvlatin_s FORCE_STATIC_LIB = 1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif + CPPSRCS = \ nsAsciiToUnicode.cpp \ diff --git a/intl/uconv/ucvtw/Makefile.in b/intl/uconv/ucvtw/Makefile.in index 178ba41361a..eb20a17cd14 100644 --- a/intl/uconv/ucvtw/Makefile.in +++ b/intl/uconv/ucvtw/Makefile.in @@ -45,9 +45,8 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvtw LIBRARY_NAME = ucvtw_s FORCE_STATIC_LIB=1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif + CPPSRCS = \ nsBIG5ToUnicode.cpp \ diff --git a/intl/uconv/ucvtw2/Makefile.in b/intl/uconv/ucvtw2/Makefile.in index 480f8596156..16f6f3df196 100644 --- a/intl/uconv/ucvtw2/Makefile.in +++ b/intl/uconv/ucvtw2/Makefile.in @@ -45,9 +45,8 @@ include $(DEPTH)/config/autoconf.mk MODULE = ucvtw2 LIBRARY_NAME = ucvtw2_s FORCE_STATIC_LIB=1 -ifneq (WINNT,$(OS_TARGET)) LIBXUL_LIBRARY = 1 -endif + CPPSRCS = \ nsEUCTWToUnicode.cpp \ diff --git a/intl/uconv/util/Makefile.in b/intl/uconv/util/Makefile.in index edce9d575ee..276648b0613 100644 --- a/intl/uconv/util/Makefile.in +++ b/intl/uconv/util/Makefile.in @@ -42,8 +42,6 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -DIRS = external - MODULE = uconv LIBRARY_NAME = ucvutil_s EXPORT_LIBRARY = 1 @@ -52,7 +50,18 @@ LIBXUL_LIBRARY = 1 MODULE_NAME = nsUCUtil -include $(srcdir)/objs.mk +CSRCS = \ + ugen.c \ + uscan.c \ + umap.c \ + $(NULL) + +CPPSRCS = \ + nsUCSupport.cpp \ + nsUCConstructors.cpp \ + nsUnicodeDecodeHelper.cpp \ + nsUnicodeEncodeHelper.cpp \ + $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/intl/uconv/util/objs.mk b/intl/uconv/util/objs.mk deleted file mode 100644 index 5d32b3a9646..00000000000 --- a/intl/uconv/util/objs.mk +++ /dev/null @@ -1,12 +0,0 @@ -CSRCS = \ - ugen.c \ - uscan.c \ - umap.c \ - $(NULL) - -CPPSRCS = \ - nsUCSupport.cpp \ - nsUCConstructors.cpp \ - nsUnicodeDecodeHelper.cpp \ - nsUnicodeEncodeHelper.cpp \ - $(NULL) diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index ac70c2ee0c9..e27a592700f 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -913,11 +913,6 @@ abstract public class GeckoApp setLaunchState(GeckoApp.LaunchState.GeckoRunning); GeckoAppShell.sendPendingEventsToGecko(); connectGeckoLayerClient(); - GeckoAppShell.getHandler().post(new Runnable() { - public void run() { - Looper.myQueue().addIdleHandler(new UpdateIdleHandler()); - } - }); } else if (event.equals("ToggleChrome:Hide")) { mMainHandler.post(new Runnable() { public void run() { @@ -1435,7 +1430,32 @@ abstract public class GeckoApp mSmsReceiver = new GeckoSmsManager(); registerReceiver(mSmsReceiver, smsFilter); - final GeckoApp self = this; + final GeckoApp self = this; + + mMainHandler.postDelayed(new Runnable() { + public void run() { + + Log.w(LOGTAG, "zerdatime " + new Date().getTime() + " - pre checkLaunchState"); + + /* + XXXX see bug 635342 + We want to disable this code if possible. It is about 145ms in runtime + SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE); + String localeCode = settings.getString(getPackageName() + ".locale", ""); + if (localeCode != null && localeCode.length() > 0) + GeckoAppShell.setSelectedLocale(localeCode); + */ + + if (!checkLaunchState(LaunchState.Launched)) { + return; + } + + // it would be good only to do this if MOZ_UPDATER was defined + long startTime = new Date().getTime(); + checkAndLaunchUpdate(); + Log.w(LOGTAG, "checking for an update took " + (new Date().getTime() - startTime) + "ms"); + } + }, 50); } /** @@ -1700,21 +1720,6 @@ abstract public class GeckoApp GeckoAppShell.handleNotification(action, alertName, alertCookie); } - // it would be good only to do this if MOZ_UPDATER was defined - private class UpdateIdleHandler implements MessageQueue.IdleHandler { - public boolean queueIdle() { - mMainHandler.post(new Runnable() { - public void run() { - long startTime = new Date().getTime(); - checkAndLaunchUpdate(); - Log.w(LOGTAG, "checking for an update took " + (new Date().getTime() - startTime) + "ms"); - } - }); - // only need to run this once. - return false; - } - } - private void checkAndLaunchUpdate() { Log.i(LOGTAG, "Checking for an update"); diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 4b67c535a5d..afc0ae77603 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -791,6 +791,13 @@ pref("network.http.connection-retry-timeout", 250); // IPv6 connectivity. pref("network.http.fast-fallback-to-IPv4", true); +// Try and use SPDY when using SSL +pref("network.http.spdy.enabled", false); +pref("network.http.spdy.chunk-size", 4096); +pref("network.http.spdy.timeout", 180); +pref("network.http.spdy.coalesce-hostnames", true); +pref("network.http.spdy.use-alternate-protocol", true); + // default values for FTP // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594, // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22) diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index 0a243477c1b..ec8e31f8122 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -552,7 +552,8 @@ nsSocketOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *countWrit *countWritten = 0; - if (count == 0) + // A write of 0 bytes can be used to force the initial SSL handshake. + if (count == 0 && mByteCount) return NS_OK; PRFileDesc *fd; diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index ff7f5b1a177..c2a25b2aff1 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -80,6 +80,7 @@ HttpBaseChannel::HttpBaseChannel() , mChannelIsForDownload(false) , mTracingEnabled(true) , mTimingEnabled(false) + , mAllowSpdy(true) , mSuspendCount(0) , mRedirectedCachekeys(nsnull) { @@ -1310,6 +1311,22 @@ HttpBaseChannel::HTTPUpgrade(const nsACString &aProtocolName, return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::GetAllowSpdy(bool *aAllowSpdy) +{ + NS_ENSURE_ARG_POINTER(aAllowSpdy); + + *aAllowSpdy = mAllowSpdy; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) +{ + mAllowSpdy = aAllowSpdy; + return NS_OK; +} + //----------------------------------------------------------------------------- // HttpBaseChannel::nsISupportsPriority //----------------------------------------------------------------------------- @@ -1619,6 +1636,8 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, if (httpInternal) { // convey the mForceAllowThirdPartyCookie flag httpInternal->SetForceAllowThirdPartyCookie(mForceAllowThirdPartyCookie); + // convey the spdy flag + httpInternal->SetAllowSpdy(mAllowSpdy); // update the DocumentURI indicator since we are being redirected. // if this was a top-level document channel, then the new channel diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 9e455003047..3aaf1886c44 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -167,6 +167,9 @@ public: NS_IMETHOD GetLocalPort(PRInt32* port); NS_IMETHOD GetRemoteAddress(nsACString& addr); NS_IMETHOD GetRemotePort(PRInt32* port); + NS_IMETHOD GetAllowSpdy(bool *aAllowSpdy); + NS_IMETHOD SetAllowSpdy(bool aAllowSpdy); + inline void CleanRedirectCacheChainIfNecessary() { if (mRedirectedCachekeys) { @@ -295,6 +298,7 @@ protected: PRUint32 mTracingEnabled : 1; // True if timing collection is enabled PRUint32 mTimingEnabled : 1; + PRUint32 mAllowSpdy : 1; // Current suspension depth for this channel object PRUint32 mSuspendCount; diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 7c75209965b..bbc35ff884c 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1079,7 +1079,7 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) mPriority, mRedirectionLimit, mAllowPipelining, mForceAllowThirdPartyCookie, mSendResumeAt, mStartPos, mEntityID, mChooseApplicationCache, - appCacheClientId); + appCacheClientId, mAllowSpdy); return NS_OK; } diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 2a4b67ea410..3bf147d120e 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -143,7 +143,8 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI, const PRUint64& startPos, const nsCString& entityID, const bool& chooseApplicationCache, - const nsCString& appCacheClientID) + const nsCString& appCacheClientID, + const bool& allowSpdy) { nsCOMPtr uri(aURI); nsCOMPtr originalUri(aOriginalURI); @@ -203,6 +204,7 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI, httpChan->SetRedirectionLimit(redirectionLimit); httpChan->SetAllowPipelining(allowPipelining); httpChan->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie); + httpChan->SetAllowSpdy(allowSpdy); nsCOMPtr appCacheChan = do_QueryInterface(mChannel); diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h index 9c47bc7d5fc..ab82edc095a 100644 --- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -97,7 +97,8 @@ protected: const PRUint64& startPos, const nsCString& entityID, const bool& chooseApplicationCache, - const nsCString& appCacheClientID); + const nsCString& appCacheClientID, + const bool& allowSpdy); virtual bool RecvConnectChannel(const PRUint32& channelId); virtual bool RecvSetPriority(const PRUint16& priority); diff --git a/netwerk/protocol/http/Makefile.in b/netwerk/protocol/http/Makefile.in index 486c214d6bd..4f3155387cb 100644 --- a/netwerk/protocol/http/Makefile.in +++ b/netwerk/protocol/http/Makefile.in @@ -110,6 +110,8 @@ CPPSRCS = \ HttpChannelParent.cpp \ HttpChannelChild.cpp \ HttpChannelParentListener.cpp \ + SpdySession.cpp \ + SpdyStream.cpp \ $(NULL) LOCAL_INCLUDES = \ diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl index 649f61846b2..bc38abaf110 100644 --- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -81,7 +81,8 @@ parent: PRUint64 startPos, nsCString entityID, bool chooseApplicationCache, - nsCString appCacheClientID); + nsCString appCacheClientID, + bool allowSpdy); // Used to connect redirected-to channel on the parent with redirected-to // channel on the child. diff --git a/netwerk/protocol/http/SpdySession.cpp b/netwerk/protocol/http/SpdySession.cpp new file mode 100644 index 00000000000..58e777eebe3 --- /dev/null +++ b/netwerk/protocol/http/SpdySession.cpp @@ -0,0 +1,1875 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "SpdySession.h" +#include "SpdyStream.h" +#include "nsHttpConnection.h" +#include "prnetdb.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" +#include "prprf.h" + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +// SpdySession has multiple inheritance of things that implement +// nsISupports, so this magic is taken from nsHttpPipeline that +// implements some of the same abstract classes. +NS_IMPL_THREADSAFE_ADDREF(SpdySession) +NS_IMPL_THREADSAFE_RELEASE(SpdySession) +NS_INTERFACE_MAP_BEGIN(SpdySession) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) +NS_INTERFACE_MAP_END + +SpdySession::SpdySession(nsAHttpTransaction *aHttpTransaction, + nsISocketTransport *aSocketTransport, + PRInt32 firstPriority) + : mSocketTransport(aSocketTransport), + mSegmentReader(nsnull), + mSegmentWriter(nsnull), + mSendingChunkSize(kSendingChunkSize), + mNextStreamID(1), + mConcurrentHighWater(0), + mDownstreamState(BUFFERING_FRAME_HEADER), + mPartialFrame(nsnull), + mFrameBufferSize(kDefaultBufferSize), + mFrameBufferUsed(0), + mFrameDataLast(false), + mFrameDataStream(nsnull), + mNeedsCleanup(nsnull), + mDecompressBufferSize(kDefaultBufferSize), + mDecompressBufferUsed(0), + mShouldGoAway(false), + mClosed(false), + mCleanShutdown(false), + mGoAwayID(0), + mMaxConcurrent(kDefaultMaxConcurrent), + mConcurrent(0), + mServerPushedResources(0), + mOutputQueueSize(kDefaultQueueSize), + mOutputQueueUsed(0), + mOutputQueueSent(0) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + LOG3(("SpdySession::SpdySession %p transaction 1 = %p", + this, aHttpTransaction)); + + mStreamIDHash.Init(); + mStreamTransactionHash.Init(); + mConnection = aHttpTransaction->Connection(); + mFrameBuffer = new char[mFrameBufferSize]; + mDecompressBuffer = new char[mDecompressBufferSize]; + mOutputQueueBuffer = new char[mOutputQueueSize]; + zlibInit(); + + mSendingChunkSize = + Preferences::GetInt("network.http.spdy.chunk-size", kSendingChunkSize); + AddStream(aHttpTransaction, firstPriority); +} + +PLDHashOperator +SpdySession::Shutdown(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + SpdySession *self = static_cast(closure); + + if (self->mCleanShutdown && + self->mGoAwayID < stream->StreamID()) + stream->Close(NS_ERROR_NET_RESET); // can be restarted + else + stream->Close(NS_ERROR_ABORT); + + return PL_DHASH_NEXT; +} + +SpdySession::~SpdySession() +{ + LOG3(("SpdySession::~SpdySession %p mDownstreamState=%X", + this, mDownstreamState)); + + inflateEnd(&mDownstreamZlib); + deflateEnd(&mUpstreamZlib); + + mStreamTransactionHash.Enumerate(Shutdown, this); + Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater); + Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2); + Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS, + mServerPushedResources); +} + +void +SpdySession::LogIO(SpdySession *self, SpdyStream *stream, const char *label, + const char *data, PRUint32 datalen) +{ + if (!LOG4_ENABLED()) + return; + + LOG4(("SpdySession::LogIO %p stream=%p id=0x%X [%s]", + self, stream, stream ? stream->StreamID() : 0, label)); + + // Max line is (16 * 3) + 10(prefix) + newline + null + char linebuf[128]; + PRUint32 index; + char *line = linebuf; + + linebuf[127] = 0; + + for (index = 0; index < datalen; ++index) { + if (!(index % 16)) { + if (index) { + *line = 0; + LOG4(("%s", linebuf)); + } + line = linebuf; + PR_snprintf(line, 128, "%08X: ", index); + line += 10; + } + PR_snprintf(line, 128 - (line - linebuf), "%02X ", + ((unsigned char *)data)[index]); + line += 3; + } + if (index) { + *line = 0; + LOG4(("%s", linebuf)); + } +} + +typedef nsresult (*Control_FX) (SpdySession *self); +static Control_FX sControlFunctions[] = +{ + nsnull, + SpdySession::HandleSynStream, + SpdySession::HandleSynReply, + SpdySession::HandleRstStream, + SpdySession::HandleSettings, + SpdySession::HandleNoop, + SpdySession::HandlePing, + SpdySession::HandleGoAway, + SpdySession::HandleHeaders, + SpdySession::HandleWindowUpdate +}; + +bool +SpdySession::RoomForMoreConcurrent() +{ + return (mConcurrent < mMaxConcurrent); +} + +bool +SpdySession::RoomForMoreStreams() +{ + if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) + return false; + + return !mShouldGoAway; +} + +PRUint32 +SpdySession::RegisterStreamID(SpdyStream *stream) +{ + LOG3(("SpdySession::RegisterStreamID session=%p stream=%p id=0x%X " + "concurrent=%d",this, stream, mNextStreamID, mConcurrent)); + + NS_ABORT_IF_FALSE(mNextStreamID < 0xfffffff0, + "should have stopped admitting streams"); + + PRUint32 result = mNextStreamID; + mNextStreamID += 2; + + // We've used up plenty of ID's on this session. Start + // moving to a new one before there is a crunch involving + // server push streams or concurrent non-registered submits + if (mNextStreamID >= kMaxStreamID) + mShouldGoAway = true; + + mStreamIDHash.Put(result, stream); + return result; +} + +bool +SpdySession::AddStream(nsAHttpTransaction *aHttpTransaction, + PRInt32 aPriority) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mStreamTransactionHash.Get(aHttpTransaction), + "AddStream duplicate transaction pointer"); + + aHttpTransaction->SetConnection(this); + SpdyStream *stream = new SpdyStream(aHttpTransaction, + this, + mSocketTransport, + mSendingChunkSize, + &mUpstreamZlib, + aPriority); + + + LOG3(("SpdySession::AddStream session=%p stream=%p NextID=0x%X (tentative)", + this, stream, mNextStreamID)); + + mStreamTransactionHash.Put(aHttpTransaction, stream); + + if (RoomForMoreConcurrent()) { + LOG3(("SpdySession::AddStream %p stream %p activated immediately.", + this, stream)); + ActivateStream(stream); + } + else { + LOG3(("SpdySession::AddStream %p stream %p queued.", + this, stream)); + mQueuedStreams.Push(stream); + } + + return true; +} + +void +SpdySession::ActivateStream(SpdyStream *stream) +{ + mConcurrent++; + if (mConcurrent > mConcurrentHighWater) + mConcurrentHighWater = mConcurrent; + LOG3(("SpdySession::AddStream %p activating stream %p Currently %d " + "streams in session, high water mark is %d", + this, stream, mConcurrent, mConcurrentHighWater)); + + mReadyForWrite.Push(stream); + SetWriteCallbacks(stream->Transaction()); + + // Kick off the SYN transmit without waiting for the poll loop + PRUint32 countRead; + ReadSegments(nsnull, kDefaultBufferSize, &countRead); +} + +void +SpdySession::ProcessPending() +{ + while (RoomForMoreConcurrent()) { + SpdyStream *stream = static_cast(mQueuedStreams.PopFront()); + if (!stream) + return; + LOG3(("SpdySession::ProcessPending %p stream %p activated from queue.", + this, stream)); + ActivateStream(stream); + } +} + +void +SpdySession::SetWriteCallbacks(nsAHttpTransaction *aTrans) +{ + if (mConnection && (WriteQueueSize() || mOutputQueueUsed)) + mConnection->ResumeSend(aTrans); +} + +void +SpdySession::FlushOutputQueue() +{ + if (!mSegmentReader || !mOutputQueueUsed) + return; + + nsresult rv; + PRUint32 countRead; + PRUint32 avail = mOutputQueueUsed - mOutputQueueSent; + + rv = mSegmentReader-> + OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, + &countRead); + LOG3(("SpdySession::FlushOutputQueue %p sz=%d rv=%x actual=%d", + this, avail, rv, countRead)); + + // Dont worry about errors on write, we will pick this up as a read error too + if (NS_FAILED(rv)) + return; + + if (countRead == avail) { + mOutputQueueUsed = 0; + mOutputQueueSent = 0; + return; + } + + mOutputQueueSent += countRead; + if (mOutputQueueSize - mOutputQueueUsed < kQueueTailRoom) { + // The output queue is filling up and we just sent some data out, so + // this is a good time to rearrange the output queue. + + mOutputQueueUsed -= mOutputQueueSent; + memmove(mOutputQueueBuffer.get(), + mOutputQueueBuffer.get() + mOutputQueueSent, + mOutputQueueUsed); + mOutputQueueSent = 0; + } +} + +void +SpdySession::DontReuse() +{ + mShouldGoAway = true; + if(!mStreamTransactionHash.Count()) + Close(NS_OK); +} + +PRUint32 +SpdySession::WriteQueueSize() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + PRUint32 count = mUrgentForWrite.GetSize() + mReadyForWrite.GetSize(); + + if (mPartialFrame) + ++count; + return count; +} + +void +SpdySession::ChangeDownstreamState(enum stateType newState) +{ + LOG3(("SpdyStream::ChangeDownstreamState() %p from %X to %X", + this, mDownstreamState, newState)); + mDownstreamState = newState; + + if (mDownstreamState == BUFFERING_FRAME_HEADER) { + if (mFrameDataLast && mFrameDataStream) { + mFrameDataLast = 0; + if (!mFrameDataStream->RecvdFin()) { + mFrameDataStream->SetRecvdFin(true); + --mConcurrent; + ProcessPending(); + } + } + mFrameBufferUsed = 0; + mFrameDataStream = nsnull; + } + + return; +} + +void +SpdySession::EnsureBuffer(nsAutoArrayPtr &buf, + PRUint32 newSize, + PRUint32 preserve, + PRUint32 &objSize) +{ + if (objSize >= newSize) + return; + + objSize = newSize; + nsAutoArrayPtr tmp(new char[objSize]); + memcpy (tmp, buf, preserve); + buf = tmp; +} + +void +SpdySession::zlibInit() +{ + mDownstreamZlib.zalloc = SpdyStream::zlib_allocator; + mDownstreamZlib.zfree = SpdyStream::zlib_destructor; + mDownstreamZlib.opaque = Z_NULL; + + inflateInit(&mDownstreamZlib); + + mUpstreamZlib.zalloc = SpdyStream::zlib_allocator; + mUpstreamZlib.zfree = SpdyStream::zlib_destructor; + mUpstreamZlib.opaque = Z_NULL; + + deflateInit(&mUpstreamZlib, Z_DEFAULT_COMPRESSION); + deflateSetDictionary(&mUpstreamZlib, + reinterpret_cast + (SpdyStream::kDictionary), + strlen(SpdyStream::kDictionary) + 1); + +} + +nsresult +SpdySession::DownstreamUncompress(char *blockStart, PRUint32 blockLen) +{ + mDecompressBufferUsed = 0; + + mDownstreamZlib.avail_in = blockLen; + mDownstreamZlib.next_in = reinterpret_cast(blockStart); + + do { + mDownstreamZlib.next_out = + reinterpret_cast(mDecompressBuffer.get()) + + mDecompressBufferUsed; + mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed; + int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH); + + if (zlib_rv == Z_NEED_DICT) + inflateSetDictionary(&mDownstreamZlib, + reinterpret_cast + (SpdyStream::kDictionary), + strlen(SpdyStream::kDictionary) + 1); + + if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR) + return NS_ERROR_FAILURE; + + mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed - + mDownstreamZlib.avail_out; + + // When there is no more output room, but input still available then + // increase the output space + if (zlib_rv == Z_OK && + !mDownstreamZlib.avail_out && mDownstreamZlib.avail_in) { + LOG3(("SpdySession::DownstreamUncompress %p Large Headers - so far %d", + this, mDecompressBufferSize)); + EnsureBuffer(mDecompressBuffer, + mDecompressBufferSize + 4096, + mDecompressBufferUsed, + mDecompressBufferSize); + } + } + while (mDownstreamZlib.avail_in); + return NS_OK; +} + +nsresult +SpdySession::FindHeader(nsCString name, + nsDependentCSubstring &value) +{ + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 2; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 numPairs = + PR_ntohs(reinterpret_cast(mDecompressBuffer.get())[0]); + for (PRUint16 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 2) + return NS_ERROR_ILLEGAL_VALUE; + PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; + if (lastHeaderByte < nvpair + 2 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + nsDependentCSubstring nameString = + Substring (reinterpret_cast(nvpair) + 2, + reinterpret_cast(nvpair) + 2 + nameLen); + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; + if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + if (nameString.Equals(name)) { + value.Assign(((char *)nvpair) + 4 + nameLen, valueLen); + return NS_OK; + } + nvpair += 4 + nameLen + valueLen; + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult +SpdySession::ConvertHeaders(nsDependentCSubstring &status, + nsDependentCSubstring &version) +{ + + mFlatHTTPResponseHeaders.Truncate(); + mFlatHTTPResponseHeadersOut = 0; + mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64); + + // Connection, Keep-Alive and chunked transfer encodings are to be + // removed. + + // Content-Length is 'advisory'.. we will not strip it because it can + // create UI feedback. + + mFlatHTTPResponseHeaders.Append(version); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" ")); + mFlatHTTPResponseHeaders.Append(status); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 2; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint16 numPairs = + PR_ntohs(reinterpret_cast(mDecompressBuffer.get())[0]); + + for (PRUint16 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 2) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; + if (lastHeaderByte < nvpair + 2 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + + nsDependentCSubstring nameString = + Substring (reinterpret_cast(nvpair) + 2, + reinterpret_cast(nvpair) + 2 + nameLen); + + // a null in the name string is particularly wrong because it will + // break the fix-up-nulls-in-value-string algorithm. + if (nameString.FindChar(0) != -1) + return NS_ERROR_ILLEGAL_VALUE; + + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; + if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + + // Look for upper case characters in the name. They are illegal. + for (char *cPtr = nameString.BeginWriting(); + cPtr && cPtr < nameString.EndWriting(); + ++cPtr) { + if (*cPtr <= 'Z' && *cPtr >= 'A') { + nsCString toLog(nameString); + + LOG3(("SpdySession::ConvertHeaders session=%p stream=%p " + "upper case response header found. [%s]\n", + this, mFrameDataStream, toLog.get())); + + return NS_ERROR_ILLEGAL_VALUE; + } + } + + // HTTP Chunked responses are not legal over spdy. We do not need + // to look for chunked specifically because it is the only HTTP + // allowed default encoding and we did not negotiate further encodings + // via TE + if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { + LOG3(("SpdySession::ConvertHeaders session=%p stream=%p " + "transfer-encoding found. Chunked is invalid and no TE sent.", + this, mFrameDataStream)); + + return NS_ERROR_ILLEGAL_VALUE; + } + + if (!nameString.Equals(NS_LITERAL_CSTRING("version")) && + !nameString.Equals(NS_LITERAL_CSTRING("status")) && + !nameString.Equals(NS_LITERAL_CSTRING("connection")) && + !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { + nsDependentCSubstring valueString = + Substring (reinterpret_cast(nvpair) + 4 + nameLen, + reinterpret_cast(nvpair) + 4 + nameLen + + valueLen); + + mFlatHTTPResponseHeaders.Append(nameString); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": ")); + + PRInt32 valueIndex; + // NULLs are really "\r\nhdr: " + while ((valueIndex = valueString.FindChar(0)) != -1) { + nsCString replacement = NS_LITERAL_CSTRING("\r\n"); + replacement.Append(nameString); + replacement.Append(NS_LITERAL_CSTRING(": ")); + valueString.Replace(valueIndex, 1, replacement); + } + + mFlatHTTPResponseHeaders.Append(valueString); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + } + nvpair += 4 + nameLen + valueLen; + } + + mFlatHTTPResponseHeaders.Append( + NS_LITERAL_CSTRING("X-Firefox-Spdy: 1\r\n\r\n")); + LOG (("decoded response headers are:\n%s", + mFlatHTTPResponseHeaders.get())); + + return NS_OK; +} + +void +SpdySession::GeneratePing(PRUint32 aID) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::GeneratePing %p 0x%X\n", this, aID)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 12; + + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[2] = 0; + packet[3] = CONTROL_TYPE_PING; + packet[4] = 0; /* flags */ + packet[5] = 0; + packet[6] = 0; + packet[7] = 4; /* length */ + + aID = PR_htonl(aID); + memcpy (packet + 8, &aID, 4); + + FlushOutputQueue(); +} + +void +SpdySession::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 16; + + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[2] = 0; + packet[3] = CONTROL_TYPE_RST_STREAM; + packet[4] = 0; /* flags */ + packet[5] = 0; + packet[6] = 0; + packet[7] = 8; /* length */ + + aID = PR_htonl(aID); + memcpy (packet + 8, &aID, 4); + aStatusCode = PR_htonl(aStatusCode); + memcpy (packet + 12, &aStatusCode, 4); + + FlushOutputQueue(); +} + +void +SpdySession::GenerateGoAway() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::GenerateGoAway %p\n", this)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 12; + + memset (packet, 0, 12); + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[3] = CONTROL_TYPE_GOAWAY; + packet[7] = 4; /* data length */ + + // last-good-stream-id are bytes 8-11, when we accept server push this will + // need to be set non zero + + FlushOutputQueue(); +} + +void +SpdySession::CleanupStream(SpdyStream *aStream, nsresult aResult) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::CleanupStream %p %p 0x%x %X\n", + this, aStream, aStream->StreamID(), aResult)); + + nsresult abortCode = NS_OK; + + if (!aStream->RecvdFin() && aStream->StreamID()) { + LOG3(("Stream had not processed recv FIN, sending RST")); + GenerateRstStream(RST_CANCEL, aStream->StreamID()); + --mConcurrent; + ProcessPending(); + } + + // Check if partial frame writer + if (mPartialFrame == aStream) { + LOG3(("Stream had active partial write frame - need to abort session")); + abortCode = aResult; + if (NS_SUCCEEDED(abortCode)) + abortCode = NS_ERROR_ABORT; + + mPartialFrame = nsnull; + } + + // Check if partial frame reader + if (aStream == mFrameDataStream) { + LOG3(("Stream had active partial read frame on close")); + ChangeDownstreamState(DISCARD_DATA_FRAME); + mFrameDataStream = nsnull; + } + + // check the streams blocked on write, this is linear but the list + // should be pretty short. + PRUint32 size = mReadyForWrite.GetSize(); + for (PRUint32 count = 0; count < size; ++count) { + SpdyStream *stream = static_cast(mReadyForWrite.PopFront()); + if (stream != aStream) + mReadyForWrite.Push(stream); + } + + // Check the streams blocked on urgent (i.e. window update) writing. + // This should also be short. + size = mUrgentForWrite.GetSize(); + for (PRUint32 count = 0; count < size; ++count) { + SpdyStream *stream = static_cast(mUrgentForWrite.PopFront()); + if (stream != aStream) + mUrgentForWrite.Push(stream); + } + + // Remove the stream from the ID hash table. (this one isn't short, which is + // why it is hashed.) + mStreamIDHash.Remove(aStream->StreamID()); + + // Send the stream the close() indication + aStream->Close(aResult); + + // removing from the stream transaction hash will + // delete the SpdyStream and drop the reference to + // its transaction + mStreamTransactionHash.Remove(aStream->Transaction()); + + if (NS_FAILED(abortCode)) + Close(abortCode); + else if (mShouldGoAway && !mStreamTransactionHash.Count()) + Close(NS_OK); +} + +nsresult +SpdySession::HandleSynStream(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM, + "wrong control type"); + + if (self->mFrameDataSize < 12) { + LOG3(("SpdySession::HandleSynStream %p SYN_STREAM too short data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + LOG3(("SpdySession::HandleSynStream %p recv SYN_STREAM (push) for ID 0x%X.", + self, streamID)); + + if (streamID & 0x01) { // test for odd stream ID + LOG3(("SpdySession::HandleSynStream %p recvd SYN_STREAM id must be even.", + self)); + return NS_ERROR_ILLEGAL_VALUE; + } + + ++(self->mServerPushedResources); + + // Anytime we start using the high bit of stream ID (either client or server) + // begin to migrate to a new session. + if (streamID >= kMaxStreamID) + self->mShouldGoAway = true; + + // todo populate cache. For now, just reject server push p3 + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleSynReply(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY, + "wrong control type"); + + if (self->mFrameDataSize < 8) { + LOG3(("SpdySession::HandleSynReply %p SYN REPLY too short data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + self->mFrameDataStream = self->mStreamIDHash.Get(streamID); + if (!self->mFrameDataStream) { + LOG3(("SpdySession::HandleSynReply %p lookup streamID in syn_reply " + "0x%X failed. NextStreamID = 0x%x", self, streamID, + self->mNextStreamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + // It is likely that this is a reply to a stream ID that has been canceled. + // For the most part we would like to ignore it, but the header needs to be + // be parsed to keep the compression context synchronized + self->DownstreamUncompress(self->mFrameBuffer + 14, + self->mFrameDataSize - 6); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + if (!self->mFrameDataStream->SetFullyOpen()) { + // "If an endpoint receives multiple SYN_REPLY frames for the same active + // stream ID, it must drop the stream, and send a RST_STREAM for the + // stream with the error PROTOCOL_ERROR." + // + // In addition to that we abort the session - this is a serious protocol + // violation. + + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->mFrameDataLast = self->mFrameBuffer[4] & kFlag_Data_FIN; + + if (self->mFrameBuffer[4] & kFlag_Data_UNI) { + LOG3(("SynReply had unidirectional flag set on it - nonsensical")); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession::HandleSynReply %p SYN_REPLY for 0x%X fin=%d", + self, streamID, self->mFrameDataLast)); + + // The spdystream needs to see flattened http headers + // The Frame Buffer currently holds the complete SYN_REPLY + // frame. The interesting data is at offset 14, where the + // compressed name/value header block lives. + // We unpack that into the mDecompressBuffer - we can't do + // it streamed because the version and status information + // is not guaranteed to be first. This is then finally + // converted to HTTP format in mFlatHTTPResponseHeaders + + nsresult rv = self->DownstreamUncompress(self->mFrameBuffer + 14, + self->mFrameDataSize - 6); + if (NS_FAILED(rv)) + return rv; + + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, + self->mFrameDataSize - 6); + PRUint32 ratio = + (self->mFrameDataSize - 6) * 100 / self->mDecompressBufferUsed; + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); + + // status and version are required. + nsDependentCSubstring status, version; + rv = self->FindHeader(NS_LITERAL_CSTRING("status"), status); + if (NS_FAILED(rv)) + return rv; + + rv = self->FindHeader(NS_LITERAL_CSTRING("version"), version); + if (NS_FAILED(rv)) + return rv; + + rv = self->ConvertHeaders(status, version); + if (NS_FAILED(rv)) + return rv; + + self->ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY); + return NS_OK; +} + +nsresult +SpdySession::HandleRstStream(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_RST_STREAM, + "wrong control type"); + + if (self->mFrameDataSize != 8) { + LOG3(("SpdySession::HandleRstStream %p RST_STREAM wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + self->mDownstreamRstReason = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[3]); + + LOG3(("SpdySession::HandleRstStream %p RST_STREAM Reason Code %u ID %x", + self, self->mDownstreamRstReason, streamID)); + + if (self->mDownstreamRstReason == RST_INVALID_STREAM || + self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) { + // basically just ignore this + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + self->mFrameDataStream = self->mStreamIDHash.Get(streamID); + if (!self->mFrameDataStream) { + LOG3(("SpdySession::HandleRstStream %p lookup streamID for RST Frame " + "0x%X failed", self, streamID)); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); + return NS_OK; +} + +nsresult +SpdySession::HandleSettings(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SETTINGS, + "wrong control type"); + + if (self->mFrameDataSize < 4) { + LOG3(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 numEntries = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + // Ensure frame is large enough for supplied number of entries + // Each entry is 8 bytes, frame data is reduced by 4 to account for + // the NumEntries value. + if ((self->mFrameDataSize - 4) < (numEntries * 8)) { + LOG3(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession::HandleSettings %p SETTINGS Control Frame with %d entries", + self, numEntries)); + + for (PRUint32 index = 0; index < numEntries; ++index) { + // To clarify the v2 spec: + // Each entry is a 24 bits of a little endian id + // followed by 8 bits of flags + // followed by a 32 bit big endian value + + unsigned char *setting = reinterpret_cast + (self->mFrameBuffer.get()) + 12 + index * 8; + + PRUint32 id = (setting[2] << 16) + (setting[1] << 8) + setting[0]; + PRUint32 flags = setting[3]; + PRUint32 value = PR_ntohl(reinterpret_cast(setting)[1]); + + LOG3(("Settings ID %d, Flags %X, Value %d", id, flags, value)); + + switch (id) + { + case SETTINGS_TYPE_UPLOAD_BW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value); + break; + + case SETTINGS_TYPE_DOWNLOAD_BW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value); + break; + + case SETTINGS_TYPE_RTT: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value); + break; + + case SETTINGS_TYPE_MAX_CONCURRENT: + self->mMaxConcurrent = value; + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value); + break; + + case SETTINGS_TYPE_CWND: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value); + break; + + case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value); + break; + + case SETTINGS_TYPE_INITIAL_WINDOW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10); + break; + + default: + break; + } + + } + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleNoop(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_NOOP, + "wrong control type"); + + if (self->mFrameDataSize != 0) { + LOG3(("SpdySession::HandleNoop %p NOP had data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession::HandleNoop %p NOP.", self)); + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandlePing(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_PING, + "wrong control type"); + + if (self->mFrameDataSize != 4) { + LOG3(("SpdySession::HandlePing %p PING had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 pingID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + LOG3(("SpdySession::HandlePing %p PING ID 0x%X.", self, pingID)); + + if (pingID & 0x01) { + // We never expect to see an odd PING beacuse we never generate PING. + // The spec mandates ignoring this + LOG3(("SpdySession::HandlePing %p PING ID from server was odd.", + self)); + } + else { + self->GeneratePing(pingID); + } + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleGoAway(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_GOAWAY, + "wrong control type"); + + if (self->mFrameDataSize != 4) { + LOG3(("SpdySession::HandleGoAway %p GOAWAY had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->mShouldGoAway = true; + self->mGoAwayID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + self->mCleanShutdown = true; + + LOG3(("SpdySession::HandleGoAway %p GOAWAY Last-Good-ID 0x%X.", + self, self->mGoAwayID)); + self->ResumeRecv(self); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleHeaders(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_HEADERS, + "wrong control type"); + + if (self->mFrameDataSize < 10) { + LOG3(("SpdySession::HandleHeaders %p HEADERS had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + // this is actually not legal in the HTTP mapping of SPDY. All + // headers are in the syn or syn reply. Log and ignore it. + + LOG3(("SpdySession::HandleHeaders %p HEADERS for Stream 0x%X. " + "They are ignored in the HTTP/SPDY mapping.", + self, streamID)); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleWindowUpdate(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE, + "wrong control type"); + LOG3(("SpdySession::HandleWindowUpdate %p WINDOW UPDATE was " + "received. WINDOW UPDATE is no longer defined in v2. Ignoring.", + self)); + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +// Used for the hashtable enumeration to propogate OnTransportStatus events +struct transportStatus +{ + nsITransport *transport; + nsresult status; + PRUint64 progress; +}; + +static PLDHashOperator +StreamTransportStatus(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + struct transportStatus *status = + static_cast(closure); + + stream->Transaction()->OnTransportStatus(status->transport, + status->status, + status->progress); + return PL_DHASH_NEXT; +} + + +//----------------------------------------------------------------------------- +// nsAHttpTransaction. It is expected that nsHttpConnection is the caller +// of these methods +//----------------------------------------------------------------------------- + +void +SpdySession::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, + PRUint64 aProgress) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // nsHttpChannel synthesizes progress events in OnDataAvailable + if (aStatus == nsISocketTransport::STATUS_RECEIVING_FROM) + return; + + // STATUS_SENDING_TO is handled by SpdyStream + if (aStatus == nsISocketTransport::STATUS_SENDING_TO) + return; + + struct transportStatus status; + + status.transport = aTransport; + status.status = aStatus; + status.progress = aProgress; + + mStreamTransactionHash.Enumerate(StreamTransportStatus, &status); +} + +// ReadSegments() is used to write data to the network. Generally, HTTP +// request data is pulled from the approriate transaction and +// converted to SPDY data. Sometimes control data like window-update are +// generated instead. + +nsresult +SpdySession::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, + PRUint32 *countRead) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + *countRead = 0; + + // First priority goes to frames that were writing to the network but were + // blocked part way through. Then to frames that have no streams (e.g ping + // reply) and then third to streams marked urgent (generally they have + // window updates), and finally to streams generally + // ready to send data frames (http requests). + + LOG3(("SpdySession::ReadSegments %p partial frame stream=%p", + this, mPartialFrame)); + + SpdyStream *stream = mPartialFrame; + mPartialFrame = nsnull; + + if (!stream) + stream = static_cast(mUrgentForWrite.PopFront()); + if (!stream) + stream = static_cast(mReadyForWrite.PopFront()); + if (!stream) { + LOG3(("SpdySession %p could not identify a stream to write; suspending.", + this)); + FlushOutputQueue(); + SetWriteCallbacks(nsnull); + return NS_BASE_STREAM_WOULD_BLOCK; + } + + LOG3(("SpdySession %p will write from SpdyStream %p", this, stream)); + + NS_ABORT_IF_FALSE(!mSegmentReader || !reader || (mSegmentReader == reader), + "Inconsistent Write Function Callback"); + + if (reader) + mSegmentReader = reader; + rv = stream->ReadSegments(this, count, countRead); + + FlushOutputQueue(); + + if (stream->BlockedOnWrite()) { + + // We are writing a frame out, but it is blocked on the output stream. + // Make sure to service that stream next write because we can only + // multiplex between complete frames. + + LOG3(("SpdySession::ReadSegments %p dealing with block on write", this)); + + NS_ABORT_IF_FALSE(!mPartialFrame, "partial frame should be empty"); + + mPartialFrame = stream; + SetWriteCallbacks(stream->Transaction()); + return rv; + } + + if (stream->RequestBlockedOnRead()) { + + // We are blocked waiting for input - either more http headers or + // any request body data. When more data from the request stream + // becomes available the httptransaction will call conn->ResumeSend(). + + LOG3(("SpdySession::ReadSegments %p dealing with block on read", this)); + + // call readsegments again if there are other streams ready + // to run in this session + if (WriteQueueSize()) + rv = NS_OK; + else + rv = NS_BASE_STREAM_WOULD_BLOCK; + SetWriteCallbacks(stream->Transaction()); + return rv; + } + + NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK, + "Stream Would Block inconsistency"); + + if (NS_FAILED(rv)) { + LOG3(("SpdySession::ReadSegments %p returning FAIL code %X", + this, rv)); + return rv; + } + + if (*countRead > 0) { + LOG3(("SpdySession::ReadSegments %p stream=%p generated end of frame %d", + this, stream, *countRead)); + mReadyForWrite.Push(stream); + SetWriteCallbacks(stream->Transaction()); + return rv; + } + + LOG3(("SpdySession::ReadSegments %p stream=%p stream send complete", + this, stream)); + + // in normal http this is done by nshttpconnection, but that class does not + // know which http transaction has made this state transition. + stream->Transaction()-> + OnTransportStatus(mSocketTransport, nsISocketTransport::STATUS_WAITING_FOR, + LL_ZERO); + /* we now want to recv data */ + mConnection->ResumeRecv(stream->Transaction()); + + // call readsegments again if there are other streams ready + // to go in this session + SetWriteCallbacks(stream->Transaction()); + + return rv; +} + +// WriteSegments() is used to read data off the socket. Generally this is +// just the SPDY frame header and from there the appropriate SPDYStream +// is identified from the Stream-ID. The http transaction associated with +// that read then pulls in the data directly, which it will feed to +// OnWriteSegment(). That function will gateway it into http and feed +// it to the appropriate transaction. + +// we call writer->OnWriteSegment to get a spdy header.. and decide if it is +// data or control.. if it is control, just deal with it. +// if it is data, identify the spdy stream +// call stream->WriteSegemnts which can call this::OnWriteSegment to get the +// data. It always gets full frames if they are part of the stream + +nsresult +SpdySession::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, + PRUint32 *countWritten) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + *countWritten = 0; + + if (mClosed) + return NS_ERROR_FAILURE; + + SetWriteCallbacks(nsnull); + + // We buffer all control frames and act on them in this layer. + // We buffer the first 8 bytes of data frames (the header) but + // the actual data is passed through unprocessed. + + if (mDownstreamState == BUFFERING_FRAME_HEADER) { + // The first 8 bytes of every frame is header information that + // we are going to want to strip before passing to http. That is + // true of both control and data packets. + + NS_ABORT_IF_FALSE(mFrameBufferUsed < 8, + "Frame Buffer Used Too Large for State"); + + rv = writer->OnWriteSegment(mFrameBuffer + mFrameBufferUsed, + 8 - mFrameBufferUsed, + countWritten); + if (NS_FAILED(rv)) { + LOG3(("SpdySession %p buffering frame header read failure %x\n", + this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + ResumeRecv(nsnull); + return rv; + } + + LogIO(this, nsnull, "Reading Frame Header", + mFrameBuffer + mFrameBufferUsed, *countWritten); + + mFrameBufferUsed += *countWritten; + + if (mFrameBufferUsed < 8) + { + LOG3(("SpdySession::WriteSegments %p " + "BUFFERING FRAME HEADER incomplete size=%d", + this, mFrameBufferUsed)); + return rv; + } + + // For both control and data frames the second 32 bit word of the header + // is 8-flags, 24-length. (network byte order) + mFrameDataSize = + PR_ntohl(reinterpret_cast(mFrameBuffer.get())[1]); + mFrameDataSize &= 0x00ffffff; + mFrameDataRead = 0; + + if (mFrameBuffer[0] & kFlag_Control) { + EnsureBuffer(mFrameBuffer, mFrameDataSize + 8, 8, mFrameBufferSize); + ChangeDownstreamState(BUFFERING_CONTROL_FRAME); + + // The first 32 bit word of the header is + // 1 ctrl - 15 version - 16 type + PRUint16 version = + PR_ntohs(reinterpret_cast(mFrameBuffer.get())[0]); + version &= 0x7fff; + + mFrameControlType = + PR_ntohs(reinterpret_cast(mFrameBuffer.get())[1]); + + LOG3(("SpdySession::WriteSegments %p - Control Frame Identified " + "type %d version %d data len %d", + this, mFrameControlType, version, mFrameDataSize)); + + if (mFrameControlType >= CONTROL_TYPE_LAST || + mFrameControlType <= CONTROL_TYPE_FIRST) + return NS_ERROR_ILLEGAL_VALUE; + + // The protocol document says this value must be 1 even though this + // is known as version 2.. Testing interop indicates that is a typo + // in the protocol document + if (version != 2) { + return NS_ERROR_ILLEGAL_VALUE; + } + } + else { + ChangeDownstreamState(PROCESSING_DATA_FRAME); + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(mFrameBuffer.get())[0]); + mFrameDataStream = mStreamIDHash.Get(streamID); + if (!mFrameDataStream) { + LOG3(("SpdySession::WriteSegments %p lookup streamID 0x%X failed. " + "Next = 0x%x", this, streamID, mNextStreamID)); + if (streamID >= mNextStreamID) + GenerateRstStream(RST_INVALID_STREAM, streamID); + ChangeDownstreamState(DISCARD_DATA_FRAME); + } + mFrameDataLast = (mFrameBuffer[4] & kFlag_Data_FIN); + Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mFrameDataSize >> 10); + LOG3(("Start Processing Data Frame. " + "Session=%p Stream ID 0x%x Stream Ptr %p Fin=%d Len=%d", + this, streamID, mFrameDataStream, mFrameDataLast, mFrameDataSize)); + + if (mFrameBuffer[4] & kFlag_Data_ZLIB) { + LOG3(("Data flag has ZLIB flag set which is not valid >=2 spdy")); + return NS_ERROR_ILLEGAL_VALUE; + } + } + } + + if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { + if (mDownstreamRstReason == RST_REFUSED_STREAM) + rv = NS_ERROR_NET_RESET; //we can retry this 100% safely + else if (mDownstreamRstReason == RST_CANCEL || + mDownstreamRstReason == RST_PROTOCOL_ERROR || + mDownstreamRstReason == RST_INTERNAL_ERROR || + mDownstreamRstReason == RST_UNSUPPORTED_VERSION) + rv = NS_ERROR_NET_INTERRUPT; + else + rv = NS_ERROR_ILLEGAL_VALUE; + + if (mDownstreamRstReason != RST_REFUSED_STREAM && + mDownstreamRstReason != RST_CANCEL) + mShouldGoAway = true; + + // mFrameDataStream is reset by ChangeDownstreamState + SpdyStream *stream = mFrameDataStream; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + CleanupStream(stream, rv); + return NS_OK; + } + + if (mDownstreamState == PROCESSING_DATA_FRAME || + mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + + mSegmentWriter = writer; + rv = mFrameDataStream->WriteSegments(this, count, countWritten); + mSegmentWriter = nsnull; + + if (rv == NS_BASE_STREAM_CLOSED) { + // This will happen when the transaction figures out it is EOF, generally + // due to a content-length match being made + SpdyStream *stream = mFrameDataStream; + if (mFrameDataRead == mFrameDataSize) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + CleanupStream(stream, NS_OK); + NS_ABORT_IF_FALSE(!mNeedsCleanup, "double cleanup out of data frame"); + return NS_OK; + } + + if (mNeedsCleanup) { + CleanupStream(mNeedsCleanup, NS_OK); + mNeedsCleanup = nsnull; + } + + // In v3 this is where we would generate a window update + + return rv; + } + + if (mDownstreamState == DISCARD_DATA_FRAME) { + char trash[4096]; + PRUint32 count = NS_MIN(4096U, mFrameDataSize - mFrameDataRead); + + if (!count) { + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + *countWritten = 1; + return NS_OK; + } + + rv = writer->OnWriteSegment(trash, count, countWritten); + + if (NS_FAILED(rv)) { + LOG3(("SpdySession %p discard frame read failure %x\n", this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + ResumeRecv(nsnull); + return rv; + } + + LogIO(this, nsnull, "Discarding Frame", trash, *countWritten); + + mFrameDataRead += *countWritten; + + if (mFrameDataRead == mFrameDataSize) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return rv; + } + + NS_ABORT_IF_FALSE(mDownstreamState == BUFFERING_CONTROL_FRAME, + "Not in Bufering Control Frame State"); + NS_ABORT_IF_FALSE(mFrameBufferUsed == 8, + "Frame Buffer Header Not Present"); + + rv = writer->OnWriteSegment(mFrameBuffer + 8 + mFrameDataRead, + mFrameDataSize - mFrameDataRead, + countWritten); + if (NS_FAILED(rv)) { + LOG3(("SpdySession %p buffering control frame read failure %x\n", + this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + ResumeRecv(nsnull); + return rv; + } + + LogIO(this, nsnull, "Reading Control Frame", + mFrameBuffer + 8 + mFrameDataRead, *countWritten); + + mFrameDataRead += *countWritten; + + if (mFrameDataRead != mFrameDataSize) + return NS_OK; + + rv = sControlFunctions[mFrameControlType](this); + + NS_ABORT_IF_FALSE(NS_FAILED(rv) || + mDownstreamState != BUFFERING_CONTROL_FRAME, + "Control Handler returned OK but did not change state"); + + if (mShouldGoAway && !mStreamTransactionHash.Count()) + Close(NS_OK); + return rv; +} + +void +SpdySession::Close(nsresult aReason) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (mClosed) + return; + + LOG3(("SpdySession::Close %p %X", this, aReason)); + + mClosed = true; + mStreamTransactionHash.Enumerate(Shutdown, this); + GenerateGoAway(); + mConnection = nsnull; +} + +void +SpdySession::CloseTransaction(nsAHttpTransaction *aTransaction, + nsresult aResult) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::CloseTransaction %p %p %x", this, aTransaction, aResult)); + + // Generally this arrives as a cancel event from the connection manager. + + // need to find the stream and call CleanupStream() on it. + SpdyStream *stream = mStreamTransactionHash.Get(aTransaction); + if (!stream) { + LOG3(("SpdySession::CloseTransaction %p %p %x - not found.", + this, aTransaction, aResult)); + return; + } + LOG3(("SpdySession::CloseTranscation probably a cancel. " + "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p", + this, aTransaction, aResult, stream->StreamID(), stream)); + CleanupStream(stream, aResult); + ResumeRecv(this); +} + + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdySession::OnReadSegment(const char *buf, + PRUint32 count, + PRUint32 *countRead) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + + if (!mOutputQueueUsed && mSegmentReader) { + + // try and write directly without output queue + rv = mSegmentReader->OnReadSegment(buf, count, countRead); + if (NS_SUCCEEDED(rv) || (rv != NS_BASE_STREAM_WOULD_BLOCK)) + return rv; + } + + if (mOutputQueueUsed + count > mOutputQueueSize) + FlushOutputQueue(); + + if (mOutputQueueUsed + count > mOutputQueueSize) + count = mOutputQueueSize - mOutputQueueUsed; + + if (!count) + return NS_BASE_STREAM_WOULD_BLOCK; + + memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count); + mOutputQueueUsed += count; + *countRead = count; + + FlushOutputQueue(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdySession::OnWriteSegment(char *buf, + PRUint32 count, + PRUint32 *countWritten) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + nsresult rv; + + if (mDownstreamState == PROCESSING_DATA_FRAME) { + + if (mFrameDataLast && + mFrameDataRead == mFrameDataSize) { + // This will result in Close() being called + mNeedsCleanup = mFrameDataStream; + + LOG3(("SpdySession::OnWriteSegment %p - recorded downstream fin of " + "stream %p 0x%X", this, mFrameDataStream, + mFrameDataStream->StreamID())); + *countWritten = 0; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_BASE_STREAM_CLOSED; + } + + count = NS_MIN(count, mFrameDataSize - mFrameDataRead); + rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + LogIO(this, mFrameDataStream, "Reading Data Frame", buf, *countWritten); + + mFrameDataRead += *countWritten; + + if ((mFrameDataRead == mFrameDataSize) && !mFrameDataLast) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + + return rv; + } + + if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && + mFrameDataLast) { + *countWritten = 0; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_BASE_STREAM_CLOSED; + } + + count = NS_MIN(count, + mFlatHTTPResponseHeaders.Length() - + mFlatHTTPResponseHeadersOut); + memcpy(buf, + mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut, + count); + mFlatHTTPResponseHeadersOut += count; + *countWritten = count; + + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && + !mFrameDataLast) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +//----------------------------------------------------------------------------- +// Modified methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsresult +SpdySession::ResumeSend(nsAHttpTransaction *caller) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession::ResumeSend %p caller=%p", this, caller)); + + // a trapped signal from the http transaction to the connection that + // it is no longer blocked on read. + + if (!mConnection) + return NS_ERROR_FAILURE; + + SpdyStream *stream = mStreamTransactionHash.Get(caller); + if (stream) + mReadyForWrite.Push(stream); + else + LOG3(("SpdySession::ResumeSend %p caller %p not found", this, caller)); + + return mConnection->ResumeSend(caller); +} + +nsresult +SpdySession::ResumeRecv(nsAHttpTransaction *caller) +{ + if (!mConnection) + return NS_ERROR_FAILURE; + + return mConnection->ResumeRecv(caller); +} + +bool +SpdySession::IsPersistent() +{ + return true; +} + +nsresult +SpdySession::TakeTransport(nsISocketTransport **, + nsIAsyncInputStream **, + nsIAsyncOutputStream **) +{ + NS_ABORT_IF_FALSE(false, "TakeTransport of SpdySession"); + return NS_ERROR_UNEXPECTED; +} + +nsHttpConnection * +SpdySession::TakeHttpConnection() +{ + NS_ABORT_IF_FALSE(false, "TakeHttpConnection of SpdySession"); + return nsnull; +} + +nsISocketTransport * +SpdySession::Transport() +{ + if (!mConnection) + return nsnull; + return mConnection->Transport(); +} + +//----------------------------------------------------------------------------- +// unused methods of nsAHttpTransaction +// We can be sure of this because SpdySession is only constructed in +// nsHttpConnection and is never passed out of that object +//----------------------------------------------------------------------------- + +void +SpdySession::SetConnection(nsAHttpConnection *) +{ + // This is unexpected + NS_ABORT_IF_FALSE(false, "SpdySession::SetConnection()"); +} + +void +SpdySession::GetSecurityCallbacks(nsIInterfaceRequestor **, + nsIEventTarget **) +{ + // This is unexpected + NS_ABORT_IF_FALSE(false, "SpdySession::GetSecurityCallbacks()"); +} + +void +SpdySession::SetSSLConnectFailed() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::SetSSLConnectFailed()"); +} + +bool +SpdySession::IsDone() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()"); + return false; +} + +nsresult +SpdySession::Status() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Status()"); + return NS_ERROR_UNEXPECTED; +} + +PRUint32 +SpdySession::Available() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Available()"); + return 0; +} + +nsHttpRequestHead * +SpdySession::RequestHead() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(false, + "SpdySession::RequestHead() " + "should not be called after SPDY is setup"); + return NULL; +} + +PRUint32 +SpdySession::Http1xTransactionCount() +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Pass through methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsAHttpConnection * +SpdySession::Connection() +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + return mConnection; +} + +nsresult +SpdySession::OnHeadersAvailable(nsAHttpTransaction *transaction, + nsHttpRequestHead *requestHead, + nsHttpResponseHead *responseHead, + bool *reset) +{ + return mConnection->OnHeadersAvailable(transaction, + requestHead, + responseHead, + reset); +} + +void +SpdySession::GetConnectionInfo(nsHttpConnectionInfo **connInfo) +{ + mConnection->GetConnectionInfo(connInfo); +} + +void +SpdySession::GetSecurityInfo(nsISupports **supports) +{ + mConnection->GetSecurityInfo(supports); +} + +bool +SpdySession::IsReused() +{ + return mConnection->IsReused(); +} + +nsresult +SpdySession::PushBack(const char *buf, PRUint32 len) +{ + return mConnection->PushBack(buf, len); +} + +bool +SpdySession::LastTransactionExpectedNoContent() +{ + return mConnection->LastTransactionExpectedNoContent(); +} + +void +SpdySession::SetLastTransactionExpectedNoContent(bool val) +{ + mConnection->SetLastTransactionExpectedNoContent(val); +} + +} // namespace mozilla::net +} // namespace mozilla + diff --git a/netwerk/protocol/http/SpdySession.h b/netwerk/protocol/http/SpdySession.h new file mode 100644 index 00000000000..210ea21a913 --- /dev/null +++ b/netwerk/protocol/http/SpdySession.h @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_SpdySession_h +#define mozilla_net_SpdySession_h + +// SPDY as defined by +// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 + +#include "nsAHttpTransaction.h" +#include "nsAHttpConnection.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsDeque.h" +#include "nsHashKeys.h" +#include "zlib.h" + +class nsHttpConnection; +class nsISocketTransport; + +namespace mozilla { namespace net { + +class SpdyStream; + +class SpdySession : public nsAHttpTransaction + , public nsAHttpConnection + , public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + NS_DECL_NSAHTTPCONNECTION + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdySession(nsAHttpTransaction *, nsISocketTransport *, PRInt32); + ~SpdySession(); + + bool AddStream(nsAHttpTransaction *, PRInt32); + bool CanReuse() { return !mShouldGoAway && !mClosed; } + void DontReuse(); + bool RoomForMoreStreams(); + PRUint32 RegisterStreamID(SpdyStream *); + + const static PRUint8 kFlag_Control = 0x80; + + const static PRUint8 kFlag_Data_FIN = 0x01; + const static PRUint8 kFlag_Data_UNI = 0x02; + const static PRUint8 kFlag_Data_ZLIB = 0x02; + + const static PRUint8 kPri00 = 0x00; + const static PRUint8 kPri01 = 0x40; + const static PRUint8 kPri02 = 0x80; + const static PRUint8 kPri03 = 0xC0; + + enum + { + CONTROL_TYPE_FIRST = 0, + CONTROL_TYPE_SYN_STREAM = 1, + CONTROL_TYPE_SYN_REPLY = 2, + CONTROL_TYPE_RST_STREAM = 3, + CONTROL_TYPE_SETTINGS = 4, + CONTROL_TYPE_NOOP = 5, + CONTROL_TYPE_PING = 6, + CONTROL_TYPE_GOAWAY = 7, + CONTROL_TYPE_HEADERS = 8, + CONTROL_TYPE_WINDOW_UPDATE = 9, /* no longer in v2 */ + CONTROL_TYPE_LAST = 10 + }; + + enum + { + RST_PROTOCOL_ERROR = 1, + RST_INVALID_STREAM = 2, + RST_REFUSED_STREAM = 3, + RST_UNSUPPORTED_VERSION = 4, + RST_CANCEL = 5, + RST_INTERNAL_ERROR = 6, + RST_FLOW_CONTROL_ERROR = 7, + RST_BAD_ASSOC_STREAM = 8 + }; + + enum + { + SETTINGS_TYPE_UPLOAD_BW = 1, // kb/s + SETTINGS_TYPE_DOWNLOAD_BW = 2, // kb/s + SETTINGS_TYPE_RTT = 3, // ms + SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams + SETTINGS_TYPE_CWND = 5, // packets + SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage + SETTINGS_TYPE_INITIAL_WINDOW = 7 // bytes. Not used in v2. + }; + + // This should be big enough to hold all of your control packets, + // but if it needs to grow for huge headers it can do so dynamically. + // About 1% of requests to SPDY google services seem to be > 1000 + // with all less than 2000. + const static PRUint32 kDefaultBufferSize = 2000; + + const static PRUint32 kDefaultQueueSize = 16000; + const static PRUint32 kQueueTailRoom = 4000; + const static PRUint32 kSendingChunkSize = 4000; + const static PRUint32 kDefaultMaxConcurrent = 100; + const static PRUint32 kMaxStreamID = 0x7800000; + + static nsresult HandleSynStream(SpdySession *); + static nsresult HandleSynReply(SpdySession *); + static nsresult HandleRstStream(SpdySession *); + static nsresult HandleSettings(SpdySession *); + static nsresult HandleNoop(SpdySession *); + static nsresult HandlePing(SpdySession *); + static nsresult HandleGoAway(SpdySession *); + static nsresult HandleHeaders(SpdySession *); + static nsresult HandleWindowUpdate(SpdySession *); + + static void EnsureBuffer(nsAutoArrayPtr &, + PRUint32, PRUint32, PRUint32 &); + + // For writing the SPDY data stream to LOG4 + static void LogIO(SpdySession *, SpdyStream *, const char *, + const char *, PRUint32); + +private: + + enum stateType { + BUFFERING_FRAME_HEADER, + BUFFERING_CONTROL_FRAME, + PROCESSING_DATA_FRAME, + DISCARD_DATA_FRAME, + PROCESSING_CONTROL_SYN_REPLY, + PROCESSING_CONTROL_RST_STREAM + }; + + PRUint32 WriteQueueSize(); + void ChangeDownstreamState(enum stateType); + nsresult DownstreamUncompress(char *, PRUint32); + void zlibInit(); + nsresult FindHeader(nsCString, nsDependentCSubstring &); + nsresult ConvertHeaders(nsDependentCSubstring &, + nsDependentCSubstring &); + void GeneratePing(PRUint32); + void GenerateRstStream(PRUint32, PRUint32); + void GenerateGoAway(); + void CleanupStream(SpdyStream *, nsresult); + + void SetWriteCallbacks(nsAHttpTransaction *); + void FlushOutputQueue(); + + bool RoomForMoreConcurrent(); + void ActivateStream(SpdyStream *); + void ProcessPending(); + + static PLDHashOperator Shutdown(nsAHttpTransaction *, + nsAutoPtr &, + void *); + + // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken + // from the first transcation on this session. That object contains the + // pointer to the real network-level nsHttpConnection object. + nsRefPtr mConnection; + + // The underlying socket transport object is needed to propogate some events + nsISocketTransport *mSocketTransport; + + // These are temporary state variables to hold the argument to + // Read/WriteSegments so it can be accessed by On(read/write)segment + // further up the stack. + nsAHttpSegmentReader *mSegmentReader; + nsAHttpSegmentWriter *mSegmentWriter; + + PRUint32 mSendingChunkSize; /* the transmisison chunk size */ + PRUint32 mNextStreamID; /* 24 bits */ + PRUint32 mConcurrentHighWater; /* max parallelism on session */ + + stateType mDownstreamState; /* in frame, between frames, etc.. */ + + // Maintain 5 indexes - one by stream ID, one by transaction ptr, + // one list of streams ready to write, one list of streams that are queued + // due to max parallelism settings, and one list of streams + // that must be given priority to write for window updates. The objects + // are not ref counted - they get destryoed + // by the nsClassHashtable implementation when they are removed from + // there. + nsDataHashtable mStreamIDHash; + nsClassHashtable, + SpdyStream> mStreamTransactionHash; + nsDeque mReadyForWrite; + nsDeque mQueuedStreams; + + // UrgentForWrite is meant to carry window updates. They were defined in + // the v2 spec but apparently never implemented so are now scheduled to + // be removed. But they will be reintroduced for v3, so we will leave + // this queue in place to ease that transition. + nsDeque mUrgentForWrite; + + // If we block while wrting out a frame then this points to the stream + // that was blocked. When writing again that stream must be the first + // one to write. It is null if there is not a partial frame. + SpdyStream *mPartialFrame; + + // Compression contexts for header transport using deflate. + // SPDY compresses only HTTP headers and does not reset zlib in between + // frames. + z_stream mDownstreamZlib; + z_stream mUpstreamZlib; + + // mFrameBuffer is used to store received control packets and the 8 bytes + // of header on data packets + PRUint32 mFrameBufferSize; + PRUint32 mFrameBufferUsed; + nsAutoArrayPtr mFrameBuffer; + + // mFrameDataSize/Read are used for tracking the amount of data consumed + // in a data frame. the data itself is not buffered in spdy + // The frame size is mFrameDataSize + the constant 8 byte header + PRUint32 mFrameDataSize; + PRUint32 mFrameDataRead; + bool mFrameDataLast; // This frame was marked FIN + + // When a frame has been received that is addressed to a particular stream + // (e.g. a data frame after the stream-id has been decoded), this points + // to the stream. + SpdyStream *mFrameDataStream; + + // A state variable to cleanup a closed stream after the stack has unwound. + SpdyStream *mNeedsCleanup; + + // The CONTROL_TYPE value for a control frame + PRUint32 mFrameControlType; + + // This reason code in the last processed RESET frame + PRUint32 mDownstreamRstReason; + + // These are used for decompressing downstream spdy response headers + // This is done at the session level because sometimes the stream + // has already been canceled but the decompression still must happen + // to keep the zlib state correct for the next state of headers. + PRUint32 mDecompressBufferSize; + PRUint32 mDecompressBufferUsed; + nsAutoArrayPtr mDecompressBuffer; + + // for the conversion of downstream http headers into spdy formatted headers + nsCString mFlatHTTPResponseHeaders; + PRUint32 mFlatHTTPResponseHeadersOut; + + // when set, the session will go away when it reaches 0 streams + bool mShouldGoAway; + + // the session has received a nsAHttpTransaction::Close() call + bool mClosed; + + // the session received a GoAway frame with a valid GoAwayID + bool mCleanShutdown; + + // If a GoAway message was received this is the ID of the last valid + // stream. 0 otherwise. (0 is never a valid stream id.) + PRUint32 mGoAwayID; + + // The limit on number of concurrent streams for this session. Normally it + // is basically unlimited, but the SETTINGS control message from the + // server might bring it down. + PRUint32 mMaxConcurrent; + + // The actual number of concurrent streams at this moment. Generally below + // mMaxConcurrent, but the max can be lowered in real time to a value + // below the current value + PRUint32 mConcurrent; + + // The number of server initiated SYN-STREAMS, tracked for telemetry + PRUint32 mServerPushedResources; + + // This is a output queue of bytes ready to be written to the SSL stream. + // When that streams returns WOULD_BLOCK on direct write the bytes get + // coalesced together here. This results in larger writes to the SSL layer. + // The buffer is not dynamically grown to accomodate stream writes, but + // does expand to accept infallible session wide frames like GoAway and RST. + PRUint32 mOutputQueueSize; + PRUint32 mOutputQueueUsed; + PRUint32 mOutputQueueSent; + nsAutoArrayPtr mOutputQueueBuffer; +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdySession_h diff --git a/netwerk/protocol/http/SpdyStream.cpp b/netwerk/protocol/http/SpdyStream.cpp new file mode 100644 index 00000000000..e57d28f0a46 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream.cpp @@ -0,0 +1,849 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "SpdySession.h" +#include "SpdyStream.h" +#include "nsAlgorithm.h" +#include "prnetdb.h" +#include "nsHttpRequestHead.h" +#include "mozilla/Telemetry.h" +#include "nsISocketTransport.h" +#include "nsISupportsPriority.h" + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +SpdyStream::SpdyStream(nsAHttpTransaction *httpTransaction, + SpdySession *spdySession, + nsISocketTransport *socketTransport, + PRUint32 chunkSize, + z_stream *compressionContext, + PRInt32 priority) + : mUpstreamState(GENERATING_SYN_STREAM), + mTransaction(httpTransaction), + mSession(spdySession), + mSocketTransport(socketTransport), + mSegmentReader(nsnull), + mSegmentWriter(nsnull), + mStreamID(0), + mChunkSize(chunkSize), + mSynFrameComplete(0), + mBlockedOnWrite(0), + mRequestBlockedOnRead(0), + mSentFinOnData(0), + mRecvdFin(0), + mFullyOpen(0), + mTxInlineFrameAllocation(SpdySession::kDefaultBufferSize), + mTxInlineFrameSize(0), + mTxInlineFrameSent(0), + mTxStreamFrameSize(0), + mTxStreamFrameSent(0), + mZlib(compressionContext), + mRequestBodyLen(0), + mPriority(priority) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + LOG3(("SpdyStream::SpdyStream %p", this)); + + mTxInlineFrame = new char[mTxInlineFrameAllocation]; +} + +SpdyStream::~SpdyStream() +{ +} + +// ReadSegments() is used to write data down the socket. Generally, HTTP +// request data is pulled from the approriate transaction and +// converted to SPDY data. Sometimes control data like a window-update is +// generated instead. + +nsresult +SpdyStream::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, + PRUint32 *countRead) +{ + LOG3(("SpdyStream %p ReadSegments reader=%p count=%d state=%x", + this, reader, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv = NS_ERROR_UNEXPECTED; + mBlockedOnWrite = 0; + mRequestBlockedOnRead = 0; + + switch (mUpstreamState) { + case GENERATING_SYN_STREAM: + case GENERATING_REQUEST_BODY: + case SENDING_REQUEST_BODY: + // Call into the HTTP Transaction to generate the HTTP request + // stream. That stream will show up in OnReadSegment(). + mSegmentReader = reader; + rv = mTransaction->ReadSegments(this, count, countRead); + mSegmentReader = nsnull; + + if (NS_SUCCEEDED(rv) && + mUpstreamState == GENERATING_SYN_STREAM && + !mSynFrameComplete) + mBlockedOnWrite = 1; + + // Mark that we are blocked on read if we the http transaction + // is going to get us going again. + if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mBlockedOnWrite) + mRequestBlockedOnRead = 1; + + if (!mBlockedOnWrite && NS_SUCCEEDED(rv) && (!*countRead)) { + LOG3(("ReadSegments %p Send Request data complete from %x", + this, mUpstreamState)); + if (mSentFinOnData) { + ChangeState(UPSTREAM_COMPLETE); + } + else { + GenerateDataFrameHeader(0, true); + ChangeState(SENDING_FIN_STREAM); + mBlockedOnWrite = 1; + rv = NS_BASE_STREAM_WOULD_BLOCK; + } + } + + break; + + case SENDING_SYN_STREAM: + // We were trying to send the SYN-STREAM but only got part of it out + // before being blocked. Try and send more. + mSegmentReader = reader; + rv = TransmitFrame(nsnull, nsnull); + mSegmentReader = nsnull; + *countRead = 0; + if (NS_SUCCEEDED(rv)) + rv = NS_BASE_STREAM_WOULD_BLOCK; + + if (!mTxInlineFrameSize) { + if (mSentFinOnData) { + ChangeState(UPSTREAM_COMPLETE); + rv = NS_OK; + } + else { + ChangeState(GENERATING_REQUEST_BODY); + mBlockedOnWrite = 1; + } + } + break; + + case SENDING_FIN_STREAM: + // We were trying to send the SYN-STREAM but only got part of it out + // before being blocked. Try and send more. + if (!mSentFinOnData) { + mSegmentReader = reader; + rv = TransmitFrame(nsnull, nsnull); + mSegmentReader = nsnull; + if (!mTxInlineFrameSize) + ChangeState(UPSTREAM_COMPLETE); + } + else { + rv = NS_OK; + mTxInlineFrameSize = 0; // cancel fin data packet + ChangeState(UPSTREAM_COMPLETE); + } + + *countRead = 0; + + // don't change OK to WOULD BLOCK. we are really done sending if OK + break; + + default: + NS_ABORT_IF_FALSE(false, "SpdyStream::ReadSegments unknown state"); + break; + } + + return rv; +} + +// WriteSegments() is used to read data off the socket. Generally this is +// just the SPDY frame header and from there the appropriate SPDYStream +// is identified from the Stream-ID. The http transaction associated with +// that read then pulls in the data directly. + +nsresult +SpdyStream::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, + PRUint32 *countWritten) +{ + LOG3(("SpdyStream::WriteSegments %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress"); + + mSegmentWriter = writer; + nsresult rv = mTransaction->WriteSegments(writer, count, countWritten); + mSegmentWriter = nsnull; + return rv; +} + +PLDHashOperator +SpdyStream::hdrHashEnumerate(const nsACString &key, + nsAutoPtr &value, + void *closure) +{ + SpdyStream *self = static_cast(closure); + + self->CompressToFrame(key); + self->CompressToFrame(value.get()); + return PL_DHASH_NEXT; +} + +nsresult +SpdyStream::ParseHttpRequestHeaders(const char *buf, + PRUint32 avail, + PRUint32 *countUsed) +{ + // Returns NS_OK even if the headers are incomplete + // set mSynFrameComplete flag if they are complete + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state"); + + LOG3(("SpdyStream::ParseHttpRequestHeaders %p avail=%d state=%x", + this, avail, mUpstreamState)); + + mFlatHttpRequestHeaders.Append(buf, avail); + + // We can use the simple double crlf because firefox is the + // only client we are parsing + PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); + + if (endHeader == -1) { + // We don't have all the headers yet + LOG3(("SpdyStream::ParseHttpRequestHeaders %p " + "Need more header bytes. Len = %d", + this, mFlatHttpRequestHeaders.Length())); + *countUsed = avail; + return NS_OK; + } + + // We have recvd all the headers, trim the local + // buffer of the final empty line, and set countUsed to reflect + // the whole header has been consumed. + PRUint32 oldLen = mFlatHttpRequestHeaders.Length(); + mFlatHttpRequestHeaders.SetLength(endHeader + 2); + *countUsed = avail - (oldLen - endHeader) + 4; + mSynFrameComplete = 1; + + // It is now OK to assign a streamID that we are assured will + // be monotonically increasing amongst syn-streams on this + // session + mStreamID = mSession->RegisterStreamID(this); + NS_ABORT_IF_FALSE(mStreamID & 1, + "Spdy Stream Channel ID must be odd"); + + if (mStreamID >= 0x80000000) { + // streamID must fit in 31 bits. This is theoretically possible + // because stream ID assignment is asynchronous to stream creation + // because of the protocol requirement that the ID in syn-stream + // be monotonically increasing. In reality this is really not possible + // because new streams stop being added to a session with 0x10000000 / 2 + // IDs still available and no race condition is going to bridge that gap, + // so we can be comfortable on just erroring out for correctness in that + // case. + LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); + return NS_ERROR_UNEXPECTED; + } + + // Now we need to convert the flat http headers into a set + // of SPDY headers.. writing to mTxInlineFrame{sz} + + mTxInlineFrame[0] = SpdySession::kFlag_Control; + mTxInlineFrame[1] = 2; /* version */ + mTxInlineFrame[2] = 0; + mTxInlineFrame[3] = SpdySession::CONTROL_TYPE_SYN_STREAM; + // 4 to 7 are length and flags, we'll fill that in later + + PRUint32 networkOrderID = PR_htonl(mStreamID); + memcpy(mTxInlineFrame + 8, &networkOrderID, 4); + + // this is the associated-to field, which is not used sending + // from the client in the http binding + memset (mTxInlineFrame + 12, 0, 4); + + // Priority flags are the C0 mask of byte 16. + // From low to high: 00 40 80 C0 + // higher raw priority values are actually less important + // + // The other 6 bits of 16 are unused. Spdy/3 will expand + // priority to 4 bits. + // + // When Spdy/3 implements WINDOW_UPDATE the lowest priority + // streams over a threshold (32?) should be given tiny + // receive windows, separate from their spdy priority + // + if (mPriority >= nsISupportsPriority::PRIORITY_LOW) + mTxInlineFrame[16] = SpdySession::kPri00; + else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL) + mTxInlineFrame[16] = SpdySession::kPri01; + else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH) + mTxInlineFrame[16] = SpdySession::kPri02; + else + mTxInlineFrame[16] = SpdySession::kPri03; + + mTxInlineFrame[17] = 0; /* unused */ + +// nsCString methodHeader; +// mTransaction->RequestHead()->Method()->ToUTF8String(methodHeader); + const char *methodHeader = mTransaction->RequestHead()->Method().get(); + + nsCString hostHeader; + mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); + + nsCString versionHeader; + if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1) + versionHeader = NS_LITERAL_CSTRING("HTTP/1.1"); + else + versionHeader = NS_LITERAL_CSTRING("HTTP/1.0"); + + nsClassHashtable hdrHash; + + // use mRequestHead() to get a sense of how big to make the hash, + // even though we are parsing the actual text stream because + // it is legit to append headers. + hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2)); + + const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); + + // need to hash all the headers together to remove duplicates, special + // headers, etc.. + + PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); + while (true) { + PRInt32 startIndex = crlfIndex + 2; + + crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); + if (crlfIndex == -1) + break; + + PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, + crlfIndex - startIndex); + if (colonIndex == -1) + break; + + nsDependentCSubstring name = Substring(beginBuffer + startIndex, + beginBuffer + colonIndex); + // all header names are lower case in spdy + ToLowerCase(name); + + if (name.Equals("method") || + name.Equals("version") || + name.Equals("scheme") || + name.Equals("keep-alive") || + name.Equals("accept-encoding") || + name.Equals("te") || + name.Equals("connection") || + name.Equals("proxy-connection") || + name.Equals("url")) + continue; + + nsCString *val = hdrHash.Get(name); + if (!val) { + val = new nsCString(); + hdrHash.Put(name, val); + } + + PRInt32 valueIndex = colonIndex + 1; + while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') + ++valueIndex; + + nsDependentCSubstring v = Substring(beginBuffer + valueIndex, + beginBuffer + crlfIndex); + if (!val->IsEmpty()) + val->Append(static_cast(0)); + val->Append(v); + + if (name.Equals("content-length")) { + PRInt64 len; + if (nsHttp::ParseInt64(val->get(), nsnull, &len)) + mRequestBodyLen = len; + } + } + + mTxInlineFrameSize = 18; + + LOG3(("http request headers to encode are: \n%s", + mFlatHttpRequestHeaders.get())); + + // The header block length + PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */ + CompressToFrame(count); + + // method, scheme, url, and version headers for request line + + CompressToFrame(NS_LITERAL_CSTRING("method")); + CompressToFrame(methodHeader, strlen(methodHeader)); + CompressToFrame(NS_LITERAL_CSTRING("scheme")); + CompressToFrame(NS_LITERAL_CSTRING("https")); + CompressToFrame(NS_LITERAL_CSTRING("url")); + CompressToFrame(mTransaction->RequestHead()->RequestURI()); + CompressToFrame(NS_LITERAL_CSTRING("version")); + CompressToFrame(versionHeader); + + hdrHash.Enumerate(hdrHashEnumerate, this); + CompressFlushFrame(); + + // 4 to 7 are length and flags, which we can now fill in + (reinterpret_cast(mTxInlineFrame.get()))[1] = + PR_htonl(mTxInlineFrameSize - 8); + + NS_ABORT_IF_FALSE(!mTxInlineFrame[4], + "Size greater than 24 bits"); + + // For methods other than POST and PUT, we will set the fin bit + // right on the syn stream packet. + + if (mTransaction->RequestHead()->Method() != nsHttp::Post && + mTransaction->RequestHead()->Method() != nsHttp::Put) { + mSentFinOnData = 1; + mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN; + } + + Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameSize - 18); + + // The size of the input headers is approximate + PRUint32 ratio = + (mTxInlineFrameSize - 18) * 100 / + (11 + mTransaction->RequestHead()->RequestURI().Length() + + mFlatHttpRequestHeaders.Length()); + + Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); + return NS_OK; +} + +nsresult +SpdyStream::TransmitFrame(const char *buf, + PRUint32 *countUsed) +{ + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "empty stream frame in transmit"); + NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader"); + + PRUint32 transmittedCount; + nsresult rv; + + LOG3(("SpdyStream::TransmitFrame %p inline=%d of %d stream=%d of %d", + this, mTxInlineFrameSent, mTxInlineFrameSize, + mTxStreamFrameSent, mTxStreamFrameSize)); + if (countUsed) + *countUsed = 0; + mBlockedOnWrite = 0; + + // In the (relatively common) event that we have a small amount of data + // split between the inlineframe and the streamframe, then move the stream + // data into the inlineframe via copy in order to coalesce into one write. + // Given the interaction with ssl this is worth the small copy cost. + if (mTxStreamFrameSize && mTxInlineFrameSize && + !mTxInlineFrameSent && !mTxStreamFrameSent && + mTxStreamFrameSize < SpdySession::kDefaultBufferSize && + mTxInlineFrameSize + mTxStreamFrameSize < mTxInlineFrameAllocation) { + LOG3(("Coalesce Transmit")); + memcpy (mTxInlineFrame + mTxInlineFrameSize, + buf, mTxStreamFrameSize); + mTxInlineFrameSize += mTxStreamFrameSize; + mTxStreamFrameSent = 0; + mTxStreamFrameSize = 0; + } + + // This function calls mSegmentReader->OnReadSegment to report the actual SPDY + // bytes through to the SpdySession and then the HttpConnection which calls + // the socket write function. + + while (mTxInlineFrameSent < mTxInlineFrameSize) { + rv = mSegmentReader->OnReadSegment(mTxInlineFrame + mTxInlineFrameSent, + mTxInlineFrameSize - mTxInlineFrameSent, + &transmittedCount); + LOG3(("SpdyStream::TransmitFrame for inline session=%p " + "stream=%p result %x len=%d", + mSession, this, rv, transmittedCount)); + SpdySession::LogIO(mSession, this, "Writing from Inline Buffer", + mTxInlineFrame + mTxInlineFrameSent, + transmittedCount); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + mBlockedOnWrite = 1; + + if (NS_FAILED(rv)) // this will include WOULD_BLOCK + return rv; + + mTxInlineFrameSent += transmittedCount; + } + + PRUint32 offset = 0; + NS_ABORT_IF_FALSE(mTxStreamFrameSize >= mTxStreamFrameSent, + "negative unsent"); + PRUint32 avail = mTxStreamFrameSize - mTxStreamFrameSent; + + while (avail) { + NS_ABORT_IF_FALSE(countUsed, "null countused pointer in a stream context"); + rv = mSegmentReader->OnReadSegment(buf + offset, avail, &transmittedCount); + + LOG3(("SpdyStream::TransmitFrame for regular session=%p " + "stream=%p result %x len=%d", + mSession, this, rv, transmittedCount)); + SpdySession::LogIO(mSession, this, "Writing from Transaction Buffer", + buf + offset, transmittedCount); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + mBlockedOnWrite = 1; + + if (NS_FAILED(rv)) // this will include WOULD_BLOCK + return rv; + + if (mUpstreamState == SENDING_REQUEST_BODY) { + mTransaction->OnTransportStatus(mSocketTransport, + nsISocketTransport::STATUS_SENDING_TO, + transmittedCount); + } + + *countUsed += transmittedCount; + avail -= transmittedCount; + offset += transmittedCount; + mTxStreamFrameSent += transmittedCount; + } + + if (!avail) { + mTxInlineFrameSent = 0; + mTxInlineFrameSize = 0; + mTxStreamFrameSent = 0; + mTxStreamFrameSize = 0; + } + + return NS_OK; +} + +void +SpdyStream::ChangeState(enum stateType newState) +{ + LOG3(("SpdyStream::ChangeState() %p from %X to %X", + this, mUpstreamState, newState)); + mUpstreamState = newState; + return; +} + +void +SpdyStream::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame) +{ + LOG3(("SpdyStream::GenerateDataFrameHeader %p len=%d last=%d", + this, dataLength, lastFrame)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mTxInlineFrameSize, "inline frame not empty"); + NS_ABORT_IF_FALSE(!mTxInlineFrameSent, "inline partial send not 0"); + NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty"); + NS_ABORT_IF_FALSE(!mTxStreamFrameSent, "stream partial send not 0"); + NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits"); + + (reinterpret_cast(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID); + (reinterpret_cast(mTxInlineFrame.get()))[1] = + PR_htonl(dataLength); + + NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80), + "control bit set unexpectedly"); + NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly"); + + mTxInlineFrameSize = 8; + mTxStreamFrameSize = dataLength; + + if (lastFrame) { + mTxInlineFrame[4] |= SpdySession::kFlag_Data_FIN; + if (dataLength) + mSentFinOnData = 1; + } +} + +void +SpdyStream::CompressToFrame(const nsACString &str) +{ + CompressToFrame(str.BeginReading(), str.Length()); +} + +void +SpdyStream::CompressToFrame(const nsACString *str) +{ + CompressToFrame(str->BeginReading(), str->Length()); +} + +// Dictionary taken from +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 +// Name/Value Header Block Format +// spec indicates that the compression dictionary is not null terminated +// but in reality it is. see: +// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs + +const char *SpdyStream::kDictionary = + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + "-agent10010120020120220320420520630030130230330430530630740040140240340440" + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + ".1statusversionurl"; + +// use for zlib data types +void * +SpdyStream::zlib_allocator(void *opaque, uInt items, uInt size) +{ + return moz_xmalloc(items * size); +} + +// use for zlib data types +void +SpdyStream::zlib_destructor(void *opaque, void *addr) +{ + moz_free(addr); +} + +void +SpdyStream::ExecuteCompress(PRUint32 flushMode) +{ + // Expect mZlib->avail_in and mZlib->next_in to be set. + // Append the compressed version of next_in to mTxInlineFrame + + do + { + PRUint32 avail = mTxInlineFrameAllocation - mTxInlineFrameSize; + if (avail < 1) { + SpdySession::EnsureBuffer(mTxInlineFrame, + mTxInlineFrameAllocation + 2000, + mTxInlineFrameSize, + mTxInlineFrameAllocation); + avail = mTxInlineFrameAllocation - mTxInlineFrameSize; + } + + mZlib->next_out = reinterpret_cast (mTxInlineFrame.get()) + + mTxInlineFrameSize; + mZlib->avail_out = avail; + deflate(mZlib, flushMode); + mTxInlineFrameSize += avail - mZlib->avail_out; + } while (mZlib->avail_in > 0 || !mZlib->avail_out); +} + +void +SpdyStream::CompressToFrame(PRUint16 data) +{ + // convert the data to network byte order and write that + // to the compressed stream + + data = PR_htons(data); + + mZlib->next_in = reinterpret_cast (&data); + mZlib->avail_in = 2; + ExecuteCompress(Z_NO_FLUSH); +} + + +void +SpdyStream::CompressToFrame(const char *data, PRUint32 len) +{ + // Format calls for a network ordered 16 bit length + // followed by the utf8 string + + // for now, silently truncate headers greater than 64KB. Spdy/3 will + // fix this by making the len a 32 bit quantity + if (len > 0xffff) + len = 0xffff; + + PRUint16 networkLen = len; + networkLen = PR_htons(len); + + // write out the length + mZlib->next_in = reinterpret_cast (&networkLen); + mZlib->avail_in = 2; + ExecuteCompress(Z_NO_FLUSH); + + // write out the data + mZlib->next_in = (unsigned char *)data; + mZlib->avail_in = len; + ExecuteCompress(Z_NO_FLUSH); +} + +void +SpdyStream::CompressFlushFrame() +{ + mZlib->next_in = (unsigned char *) ""; + mZlib->avail_in = 0; + ExecuteCompress(Z_SYNC_FLUSH); +} + +void +SpdyStream::Close(nsresult reason) +{ + mTransaction->Close(reason); +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdyStream::OnReadSegment(const char *buf, + PRUint32 count, + PRUint32 *countRead) +{ + LOG3(("SpdyStream::OnReadSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader"); + + nsresult rv = NS_ERROR_UNEXPECTED; + PRUint32 dataLength; + + switch (mUpstreamState) { + case GENERATING_SYN_STREAM: + // The buffer is the HTTP request stream, including at least part of the + // HTTP request header. This state's job is to build a SYN_STREAM frame + // from the header information. count is the number of http bytes available + // (which may include more than the header), and in countRead we return + // the number of those bytes that we consume (i.e. the portion that are + // header bytes) + + rv = ParseHttpRequestHeaders(buf, count, countRead); + if (NS_FAILED(rv)) + return rv; + LOG3(("ParseHttpRequestHeaders %p used %d of %d.", + this, *countRead, count)); + if (mSynFrameComplete) { + NS_ABORT_IF_FALSE(mTxInlineFrameSize, + "OnReadSegment SynFrameComplete 0b"); + rv = TransmitFrame(nsnull, nsnull); + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) + rv = NS_OK; + if (mTxInlineFrameSize) + ChangeState(SENDING_SYN_STREAM); + else + ChangeState(GENERATING_REQUEST_BODY); + break; + } + NS_ABORT_IF_FALSE(*countRead == count, + "Header parsing not complete but unused data"); + break; + + case GENERATING_REQUEST_BODY: + NS_ABORT_IF_FALSE(!mTxInlineFrameSent, + "OnReadSegment in generating_request_body with " + "frame in progress"); + if (count < mChunkSize && count < mRequestBodyLen) { + LOG3(("SpdyStream %p id %x has %d to write out of a bodylen %d" + " with a chunk size of %d. Waiting for more.", + this, mStreamID, count, mChunkSize, mRequestBodyLen)); + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + } + + dataLength = NS_MIN(count, mChunkSize); + if (dataLength > mRequestBodyLen) + return NS_ERROR_UNEXPECTED; + mRequestBodyLen -= dataLength; + GenerateDataFrameHeader(dataLength, !mRequestBodyLen); + ChangeState(SENDING_REQUEST_BODY); + // NO BREAK + + case SENDING_REQUEST_BODY: + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment Send Data Header 0b"); + rv = TransmitFrame(buf, countRead); + LOG3(("TransmitFrame() rv=%x returning %d data bytes. " + "Header is %d/%d Body is %d/%d.", + rv, *countRead, + mTxInlineFrameSent, mTxInlineFrameSize, + mTxStreamFrameSent, mTxStreamFrameSize)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) + rv = NS_OK; + + // If that frame was all sent, look for another one + if (!mTxInlineFrameSize) + ChangeState(GENERATING_REQUEST_BODY); + break; + + case SENDING_SYN_STREAM: + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + + case SENDING_FIN_STREAM: + NS_ABORT_IF_FALSE(false, + "resuming partial fin stream out of OnReadSegment"); + break; + + default: + NS_ABORT_IF_FALSE(false, "SpdyStream::OnReadSegment non-write state"); + break; + } + + return rv; +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdyStream::OnWriteSegment(char *buf, + PRUint32 count, + PRUint32 *countWritten) +{ + LOG3(("SpdyStream::OnWriteSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + + return mSegmentWriter->OnWriteSegment(buf, count, countWritten); +} + +} // namespace mozilla::net +} // namespace mozilla + diff --git a/netwerk/protocol/http/SpdyStream.h b/netwerk/protocol/http/SpdyStream.h new file mode 100644 index 00000000000..8169604bf80 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream.h @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_SpdyStream_h +#define mozilla_net_SpdyStream_h + +#include "nsAHttpTransaction.h" + +namespace mozilla { namespace net { + +class SpdyStream : public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdyStream(nsAHttpTransaction *, + SpdySession *, nsISocketTransport *, + PRUint32, z_stream *, PRInt32); + ~SpdyStream(); + + PRUint32 StreamID() { return mStreamID; } + + nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); + nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); + + bool BlockedOnWrite() + { + return static_cast(mBlockedOnWrite); + } + + bool RequestBlockedOnRead() + { + return static_cast(mRequestBlockedOnRead); + } + + // returns false if called more than once + bool SetFullyOpen() + { + bool result = !mFullyOpen; + mFullyOpen = 1; + return result; + } + + nsAHttpTransaction *Transaction() + { + return mTransaction; + } + + void Close(nsresult reason); + + void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; } + bool RecvdFin() { return mRecvdFin; } + + // The zlib header compression dictionary defined by SPDY, + // and hooks to the mozilla allocator for zlib to use. + static const char *kDictionary; + static void *zlib_allocator(void *, uInt, uInt); + static void zlib_destructor(void *, void *); + +private: + + enum stateType { + GENERATING_SYN_STREAM, + SENDING_SYN_STREAM, + GENERATING_REQUEST_BODY, + SENDING_REQUEST_BODY, + SENDING_FIN_STREAM, + UPSTREAM_COMPLETE + }; + + static PLDHashOperator hdrHashEnumerate(const nsACString &, + nsAutoPtr &, + void *); + + void ChangeState(enum stateType ); + nsresult ParseHttpRequestHeaders(const char *, PRUint32, PRUint32 *); + nsresult TransmitFrame(const char *, PRUint32 *); + void GenerateDataFrameHeader(PRUint32, bool); + + void CompressToFrame(const nsACString &); + void CompressToFrame(const nsACString *); + void CompressToFrame(const char *, PRUint32); + void CompressToFrame(PRUint16); + void CompressFlushFrame(); + void ExecuteCompress(PRUint32); + + // Each stream goes from syn_stream to upstream_complete, perhaps + // looping on multiple instances of generating_request_body and + // sending_request_body for each SPDY chunk in the upload. + enum stateType mUpstreamState; + + // The underlying HTTP transaction + nsRefPtr mTransaction; + + // The session that this stream is a subset of + SpdySession *mSession; + + // The underlying socket transport object is needed to propogate some events + nsISocketTransport *mSocketTransport; + + // These are temporary state variables to hold the argument to + // Read/WriteSegments so it can be accessed by On(read/write)segment + // further up the stack. + nsAHttpSegmentReader *mSegmentReader; + nsAHttpSegmentWriter *mSegmentWriter; + + // The 24 bit SPDY stream ID + PRUint32 mStreamID; + + // The quanta upstream data frames are chopped into + PRUint32 mChunkSize; + + // Flag is set when all http request headers have been read + PRUint32 mSynFrameComplete : 1; + + // Flag is set when there is more request data to send and the + // stream needs to be rescheduled for writing. Sometimes this + // is done as a matter of fairness, not really due to blocking + PRUint32 mBlockedOnWrite : 1; + + // Flag is set when the HTTP processor has more data to send + // but has blocked in doing so. + PRUint32 mRequestBlockedOnRead : 1; + + // Flag is set when a FIN has been placed on a data or syn packet + // (i.e after the client has closed) + PRUint32 mSentFinOnData : 1; + + // Flag is set after the response frame bearing the fin bit has + // been processed. (i.e. after the server has closed). + PRUint32 mRecvdFin : 1; + + // Flag is set after syn reply received + PRUint32 mFullyOpen : 1; + + // The InlineFrame and associated data is used for composing control + // frames and data frame headers. + nsAutoArrayPtr mTxInlineFrame; + PRUint32 mTxInlineFrameAllocation; + PRUint32 mTxInlineFrameSize; + PRUint32 mTxInlineFrameSent; + + // mTxStreamFrameSize and mTxStreamFrameSent track the progress of + // transmitting a request body data frame. The data frame itself + // is never copied into the spdy layer. + PRUint32 mTxStreamFrameSize; + PRUint32 mTxStreamFrameSent; + + // Compression context and buffer for request header compression. + // This is a copy of SpdySession::mUpstreamZlib because it needs + // to remain the same in all streams of a session. + z_stream *mZlib; + nsCString mFlatHttpRequestHeaders; + + // Track the content-length of a request body so that we can + // place the fin flag on the last data packet instead of waiting + // for a stream closed indication. Relying on stream close results + // in an extra 0-length runt packet and seems to have some interop + // problems with the google servers. + PRInt64 mRequestBodyLen; + + // based on nsISupportsPriority definitions + PRInt32 mPriority; + +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdyStream_h diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h index 1e0e42dfbed..9a1ac339061 100644 --- a/netwerk/protocol/http/nsAHttpConnection.h +++ b/netwerk/protocol/http/nsAHttpConnection.h @@ -77,8 +77,8 @@ public: // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its // ReadSegments/WriteSegments methods. // - virtual nsresult ResumeSend() = 0; - virtual nsresult ResumeRecv() = 0; + virtual nsresult ResumeSend(nsAHttpTransaction *caller) = 0; + virtual nsresult ResumeRecv(nsAHttpTransaction *caller) = 0; // // called by the connection manager to close a transaction being processed @@ -132,8 +132,8 @@ public: #define NS_DECL_NSAHTTPCONNECTION \ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset); \ - nsresult ResumeSend(); \ - nsresult ResumeRecv(); \ + nsresult ResumeSend(nsAHttpTransaction *); \ + nsresult ResumeRecv(nsAHttpTransaction *); \ void CloseTransaction(nsAHttpTransaction *, nsresult); \ void GetConnectionInfo(nsHttpConnectionInfo **); \ nsresult TakeTransport(nsISocketTransport **, \ diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index 9cea7f46b35..fa5f3ab5b15 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -62,6 +62,7 @@ class nsAHttpTransaction : public nsISupports public: // called by the connection when it takes ownership of the transaction. virtual void SetConnection(nsAHttpConnection *) = 0; + virtual nsAHttpConnection *Connection() = 0; // called by the connection to get security callbacks to set on the // socket transport. @@ -95,10 +96,16 @@ public: // called to retrieve the request headers of the transaction virtual nsHttpRequestHead *RequestHead() = 0; + + // determine the number of real http/1.x transactions on this + // abstract object. Pipelines may have multiple, SPDY has 0, + // normal http transactions have 1. + virtual PRUint32 Http1xTransactionCount() = 0; }; #define NS_DECL_NSAHTTPTRANSACTION \ void SetConnection(nsAHttpConnection *); \ + nsAHttpConnection *Connection(); \ void GetSecurityCallbacks(nsIInterfaceRequestor **, \ nsIEventTarget **); \ void OnTransportStatus(nsITransport* transport, \ @@ -110,7 +117,8 @@ public: nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); \ void Close(nsresult reason); \ void SetSSLConnectFailed(); \ - nsHttpRequestHead *RequestHead(); + nsHttpRequestHead *RequestHead(); \ + PRUint32 Http1xTransactionCount(); //----------------------------------------------------------------------------- // nsAHttpSegmentReader diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index c2a7c9ed643..f2106d9175b 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -131,6 +131,11 @@ typedef PRUint8 nsHttpVersion; // host. Used by a forced reload to reset the connection states. #define NS_HTTP_CLEAR_KEEPALIVES (1<<6) +// Disallow the use of the SPDY protocol. This is meant for the contexts +// such as HTTP upgrade which are nonsensical for SPDY, it is not the +// SPDY configuration variable. +#define NS_HTTP_DISALLOW_SPDY (1<<7) + //----------------------------------------------------------------------------- // some default values //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.h index 8d085b26d99..403f559059f 100644 --- a/netwerk/protocol/http/nsHttpAtomList.h +++ b/netwerk/protocol/http/nsHttpAtomList.h @@ -56,6 +56,7 @@ HTTP_ATOM(Accept_Language, "Accept-Language") HTTP_ATOM(Accept_Ranges, "Accept-Ranges") HTTP_ATOM(Age, "Age") HTTP_ATOM(Allow, "Allow") +HTTP_ATOM(Alternate_Protocol, "Alternate-Protocol") HTTP_ATOM(Authentication, "Authentication") HTTP_ATOM(Authorization, "Authorization") HTTP_ATOM(Cache_Control, "Cache-Control") diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 1c4ae7d2528..c1baf5aea72 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -209,6 +209,18 @@ nsHttpChannel::Connect(bool firstTime) LOG(("nsHttpChannel::Connect() STS permissions found\n")); return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); } + + // Check for a previous SPDY Alternate-Protocol directive + if (gHttpHandler->IsSpdyEnabled() && mAllowSpdy) { + nsCAutoString hostPort; + + if (NS_SUCCEEDED(mURI->GetHostPort(hostPort)) && + gHttpHandler->ConnMgr()->GetSpdyAlternateProtocol(hostPort)) { + LOG(("nsHttpChannel::Connect() Alternate-Protocol found\n")); + return AsyncCall( + &nsHttpChannel::HandleAsyncRedirectChannelToHttps); + } + } } // ensure that we are using a valid hostname @@ -507,6 +519,9 @@ nsHttpChannel::SetupTransaction() } } + if (!mAllowSpdy) + mCaps |= NS_HTTP_DISALLOW_SPDY; + // use the URI path if not proxying (transparent proxying such as SSL proxy // does not count here). also, figure out what version we should be speaking. nsCAutoString buf, path; @@ -634,6 +649,7 @@ nsHttpChannel::SetupTransaction() mCaps |= NS_HTTP_STICKY_CONNECTION; mCaps &= ~NS_HTTP_ALLOW_PIPELINING; mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE; + mCaps |= NS_HTTP_DISALLOW_SPDY; } nsCOMPtr responseStream; @@ -4090,6 +4106,16 @@ nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) mSecurityInfo = mTransaction->SecurityInfo(); } + if (gHttpHandler->IsSpdyEnabled() && !mCachePump && NS_FAILED(mStatus) && + (mLoadFlags & LOAD_REPLACE) && mOriginalURI && mAllowSpdy) { + // For sanity's sake we may want to cancel an alternate protocol + // redirection involving the original host name + + nsCAutoString hostPort; + if (NS_SUCCEEDED(mOriginalURI->GetHostPort(hostPort))) + gHttpHandler->ConnMgr()->RemoveSpdyAlternateProtocol(hostPort); + } + // don't enter this block if we're reading from the cache... if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) { // mTransactionPump doesn't hit OnInputStreamReady and call this until diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 6be6fc65958..d5e7aa1c539 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -53,6 +53,9 @@ #include "nsProxyRelease.h" #include "prmem.h" #include "nsPreloadedStream.h" +#include "SpdySession.h" +#include "mozilla/Telemetry.h" +#include "nsISupportsPriority.h" #ifdef DEBUG // defined by the socket transport service while active @@ -75,6 +78,7 @@ nsHttpConnection::nsHttpConnection() , mConsiderReusedAfterEpoch(0) , mCurrentBytesRead(0) , mMaxBytesRead(0) + , mTotalBytesRead(0) , mKeepAlive(true) // assume to keep-alive by default , mKeepAliveMask(true) , mSupportsPipelining(false) // assume low-grade server @@ -82,6 +86,13 @@ nsHttpConnection::nsHttpConnection() , mCompletedProxyConnect(false) , mLastTransactionExpectedNoContent(false) , mIdleMonitoring(false) + , mHttp1xTransactionCount(0) + , mNPNComplete(false) + , mSetupNPNCalled(false) + , mUsingSpdy(false) + , mPriority(nsISupportsPriority::PRIORITY_NORMAL) + , mReportedSpdy(false) + , mEverUsedSpdy(false) { LOG(("Creating nsHttpConnection @%x\n", this)); @@ -103,6 +114,24 @@ nsHttpConnection::~nsHttpConnection() // release our reference to the handler nsHttpHandler *handler = gHttpHandler; NS_RELEASE(handler); + + if (!mEverUsedSpdy) { + LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n", + this, mHttp1xTransactionCount)); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::HTTP_REQUEST_PER_CONN, mHttp1xTransactionCount); + } + + if (mTotalBytesRead) { + PRUint32 totalKBRead = static_cast(mTotalBytesRead >> 10); + LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n", + this, totalKBRead, mEverUsedSpdy)); + mozilla::Telemetry::Accumulate( + mEverUsedSpdy ? + mozilla::Telemetry::SPDY_KBREAD_PER_CONN : + mozilla::Telemetry::HTTP_KBREAD_PER_CONN, + totalKBRead); + } } nsresult @@ -141,9 +170,79 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info, return NS_OK; } +bool +nsHttpConnection::EnsureNPNComplete() +{ + // NPN is only used by SPDY right now. + // + // If for some reason the components to check on NPN aren't available, + // this function will just return true to continue on and disable SPDY + + NS_ABORT_IF_FALSE(mSocketTransport, "EnsureNPNComplete " + "socket transport precondition"); + + if (mNPNComplete) + return true; + + nsresult rv; + + nsCOMPtr securityInfo; + nsCOMPtr ssl; + nsCAutoString negotiatedNPN; + + rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) + goto npnComplete; + + ssl = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + goto npnComplete; + + rv = ssl->GetNegotiatedNPN(negotiatedNPN); + if (rv == NS_ERROR_NOT_CONNECTED) { + + // By writing 0 bytes to the socket the SSL handshake machine is + // pushed forward. + PRUint32 count = 0; + rv = mSocketOut->Write("", 0, &count); + + if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) + goto npnComplete; + return false; + } + + if (NS_FAILED(rv)) + goto npnComplete; + + LOG(("nsHttpConnection::EnsureNPNComplete %p negotiated to '%s'", + this, negotiatedNPN.get())); + + if (negotiatedNPN.Equals(NS_LITERAL_CSTRING("spdy/2"))) { + mUsingSpdy = true; + mEverUsedSpdy = true; + mIsReused = true; /* all spdy streams are reused */ + + // Wrap the old http transaction into the new spdy session + // as the first stream + mSpdySession = new SpdySession(mTransaction, + mSocketTransport, + mPriority); + mTransaction = mSpdySession; + mIdleTimeout = gHttpHandler->SpdyTimeout(); + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_CONNECT, + mUsingSpdy); + +npnComplete: + LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true")); + mNPNComplete = true; + return true; +} + // called on the socket thread nsresult -nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps) +nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri) { nsresult rv; @@ -151,6 +250,10 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps) LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n", this, trans, caps)); + mPriority = pri; + if (mTransaction && mUsingSpdy) + return AddTransaction(trans, pri); + NS_ENSURE_ARG_POINTER(trans); NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS); @@ -166,6 +269,8 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps) mCallbackTarget = callbackTarget; } + SetupNPN(caps); // only for spdy + // take ownership of the transaction mTransaction = trans; @@ -198,6 +303,95 @@ failed_activation: return rv; } +void +nsHttpConnection::SetupNPN(PRUint8 caps) +{ + if (mSetupNPNCalled) /* do only once */ + return; + mSetupNPNCalled = true; + + // Setup NPN Negotiation if necessary (only for SPDY) + if (!mNPNComplete) { + + mNPNComplete = true; + + if (mConnInfo->UsingSSL() && + !(caps & NS_HTTP_DISALLOW_SPDY) && + !mConnInfo->UsingHttpProxy() && + gHttpHandler->IsSpdyEnabled()) { + LOG(("nsHttpConnection::Init Setting up SPDY Negotiation")); + nsCOMPtr securityInfo; + nsresult rv = + mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr ssl = + do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + return; + + nsTArray protocolArray; + protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2")); + protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1")); + if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) { + LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK")); + mNPNComplete = false; + } + } + } +} + +void +nsHttpConnection::HandleAlternateProtocol(nsHttpResponseHead *responseHead) +{ + // Look for the Alternate-Protocol header. Alternate-Protocol is + // essentially a way to rediect future transactions from http to + // spdy. + // + + if (!gHttpHandler->IsSpdyEnabled() || mUsingSpdy) + return; + + const char *val = responseHead->PeekHeader(nsHttp::Alternate_Protocol); + if (!val) + return; + + // The spec allows redirections to any port, but due to concerns over + // silently redirecting to stealth ports we only allow port 443 + // + // Alternate-Protocol: 5678:somethingelse, 443:npn-spdy/2 + + if (nsHttp::FindToken(val, "443:npn-spdy/2", HTTP_HEADER_VALUE_SEPS)) { + LOG(("Connection %p Transaction %p found Alternate-Protocol " + "header %s", this, mTransaction.get(), val)); + gHttpHandler->ConnMgr()->ReportSpdyAlternateProtocol(this); + } +} + +nsresult +nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction, + PRInt32 priority) +{ + LOG(("nsHttpConnection::AddTransaction for SPDY")); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSpdySession && mUsingSpdy, + "AddTransaction to live http connection without spdy"); + NS_ABORT_IF_FALSE(mTransaction, + "AddTransaction to idle http connection"); + + if (!mSpdySession->AddStream(httpTransaction, priority)) { + NS_ABORT_IF_FALSE(0, "AddStream should never fail due to" + "RoomForMore() admission check"); + return NS_ERROR_FAILURE; + } + + ResumeSend(httpTransaction); + + return NS_OK; +} + void nsHttpConnection::Close(nsresult reason) { @@ -237,13 +431,30 @@ nsHttpConnection::ProxyStartSSL() return ssl->ProxyStartSSL(); } +void +nsHttpConnection::DontReuse() +{ + mKeepAliveMask = false; + mKeepAlive = false; + mIdleTimeout = 0; + if (mUsingSpdy) + mSpdySession->DontReuse(); +} + bool nsHttpConnection::CanReuse() { - bool canReuse = IsKeepAlive() && + bool canReuse; + + if (mUsingSpdy) + canReuse = mSpdySession->CanReuse(); + else + canReuse = IsKeepAlive(); + + canReuse = canReuse && (NowInSeconds() - mLastReadTime < mIdleTimeout) && IsAlive(); - + // An idle persistent connection should not have data waiting to be read // before a request is sent. Data here is likely a 408 timeout response // which we would deal with later on through the restart logic, but that @@ -251,7 +462,7 @@ nsHttpConnection::CanReuse() // be removed with fixing of 631801 PRUint32 dataSize; - if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && + if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && !mUsingSpdy && NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) { LOG(("nsHttpConnection::CanReuse %p %s" "Socket not reusable because read data pending (%d) on it.\n", @@ -261,6 +472,16 @@ nsHttpConnection::CanReuse() return canReuse; } +bool +nsHttpConnection::CanDirectlyActivate() +{ + // return true if a new transaction can be addded to ths connection at any + // time through Activate(). In practice this means this is a healthy SPDY + // connection with room for more concurrent streams. + + return UsingSpdy() && CanReuse() && mSpdySession->RoomForMoreStreams(); +} + PRUint32 nsHttpConnection::TimeToLive() { PRInt32 tmp = mIdleTimeout - (NowInSeconds() - mLastReadTime); @@ -276,6 +497,10 @@ nsHttpConnection::IsAlive() if (!mSocketTransport) return false; + // SocketTransport::IsAlive can run the SSL state machine, so make sure + // the NPN options are set before that happens. + SetupNPN(0); + bool alive; nsresult rv = mSocketTransport->IsAlive(&alive); if (NS_FAILED(rv)) @@ -295,6 +520,10 @@ nsHttpConnection::IsAlive() bool nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) { + // SPDY supports infinite parallelism, so no need to pipeline. + if (mUsingSpdy) + return false; + // XXX there should be a strict mode available that disables this // blacklisting. @@ -420,20 +649,30 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, if (mKeepAlive) { val = responseHead->PeekHeader(nsHttp::Keep_Alive); - const char *cp = PL_strcasestr(val, "timeout="); - if (cp) - mIdleTimeout = (PRUint32) atoi(cp + 8); - else - mIdleTimeout = gHttpHandler->IdleTimeout(); + if (!mUsingSpdy) { + const char *cp = PL_strcasestr(val, "timeout="); + if (cp) + mIdleTimeout = (PRUint32) atoi(cp + 8); + else + mIdleTimeout = gHttpHandler->IdleTimeout(); + } + else { + mIdleTimeout = gHttpHandler->SpdyTimeout(); + } LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout)); } + if (!mProxyConnectStream) + HandleAlternateProtocol(responseHead); + // if we're doing an SSL proxy connect, then we need to check whether or not // the connect was successful. if so, then we have to reset the transaction // and step-up the socket connection to SSL. finally, we have to wake up the // socket write request. if (mProxyConnectStream) { + NS_ABORT_IF_FALSE(!mUsingSpdy, + "SPDY NPN Complete while using proxy connect stream"); mProxyConnectStream = 0; if (responseHead->Status() == 200) { LOG(("proxy CONNECT succeeded! ssl=%s\n", @@ -506,6 +745,8 @@ nsHttpConnection::TakeTransport(nsISocketTransport **aTransport, nsIAsyncInputStream **aInputStream, nsIAsyncOutputStream **aOutputStream) { + if (mUsingSpdy) + return NS_ERROR_FAILURE; if (mTransaction && !mTransaction->IsDone()) return NS_ERROR_IN_PROGRESS; if (!(mSocketTransport && mSocketIn && mSocketOut)) @@ -553,7 +794,7 @@ nsHttpConnection::PushBack(const char *data, PRUint32 length) } nsresult -nsHttpConnection::ResumeSend() +nsHttpConnection::ResumeSend(nsAHttpTransaction *) { LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this)); @@ -567,7 +808,7 @@ nsHttpConnection::ResumeSend() } nsresult -nsHttpConnection::ResumeRecv() +nsHttpConnection::ResumeRecv(nsAHttpTransaction *) { LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this)); @@ -586,7 +827,8 @@ nsHttpConnection::BeginIdleMonitoring() LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(!mTransaction, "BeginIdleMonitoring() while active"); - + NS_ABORT_IF_FALSE(!mUsingSpdy, "Idle monitoring of spdy not allowed"); + LOG(("Entering Idle Monitoring Mode [this=%p]", this)); mIdleMonitoring = true; if (mSocketIn) @@ -628,6 +870,15 @@ nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK; + if (mUsingSpdy) { + DontReuse(); + // if !mSpdySession then mUsingSpdy must be false for canreuse() + mUsingSpdy = false; + mSpdySession = nsnull; + } + + mHttp1xTransactionCount += mTransaction->Http1xTransactionCount(); + mTransaction->Close(reason); mTransaction = nsnull; @@ -692,6 +943,8 @@ nsHttpConnection::OnSocketWritable() bool again = true; do { + mSocketOutCondition = NS_OK; + // if we're doing an SSL proxy connect, then we need to bypass calling // into the transaction. // @@ -705,7 +958,26 @@ nsHttpConnection::OnSocketWritable() nsIOService::gDefaultSegmentSize, &n); } + else if (!EnsureNPNComplete()) { + // When SPDY is disabled this branch is not executed because Activate() + // sets mNPNComplete to true in that case. + + // We are ready to proceed with SSL but the handshake is not done. + // When using NPN to negotiate between HTTPS and SPDY, we need to + // see the results of the handshake to know what bytes to send, so + // we cannot proceed with the request headers. + + rv = NS_OK; + mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK; + n = 0; + } else { + if (gHttpHandler->IsSpdyEnabled() && !mReportedSpdy) { + mReportedSpdy = true; + gHttpHandler->ConnMgr()-> + ReportSpdyConnection(this, mUsingSpdy); + } + LOG((" writing transaction request stream\n")); rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n); } @@ -808,6 +1080,7 @@ nsHttpConnection::OnSocketReadable() } else { mCurrentBytesRead += n; + mTotalBytesRead += n; if (NS_FAILED(mSocketInCondition)) { // continue waiting for the socket if necessary... if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) @@ -831,6 +1104,8 @@ nsHttpConnection::SetupProxyConnect() LOG(("nsHttpConnection::SetupProxyConnect [this=%x]\n", this)); NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED); + NS_ABORT_IF_FALSE(!mUsingSpdy, + "SPDY NPN Complete while using proxy connect stream"); nsCAutoString buf; nsresult rv = nsHttpHandler::GenerateHostPort( diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index 26c3696976d..a16de8c34f9 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -47,6 +47,7 @@ #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "prinrval.h" +#include "SpdySession.h" #include "nsIStreamListener.h" #include "nsISocketTransport.h" @@ -92,8 +93,9 @@ public: nsIEventTarget *); // Activate causes the given transaction to be processed on this - // connection. It fails if there is already an existing transaction. - nsresult Activate(nsAHttpTransaction *, PRUint8 caps); + // connection. It fails if there is already an existing transaction unless + // a multiplexing protocol such as SPDY is being used + nsresult Activate(nsAHttpTransaction *, PRUint8 caps, PRInt32 pri); // Close the underlying socket transport. void Close(nsresult reason); @@ -102,15 +104,15 @@ public: // XXX document when these are ok to call bool SupportsPipelining() { return mSupportsPipelining; } - bool IsKeepAlive() { return mKeepAliveMask && mKeepAlive; } + bool IsKeepAlive() { return mUsingSpdy || + (mKeepAliveMask && mKeepAlive); } bool CanReuse(); // can this connection be reused? + bool CanDirectlyActivate(); // Returns time in seconds for how long connection can be reused. PRUint32 TimeToLive(); - void DontReuse() { mKeepAliveMask = false; - mKeepAlive = false; - mIdleTimeout = 0; } + void DontReuse(); void DropTransport() { DontReuse(); mSocketTransport = 0; } bool LastTransactionExpectedNoContent() @@ -140,8 +142,8 @@ public: void SetIsReusedAfter(PRUint32 afterMilliseconds); void SetIdleTimeout(PRUint16 val) {mIdleTimeout = val;} nsresult PushBack(const char *data, PRUint32 length); - nsresult ResumeSend(); - nsresult ResumeRecv(); + nsresult ResumeSend(nsAHttpTransaction *caller); + nsresult ResumeRecv(nsAHttpTransaction *caller); PRInt64 MaxBytesRead() {return mMaxBytesRead;} static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *, @@ -154,6 +156,8 @@ public: void BeginIdleMonitoring(); void EndIdleMonitoring(); + bool UsingSpdy() { return mUsingSpdy; } + private: // called to cause the underlying socket to start speaking SSL nsresult ProxyStartSSL(); @@ -167,6 +171,18 @@ private: bool IsAlive(); bool SupportsPipelining(nsHttpResponseHead *); + // Makes certain the SSL handshake is complete and NPN negotiation + // has had a chance to happen + bool EnsureNPNComplete(); + void SetupNPN(PRUint8 caps); + + // Inform the connection manager of any SPDY Alternate-Protocol + // redirections + void HandleAlternateProtocol(nsHttpResponseHead *); + + // Directly Add a transaction to an active connection for SPDY + nsresult AddTransaction(nsAHttpTransaction *, PRInt32); + private: nsCOMPtr mSocketTransport; nsCOMPtr mSocketIn; @@ -194,6 +210,7 @@ private: PRIntervalTime mConsiderReusedAfterEpoch; PRInt64 mCurrentBytesRead; // data read per activation PRInt64 mMaxBytesRead; // max read in 1 activation + PRInt64 mTotalBytesRead; // total data read nsRefPtr mInputOverflow; @@ -204,6 +221,21 @@ private: bool mCompletedProxyConnect; bool mLastTransactionExpectedNoContent; bool mIdleMonitoring; + + // The number of <= HTTP/1.1 transactions performed on this connection. This + // excludes spdy transactions. + PRUint32 mHttp1xTransactionCount; + + // SPDY related + bool mNPNComplete; + bool mSetupNPNCalled; + bool mUsingSpdy; + nsRefPtr mSpdySession; + PRInt32 mPriority; + bool mReportedSpdy; + + // mUsingSpdy is cleared when mSpdySession is freed, this is permanent + bool mEverUsedSpdy; }; #endif // nsHttpConnection_h__ diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h index db68f136347..fa0e43620df 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -124,7 +124,9 @@ public: PRInt32 DefaultPort() const { return mUsingSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; } void SetAnonymous(bool anon) { mHashKey.SetCharAt(anon ? 'A' : '.', 2); } + bool GetAnonymous() { return mHashKey.CharAt(2) == 'A'; } bool ShouldForceConnectMethod(); + const nsCString &GetHost() { return mHost; } private: nsrefcnt mRef; diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 5a158304b44..17d897f8175 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -48,6 +48,10 @@ #include "nsIObserverService.h" +#include "nsISSLSocketControl.h" +#include "prnetdb.h" +#include "mozilla/Telemetry.h" + using namespace mozilla; // defined by the socket transport service while active @@ -94,6 +98,7 @@ nsHttpConnectionMgr::nsHttpConnectionMgr() { LOG(("Creating nsHttpConnectionMgr @%x\n", this)); mCT.Init(); + mAlternateProtocolHash.Init(16); } nsHttpConnectionMgr::~nsHttpConnectionMgr() @@ -140,6 +145,7 @@ nsHttpConnectionMgr::Init(PRUint16 maxConns, { ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mSpdyPreferredHash.Init(); mMaxConns = maxConns; mMaxConnsPerHost = maxConnsPerHost; @@ -229,8 +235,13 @@ nsHttpConnectionMgr::PruneDeadConnectionsAfter(PRUint32 timeInSeconds) } void -nsHttpConnectionMgr::StopPruneDeadConnectionsTimer() +nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() { + // Leave the timer in place if there are connections that potentially + // need management + if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) + return; + LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. @@ -389,6 +400,50 @@ nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci) return rv; } +// Given a nsHttpConnectionInfo find the connection entry object that +// contains either the nshttpconnection or nshttptransaction parameter. +// Normally this is done by the hashkey lookup of connectioninfo, +// but if spdy coalescing is in play it might be found in a redirected +// entry +nsHttpConnectionMgr::nsConnectionEntry * +nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci, + nsHttpConnection *conn, + nsHttpTransaction *trans) +{ + if (!ci) + return nsnull; + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + + // If there is no sign of coalescing (or it is disabled) then just + // return the primary hash lookup + if (!gHttpHandler->IsSpdyEnabled() || !gHttpHandler->CoalesceSpdy() || + !ent || !ent->mUsingSpdy || ent->mCoalescingKey.IsEmpty()) + return ent; + + // If there is no preferred coalescing entry for this host (or the + // preferred entry is the one that matched the mCT hash lookup) then + // there is only option + nsConnectionEntry *preferred = mSpdyPreferredHash.Get(ent->mCoalescingKey); + if (!preferred || (preferred == ent)) + return ent; + + if (conn) { + // The connection could be either in preferred or ent. It is most + // likely the only active connection in preferred - so start with that. + if (preferred->mActiveConns.Contains(conn)) + return preferred; + if (preferred->mIdleConns.Contains(conn)) + return preferred; + } + + if (trans && preferred->mPendingQ.Contains(trans)) + return preferred; + + // Neither conn nor trans found in preferred, use the default entry + return ent; +} + nsresult nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn) { @@ -396,11 +451,11 @@ nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn) LOG(("nsHttpConnectionMgr::CloseIdleConnection %p conn=%p", this, conn)); - nsHttpConnectionInfo *ci = conn->ConnectionInfo(); - if (!ci) + if (!conn->ConnectionInfo()) return NS_ERROR_UNEXPECTED; - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), + conn, nsnull); if (!ent || !ent->mIdleConns.RemoveElement(conn)) return NS_ERROR_UNEXPECTED; @@ -408,11 +463,241 @@ nsHttpConnectionMgr::CloseIdleConnection(nsHttpConnection *conn) conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); mNumIdleConns--; - if (0 == mNumIdleConns) - StopPruneDeadConnectionsTimer(); + ConditionallyStopPruneDeadConnectionsTimer(); return NS_OK; } +void +nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn, + bool usingSpdy) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), + conn, nsnull); + + NS_ABORT_IF_FALSE(ent, "no connection entry"); + if (!ent) + return; + + ent->mTestedSpdy = true; + + if (!usingSpdy) { + if (ent->mUsingSpdy) + conn->DontReuse(); + return; + } + + ent->mUsingSpdy = true; + + PRUint32 ttl = conn->TimeToLive(); + PRUint64 timeOfExpire = NowInSeconds() + ttl; + if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) + PruneDeadConnectionsAfter(ttl); + + // Lookup preferred directly from the hash instead of using + // GetSpdyPreferred() because we want to avoid the cert compatibility + // check at this point because the cert is never part of the hash + // lookup. Filtering on that has to be done at the time of use + // rather than the time of registration (i.e. now). + nsConnectionEntry *preferred = + mSpdyPreferredHash.Get(ent->mCoalescingKey); + + LOG(("ReportSpdyConnection %s %s ent=%p ispreferred=%d\n", + ent->mConnInfo->Host(), ent->mCoalescingKey.get(), + ent, preferred)); + + if (!preferred) { + ent->mSpdyPreferred = true; + SetSpdyPreferred(ent); + preferred = ent; + } + else if (preferred != ent) { + // A different hostname is the preferred spdy host for this + // IP address. + ent->mUsingSpdy = true; + conn->DontReuse(); + } + + ProcessSpdyPendingQ(); +} + +bool +nsHttpConnectionMgr::GetSpdyAlternateProtocol(nsACString &hostPortKey) +{ + // The Alternate Protocol hash is protected under the monitor because + // it is read from both the main and the network thread. + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + return mAlternateProtocolHash.Contains(hostPortKey); +} + +void +nsHttpConnectionMgr::ReportSpdyAlternateProtocol(nsHttpConnection *conn) +{ + // Check network.http.spdy.use-alternate-protocol pref + if (!gHttpHandler->UseAlternateProtocol()) + return; + + // For now lets not bypass proxies due to the alternate-protocol header + if (conn->ConnectionInfo()->UsingHttpProxy()) + return; + + nsCString hostPortKey(conn->ConnectionInfo()->Host()); + if (conn->ConnectionInfo()->Port() != 80) { + hostPortKey.Append(NS_LITERAL_CSTRING(":")); + hostPortKey.AppendInt(conn->ConnectionInfo()->Port()); + } + + // The Alternate Protocol hash is protected under the monitor because + // it is read from both the main and the network thread. + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // Check to see if this is already present + if (mAlternateProtocolHash.Contains(hostPortKey)) + return; + + if (mAlternateProtocolHash.mHashTable.entryCount > 2000) + PL_DHashTableEnumerate(&mAlternateProtocolHash.mHashTable, + &nsHttpConnectionMgr::TrimAlternateProtocolHash, + this); + + mAlternateProtocolHash.Put(hostPortKey); +} + +void +nsHttpConnectionMgr::RemoveSpdyAlternateProtocol(nsACString &hostPortKey) +{ + // The Alternate Protocol hash is protected under the monitor because + // it is read from both the main and the network thread. + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + return mAlternateProtocolHash.Remove(hostPortKey); +} + +PLDHashOperator +nsHttpConnectionMgr::TrimAlternateProtocolHash(PLDHashTable *table, + PLDHashEntryHdr *hdr, + PRUint32 number, + void *closure) +{ + nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; + + if (self->mAlternateProtocolHash.mHashTable.entryCount > 2000) + return PL_DHASH_REMOVE; + return PL_DHASH_STOP; +} + +nsHttpConnectionMgr::nsConnectionEntry * +nsHttpConnectionMgr::GetSpdyPreferred(nsConnectionEntry *aOriginalEntry) +{ + if (!gHttpHandler->IsSpdyEnabled() || + !gHttpHandler->CoalesceSpdy() || + aOriginalEntry->mCoalescingKey.IsEmpty()) + return nsnull; + + nsConnectionEntry *preferred = + mSpdyPreferredHash.Get(aOriginalEntry->mCoalescingKey); + + // if there is no redirection no cert validation is required + if (preferred == aOriginalEntry) + return aOriginalEntry; + + // if there is no preferred host or it is no longer using spdy + // then skip pooling + if (!preferred || !preferred->mUsingSpdy) + return nsnull; + + // if there is not an active spdy session in this entry then + // we cannot pool because the cert upon activation may not + // be the same as the old one. Active sessions are prohibited + // from changing certs. + + nsHttpConnection *activeSpdy = nsnull; + + for (PRUint32 index = 0; index < preferred->mActiveConns.Length(); ++index) { + if (preferred->mActiveConns[index]->CanDirectlyActivate()) { + activeSpdy = preferred->mActiveConns[index]; + break; + } + } + + if (!activeSpdy) { + // remove the preferred status of this entry if it cannot be + // used for pooling. + preferred->mSpdyPreferred = false; + RemoveSpdyPreferred(preferred->mCoalescingKey); + LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " + "preferred host mapping %s to %s removed due to inactivity.\n", + aOriginalEntry->mConnInfo->Host(), + preferred->mConnInfo->Host())); + + return nsnull; + } + + // Check that the server cert supports redirection + nsresult rv; + bool isJoined = false; + + nsCOMPtr securityInfo; + nsCOMPtr sslSocketControl; + nsCAutoString negotiatedNPN; + + activeSpdy->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (!securityInfo) + return nsnull; + + sslSocketControl = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + return nsnull; + + rv = sslSocketControl->JoinConnection(NS_LITERAL_CSTRING("spdy/2"), + aOriginalEntry->mConnInfo->GetHost(), + aOriginalEntry->mConnInfo->Port(), + &isJoined); + + if (NS_FAILED(rv) || !isJoined) { + LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " + "Host %s cannot be confirmed to be joined " + "with %s connections", + preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host())); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_JOIN, + false); + return nsnull; + } + + // IP pooling confirmed + LOG(("nsHttpConnectionMgr::GetSpdyPreferredConnection " + "Host %s has cert valid for %s connections", + preferred->mConnInfo->Host(), aOriginalEntry->mConnInfo->Host())); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_JOIN, true); + return preferred; +} + +void +nsHttpConnectionMgr::SetSpdyPreferred(nsConnectionEntry *ent) +{ + if (!gHttpHandler->CoalesceSpdy()) + return; + + if (ent->mCoalescingKey.IsEmpty()) + return; + + mSpdyPreferredHash.Put(ent->mCoalescingKey, ent); +} + +void +nsHttpConnectionMgr::RemoveSpdyPreferred(nsACString &aHashKey) +{ + if (!gHttpHandler->CoalesceSpdy()) + return; + + if (aHashKey.IsEmpty()) + return; + + mSpdyPreferredHash.Remove(aHashKey); +} + //----------------------------------------------------------------------------- // enumeration callbacks @@ -449,8 +734,7 @@ nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key, conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; - if (0 == self->mNumIdleConns) - self->StopPruneDeadConnectionsTimer(); + self->ConditionallyStopPruneDeadConnectionsTimer(); } return PL_DHASH_STOP; } @@ -466,6 +750,7 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, // Find out how long it will take for next idle connection to not be reusable // anymore. + bool liveConnections = false; PRUint32 timeToNextExpire = PR_UINT32_MAX; PRInt32 count = ent->mIdleConns.Length(); if (count > 0) { @@ -478,14 +763,33 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, self->mNumIdleConns--; } else { timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive()); + liveConnections = true; } } } + if (ent->mUsingSpdy) { + for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) { + nsHttpConnection *conn = ent->mActiveConns[index]; + if (conn->UsingSpdy()) { + if (!conn->CanReuse()) { + // marking it dont reuse will create an active tear down if + // the spdy session is idle. + conn->DontReuse(); + } + else { + timeToNextExpire = NS_MIN(timeToNextExpire, + conn->TimeToLive()); + liveConnections = true; + } + } + } + } + // If time to next expire found is shorter than time to next wake-up, we need to // change the time for next wake-up. - PRUint32 now = NowInSeconds(); - if (0 < ent->mIdleConns.Length()) { + if (liveConnections) { + PRUint32 now = NowInSeconds(); PRUint64 timeOfNextExpire = now + timeToNextExpire; // If pruning of dead connections is not already scheduled to happen // or time found for next connection to expire is is before @@ -494,8 +798,8 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) { self->PruneDeadConnectionsAfter(timeToNextExpire); } - } else if (0 == self->mNumIdleConns) { - self->StopPruneDeadConnectionsTimer(); + } else { + self->ConditionallyStopPruneDeadConnectionsTimer(); } #ifdef DEBUG count = ent->mActiveConns.Length(); @@ -511,7 +815,10 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, if (ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 && - ent->mPendingQ.Length() == 0) { + ent->mPendingQ.Length() == 0 && + ((!ent->mTestedSpdy && !ent->mUsingSpdy) || + !gHttpHandler->IsSpdyEnabled() || + self->mCT.Count() > 300)) { LOG((" removing empty connection entry\n")); return PL_DHASH_REMOVE; } @@ -557,8 +864,7 @@ nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key, } // If all idle connections are removed, // we can stop pruning dead connections. - if (0 == self->mNumIdleConns) - self->StopPruneDeadConnectionsTimer(); + self->ConditionallyStopPruneDeadConnectionsTimer(); // close all pending transactions while (ent->mPendingQ.Length()) { @@ -582,15 +888,19 @@ nsHttpConnectionMgr::ShutdownPassCB(const nsACString &key, bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n", ent->mConnInfo->HashKey().get())); - PRInt32 i, count = ent->mPendingQ.Length(); + if (gHttpHandler->IsSpdyEnabled()) + ProcessSpdyPendingQ(ent); + + PRUint32 i, count = ent->mPendingQ.Length(); if (count > 0) { LOG((" pending-count=%u\n", count)); nsHttpTransaction *trans = nsnull; nsHttpConnection *conn = nsnull; - for (i=0; imPendingQ[i]; // When this transaction has already established a half-open @@ -609,6 +919,13 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent) GetConnection(ent, trans, alreadyHalfOpen, &conn); if (conn) break; + + // Check to see if a pending transaction was dispatched with the + // coalesce logic + if (count != ent->mPendingQ.Length()) { + count = ent->mPendingQ.Length(); + i = 0; + } } if (conn) { LOG((" dispatching pending transaction...\n")); @@ -736,7 +1053,8 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n", ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); - // First, see if an idle persistent connection may be reused instead of + // First, see if an existing connection can be used - either an idle + // persistent connection or an active spdy session may be reused instead of // establishing a new socket. We do not need to check the connection limits // yet as they govern the maximum number of open connections and reusing // an old connection never increases that. @@ -744,8 +1062,18 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, *result = nsnull; nsHttpConnection *conn = nsnull; + bool addConnToActiveList = true; if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) { + + // if willing to use spdy look for an active spdy connections + // before considering idle http ones. + if (gHttpHandler->IsSpdyEnabled()) { + conn = GetSpdyPreferredConn(ent); + if (conn) + addConnToActiveList = false; + } + // search the idle connection list. Each element in the list // has a reference, so if we remove it from the list into a local // ptr, that ptr now owns the reference @@ -768,8 +1096,7 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, // If there are no idle connections left at all, we need to make // sure that we are not pruning dead connections anymore. - if (0 == mNumIdleConns) - StopPruneDeadConnectionsTimer(); + ConditionallyStopPruneDeadConnectionsTimer(); } } @@ -780,6 +1107,19 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, if (onlyReusedConnection) return; + if (gHttpHandler->IsSpdyEnabled() && + ent->mConnInfo->UsingSSL() && + !ent->mConnInfo->UsingHttpProxy()) + { + // If this is a possible Spdy connection we need to limit the number + // of connections outstanding to 1 while we wait for the spdy/https + // ReportSpdyConnection() + + if ((!ent->mTestedSpdy || ent->mUsingSpdy) && + (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) + return; + } + // Check if we need to purge an idle connection. Note that we may have // removed one above; if so, this will be a no-op. We do this before // checking the active connection limit to catch the case where we do @@ -799,17 +1139,24 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, return; } + LOG(("nsHttpConnectionMgr::GetConnection Open Connection " + "%s %s ent=%p spdy=%d", + ent->mConnInfo->Host(), ent->mCoalescingKey.get(), + ent, ent->mUsingSpdy)); + nsresult rv = CreateTransport(ent, trans); if (NS_FAILED(rv)) trans->Close(rv); return; } - // hold an owning ref to this connection - ent->mActiveConns.AppendElement(conn); - mNumActiveConns++; + if (addConnToActiveList) { + // hold an owning ref to this connection + ent->mActiveConns.AppendElement(conn); + mNumActiveConns++; + } + NS_ADDREF(conn); - *result = conn; } @@ -850,12 +1197,25 @@ nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent, nsresult nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, - nsAHttpTransaction *trans, + nsHttpTransaction *aTrans, PRUint8 caps, nsHttpConnection *conn) { LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n", - ent->mConnInfo->HashKey().get(), trans, caps, conn)); + ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); + nsresult rv; + + PRInt32 priority = aTrans->Priority(); + + if (conn->UsingSpdy()) { + LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," + "Connection host = %s\n", + aTrans->ConnectionInfo()->Host(), + conn->ConnectionInfo()->Host())); + rv = conn->Activate(aTrans, caps, priority); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); + return rv; + } nsConnectionHandle *handle = new nsConnectionHandle(conn); if (!handle) @@ -863,6 +1223,8 @@ nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, NS_ADDREF(handle); nsHttpPipeline *pipeline = nsnull; + nsAHttpTransaction *trans = aTrans; + if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) { LOG((" looking to build pipeline...\n")); if (BuildPipeline(ent, trans, &pipeline)) @@ -872,7 +1234,7 @@ nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, // give the transaction the indirect reference to the connection. trans->SetConnection(handle); - nsresult rv = conn->Activate(trans, caps); + rv = conn->Activate(trans, caps, priority); if (NS_FAILED(rv)) { LOG((" conn->Activate failed [rv=%x]\n", rv)); @@ -967,6 +1329,17 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) mCT.Put(ci->HashKey(), ent); } + // SPDY coalescing of hostnames means we might redirect from this + // connection entry onto the preferred one. + nsConnectionEntry *preferredEntry = GetSpdyPreferred(ent); + if (preferredEntry && (preferredEntry != ent)) { + LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " + "redirected via coalescing from %s to %s\n", trans, + ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); + + ent = preferredEntry; + } + // If we are doing a force reload then close out any existing conns // to this host so that changes in DNS, LBs, etc.. are reflected if (caps & NS_HTTP_CLEAR_KEEPALIVES) @@ -1006,6 +1379,82 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) return rv; } +// This function tries to dispatch the pending spdy transactions on +// the connection entry sent in as an argument. It will do so on the +// active spdy connection either in that same entry or in the +// redirected 'preferred' entry for the same coalescing hash key if +// coalescing is enabled. + +void +nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent) +{ + nsHttpConnection *conn = GetSpdyPreferredConn(ent); + if (!conn) + return; + + for (PRInt32 index = ent->mPendingQ.Length() - 1; + index >= 0 && conn->CanDirectlyActivate(); + --index) { + nsHttpTransaction *trans = ent->mPendingQ[index]; + + if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) || + trans->Caps() & NS_HTTP_DISALLOW_SPDY) + continue; + + ent->mPendingQ.RemoveElementAt(index); + + nsresult rv2 = DispatchTransaction(ent, trans, trans->Caps(), conn); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv2), "Dispatch SPDY Transaction"); + NS_RELEASE(trans); + } +} + +PLDHashOperator +nsHttpConnectionMgr::ProcessSpdyPendingQCB(const nsACString &key, + nsAutoPtr &ent, + void *closure) +{ + nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; + self->ProcessSpdyPendingQ(ent); + return PL_DHASH_NEXT; +} + +void +nsHttpConnectionMgr::ProcessSpdyPendingQ() +{ + mCT.Enumerate(ProcessSpdyPendingQCB, this); +} + +nsHttpConnection * +nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(ent, "no connection entry"); + + nsConnectionEntry *preferred = GetSpdyPreferred(ent); + + // this entry is spdy-enabled if it is involved in a redirect + if (preferred) + ent->mUsingSpdy = true; + else + preferred = ent; + + nsHttpConnection *conn = nsnull; + + if (preferred->mUsingSpdy) { + for (PRUint32 index = 0; + index < preferred->mActiveConns.Length(); + ++index) { + if (preferred->mActiveConns[index]->CanDirectlyActivate()) { + conn = preferred->mActiveConns[index]; + break; + } + } + } + + return conn; +} + //----------------------------------------------------------------------------- void @@ -1041,8 +1490,9 @@ nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) nsHttpTransaction *trans = (nsHttpTransaction *) param; trans->SetPriority(priority); - nsHttpConnectionInfo *ci = trans->ConnectionInfo(); - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), + nsnull, trans); + if (ent) { PRInt32 index = ent->mPendingQ.IndexOf(trans); if (index >= 0) { @@ -1069,8 +1519,9 @@ nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param) if (conn && !trans->IsDone()) conn->CloseTransaction(trans, reason); else { - nsHttpConnectionInfo *ci = trans->ConnectionInfo(); - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(), + nsnull, trans); + if (ent) { PRInt32 index = ent->mPendingQ.IndexOf(trans); if (index >= 0) { @@ -1109,7 +1560,10 @@ nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *) // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; - if (mNumIdleConns > 0) + + // check canreuse() for all idle connections plus any active connections on + // connection entries that are using spdy. + if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled())) mCT.Enumerate(PruneDeadConnectionsCB, this); } @@ -1134,17 +1588,26 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) // 3) post event to process the pending transaction queue // - nsHttpConnectionInfo *ci = conn->ConnectionInfo(); - NS_ADDREF(ci); + nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(), + conn, nsnull); + nsHttpConnectionInfo *ci = nsnull; - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (!ent) { + // this should never happen + NS_ASSERTION(ent, "no connection entry"); + NS_ADDREF(ci = conn->ConnectionInfo()); + } + else { + NS_ADDREF(ci = ent->mConnInfo); - NS_ASSERTION(ent, "no connection entry"); - if (ent) { // If the connection is in the active list, remove that entry // and the reference held by the mActiveConns list. // This is never the final reference on conn as the event context // is also holding one that is released at the end of this function. + + if (ent->mUsingSpdy) + conn->DontReuse(); + if (ent->mActiveConns.RemoveElement(conn)) { nsHttpConnection *temp = conn; NS_RELEASE(temp); @@ -1173,7 +1636,7 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) conn->BeginIdleMonitoring(); // If the added connection was first idle connection or has shortest - // time to live among the idle connections, pruning dead + // time to live among the watched connections, pruning dead // connections needs to be done when it can't be reused anymore. PRUint32 timeToLive = conn->TimeToLive(); if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) @@ -1223,6 +1686,15 @@ nsHttpConnectionMgr::OnMsgUpdateParam(PRInt32, void *param) } } +// nsHttpConnectionMgr::nsConnectionEntry +nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() +{ + if (mSpdyPreferred) + gHttpHandler->ConnMgr()->RemoveSpdyPreferred(mCoalescingKey); + + NS_RELEASE(mConnInfo); +} + //----------------------------------------------------------------------------- // nsHttpConnectionMgr::nsConnectionHandle @@ -1246,15 +1718,15 @@ nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction * } nsresult -nsHttpConnectionMgr::nsConnectionHandle::ResumeSend() +nsHttpConnectionMgr::nsConnectionHandle::ResumeSend(nsAHttpTransaction *caller) { - return mConn->ResumeSend(); + return mConn->ResumeSend(caller); } nsresult -nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv() +nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv(nsAHttpTransaction *caller) { - return mConn->ResumeRecv(); + return mConn->ResumeRecv(caller); } void @@ -1416,10 +1888,14 @@ nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport, nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() { - nsresult rv = SetupStreams(getter_AddRefs(mSocketTransport), - getter_AddRefs(mStreamIn), - getter_AddRefs(mStreamOut), - false); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + + rv = SetupStreams(getter_AddRefs(mSocketTransport), + getter_AddRefs(mStreamIn), + getter_AddRefs(mStreamOut), + false); LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]", this, mEnt->mConnInfo->Host(), rv)); if (NS_FAILED(rv)) { @@ -1456,6 +1932,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() { PRUint16 timeout = gHttpHandler->GetIdleSynTimeout(); NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd"); + if (timeout) { // Setup the timer that will establish a backup socket // if we do not get a writable event on the main one. @@ -1621,12 +2098,48 @@ nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans, PRUint64 progress, PRUint64 progressMax) { + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + if (mTransaction) mTransaction->OnTransportStatus(trans, status, progress); if (trans != mSocketTransport) return NS_OK; + // if we are doing spdy coalescing and haven't recorded the ip address + // for this entry before then make the hash key if our dns lookup + // just completed + + if (status == nsISocketTransport::STATUS_CONNECTED_TO && + gHttpHandler->IsSpdyEnabled() && + gHttpHandler->CoalesceSpdy() && + mEnt && mEnt->mConnInfo && mEnt->mConnInfo->UsingSSL() && + !mEnt->mConnInfo->UsingHttpProxy() && + mEnt->mCoalescingKey.IsEmpty()) { + + PRNetAddr addr; + nsresult rv = mSocketTransport->GetPeerAddr(&addr); + if (NS_SUCCEEDED(rv)) { + mEnt->mCoalescingKey.SetCapacity(72); + PR_NetAddrToString(&addr, mEnt->mCoalescingKey.BeginWriting(), 64); + mEnt->mCoalescingKey.SetLength( + strlen(mEnt->mCoalescingKey.BeginReading())); + + if (mEnt->mConnInfo->GetAnonymous()) + mEnt->mCoalescingKey.AppendLiteral("~A:"); + else + mEnt->mCoalescingKey.AppendLiteral("~.:"); + mEnt->mCoalescingKey.AppendInt(mEnt->mConnInfo->Port()); + + LOG(("nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus " + "STATUS_CONNECTED_TO Established New Coalescing Key for host " + "%s [%s]", mEnt->mConnInfo->Host(), + mEnt->mCoalescingKey.get())); + + gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt); + } + } + switch (status) { case nsISocketTransport::STATUS_CONNECTING_TO: // Passed DNS resolution, now trying to connect, start the backup timer diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index cdf21a918cd..bb605a10df8 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -45,12 +45,15 @@ #include "nsTArray.h" #include "nsThreadUtils.h" #include "nsClassHashtable.h" +#include "nsDataHashtable.h" #include "nsAutoPtr.h" #include "mozilla/ReentrantMonitor.h" #include "nsISocketTransportService.h" +#include "nsHashSets.h" #include "nsIObserver.h" #include "nsITimer.h" +#include "nsIX509Cert3.h" class nsHttpPipeline; @@ -96,8 +99,9 @@ public: // given time. void PruneDeadConnectionsAfter(PRUint32 time); - // Stops timer scheduled for next pruning of dead connections. - void StopPruneDeadConnectionsTimer(); + // Stops timer scheduled for next pruning of dead connections if + // there are no more idle connections or active spdy ones + void ConditionallyStopPruneDeadConnectionsTimer(); // adds a transaction to the list of managed transactions. nsresult AddTransaction(nsHttpTransaction *, PRInt32 priority); @@ -130,6 +134,11 @@ public: // been initialized. nsresult UpdateParam(nsParamName name, PRUint16 value); + // Lookup/Cancel HTTP->SPDY redirections + bool GetSpdyAlternateProtocol(nsACString &key); + void ReportSpdyAlternateProtocol(nsHttpConnection *); + void RemoveSpdyAlternateProtocol(nsACString &key); + //------------------------------------------------------------------------- // NOTE: functions below may be called only on the socket thread. //------------------------------------------------------------------------- @@ -147,6 +156,11 @@ public: // that the network peer has closed the transport. nsresult CloseIdleConnection(nsHttpConnection *); + // The connection manager needs to know when a normal HTTP connection has been + // upgraded to SPDY because the dispatch and idle semantics are a little + // bit different. + void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy); + private: virtual ~nsHttpConnectionMgr(); class nsHalfOpenSocket; @@ -160,17 +174,39 @@ private: struct nsConnectionEntry { nsConnectionEntry(nsHttpConnectionInfo *ci) - : mConnInfo(ci) + : mConnInfo(ci), + mUsingSpdy(false), + mTestedSpdy(false), + mSpdyPreferred(false) { NS_ADDREF(mConnInfo); } - ~nsConnectionEntry() { NS_RELEASE(mConnInfo); } + ~nsConnectionEntry(); nsHttpConnectionInfo *mConnInfo; nsTArray mPendingQ; // pending transaction queue nsTArray mActiveConns; // active connections nsTArray mIdleConns; // idle persistent connections nsTArray mHalfOpens; + + // Spdy sometimes resolves the address in the socket manager in order + // to re-coalesce sharded HTTP hosts. The dotted decimal address is + // combined with the Anonymous flag from the connection information + // to build the hash key for hosts in the same ip pool. + // + // When a set of hosts are coalesced together one of them is marked + // mSpdyPreferred. The mapping is maintained in the connection mananger + // mSpdyPreferred hash. + // + nsCString mCoalescingKey; + + // To have the UsingSpdy flag means some host with the same hash information + // has done NPN=spdy/2 at some point. It does not mean every connection + // is currently using spdy. + bool mUsingSpdy; + + bool mTestedSpdy; + bool mSpdyPreferred; }; // nsConnectionHandle @@ -273,7 +309,7 @@ private: bool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps); void GetConnection(nsConnectionEntry *, nsHttpTransaction *, bool, nsHttpConnection **); - nsresult DispatchTransaction(nsConnectionEntry *, nsAHttpTransaction *, + nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *, PRUint8 caps, nsHttpConnection *); bool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); nsresult ProcessNewTransaction(nsHttpTransaction *); @@ -283,7 +319,23 @@ private: void AddActiveConn(nsHttpConnection *, nsConnectionEntry *); void StartedConnect(); void RecvdConnect(); - + + // Manage the preferred spdy connection entry for this address + nsConnectionEntry *GetSpdyPreferred(nsConnectionEntry *aOriginalEntry); + void SetSpdyPreferred(nsConnectionEntry *ent); + void RemoveSpdyPreferred(nsACString &aDottedDecimal); + nsHttpConnection *GetSpdyPreferredConn(nsConnectionEntry *ent); + nsDataHashtable mSpdyPreferredHash; + nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci, + nsHttpConnection *conn, + nsHttpTransaction *trans); + + void ProcessSpdyPendingQ(nsConnectionEntry *ent); + void ProcessSpdyPendingQ(); + static PLDHashOperator ProcessSpdyPendingQCB( + const nsACString &key, nsAutoPtr &ent, + void *closure); + // message handlers have this signature typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(PRInt32, void *); @@ -361,6 +413,13 @@ private: // nsConnectionEntry object. // nsClassHashtable mCT; + + // this table is protected by the monitor + nsCStringHashSet mAlternateProtocolHash; + static PLDHashOperator TrimAlternateProtocolHash(PLDHashTable *table, + PLDHashEntryHdr *hdr, + PRUint32 number, + void *closure); }; #endif // !nsHttpConnectionMgr_h__ diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index f21598e0314..d4735249e3a 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -174,6 +174,7 @@ nsHttpHandler::nsHttpHandler() , mReferrerLevel(0xff) // by default we always send a referrer , mFastFallbackToIPv4(false) , mIdleTimeout(10) + , mSpdyTimeout(180) , mMaxRequestAttempts(10) , mMaxRequestDelay(10) , mIdleSynTimeout(250) @@ -198,6 +199,9 @@ nsHttpHandler::nsHttpHandler() , mSendSecureXSiteReferrer(true) , mEnablePersistentHttpsCaching(false) , mDoNotTrackEnabled(false) + , mEnableSpdy(false) + , mCoalesceSpdy(true) + , mUseAlternateProtocol(false) { #if defined(PR_LOGGING) gHttpLog = PR_NewLogModule("nsHttp"); @@ -1084,6 +1088,31 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) mPhishyUserPassLength = (PRUint8) clamped(val, 0, 0xff); } + if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnableSpdy = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar); + if (NS_SUCCEEDED(rv)) + mCoalesceSpdy = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.use-alternate-protocol"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.use-alternate-protocol"), + &cVar); + if (NS_SUCCEEDED(rv)) + mUseAlternateProtocol = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mSpdyTimeout = (PRUint16) clamped(val, 1, 0xffff); + } + // // INTL options // diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 4eb0abae246..f1a3ff67bca 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -101,6 +101,7 @@ public: bool SendSecureXSiteReferrer() { return mSendSecureXSiteReferrer; } PRUint8 RedirectionLimit() { return mRedirectionLimit; } PRUint16 IdleTimeout() { return mIdleTimeout; } + PRUint16 SpdyTimeout() { return mSpdyTimeout; } PRUint16 MaxRequestAttempts() { return mMaxRequestAttempts; } const char *DefaultSocketType() { return mDefaultSocketType.get(); /* ok to return null */ } nsIIDNService *IDNConverter() { return mIDNConverter; } @@ -112,6 +113,10 @@ public: bool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; } + bool IsSpdyEnabled() { return mEnableSpdy; } + bool CoalesceSpdy() { return mCoalesceSpdy; } + bool UseAlternateProtocol() { return mUseAlternateProtocol; } + bool PromptTempRedirect() { return mPromptTempRedirect; } nsHttpAuthCache *AuthCache() { return &mAuthCache; } @@ -264,6 +269,7 @@ private: bool mFastFallbackToIPv4; PRUint16 mIdleTimeout; + PRUint16 mSpdyTimeout; PRUint16 mMaxRequestAttempts; PRUint16 mMaxRequestDelay; PRUint16 mIdleSynTimeout; @@ -331,6 +337,11 @@ private: // For broadcasting the preference to not be tracked bool mDoNotTrackEnabled; + + // Try to use SPDY features instead of HTTP/1.1 over SSL + bool mEnableSpdy; + bool mCoalesceSpdy; + bool mUseAlternateProtocol; }; //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp index e8d48d95cd5..b78b8809123 100644 --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -101,6 +101,7 @@ nsHttpPipeline::nsHttpPipeline() , mPushBackBuf(nsnull) , mPushBackLen(0) , mPushBackMax(0) + , mHttp1xTransactionCount(0) , mReceivingFromProgress(0) , mSendingToProgress(0) , mSuppressSendEvents(true) @@ -128,7 +129,7 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) trans->SetConnection(this); if (mRequestQ.Length() == 1) - mConnection->ResumeSend(); + mConnection->ResumeSend(trans); } return NS_OK; @@ -167,19 +168,19 @@ nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans, } nsresult -nsHttpPipeline::ResumeSend() +nsHttpPipeline::ResumeSend(nsAHttpTransaction *trans) { if (mConnection) - return mConnection->ResumeSend(); + return mConnection->ResumeSend(trans); return NS_ERROR_UNEXPECTED; } nsresult -nsHttpPipeline::ResumeRecv() +nsHttpPipeline::ResumeRecv(nsAHttpTransaction *trans) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mConnection, "no connection"); - return mConnection->ResumeRecv(); + return mConnection->ResumeRecv(trans); } void @@ -355,6 +356,12 @@ nsHttpPipeline::RequestHead() return nsnull; } +PRUint32 +nsHttpPipeline::Http1xTransactionCount() +{ + return mHttp1xTransactionCount; +} + //----------------------------------------------------------------------------- // nsHttpPipeline::nsAHttpConnection //----------------------------------------------------------------------------- @@ -374,6 +381,15 @@ nsHttpPipeline::SetConnection(nsAHttpConnection *conn) Request(i)->SetConnection(this); } +nsAHttpConnection * +nsHttpPipeline::Connection() +{ + LOG(("nsHttpPipeline::Connection [this=%x conn=%x]\n", this, mConnection)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + return mConnection; +} + void nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result, nsIEventTarget **target) @@ -605,6 +621,7 @@ nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer, NS_RELEASE(trans); mResponseQ.RemoveElementAt(0); mResponseIsPartial = false; + ++mHttp1xTransactionCount; // ask the connection manager to add additional transactions // to our pipeline. diff --git a/netwerk/protocol/http/nsHttpPipeline.h b/netwerk/protocol/http/nsHttpPipeline.h index c78a72589d8..ca45fc30f2d 100644 --- a/netwerk/protocol/http/nsHttpPipeline.h +++ b/netwerk/protocol/http/nsHttpPipeline.h @@ -112,6 +112,9 @@ private: PRUint32 mPushBackLen; PRUint32 mPushBackMax; + // The number of transactions completed on this pipeline. + PRUint32 mHttp1xTransactionCount; + // For support of OnTransportStatus() PRUint64 mReceivingFromProgress; PRUint64 mSendingToProgress; diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 51d3afcf015..bb149d7bfa8 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -305,6 +305,12 @@ nsHttpTransaction::Init(PRUint8 caps, return NS_OK; } +nsAHttpConnection * +nsHttpTransaction::Connection() +{ + return mConnection; +} + nsHttpResponseHead * nsHttpTransaction::TakeResponseHead() { @@ -330,6 +336,12 @@ nsHttpTransaction::RequestHead() return mRequestHead; } +PRUint32 +nsHttpTransaction::Http1xTransactionCount() +{ + return 1; +} + //---------------------------------------------------------------------------- // nsHttpTransaction::nsAHttpTransaction //---------------------------------------------------------------------------- @@ -1287,7 +1299,7 @@ NS_IMETHODIMP nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out) { if (mConnection) { - nsresult rv = mConnection->ResumeSend(); + nsresult rv = mConnection->ResumeSend(this); if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed"); } @@ -1303,7 +1315,7 @@ NS_IMETHODIMP nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) { if (mConnection) { - nsresult rv = mConnection->ResumeRecv(); + nsresult rv = mConnection->ResumeRecv(this); if (NS_FAILED(rv)) NS_ERROR("ResumeRecv failed"); } diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 26e07ad2deb..93bd4e7ad6e 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -120,7 +120,6 @@ public: nsIInterfaceRequestor *Callbacks() { return mCallbacks; } nsIEventTarget *ConsumerTarget() { return mConsumerTarget; } - nsAHttpConnection *Connection() { return mConnection; } // Called to take ownership of the response headers; the transaction // will drop any reference to the response headers after this call. @@ -131,7 +130,7 @@ public: bool SSLConnectFailed() { return mSSLConnectFailed; } - // These methods may only be used by the connection manager. + // SetPriority() may only be used by the connection manager. void SetPriority(PRInt32 priority) { mPriority = priority; } PRInt32 Priority() { return mPriority; } diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 2f9ebe2d772..56d039c39f6 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -53,7 +53,7 @@ interface nsIProxyInfo; * The callback interface for nsIHttpChannelInternal::HTTPUpgrade() */ -[scriptable, uuid(5644af88-09e1-4fbd-83da-f012b3b30180)] +[scriptable, uuid(4b967b6d-cd1c-49ae-a457-23ff76f5a2e8)] interface nsIHttpUpgradeListener : nsISupports { void onTransportAvailable(in nsISocketTransport aTransport, @@ -181,4 +181,11 @@ interface nsIHttpChannelInternal : nsISupports void HTTPUpgrade(in ACString aProtocolName, in nsIHttpUpgradeListener aListener); + /** + * Enable/Disable Spdy negotiation on per channel basis. + * The network.http.spdy.enabled preference is still a pre-requisite + * for starting spdy. + */ + attribute boolean allowSpdy; + }; diff --git a/netwerk/socket/nsISSLSocketControl.idl b/netwerk/socket/nsISSLSocketControl.idl index 3fede66f078..516249e6916 100644 --- a/netwerk/socket/nsISSLSocketControl.idl +++ b/netwerk/socket/nsISSLSocketControl.idl @@ -42,11 +42,47 @@ interface nsIInterfaceRequestor; -[scriptable, uuid(a092097c-8386-4f1b-97b1-90eb70008c2d)] +%{C++ +#include "nsTArray.h" +class nsCString; +%} +[ref] native nsCStringTArrayRef(nsTArray); + +[scriptable, uuid(753f0f13-681d-4de3-a6c6-11aa7e0b3afd)] interface nsISSLSocketControl : nsISupports { attribute nsIInterfaceRequestor notificationCallbacks; void proxyStartSSL(); void StartTLS(); + + /* NPN (Next Protocol Negotiation) is a mechanism for + negotiating the protocol to be spoken inside the SSL + tunnel during the SSL handshake. The NPNList is the list + of offered client side protocols. setNPNList() needs to + be called before any data is read or written (including the + handshake to be setup correctly. */ + + [noscript] void setNPNList(in nsCStringTArrayRef aNPNList); + + /* negotiatedNPN is '' if no NPN list was provided by the client, + * or if the server did not select any protocol choice from that + * list. That also includes the case where the server does not + * implement NPN. + * + * If negotiatedNPN is read before NPN has progressed to the point + * where this information is available NS_ERROR_NOT_CONNECTED is + * raised. + */ + readonly attribute ACString negotiatedNPN; + + /* Determine if a potential SSL connection to hostname:port with + * a desired NPN negotiated protocol of npnProtocol can use the socket + * associated with this object instead of making a new one. + */ + boolean joinConnection( + in ACString npnProtocol, /* e.g. "spdy/2" */ + in ACString hostname, + in long port); + }; diff --git a/parser/htmlparser/src/Makefile.in b/parser/htmlparser/src/Makefile.in index 546a4d8c342..f7543f78d96 100644 --- a/parser/htmlparser/src/Makefile.in +++ b/parser/htmlparser/src/Makefile.in @@ -74,7 +74,6 @@ CPPSRCS = \ nsScanner.cpp \ nsToken.cpp \ nsParserMsgUtils.cpp\ - nsViewSourceHTML.cpp \ $(NULL) ifdef MOZ_DEBUG diff --git a/parser/htmlparser/src/nsParser.cpp b/parser/htmlparser/src/nsParser.cpp index ab6639a4b0c..4748cd34858 100644 --- a/parser/htmlparser/src/nsParser.cpp +++ b/parser/htmlparser/src/nsParser.cpp @@ -71,7 +71,6 @@ #include "nsIThreadPool.h" #include "nsXPCOMCIDInternal.h" #include "nsMimeTypes.h" -#include "nsViewSourceHTML.h" #include "mozilla/CondVar.h" #include "mozilla/Mutex.h" #include "nsParserConstants.h" @@ -1376,9 +1375,8 @@ FindSuitableDTD(CParserContext& aParserContext) aParserContext.mAutoDetectStatus = ePrimaryDetect; // Quick check for view source. - if (aParserContext.mParserCommand == eViewSource) { - return new CViewSourceHTML(); - } + NS_ABORT_IF_FALSE(aParserContext.mParserCommand != eViewSource, + "The old parser is not supposed to be used for View Source anymore."); // Now see if we're parsing HTML (which, as far as we're concerned, simply // means "not XML"). diff --git a/parser/htmlparser/src/nsParserModule.cpp b/parser/htmlparser/src/nsParserModule.cpp index 611253aceb8..ba2a84bf310 100644 --- a/parser/htmlparser/src/nsParserModule.cpp +++ b/parser/htmlparser/src/nsParserModule.cpp @@ -52,7 +52,6 @@ #include "nsSAXAttributes.h" #include "nsSAXLocator.h" #include "nsSAXXMLReader.h" -#include "nsViewSourceHTML.h" #if defined(NS_DEBUG) #include "nsLoggingSink.h" @@ -70,8 +69,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsParser) NS_GENERIC_FACTORY_CONSTRUCTOR(CNavDTD) NS_GENERIC_FACTORY_CONSTRUCTOR(nsParserService) -NS_GENERIC_FACTORY_CONSTRUCTOR(CViewSourceHTML) - NS_GENERIC_FACTORY_CONSTRUCTOR(nsSAXAttributes) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSAXXMLReader) @@ -81,7 +78,6 @@ NS_DEFINE_NAMED_CID(NS_EXPAT_DRIVER_CID); #endif NS_DEFINE_NAMED_CID(NS_PARSER_CID); NS_DEFINE_NAMED_CID(NS_CNAVDTD_CID); -NS_DEFINE_NAMED_CID(NS_VIEWSOURCE_DTD_CID); NS_DEFINE_NAMED_CID(NS_PARSERSERVICE_CID); NS_DEFINE_NAMED_CID(NS_SAXATTRIBUTES_CID); NS_DEFINE_NAMED_CID(NS_SAXXMLREADER_CID); @@ -93,7 +89,6 @@ static const mozilla::Module::CIDEntry kParserCIDs[] = { #endif { &kNS_PARSER_CID, false, NULL, nsParserConstructor }, { &kNS_CNAVDTD_CID, false, NULL, CNavDTDConstructor }, - { &kNS_VIEWSOURCE_DTD_CID, false, NULL, CViewSourceHTMLConstructor }, { &kNS_PARSERSERVICE_CID, false, NULL, nsParserServiceConstructor }, { &kNS_SAXATTRIBUTES_CID, false, NULL, nsSAXAttributesConstructor }, { &kNS_SAXXMLREADER_CID, false, NULL, nsSAXXMLReaderConstructor }, diff --git a/parser/htmlparser/src/nsViewSourceHTML.cpp b/parser/htmlparser/src/nsViewSourceHTML.cpp deleted file mode 100644 index 58ec2a6e1c1..00000000000 --- a/parser/htmlparser/src/nsViewSourceHTML.cpp +++ /dev/null @@ -1,1371 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set sw=2 ts=2 et tw=78: */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * jce2@po.cwru.edu : Added pref to turn on/off - * Boris Zbarsky - * rbs@maths.uq.edu.au - * Andreas M. Schneider - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* - * Set NS_VIEWSOURCE_TOKENS_PER_BLOCK to 0 to disable multi-block - * output. Multi-block output helps reduce the amount of bidi - * processing we have to do on the resulting content model. - */ -#define NS_VIEWSOURCE_TOKENS_PER_BLOCK 16 - -#include "nsIAtom.h" -#include "nsViewSourceHTML.h" -#include "nsCRT.h" -#include "nsParser.h" -#include "nsScanner.h" -#include "nsDTDUtils.h" -#include "nsIContentSink.h" -#include "nsIHTMLContentSink.h" -#include "nsHTMLTokenizer.h" -#include "nsUnicharUtils.h" -#include "nsPrintfCString.h" -#include "nsNetUtil.h" -#include "nsHTMLEntities.h" -#include "nsParserConstants.h" - -#include "nsIServiceManager.h" - -#include "nsElementTable.h" - -#include "prenv.h" //this is here for debug reasons... -#include "prtypes.h" //this is here for debug reasons... -#include "prio.h" -#include "plstr.h" -#include "prmem.h" - -#include "mozilla/Preferences.h" - -#ifdef RAPTOR_PERF_METRICS -#include "stopwatch.h" -Stopwatch vsTimer; -#endif - -using namespace mozilla; - -// Define this to dump the viewsource stuff to a file -//#define DUMP_TO_FILE -#ifdef DUMP_TO_FILE -#include - FILE* gDumpFile=0; - static const char* gDumpFileName = "/tmp/viewsource.html"; -// static const char* gDumpFileName = "\\temp\\viewsource.html"; -#endif // DUMP_TO_FILE - -// bug 22022 - these are used to toggle 'Wrap Long Lines' on the viewsource -// window by selectively setting/unsetting the following class defined in -// viewsource.css; the setting is remembered between invocations using a pref. -static const char kBodyId[] = "viewsource"; -static const char kBodyClassWrap[] = "wrap"; -static const char kBodyTabSize[] = "-moz-tab-size: "; - -NS_IMPL_ISUPPORTS1(CViewSourceHTML, nsIDTD) - -/******************************************** - ********************************************/ - -enum { - kStartTag = 0, - kEndTag, - kComment, - kCData, - kDoctype, - kPI, - kEntity, - kText, - kAttributeName, - kAttributeValue, - kMarkupDecl -}; - -static const char* const kElementClasses[] = { - "start-tag", - "end-tag", - "comment", - "cdata", - "doctype", - "pi", - "entity", - "text", - "attribute-name", - "attribute-value", - "markupdeclaration" -}; - -static const char* const kBeforeText[] = { - "<", - "", - ">", - "", - "", - "", - "", - "", - "", - "", - "", - "" -}; - -#ifdef DUMP_TO_FILE -static const char* const kDumpFileBeforeText[] = { - "<", - "</", - "", - "", - "", - "", - "&", - "", - "", - "=", - "" -}; - -static const char* const kDumpFileAfterText[] = { - ">", - ">", - "", - "", - "", - "", - "", - "", - "", - "", - "" -}; -#endif // DUMP_TO_FILE - -/** - * Default constructor - * - * @update gess 4/9/98 - * @param - * @return - */ -CViewSourceHTML::CViewSourceHTML() -{ - mSyntaxHighlight = - Preferences::GetBool("view_source.syntax_highlight", true); - - mWrapLongLines = Preferences::GetBool("view_source.wrap_long_lines", false); - - mTabSize = Preferences::GetInt("view_source.tab_size", -1); - - mSink = 0; - mLineNumber = 1; - mTokenizer = 0; - mDocType=eHTML_Quirks; - mHasOpenRoot=false; - mHasOpenBody=false; - - mTokenCount=0; - -#ifdef DUMP_TO_FILE - gDumpFile = fopen(gDumpFileName,"w"); -#endif // DUMP_TO_FILE - -} - - - -/** - * Default destructor - * - * @update gess 4/9/98 - * @param - * @return - */ -CViewSourceHTML::~CViewSourceHTML(){ - mSink=0; //just to prove we destructed... -} - -/** - * The parser uses a code sandwich to wrap the parsing process. Before - * the process begins, WillBuildModel() is called. Afterwards the parser - * calls DidBuildModel(). - * @update rickg 03.20.2000 - * @param aParserContext - * @param aSink - * @return error code (almost always 0) - */ -NS_IMETHODIMP -CViewSourceHTML::WillBuildModel(const CParserContext& aParserContext, - nsITokenizer* aTokenizer, - nsIContentSink* aSink) -{ - nsresult result=NS_OK; - -#ifdef RAPTOR_PERF_METRICS - vsTimer.Reset(); - NS_START_STOPWATCH(vsTimer); -#endif - - mSink=(nsIHTMLContentSink*)aSink; - - if((!aParserContext.mPrevContext) && (mSink)) { - - nsAString & contextFilename = aParserContext.mScanner->GetFilename(); - mFilename = Substring(contextFilename, - 12, // The length of "view-source:" - contextFilename.Length() - 12); - - mDocType=aParserContext.mDocType; - mMimeType=aParserContext.mMimeType; - mDTDMode=aParserContext.mDTDMode; - mParserCommand=aParserContext.mParserCommand; - mTokenizer = aTokenizer; - -#ifdef DUMP_TO_FILE - if (gDumpFile) { - - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, ""); - fprintf(gDumpFile, "Source of: "); - fputs(NS_ConvertUTF16toUTF8(mFilename).get(), gDumpFile); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "
\n");
-    }
-#endif //DUMP_TO_FILE
-  }
-
-
-  if(eViewSource!=aParserContext.mParserCommand)
-    mDocType=ePlainText;
-  else mDocType=aParserContext.mDocType;
-
-  mLineNumber = 1;
-
-  return result;
-}
-
-/**
-  * The parser uses a code sandwich to wrap the parsing process. Before
-  * the process begins, WillBuildModel() is called. Afterwards the parser
-  * calls DidBuildModel().
-  * @update gess5/18/98
-  * @param  aFilename is the name of the file being parsed.
-  * @return error code (almost always 0)
-  */
-NS_IMETHODIMP CViewSourceHTML::BuildModel(nsITokenizer* aTokenizer,
-                                          bool aCanInterrupt,
-                                          bool aCountLines,
-                                          const nsCString* aCharsetPtr)
-{
-  nsresult result=NS_OK;
-
-  if(aTokenizer) {
-
-    nsITokenizer*  oldTokenizer=mTokenizer;
-    mTokenizer=aTokenizer;
-    nsTokenAllocator* theAllocator=mTokenizer->GetTokenAllocator();
-
-    if(!mHasOpenRoot) {
-      // For the stack-allocated tokens below, it's safe to pass a null
-      // token allocator, because there are no attributes on the tokens.
-      CStartToken htmlToken(NS_LITERAL_STRING("HTML"), eHTMLTag_html);
-      nsCParserNode htmlNode(&htmlToken, 0/*stack token*/);
-      mSink->OpenContainer(htmlNode);
-
-      CStartToken headToken(NS_LITERAL_STRING("HEAD"), eHTMLTag_head);
-      nsCParserNode headNode(&headToken, 0/*stack token*/);
-      mSink->OpenContainer(headNode);
-
-      CStartToken titleToken(NS_LITERAL_STRING("TITLE"), eHTMLTag_title);
-      nsCParserNode titleNode(&titleToken, 0/*stack token*/);
-      mSink->OpenContainer(titleNode);
-
-      // Note that XUL will automatically add the prefix "Source of: "
-      if (StringBeginsWith(mFilename, NS_LITERAL_STRING("data:")) &&
-          mFilename.Length() > 50) {
-        nsAutoString dataFilename(Substring(mFilename, 0, 50));
-        dataFilename.AppendLiteral("...");
-        CTextToken titleText(dataFilename);
-        nsCParserNode titleTextNode(&titleText, 0/*stack token*/);
-        mSink->AddLeaf(titleTextNode);
-      } else {
-        CTextToken titleText(mFilename);
-        nsCParserNode titleTextNode(&titleText, 0/*stack token*/);
-        mSink->AddLeaf(titleTextNode);
-      }
-
-      mSink->CloseContainer(eHTMLTag_title);
-
-      if (theAllocator) {
-        CStartToken* theToken=
-          static_cast
-                     (theAllocator->CreateTokenOfType(eToken_start,
-                                                         eHTMLTag_link,
-                                                         NS_LITERAL_STRING("LINK")));
-        if (theToken) {
-          nsCParserStartNode theNode(theToken, theAllocator);
-
-          AddAttrToNode(theNode, theAllocator,
-                        NS_LITERAL_STRING("rel"),
-                        NS_LITERAL_STRING("stylesheet"));
-
-          AddAttrToNode(theNode, theAllocator,
-                        NS_LITERAL_STRING("type"),
-                        NS_LITERAL_STRING("text/css"));
-
-          AddAttrToNode(theNode, theAllocator,
-                        NS_LITERAL_STRING("href"),
-                        NS_LITERAL_STRING("resource://gre-resources/viewsource.css"));
-
-          mSink->AddLeaf(theNode);
-        }
-        IF_FREE(theToken, theAllocator);
-      }
-
-      result = mSink->CloseContainer(eHTMLTag_head);
-      if(NS_SUCCEEDED(result)) {
-        mHasOpenRoot = true;
-      }
-    }
-    if (NS_SUCCEEDED(result) && !mHasOpenBody) {
-      if (theAllocator) {
-        CStartToken* bodyToken=
-          static_cast
-                     (theAllocator->CreateTokenOfType(eToken_start,
-                                                         eHTMLTag_body,
-                                                         NS_LITERAL_STRING("BODY")));
-        if (bodyToken) {
-          nsCParserStartNode bodyNode(bodyToken, theAllocator);
-
-          AddAttrToNode(bodyNode, theAllocator,
-                        NS_LITERAL_STRING("id"),
-                        NS_ConvertASCIItoUTF16(kBodyId));
-
-          if (mWrapLongLines) {
-            AddAttrToNode(bodyNode, theAllocator,
-                          NS_LITERAL_STRING("class"),
-                          NS_ConvertASCIItoUTF16(kBodyClassWrap));
-          }
-          if (mTabSize >= 0) {
-            nsAutoString styleValue = NS_ConvertASCIItoUTF16(kBodyTabSize);
-            styleValue.AppendInt(mTabSize);
-            AddAttrToNode(bodyNode, theAllocator,
-                          NS_LITERAL_STRING("style"),
-                          styleValue);
-          }
-          result = mSink->OpenContainer(bodyNode);
-          if(NS_SUCCEEDED(result)) mHasOpenBody=true;
-        }
-        IF_FREE(bodyToken, theAllocator);
-
-        if (NS_SUCCEEDED(result)) {
-          CStartToken* preToken =
-            static_cast
-                       (theAllocator->CreateTokenOfType(eToken_start,
-                                                           eHTMLTag_pre,
-                                                           NS_LITERAL_STRING("PRE")));
-          if (preToken) {
-            nsCParserStartNode preNode(preToken, theAllocator);
-            AddAttrToNode(preNode, theAllocator,
-                          NS_LITERAL_STRING("id"),
-                          NS_LITERAL_STRING("line1"));
-            result = mSink->OpenContainer(preNode);
-          } else {
-            result = NS_ERROR_OUT_OF_MEMORY;
-          }
-          IF_FREE(preToken, theAllocator);
-        }
-      }
-    }
-
-    NS_ASSERTION(aCharsetPtr, "CViewSourceHTML::BuildModel expects a charset!");
-    mCharset = *aCharsetPtr;
-
-    NS_ASSERTION(aCanInterrupt, "CViewSourceHTML can't run scripts, so "
-                 "document.write should not forbid interruptions. Why is "
-                 "the parser telling us not to interrupt?");
-
-    while(NS_SUCCEEDED(result)){
-      CToken* theToken=mTokenizer->PopToken();
-      if(theToken) {
-        result=HandleToken(theToken);
-        if(NS_SUCCEEDED(result)) {
-          IF_FREE(theToken, mTokenizer->GetTokenAllocator());
-          if (mSink->DidProcessAToken() == NS_ERROR_HTMLPARSER_INTERRUPTED) {
-            result = NS_ERROR_HTMLPARSER_INTERRUPTED;
-            break;
-          }
-        } else {
-          mTokenizer->PushTokenFront(theToken);
-        }
-      }
-      else break;
-    }//while
-
-    mTokenizer=oldTokenizer;
-  }
-  else result=NS_ERROR_HTMLPARSER_BADTOKENIZER;
-  return result;
-}
-
-/**
- * Call this to start a new PRE block.  See bug 86355 for why this
- * makes some pages much faster.
- */
-void CViewSourceHTML::StartNewPreBlock(void){
-  CEndToken endToken(eHTMLTag_pre);
-  nsCParserNode endNode(&endToken, 0/*stack token*/);
-  mSink->CloseContainer(eHTMLTag_pre);
-
-  nsTokenAllocator* theAllocator = mTokenizer->GetTokenAllocator();
-  if (!theAllocator) {
-    return;
-  }
-
-  CStartToken* theToken =
-    static_cast
-               (theAllocator->CreateTokenOfType(eToken_start,
-                                                   eHTMLTag_pre,
-                                                   NS_LITERAL_STRING("PRE")));
-  if (!theToken) {
-    return;
-  }
-
-  nsCParserStartNode startNode(theToken, theAllocator);
-  AddAttrToNode(startNode, theAllocator,
-                NS_LITERAL_STRING("id"),
-                NS_ConvertASCIItoUTF16(nsPrintfCString("line%d", mLineNumber)));
-  mSink->OpenContainer(startNode);
-  IF_FREE(theToken, theAllocator);
-
-#ifdef DUMP_TO_FILE
-  if (gDumpFile) {
-    fprintf(gDumpFile, "
\n"); - fprintf(gDumpFile, "
\n", mLineNumber);
-  }
-#endif // DUMP_TO_FILE
-
-  mTokenCount = 0;
-}
-
-void CViewSourceHTML::AddAttrToNode(nsCParserStartNode& aNode,
-                                    nsTokenAllocator* aAllocator,
-                                    const nsAString& aAttrName,
-                                    const nsAString& aAttrValue)
-{
-  NS_PRECONDITION(aAllocator, "Must have a token allocator!");
-
-  CAttributeToken* theAttr =
-    (CAttributeToken*) aAllocator->CreateTokenOfType(eToken_attribute,
-                                                     eHTMLTag_unknown,
-                                                     aAttrValue);
-  if (!theAttr) {
-    NS_ERROR("Failed to allocate attribute token");
-    return;
-  }
-
-  theAttr->SetKey(aAttrName);
-  aNode.AddAttribute(theAttr);
-
-  // Parser nodes assume that they are being handed a ref when AddAttribute is
-  // called, unlike Init() and construction, when they actually addref the
-  // incoming token.  Do NOT release here unless this setup changes.
-}
-
-/**
- *
- * @update  gess5/18/98
- * @param
- * @return
- */
-NS_IMETHODIMP CViewSourceHTML::DidBuildModel(nsresult anErrorCode)
-{
-  nsresult result= NS_OK;
-
-  //ADD CODE HERE TO CLOSE OPEN CONTAINERS...
-
-  if (mSink) {
-      //now let's close automatically auto-opened containers...
-
-#ifdef DUMP_TO_FILE
-    if(gDumpFile) {
-      fprintf(gDumpFile, "
\n"); - fprintf(gDumpFile, "\n"); - fprintf(gDumpFile, "\n"); - fclose(gDumpFile); - } -#endif // DUMP_TO_FILE - - if(ePlainText!=mDocType) { - mSink->CloseContainer(eHTMLTag_pre); - mSink->CloseContainer(eHTMLTag_body); - mSink->CloseContainer(eHTMLTag_html); - } - } - -#ifdef RAPTOR_PERF_METRICS - NS_STOP_STOPWATCH(vsTimer); - printf("viewsource timer: "); - vsTimer.Print(); - printf("\n"); -#endif - - return result; -} - -/** - * Use this id you want to stop the building content model - * --------------[ Sets DTD to STOP mode ]---------------- - * It's recommended to use this method in accordance with - * the parser's terminate() method. - * - * @update harishd 07/22/99 - * @param - * @return - */ -NS_IMETHODIMP_(void) -CViewSourceHTML::Terminate() { -} - -NS_IMETHODIMP_(PRInt32) -CViewSourceHTML::GetType() { - return NS_IPARSER_FLAG_HTML; -} - -NS_IMETHODIMP_(nsDTDMode) -CViewSourceHTML::GetMode() const -{ - // Quirks mode needn't affect how the source is viewed, so parse the source - // view in full standards mode no matter what: - return eDTDMode_full_standards; -} - -/** - * Called by the parser to enable/disable dtd verification of the - * internal context stack. - * @update gess 7/23/98 - * @param - * @return - */ -void CViewSourceHTML::SetVerification(bool aEnabled) -{ -} - -/** - * This method is called to determine whether or not a tag - * of one type can contain a tag of another type. - * - * @update gess 3/25/98 - * @param aParent -- int tag of parent container - * @param aChild -- int tag of child container - * @return true if parent can contain child - */ -NS_IMETHODIMP_(bool) -CViewSourceHTML::CanContain(PRInt32 aParent, PRInt32 aChild) const -{ - bool result=true; - return result; -} - -/** - * This method gets called to determine whether a given - * tag is itself a container - * - * @update gess 3/25/98 - * @param aTag -- tag to test for containership - * @return true if given tag can contain other tags - */ -NS_IMETHODIMP_(bool) -CViewSourceHTML::IsContainer(PRInt32 aTag) const -{ - bool result=true; - return result; -} - -/** - * This method gets called when a tag needs to write it's attributes - * - * @update gess 3/25/98 - * @param - * @return result status - */ -nsresult CViewSourceHTML::WriteAttributes(const nsAString& tagName, - nsTokenAllocator* allocator, - PRInt32 attrCount, bool aOwnerInError) { - nsresult result=NS_OK; - - if(attrCount){ //go collect the attributes... - int attr = 0; - for(attr = 0; attr < attrCount; ++attr){ - CToken* theToken = mTokenizer->PeekToken(); - if(theToken) { - eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType()); - if(eToken_attribute == theType){ - mTokenizer->PopToken(); //pop it for real... - mTokenNode.AddAttribute(theToken); //and add it to the node. - - CAttributeToken* theAttrToken = (CAttributeToken*)theToken; - const nsSubstring& theKey = theAttrToken->GetKey(); - - // The attribute is only in error if its owner is NOT in error. - const bool attributeInError = - !aOwnerInError && theAttrToken->IsInError(); - - result = WriteTag(kAttributeName,theKey,0,attributeInError); - const nsSubstring& theValue = theAttrToken->GetValue(); - - if(!theValue.IsEmpty() || theAttrToken->mHasEqualWithoutValue){ - if (IsUrlAttribute(tagName, theKey, theValue)) { - WriteHrefAttribute(allocator, theValue); - } else { - WriteTag(kAttributeValue,theValue,0,attributeInError); - } - } - } - } - else return kEOF; - } - } - - return result; -} - -/** - * This method gets called when a tag needs to be sent out - * - * @update gess 3/25/98 - * @param - * @return result status - */ -nsresult CViewSourceHTML::WriteTag(PRInt32 aTagType,const nsSubstring & aText,PRInt32 attrCount,bool aTagInError) { - nsresult result=NS_OK; - - // adjust line number to what it will be after we finish writing this tag - // XXXbz life here sucks. We can't use the GetNewlineCount on the token, - // because Text tokens in