diff --git a/addon-sdk/Makefile.in b/addon-sdk/Makefile.in deleted file mode 100644 index 4ef3e5a73c..0000000000 --- a/addon-sdk/Makefile.in +++ /dev/null @@ -1,29 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -TESTADDONS = source/test/addons -ADDONSRC = $(srcdir)/$(TESTADDONS) - -include $(topsrcdir)/config/rules.mk - -# This can switch to just zipping the files when native jetpacks land -%.xpi: FORCE - $(PYTHON) $(srcdir)/source/bin/cfx xpi --no-strip-xpi --pkgdir=$(ADDONSRC)/$* --output-file=$@ - -TEST_FILES = \ - $(srcdir)/source/app-extension \ - $(srcdir)/source/bin \ - $(srcdir)/source/python-lib \ - $(srcdir)/source/test \ - $(srcdir)/source/package.json \ - $(srcdir)/source/mapping.json \ - $(NULL) - -# Remove this once the test harness uses the APIs built into Firefox -TEST_FILES += $(srcdir)/source/lib - -PKG_STAGE = $(DIST)/test-stage - -stage-tests-package:: $(TEST_FILES) - $(INSTALL) $^ $(PKG_STAGE)/jetpack diff --git a/addon-sdk/mach_commands.py b/addon-sdk/mach_commands.py deleted file mode 100644 index 36a8a24ba9..0000000000 --- a/addon-sdk/mach_commands.py +++ /dev/null @@ -1,107 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Integrates the xpcshell test runner with mach. - -from __future__ import absolute_import - -import os - -import mozpack.path as mozpath - -from mozbuild.base import ( - MachCommandBase, -) - -from mach.decorators import ( - CommandArgument, - CommandProvider, - Command, -) - -@CommandProvider -class MachCommands(MachCommandBase): - @Command('generate-addon-sdk-moz-build', category='misc', - description='Generates the moz.build file for the addon-sdk/ directory.') - def run_addon_sdk_moz_build(self, **params): - addon_sdk_dir = mozpath.join(self.topsrcdir, 'addon-sdk') - js_src_dir = mozpath.join(addon_sdk_dir, 'source/lib') - dirs_to_files = {} - - for path, dirs, files in os.walk(js_src_dir): - js_files = [f for f in files if f.endswith(('.js', '.jsm', '.html'))] - if not js_files: - continue - - relative = mozpath.relpath(path, js_src_dir) - dirs_to_files[relative] = js_files - - moz_build = """# AUTOMATICALLY GENERATED FROM mozbuild.template AND mach. DO NOT EDIT. -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -%(moz-build-template)s -if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk": -%(non-b2g-modules)s -%(always-on-modules)s""" - - non_b2g_paths = [ - 'method/test', - 'sdk/ui', - 'sdk/ui/button', - 'sdk/ui/sidebar', - 'sdk/places', - 'sdk/places/host', - 'sdk/tabs', - 'sdk/panel', - 'sdk/frame', - 'sdk/test', - 'sdk/window', - 'sdk/windows', - 'sdk/deprecated', - ] - - non_b2g_modules = [] - always_on_modules = [] - - for d, files in sorted(dirs_to_files.items()): - if d in non_b2g_paths: - non_b2g_modules.append((d, files)) - else: - always_on_modules.append((d, files)) - - def list_to_js_modules(l, indent=''): - js_modules = [] - for d, files in l: - if d == '': - module_path = '' - dir_path = '' - else: - # Ensure that we don't have things like: - # EXTRA_JS_MODULES.commonjs.sdk.private-browsing - # which would be a Python syntax error. - path = d.split('/') - module_path = ''.join('.' + p if p.find('-') == -1 else "['%s']" % p for p in path) - dir_path = d + '/' - filelist = ["'source/lib/%s%s'" % (dir_path, f) - for f in sorted(files, key=lambda x: x.lower())] - js_modules.append("EXTRA_JS_MODULES.commonjs%s += [\n %s,\n]\n" - % (module_path, ',\n '.join(filelist))) - stringified = '\n'.join(js_modules) - # This isn't the same thing as |js_modules|, since |js_modules| had - # embedded newlines. - lines = stringified.split('\n') - # Indent lines while avoiding trailing whitespace. - lines = [indent + line if line else line for line in lines] - return '\n'.join(lines) - - moz_build_output = mozpath.join(addon_sdk_dir, 'moz.build') - moz_build_template = mozpath.join(addon_sdk_dir, 'mozbuild.template') - with open(moz_build_output, 'w') as f, open(moz_build_template, 'r') as t: - substs = { 'moz-build-template': t.read(), - 'non-b2g-modules': list_to_js_modules(non_b2g_modules, - indent=' '), - 'always-on-modules': list_to_js_modules(always_on_modules) } - f.write(moz_build % substs) diff --git a/addon-sdk/moz.build b/addon-sdk/moz.build deleted file mode 100644 index 8be23f1edf..0000000000 --- a/addon-sdk/moz.build +++ /dev/null @@ -1,542 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Makefile.in uses a misc target through test_addons_TARGET. -HAS_MISC_RULE = True - -BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] -JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini', - 'source/test/leak/jetpack-package.ini'] -JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini'] - -DIRS += ['source/test/fixtures'] - -addons = [ - 'addon-manager', - 'author-email', - 'child_process', - 'chrome', - 'content-permissions', - 'content-script-messages-latency', - 'contributors', - 'curly-id', - 'developers', - 'e10s-content', - 'e10s-l10n', - 'e10s-remote', - 'e10s-tabs', - 'e10s', - 'embedded-webextension', - 'l10n-properties', - 'l10n', - 'layout-change', - 'main', - 'name-in-numbers-plus', - 'name-in-numbers', - 'packaging', - 'packed', - 'page-mod-debugger-post', - 'page-mod-debugger-pre', - 'page-worker', - 'places', - 'predefined-id-with-at', - 'preferences-branch', - 'private-browsing-supported', - 'remote', - 'require', - 'self', - 'simple-prefs-l10n', - 'simple-prefs-regression', - 'simple-prefs', - 'standard-id', - 'tab-close-on-startup', - 'toolkit-require-reload', - 'translators', - 'unsafe-content-script', -] - -addons = ['%s.xpi' % f for f in addons] -GENERATED_FILES += addons - -TEST_HARNESS_FILES.testing.mochitest['jetpack-addon']['addon-sdk'].source.test.addons += [ - '!%s' % f for f in addons -] - -EXTRA_JS_MODULES.sdk += [ - 'source/app-extension/bootstrap.js', -] - -EXTRA_JS_MODULES.sdk.system += [ - 'source/modules/system/Startup.js', -] - -if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk": - EXTRA_JS_MODULES.commonjs.method.test += [ - 'source/lib/method/test/browser.js', - 'source/lib/method/test/common.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.deprecated += [ - 'source/lib/sdk/deprecated/api-utils.js', - 'source/lib/sdk/deprecated/sync-worker.js', - 'source/lib/sdk/deprecated/unit-test-finder.js', - 'source/lib/sdk/deprecated/unit-test.js', - 'source/lib/sdk/deprecated/window-utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.frame += [ - 'source/lib/sdk/frame/hidden-frame.js', - 'source/lib/sdk/frame/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.panel += [ - 'source/lib/sdk/panel/events.js', - 'source/lib/sdk/panel/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.places += [ - 'source/lib/sdk/places/bookmarks.js', - 'source/lib/sdk/places/contract.js', - 'source/lib/sdk/places/events.js', - 'source/lib/sdk/places/favicon.js', - 'source/lib/sdk/places/history.js', - 'source/lib/sdk/places/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.places.host += [ - 'source/lib/sdk/places/host/host-bookmarks.js', - 'source/lib/sdk/places/host/host-query.js', - 'source/lib/sdk/places/host/host-tags.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.tabs += [ - 'source/lib/sdk/tabs/common.js', - 'source/lib/sdk/tabs/events.js', - 'source/lib/sdk/tabs/helpers.js', - 'source/lib/sdk/tabs/namespace.js', - 'source/lib/sdk/tabs/observer.js', - 'source/lib/sdk/tabs/tab-fennec.js', - 'source/lib/sdk/tabs/tab-firefox.js', - 'source/lib/sdk/tabs/tab.js', - 'source/lib/sdk/tabs/tabs-firefox.js', - 'source/lib/sdk/tabs/utils.js', - 'source/lib/sdk/tabs/worker.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.test += [ - 'source/lib/sdk/test/assert.js', - 'source/lib/sdk/test/harness.js', - 'source/lib/sdk/test/httpd.js', - 'source/lib/sdk/test/loader.js', - 'source/lib/sdk/test/memory.js', - 'source/lib/sdk/test/options.js', - 'source/lib/sdk/test/runner.js', - 'source/lib/sdk/test/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.ui += [ - 'source/lib/sdk/ui/component.js', - 'source/lib/sdk/ui/frame.js', - 'source/lib/sdk/ui/id.js', - 'source/lib/sdk/ui/sidebar.js', - 'source/lib/sdk/ui/state.js', - 'source/lib/sdk/ui/toolbar.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.ui.button += [ - 'source/lib/sdk/ui/button/action.js', - 'source/lib/sdk/ui/button/contract.js', - 'source/lib/sdk/ui/button/toggle.js', - 'source/lib/sdk/ui/button/view.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.ui.sidebar += [ - 'source/lib/sdk/ui/sidebar/actions.js', - 'source/lib/sdk/ui/sidebar/contract.js', - 'source/lib/sdk/ui/sidebar/namespace.js', - 'source/lib/sdk/ui/sidebar/utils.js', - 'source/lib/sdk/ui/sidebar/view.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.window += [ - 'source/lib/sdk/window/browser.js', - 'source/lib/sdk/window/events.js', - 'source/lib/sdk/window/helpers.js', - 'source/lib/sdk/window/namespace.js', - 'source/lib/sdk/window/utils.js', - ] - - EXTRA_JS_MODULES.commonjs.sdk.windows += [ - 'source/lib/sdk/windows/fennec.js', - 'source/lib/sdk/windows/firefox.js', - 'source/lib/sdk/windows/observer.js', - 'source/lib/sdk/windows/tabs-fennec.js', - ] - -EXTRA_JS_MODULES.commonjs += [ - 'source/lib/index.js', - 'source/lib/test.js', -] - -EXTRA_JS_MODULES.commonjs.sdk += [ - 'source/lib/sdk/webextension.js', -] - -EXTRA_JS_MODULES.commonjs.dev += [ - 'source/lib/dev/debuggee.js', - 'source/lib/dev/frame-script.js', - 'source/lib/dev/panel.js', - 'source/lib/dev/ports.js', - 'source/lib/dev/theme.js', - 'source/lib/dev/toolbox.js', - 'source/lib/dev/utils.js', - 'source/lib/dev/volcan.js', -] - -EXTRA_JS_MODULES.commonjs.dev.panel += [ - 'source/lib/dev/panel/view.js', -] - -EXTRA_JS_MODULES.commonjs.dev.theme += [ - 'source/lib/dev/theme/hooks.js', -] - -EXTRA_JS_MODULES.commonjs.diffpatcher += [ - 'source/lib/diffpatcher/diff.js', - 'source/lib/diffpatcher/index.js', - 'source/lib/diffpatcher/patch.js', - 'source/lib/diffpatcher/rebase.js', -] - -EXTRA_JS_MODULES.commonjs.diffpatcher.test += [ - 'source/lib/diffpatcher/test/common.js', - 'source/lib/diffpatcher/test/diff.js', - 'source/lib/diffpatcher/test/index.js', - 'source/lib/diffpatcher/test/patch.js', - 'source/lib/diffpatcher/test/tap.js', -] - -EXTRA_JS_MODULES.commonjs.framescript += [ - 'source/lib/framescript/content.jsm', - 'source/lib/framescript/context-menu.js', - 'source/lib/framescript/FrameScriptManager.jsm', - 'source/lib/framescript/manager.js', - 'source/lib/framescript/util.js', -] - -EXTRA_JS_MODULES.commonjs['jetpack-id'] += [ - 'source/lib/jetpack-id/index.js', -] - -EXTRA_JS_MODULES.commonjs.method += [ - 'source/lib/method/core.js', -] - -EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'] += [ - 'source/lib/mozilla-toolkit-versioning/index.js', -] - -EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'].lib += [ - 'source/lib/mozilla-toolkit-versioning/lib/utils.js', -] - -EXTRA_JS_MODULES.commonjs.node += [ - 'source/lib/node/os.js', -] - -EXTRA_JS_MODULES.commonjs.sdk += [ - 'source/lib/sdk/base64.js', - 'source/lib/sdk/clipboard.js', - 'source/lib/sdk/context-menu.js', - 'source/lib/sdk/context-menu@2.js', - 'source/lib/sdk/hotkeys.js', - 'source/lib/sdk/indexed-db.js', - 'source/lib/sdk/l10n.js', - 'source/lib/sdk/messaging.js', - 'source/lib/sdk/notifications.js', - 'source/lib/sdk/page-mod.js', - 'source/lib/sdk/page-worker.js', - 'source/lib/sdk/panel.js', - 'source/lib/sdk/passwords.js', - 'source/lib/sdk/private-browsing.js', - 'source/lib/sdk/querystring.js', - 'source/lib/sdk/request.js', - 'source/lib/sdk/selection.js', - 'source/lib/sdk/self.js', - 'source/lib/sdk/simple-prefs.js', - 'source/lib/sdk/simple-storage.js', - 'source/lib/sdk/system.js', - 'source/lib/sdk/tabs.js', - 'source/lib/sdk/test.js', - 'source/lib/sdk/timers.js', - 'source/lib/sdk/ui.js', - 'source/lib/sdk/url.js', - 'source/lib/sdk/windows.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.addon += [ - 'source/lib/sdk/addon/bootstrap.js', - 'source/lib/sdk/addon/events.js', - 'source/lib/sdk/addon/host.js', - 'source/lib/sdk/addon/installer.js', - 'source/lib/sdk/addon/manager.js', - 'source/lib/sdk/addon/runner.js', - 'source/lib/sdk/addon/window.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.browser += [ - 'source/lib/sdk/browser/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.console += [ - 'source/lib/sdk/console/plain-text.js', - 'source/lib/sdk/console/traceback.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.content += [ - 'source/lib/sdk/content/content-worker.js', - 'source/lib/sdk/content/content.js', - 'source/lib/sdk/content/context-menu.js', - 'source/lib/sdk/content/events.js', - 'source/lib/sdk/content/l10n-html.js', - 'source/lib/sdk/content/loader.js', - 'source/lib/sdk/content/mod.js', - 'source/lib/sdk/content/page-mod.js', - 'source/lib/sdk/content/page-worker.js', - 'source/lib/sdk/content/sandbox.js', - 'source/lib/sdk/content/tab-events.js', - 'source/lib/sdk/content/thumbnail.js', - 'source/lib/sdk/content/utils.js', - 'source/lib/sdk/content/worker-child.js', - 'source/lib/sdk/content/worker.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.content.sandbox += [ - 'source/lib/sdk/content/sandbox/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk['context-menu'] += [ - 'source/lib/sdk/context-menu/context.js', - 'source/lib/sdk/context-menu/core.js', - 'source/lib/sdk/context-menu/readers.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.core += [ - 'source/lib/sdk/core/disposable.js', - 'source/lib/sdk/core/heritage.js', - 'source/lib/sdk/core/namespace.js', - 'source/lib/sdk/core/observer.js', - 'source/lib/sdk/core/promise.js', - 'source/lib/sdk/core/reference.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.deprecated.events += [ - 'source/lib/sdk/deprecated/events/assembler.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.dom += [ - 'source/lib/sdk/dom/events-shimmed.js', - 'source/lib/sdk/dom/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.dom.events += [ - 'source/lib/sdk/dom/events/keys.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.event += [ - 'source/lib/sdk/event/chrome.js', - 'source/lib/sdk/event/core.js', - 'source/lib/sdk/event/dom.js', - 'source/lib/sdk/event/target.js', - 'source/lib/sdk/event/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.fs += [ - 'source/lib/sdk/fs/path.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.input += [ - 'source/lib/sdk/input/browser.js', - 'source/lib/sdk/input/customizable-ui.js', - 'source/lib/sdk/input/frame.js', - 'source/lib/sdk/input/system.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.io += [ - 'source/lib/sdk/io/buffer.js', - 'source/lib/sdk/io/byte-streams.js', - 'source/lib/sdk/io/file.js', - 'source/lib/sdk/io/fs.js', - 'source/lib/sdk/io/stream.js', - 'source/lib/sdk/io/text-streams.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.keyboard += [ - 'source/lib/sdk/keyboard/hotkeys.js', - 'source/lib/sdk/keyboard/observer.js', - 'source/lib/sdk/keyboard/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.l10n += [ - 'source/lib/sdk/l10n/core.js', - 'source/lib/sdk/l10n/html.js', - 'source/lib/sdk/l10n/loader.js', - 'source/lib/sdk/l10n/locale.js', - 'source/lib/sdk/l10n/plural-rules.js', - 'source/lib/sdk/l10n/prefs.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.l10n.json += [ - 'source/lib/sdk/l10n/json/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.l10n.properties += [ - 'source/lib/sdk/l10n/properties/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.lang += [ - 'source/lib/sdk/lang/functional.js', - 'source/lib/sdk/lang/type.js', - 'source/lib/sdk/lang/weak-set.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.lang.functional += [ - 'source/lib/sdk/lang/functional/concurrent.js', - 'source/lib/sdk/lang/functional/core.js', - 'source/lib/sdk/lang/functional/helpers.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.loader += [ - 'source/lib/sdk/loader/cuddlefish.js', - 'source/lib/sdk/loader/sandbox.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.model += [ - 'source/lib/sdk/model/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.net += [ - 'source/lib/sdk/net/url.js', - 'source/lib/sdk/net/xhr.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.output += [ - 'source/lib/sdk/output/system.js', -] - -EXTRA_JS_MODULES.commonjs.sdk['page-mod'] += [ - 'source/lib/sdk/page-mod/match-pattern.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.passwords += [ - 'source/lib/sdk/passwords/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.platform += [ - 'source/lib/sdk/platform/xpcom.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.preferences += [ - 'source/lib/sdk/preferences/event-target.js', - 'source/lib/sdk/preferences/native-options.js', - 'source/lib/sdk/preferences/service.js', - 'source/lib/sdk/preferences/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk['private-browsing'] += [ - 'source/lib/sdk/private-browsing/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.remote += [ - 'source/lib/sdk/remote/child.js', - 'source/lib/sdk/remote/core.js', - 'source/lib/sdk/remote/parent.js', - 'source/lib/sdk/remote/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.stylesheet += [ - 'source/lib/sdk/stylesheet/style.js', - 'source/lib/sdk/stylesheet/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.system += [ - 'source/lib/sdk/system/child_process.js', - 'source/lib/sdk/system/environment.js', - 'source/lib/sdk/system/events-shimmed.js', - 'source/lib/sdk/system/events.js', - 'source/lib/sdk/system/globals.js', - 'source/lib/sdk/system/process.js', - 'source/lib/sdk/system/runtime.js', - 'source/lib/sdk/system/unload.js', - 'source/lib/sdk/system/xul-app.js', - 'source/lib/sdk/system/xul-app.jsm', -] - -EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [ - 'source/lib/sdk/system/child_process/subprocess.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.tab += [ - 'source/lib/sdk/tab/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.button.view += [ - 'source/lib/sdk/ui/button/view/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.frame += [ - 'source/lib/sdk/ui/frame/model.js', - 'source/lib/sdk/ui/frame/view.html', - 'source/lib/sdk/ui/frame/view.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.state += [ - 'source/lib/sdk/ui/state/events.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [ - 'source/lib/sdk/ui/toolbar/model.js', - 'source/lib/sdk/ui/toolbar/view.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.uri += [ - 'source/lib/sdk/uri/resource.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.url += [ - 'source/lib/sdk/url/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.util += [ - 'source/lib/sdk/util/array.js', - 'source/lib/sdk/util/collection.js', - 'source/lib/sdk/util/contract.js', - 'source/lib/sdk/util/deprecate.js', - 'source/lib/sdk/util/dispatcher.js', - 'source/lib/sdk/util/list.js', - 'source/lib/sdk/util/match-pattern.js', - 'source/lib/sdk/util/object.js', - 'source/lib/sdk/util/rules.js', - 'source/lib/sdk/util/sequence.js', - 'source/lib/sdk/util/uuid.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.view += [ - 'source/lib/sdk/view/core.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.worker += [ - 'source/lib/sdk/worker/utils.js', -] - -EXTRA_JS_MODULES.commonjs.sdk.zip += [ - 'source/lib/sdk/zip/utils.js', -] - -EXTRA_JS_MODULES.commonjs.toolkit += [ - 'source/lib/toolkit/loader.js', - 'source/lib/toolkit/require.js', -] diff --git a/addon-sdk/mozbuild.template b/addon-sdk/mozbuild.template deleted file mode 100644 index 56b377cbde..0000000000 --- a/addon-sdk/mozbuild.template +++ /dev/null @@ -1,20 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Makefile.in uses a misc target through test_addons_TARGET. -HAS_MISC_RULE = True - -BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] -JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini'] -JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini'] - -EXTRA_JS_MODULES.sdk += [ - 'source/app-extension/bootstrap.js', -] - -EXTRA_JS_MODULES.sdk.system += [ - 'source/modules/system/Startup.js', -] diff --git a/addon-sdk/source/.gitattributes b/addon-sdk/source/.gitattributes deleted file mode 100644 index 9006725a3f..0000000000 --- a/addon-sdk/source/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -.gitignore export-ignore -.hgignore export-ignore -.hgtags export-ignore -.gitattributes export-ignore -python-lib/cuddlefish/_version.py export-subst diff --git a/addon-sdk/source/.gitignore b/addon-sdk/source/.gitignore deleted file mode 100644 index 80d235bd11..0000000000 --- a/addon-sdk/source/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -local.json -python-lib/cuddlefish/app-extension/components/jetpack.xpt -testdocs.tgz -jetpack-sdk-docs.tgz -.test_tmp/ -doc/dev-guide/ -doc/index.html -doc/modules/ -doc/status.md5 -packages/* -node_modules -cache - -# Python -*.pyc - -# OSX -*.DS_Store - -# Windows -*Thumbs.db - -# Ignore subtrees - -# git@github.com:jsantell/jetpack-id.git -lib/jetpack-id/* -!lib/jetpack-id/index.js -!lib/jetpack-id/package.json - -# git@github.com:jsantell/mozilla-toolkit-versioning.git -lib/mozilla-toolkit-versioning/* -!lib/mozilla-toolkit-versioning/index.js -!lib/mozilla-toolkit-versioning/lib -lib/mozilla-toolkit-versioning/lib/* -!lib/mozilla-toolkit-versioning/lib/*.js -!lib/mozilla-toolkit-versioning/package.json diff --git a/addon-sdk/source/.jpmignore b/addon-sdk/source/.jpmignore deleted file mode 100644 index 5c22f9c45f..0000000000 --- a/addon-sdk/source/.jpmignore +++ /dev/null @@ -1,18 +0,0 @@ -local.json -mapping.json -CONTRIBUTING.md -@addon-sdk.xpi -.* -app-extension/ -bin/ -modules/ -node_modules/ -examples/ -cache/ - -# Python -python-lib/ -*.pyc - -# Windows -*Thumbs.db diff --git a/addon-sdk/source/.travis.yml b/addon-sdk/source/.travis.yml deleted file mode 100644 index 287b62a4f8..0000000000 --- a/addon-sdk/source/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -sudo: false -language: node_js -node_js: - - "0.12" - -env: - - JPM_FX_DEBUG=0 - - JPM_FX_DEBUG=1 - -notifications: - irc: "irc.mozilla.org#jetpack" - -cache: - directories: - - cache - -before_install: - - "export DISPLAY=:99.0" - - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 -extension RANDR" - -before_script: - - npm install fx-download -g - - npm install gulp -g - - bash bin/fx-download.sh - - export JPM_FIREFOX_BINARY=$TRAVIS_BUILD_DIR/../firefox/firefox - - cd $TRAVIS_BUILD_DIR diff --git a/addon-sdk/source/CONTRIBUTING.md b/addon-sdk/source/CONTRIBUTING.md deleted file mode 100644 index 059df64c45..0000000000 --- a/addon-sdk/source/CONTRIBUTING.md +++ /dev/null @@ -1,54 +0,0 @@ -## Overview - -- Changes should follow the [design guidelines], as well as the [coding style guide] -- All changes need tests -- In order to land, changes need a review by one of the Jetpack reviewers -- Changes may need an API review -- Changes may need a review from a Mozilla platform domain-expert - -If you have questions, ask in [#jetpack on IRC][jetpack irc channel] or on the [Jetpack mailing list]. - -## How to Make Code Contributions - -Follow the [standard mozilla contribution guidelines](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Introduction). All contributions and patches should be through Bugzilla. - -Pull requests on github are not accepted and new pull requests on github will be rejected. - -## Good First Bugs - -There is a list of [good first bugs here][good first bugs]. - -## Reviewers - -Changes should be reviewed by someone on the [add-on sdk review team](https://bugzilla.mozilla.org/page.cgi?id=review_suggestions.html#Add-on%20SDK) within Bugzilla. - -Others who might be able to help include: - -- [@mossop] -- [@gozala] -- [@ZER0] -- [@jsantell] -- [@zombie] - -For review of Mozilla platform usage and best practices, ask [@autonome], -[@0c0w3], or [@mossop] to find the domain expert. - -For API and developer ergonomics review, ask [@gozala]. - -[design guidelines]:https://wiki.mozilla.org/Labs/Jetpack/Design_Guidelines -[jetpack irc channel]:irc://irc.mozilla.org/#jetpack -[Jetpack mailing list]:http://groups.google.com/group/mozilla-labs-jetpack -[open bugs]:https://bugzilla.mozilla.org/buglist.cgi?quicksearch=product%3ASDK -[make bug]:https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK&component=general -[test intro]:https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Unit_testing -[test API]:https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/test_assert -[coding style guide]:https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide -[Add-on SDK repo]:https://github.com/mozilla/addon-sdk -[GitHub]:https://github.com/ -[good first bugs]:https://bugzilla.mozilla.org/buglist.cgi?list_id=7345714&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&query_based_on=jetpack-good-1st-bugs&status_whiteboard_type=allwordssubstr&query_format=advanced&status_whiteboard=[good%20first%20bug]&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&product=Add-on%20SDK&known_name=jetpack-good-1st-bugs - -[@mossop]:https://github.com/mossop/ -[@gozala]:https://github.com/Gozala/ -[@ZER0]:https://github.com/ZER0/ -[@jsantell]:https://github.com/jsantell -[@zombie]:https://github.com/zombie diff --git a/addon-sdk/source/LICENSE b/addon-sdk/source/LICENSE deleted file mode 100644 index 22e1dc915a..0000000000 --- a/addon-sdk/source/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -The files which make up the SDK are developed by Mozilla and licensed -under the MPL 2.0 (http://mozilla.org/MPL/2.0/), with the exception of the -components listed below, which are made available by their authors under -the licenses listed alongside. - -syntaxhighlighter ------------------- -doc/static-files/syntaxhighlighter -Made available under the MIT license. - -jQuery ------- -examples/reddit-panel/data/jquery-1.4.4.min.js -examples/annotator/data/jquery-1.4.2.min.js -Made available under the MIT license. - -simplejson ----------- -python-lib/simplejson -Made available under the MIT license. - -Python Markdown ---------------- -python-lib/markdown -Made available under the BSD license. - -LibraryDetector ---------------- -examples/library-detector/data/library-detector.js -Made available under the MIT license. diff --git a/addon-sdk/source/README.md b/addon-sdk/source/README.md deleted file mode 100644 index 4efa86dae4..0000000000 --- a/addon-sdk/source/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Mozilla Add-on SDK [![Build Status](https://travis-ci.org/mozilla/addon-sdk.png)](https://travis-ci.org/mozilla/addon-sdk) - -We suggest that developers of new add-ons [should look at using WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions). - -Using the Add-on SDK you can create Firefox add-ons using standard Web technologies: JavaScript, HTML, and CSS. The SDK includes JavaScript APIs which you can use to create add-ons, and tools for creating, running, testing, and packaging add-ons. - -If you find a problem, please [report the bug here](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK). - -## Developing Add-ons - -These resources offer some help: - -* [Add-on SDK Documentation](https://developer.mozilla.org/en-US/Add-ons/SDK) -* [Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules) -* [Jetpack FAQ](https://wiki.mozilla.org/Jetpack/FAQ) -* [StackOverflow Questions](http://stackoverflow.com/questions/tagged/firefox-addon-sdk) -* [Mailing List](https://wiki.mozilla.org/Jetpack#Mailing_list) -* #jetpack on irc.mozilla.org - -## Contributing Code - -Please read these two guides if you wish to make some patches to the addon-sdk: - -* [Contribute Guide](https://github.com/mozilla/addon-sdk/blob/master/CONTRIBUTING.md) -* [Style Guide](https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide) - -## Issues - -We use [bugzilla](https://bugzilla.mozilla.org/) as our issue tracker, here are some useful links: - -* [File a bug](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK) -* [Open bugs](https://bugzilla.mozilla.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&product=Add-on%20SDK&query_format=advanced&order=priority) -* [Good first bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+first+bug]&&resolution=---&product=Add-on+SDK) -* [Good next bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+next+bug]&&resolution=---&product=Add-on+SDK) diff --git a/addon-sdk/source/bin/activate b/addon-sdk/source/bin/activate deleted file mode 100644 index 0104f7d9d7..0000000000 --- a/addon-sdk/source/bin/activate +++ /dev/null @@ -1,84 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# This file must be used with "source bin/activate" *from bash* -# you cannot run it directly - -deactivate () { - if [ -n "$_OLD_VIRTUAL_PATH" ] ; then - PATH="$_OLD_VIRTUAL_PATH" - export PATH - unset _OLD_VIRTUAL_PATH - fi - - # This should detect bash and zsh, which have a hash command that must - # be called to get it to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected - if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then - hash -r - fi - - if [ -n "$_OLD_VIRTUAL_PS1" ] ; then - PS1="$_OLD_VIRTUAL_PS1" - export PS1 - unset _OLD_VIRTUAL_PS1 - fi - - PYTHONPATH="$_OLD_PYTHONPATH" - export PYTHONPATH - unset _OLD_PYTHONPATH - - unset CUDDLEFISH_ROOT - - unset VIRTUAL_ENV - if [ ! "$1" = "nondestructive" ] ; then - # Self destruct! - unset deactivate - fi -} - -# unset irrelavent variables -deactivate nondestructive - -_OLD_PYTHONPATH="$PYTHONPATH" -_OLD_VIRTUAL_PATH="$PATH" - -VIRTUAL_ENV="`pwd`" - -if [ "x$OSTYPE" = "xmsys" ] ; then - CUDDLEFISH_ROOT="`pwd -W | sed s,/,\\\\\\\\,g`" - PATH="`pwd`/bin:$PATH" - # msys will convert any env vars with PATH in it to use msys - # form and will unconvert before launching - PYTHONPATH="`pwd -W`/python-lib;$PYTHONPATH" -else - CUDDLEFISH_ROOT="$VIRTUAL_ENV" - PYTHONPATH="$VIRTUAL_ENV/python-lib:$PYTHONPATH" - PATH="$VIRTUAL_ENV/bin:$PATH" -fi - -VIRTUAL_ENV="`pwd`" - -export CUDDLEFISH_ROOT -export PYTHONPATH -export PATH - -_OLD_VIRTUAL_PS1="$PS1" -if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then - # special case for Aspen magic directories - # see http://www.zetadev.com/software/aspen/ - PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" -else - PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" -fi -export PS1 - -# This should detect bash and zsh, which have a hash command that must -# be called to get it to forget past commands. Without forgetting -# past commands the $PATH changes we made may not be respected -if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then - hash -r -fi - -python -c "from jetpack_sdk_env import welcome; welcome()" diff --git a/addon-sdk/source/bin/activate.bat b/addon-sdk/source/bin/activate.bat deleted file mode 100644 index 1f69014590..0000000000 --- a/addon-sdk/source/bin/activate.bat +++ /dev/null @@ -1,134 +0,0 @@ -@echo off -rem This Source Code Form is subject to the terms of the Mozilla Public -rem License, v. 2.0. If a copy of the MPL was not distributed with this -rem file, You can obtain one at http://mozilla.org/MPL/2.0/. - -set VIRTUAL_ENV=%~dp0 -set VIRTUAL_ENV=%VIRTUAL_ENV:~0,-5% -set CUDDLEFISH_ROOT=%VIRTUAL_ENV% - -SET PYTHONKEY=SOFTWARE\Python\PythonCore - -rem look for 32-bit windows and python, or 64-bit windows and python - -SET PYTHONVERSION=2.7 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.6 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.5 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -if not defined ProgramFiles(x86) goto win32 - -rem look for 32-bit python on 64-bit windows - -SET PYTHONKEY=SOFTWARE\Wow6432Node\Python\PythonCore - -SET PYTHONVERSION=2.7 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.6 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -SET PYTHONVERSION=2.5 -call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath -if "%PYTHONINSTALL%" NEQ "" goto FoundPython - -:win32 - -SET PYTHONVERSION= -set PYTHONKEY= -echo Warning: Failed to find Python installation directory -goto :EOF - -:FoundPython - -if defined _OLD_PYTHONPATH ( - set PYTHONPATH=%_OLD_PYTHONPATH% -) -if not defined PYTHONPATH ( - set PYTHONPATH=; -) -set _OLD_PYTHONPATH=%PYTHONPATH% -set PYTHONPATH=%VIRTUAL_ENV%\python-lib;%PYTHONPATH% - -if not defined PROMPT ( - set PROMPT=$P$G -) - -if defined _OLD_VIRTUAL_PROMPT ( - set PROMPT=%_OLD_VIRTUAL_PROMPT% -) - -set _OLD_VIRTUAL_PROMPT=%PROMPT% -set PROMPT=(%VIRTUAL_ENV%) %PROMPT% - -if defined _OLD_VIRTUAL_PATH goto OLDPATH -goto SKIPPATH -:OLDPATH -PATH %_OLD_VIRTUAL_PATH% - -:SKIPPATH -set _OLD_VIRTUAL_PATH=%PATH% -PATH %VIRTUAL_ENV%\bin;%PYTHONINSTALL%;%PATH% -set PYTHONKEY= -set PYTHONINSTALL= -set PYTHONVERSION= -set key= -set reg= -set _tokens= -python -c "from jetpack_sdk_env import welcome; welcome()" -GOTO :EOF - -:CheckPython -::CheckPython(retVal, key) -::Reads the registry at %2% and checks if a Python exists there. -::Checks both HKLM and HKCU, then checks the executable actually exists. -SET key=%2% -SET "%~1=" -SET reg=reg -if defined ProgramFiles(x86) ( - rem 32-bit cmd on 64-bit windows - if exist %WINDIR%\sysnative\reg.exe SET reg=%WINDIR%\sysnative\reg.exe -) -rem On Vista+, the last line of output is: -rem (default) REG_SZ the_value -rem (but note the word "default" will be localized. -rem On XP, the last line of output is: -rem \tREG_SZ\tthe_value -rem (not sure if "NO NAME" is localized or not!) -rem SO: we use ")>" as the tokens to split on, then nuke -rem the REG_SZ and any tabs or spaces. -FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKLM\%key% /ve 2^>NUL`) DO SET "%~1=%%A" -rem Remove the REG_SZ -set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=% -rem Remove tabs (note the literal \t in the next line -set PYTHONINSTALL=%PYTHONINSTALL: =% -rem Remove spaces. -set PYTHONINSTALL=%PYTHONINSTALL: =% -if exist %PYTHONINSTALL%\python.exe goto :EOF -rem It may be a 32bit Python directory built from source, in which case the -rem executable is in the PCBuild directory. -if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF) -rem Or maybe a 64bit build directory. -if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF) - -rem And try HKCU -FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKCU\%key% /ve 2^>NUL`) DO SET "%~1=%%A" -set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=% -set PYTHONINSTALL=%PYTHONINSTALL: =% -set PYTHONINSTALL=%PYTHONINSTALL: =% -if exist %PYTHONINSTALL%\python.exe goto :EOF -if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF) -if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF) -rem can't find it here, so arrange to try the next key -set PYTHONINSTALL= - -GOTO :EOF diff --git a/addon-sdk/source/bin/activate.fish b/addon-sdk/source/bin/activate.fish deleted file mode 100644 index 1f728b69be..0000000000 --- a/addon-sdk/source/bin/activate.fish +++ /dev/null @@ -1,66 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# This file must be used with "source bin/activate.fish" *from fish* -# you cannot run it directly - -# Much of this code is based off of the activate.fish file for the -# virtualenv project. http://ur1.ca/ehmd6 - -function deactivate -d "Exit addon-sdk and return to normal shell environment" - if test -n "$_OLD_VIRTUAL_PATH" - set -gx PATH $_OLD_VIRTUAL_PATH - set -e _OLD_VIRTUAL_PATH - end - - if test -n "$_OLD_PYTHONPATH" - set -gx PYTHONPATH $_OLD_PYTHONPATH - set -e _OLD_PYTHONPATH - end - - if test -n "$_OLD_FISH_PROMPT_OVERRIDE" - functions -e fish_prompt - set -e _OLD_FISH_PROMPT_OVERRIDE - . ( begin - printf "function fish_prompt\n\t#" - functions _old_fish_prompt - end | psub ) - - functions -e _old_fish_prompt - end - - set -e CUDDLEFISH_ROOT - set -e VIRTUAL_ENV - - if test "$argv[1]" != "nondestructive" - functions -e deactivate - end -end - -# unset irrelavent variables -deactivate nondestructive - -set -gx _OLD_PYTHONPATH $PYTHONPATH -set -gx _OLD_VIRTUAL_PATH $PATH -set -gx _OLD_FISH_PROMPT_OVERRIDE "true" - -set VIRTUAL_ENV (pwd) - -set -gx CUDDLEFISH_ROOT $VIRTUAL_ENV -set -gx PYTHONPATH "$VIRTUAL_ENV/python-lib" $PYTHONPATH -set -gx PATH "$VIRTUAL_ENV/bin" $PATH - -# save the current fish_prompt function as the function _old_fish_prompt -. ( begin - printf "function _old_fish_prompt\n\t#" - functions fish_prompt - end | psub ) - -# with the original prompt function renamed, we can override with our own. -function fish_prompt - printf "(%s)%s%s" (basename "$VIRTUAL_ENV") (set_color normal) (_old_fish_prompt) - return -end - -python -c "from jetpack_sdk_env import welcome; welcome()" diff --git a/addon-sdk/source/bin/activate.ps1 b/addon-sdk/source/bin/activate.ps1 deleted file mode 100644 index 5d939d4689..0000000000 --- a/addon-sdk/source/bin/activate.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -$Env:VIRTUAL_ENV = (gl); -$Env:CUDDLEFISH_ROOT = $Env:VIRTUAL_ENV; - -# http://stackoverflow.com/questions/5648931/powershell-test-if-registry-value-exists/5652674#5652674 -Function Test-RegistryValue { - param( - [Alias("PSPath")] - [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [String]$Path - , - [Parameter(Position = 1, Mandatory = $true)] - [String]$Name - , - [Switch]$PassThru - ) - - process { - if (Test-Path $Path) { - $Key = Get-Item -LiteralPath $Path - if ($Key.GetValue($Name, $null) -ne $null) { - if ($PassThru) { - Get-ItemProperty $Path $Name - } else { - $true - } - } else { - $false - } - } else { - $false - } - } -} - -$WINCURVERKEY = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion'; -$WIN64 = (Test-RegistryValue $WINCURVERKEY 'ProgramFilesDir (x86)'); - -if($WIN64) { - $PYTHONKEY='HKLM:SOFTWARE\Wow6432Node\Python\PythonCore'; -} -else { - $PYTHONKEY='HKLM:SOFTWARE\Python\PythonCore'; -} - -$Env:PYTHONVERSION = ''; -$Env:PYTHONINSTALL = ''; - -foreach ($version in @('2.6', '2.5', '2.4')) { - if (Test-RegistryValue "$PYTHONKEY\$version\InstallPath" '(default)') { - $Env:PYTHONVERSION = $version; - } -} - -if ($Env:PYTHONVERSION) { - $Env:PYTHONINSTALL = (Get-Item "$PYTHONKEY\$version\InstallPath)").'(default)'; -} - -if ($Env:PYTHONINSTALL) { - $Env:Path += ";$Env:PYTHONINSTALL"; -} - -if (Test-Path Env:_OLD_PYTHONPATH) { - $Env:PYTHONPATH = $Env:_OLD_PYTHONPATH; -} -else { - $Env:PYTHONPATH = ''; -} - -$Env:_OLD_PYTHONPATH=$Env:PYTHONPATH; -$Env:PYTHONPATH= "$Env:VIRTUAL_ENV\python-lib;$Env:PYTHONPATH"; - -if (Test-Path Function:_OLD_VIRTUAL_PROMPT) { - Set-Content Function:Prompt (Get-Content Function:_OLD_VIRTUAL_PROMPT); -} -else { - function global:_OLD_VIRTUAL_PROMPT {} -} - -Set-Content Function:_OLD_VIRTUAL_PROMPT (Get-Content Function:Prompt); - -function global:prompt { "($Env:VIRTUAL_ENV) $(_OLD_VIRTUAL_PROMPT)"; }; - -if (Test-Path Env:_OLD_VIRTUAL_PATH) { - $Env:PATH = $Env:_OLD_VIRTUAL_PATH; -} -else { - $Env:_OLD_VIRTUAL_PATH = $Env:PATH; -} - -$Env:Path="$Env:VIRTUAL_ENV\bin;$Env:Path" - -[System.Console]::WriteLine("Note: this PowerShell SDK activation script is experimental.") - -python -c "from jetpack_sdk_env import welcome; welcome()" - diff --git a/addon-sdk/source/bin/cfx b/addon-sdk/source/bin/cfx deleted file mode 100644 index 3e781faf82..0000000000 --- a/addon-sdk/source/bin/cfx +++ /dev/null @@ -1,33 +0,0 @@ -#! /usr/bin/env python -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -import os -import sys - -# set the cuddlefish "root directory" for this process if it's not already -# set in the environment -cuddlefish_root = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))) - -if 'CUDDLEFISH_ROOT' not in os.environ: - os.environ['CUDDLEFISH_ROOT'] = cuddlefish_root - -# add our own python-lib path to the python module search path. -python_lib_dir = os.path.join(cuddlefish_root, "python-lib") -if python_lib_dir not in sys.path: - sys.path.insert(0, python_lib_dir) - -# now export to env so sub-processes get it too -if 'PYTHONPATH' not in os.environ: - os.environ['PYTHONPATH'] = python_lib_dir -elif python_lib_dir not in os.environ['PYTHONPATH'].split(os.pathsep): - paths = os.environ['PYTHONPATH'].split(os.pathsep) - paths.insert(0, python_lib_dir) - os.environ['PYTHONPATH'] = os.pathsep.join(paths) - -import cuddlefish - -if __name__ == '__main__': - cuddlefish.run() diff --git a/addon-sdk/source/bin/cfx.bat b/addon-sdk/source/bin/cfx.bat deleted file mode 100644 index da01d1b924..0000000000 --- a/addon-sdk/source/bin/cfx.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -rem This Source Code Form is subject to the terms of the Mozilla Public -rem License, v. 2.0. If a copy of the MPL was not distributed with this -rem file, You can obtain one at http://mozilla.org/MPL/2.0/. - -python "%VIRTUAL_ENV%\bin\cfx" %* diff --git a/addon-sdk/source/bin/deactivate.bat b/addon-sdk/source/bin/deactivate.bat deleted file mode 100644 index e494ed7260..0000000000 --- a/addon-sdk/source/bin/deactivate.bat +++ /dev/null @@ -1,23 +0,0 @@ -@echo off -rem This Source Code Form is subject to the terms of the Mozilla Public -rem License, v. 2.0. If a copy of the MPL was not distributed with this -rem file, You can obtain one at http://mozilla.org/MPL/2.0/. - -if defined _OLD_VIRTUAL_PROMPT ( - set "PROMPT=%_OLD_VIRTUAL_PROMPT%" -) -set _OLD_VIRTUAL_PROMPT= - -if defined _OLD_VIRTUAL_PATH ( - set "PATH=%_OLD_VIRTUAL_PATH%" -) -set _OLD_VIRTUAL_PATH= - -if defined _OLD_PYTHONPATH ( - set "PYTHONPATH=%_OLD_PYTHONPATH%" -) -set _OLD_PYTHONPATH= - -set CUDDLEFISH_ROOT= - -:END diff --git a/addon-sdk/source/bin/fx-download.sh b/addon-sdk/source/bin/fx-download.sh deleted file mode 100644 index 690208a389..0000000000 --- a/addon-sdk/source/bin/fx-download.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if [ "$JPM_FX_DEBUG" = "1" ]; then - fx-download --branch nightly -c prerelease --host ftp.mozilla.org ../firefox --debug -else - fx-download --branch nightly -c prerelease --host ftp.mozilla.org ../firefox -fi diff --git a/addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper b/addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper deleted file mode 100644 index 56c76ad32c..0000000000 --- a/addon-sdk/source/bin/integration-scripts/buildbot-run-cfx-helper +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -source ./bin/activate -if [ type -P xvfb-run ] -then - xvfb-run cfx $* -else - cfx $* -fi -deactivate diff --git a/addon-sdk/source/bin/integration-scripts/integration-check b/addon-sdk/source/bin/integration-scripts/integration-check deleted file mode 100644 index 2505c15dc3..0000000000 --- a/addon-sdk/source/bin/integration-scripts/integration-check +++ /dev/null @@ -1,364 +0,0 @@ -#!/usr/bin/env python -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import signal -import threading -import urllib2, urllib -import zipfile -import tarfile -import subprocess -import optparse -import sys, re -#import win32api - - -class SDK: - def __init__(self): - try: - # Take the current working directory - self.default_path = os.getcwd() - if sys.platform == "win32": - self.mswindows = True - else: - self.mswindows = False - # Take the default home path of the user. - home = os.path.expanduser('~') - - # The following are the parameters that can be used to pass a dynamic URL, a specific path or a binry. The binary is not used yet. It will be used in version 2.0 - # If a dynamic path is to be mentioned, it should start with a '/'. For eg. "/Desktop" - parser = optparse.OptionParser() - parser.add_option('-u', '--url', dest = 'url', default = 'https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/addon-sdk-latest.zip') - parser.add_option('-p', '--path', dest = 'path', default = self.default_path) - parser.add_option('-b', '--binary', dest = 'binary')#, default='/Applications/Firefox.app') - (options, args) = parser.parse_args() - - # Get the URL from the parameter - self.link = options.url - # Set the base path for the user. If the user supplies the path, use the home variable as well. Else, take the default path of this script as the installation directory. - if options.path!=self.default_path: - if self.mswindows: - self.base_path = home + str(options.path).strip() + '\\' - else: - self.base_path = home + str(options.path).strip() + '/' - else: - if self.mswindows: - self.base_path = str(options.path).strip() + '\\' - else: - self.base_path = str(options.path).strip() + '/' - assert ' ' not in self.base_path, "You cannot have a space in your home path. Please remove the space before you continue." - print('Your Base path is =' + self.base_path) - - # This assignment is not used in this program. It will be used in version 2 of this script. - self.bin = options.binary - # if app or bin is empty, dont pass anything - - # Search for the .zip file or tarball file in the URL. - i = self.link.rfind('/') - - self.fname = self.link[i+1:] - z = re.search('zip',self.fname,re.I) - g = re.search('gz',self.fname,re.I) - if z: - print 'zip file present in the URL.' - self.zip = True - self.gz = False - elif g: - print 'gz file present in the URL' - self.gz = True - self.zip = False - else: - print 'zip/gz file not present. Check the URL.' - return - print("File name is =" + self.fname) - - # Join the base path and the zip/tar file name to crate a complete Local file path. - self.fpath = self.base_path + self.fname - print('Your local file path will be=' + self.fpath) - except AssertionError, e: - print e.args[0] - sys.exit(1) - - # Download function - to download the SDK from the URL to the local machine. - def download(self,url,fpath,fname): - try: - # Start the download - print("Downloading...Please be patient!") - urllib.urlretrieve(url,filename = fname) - print('Download was successful.') - except ValueError: # Handles broken URL errors. - print 'The URL is ether broken or the file does not exist. Please enter the correct URL.' - raise - except urllib2.URLError: # Handles URL errors - print '\nURL not correct. Check again!' - raise - - # Function to extract the downloaded zipfile. - def extract(self, zipfilepath, extfile): - try: - # Timeout is set to 30 seconds. - timeout = 30 - # Change the directory to the location of the zip file. - try: - os.chdir(zipfilepath) - except OSError: - # Will reach here if zip file doesnt exist - print 'O/S Error:' + zipfilepath + 'does not exist' - raise - - # Get the folder name of Jetpack to get the exact version number. - if self.zip: - try: - f = zipfile.ZipFile(extfile, "r") - except IOError as (errno, strerror): # Handles file errors - print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror) - raise - list = f.namelist()[0] - temp_name = list.split('/') - print('Folder Name= ' +temp_name[0]) - self.folder_name = temp_name[0] - elif self.gz: - try: - f = tarfile.open(extfile,'r') - except IOError as (errno, strerror): # Handles file errors - print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror) - raise - list = f.getnames()[0] - temp_name = list.split('/') - print('Folder Name= ' +temp_name[0]) - self.folder_name = temp_name[0] - - print ('Starting to Extract...') - - # Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned- - # timeout, the process is killed. - kill_check = threading.Event() - - if self.zip: - # Call the command to unzip the file. - if self.mswindows: - zipfile.ZipFile.extractall(f) - else: - p = subprocess.Popen('unzip '+extfile, stdout=subprocess.PIPE, shell=True) - pid = p.pid - elif self.gz: - # Call the command to untar the file. - if self.mswindows: - tarfile.TarFile.extractall(f) - else: - p = subprocess.Popen('tar -xf '+extfile, stdout=subprocess.PIPE, shell=True) - pid = p.pid - - #No need to handle for windows because windows automatically replaces old files with new files. It does not ask the user(as it does in Mac/Unix) - if self.mswindows==False: - watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows )) - watch.start() - (stdout, stderr) = p.communicate() - watch.cancel() # if it's still waiting to run - success = not kill_check.isSet() - - # Abort process if process fails. - if not success: - raise RuntimeError - kill_check.clear() - print('Extraction Successful.') - except RuntimeError: - print "Ending the program" - sys.exit(1) - except: - print "Error during file extraction: ", sys.exc_info()[0] - raise - - # Function to run the cfx testall comands and to make sure the SDK is not broken. - def run_testall(self, home_path, folder_name): - try: - timeout = 500 - - self.new_dir = home_path + folder_name - try: - os.chdir(self.new_dir) - except OSError: - # Will reach here if the jetpack 0.X directory doesnt exist - print 'O/S Error: Jetpack directory does not exist at ' + self.new_dir - raise - print '\nStarting tests...' - # Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned- - # timeout, the process is killed. - kill_check = threading.Event() - - # Set the path for the logs. They will be in the parent directory of the Jetpack SDK. - log_path = home_path + 'tests.log' - - # Subprocess call to set up the jetpack environment and to start the tests. Also sends the output to a log file. - if self.bin != None: - if self.mswindows: - p = subprocess.Popen("bin\\activate && cfx testall -a firefox -b \"" + self.bin + "\"" , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout,stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx testall -a firefox -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout,stderr) = p.communicate() - elif self.bin == None: - if self.mswindows: - p=subprocess.Popen('bin\\activate && cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout,stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout,stderr) = p.communicate() - - #Write the output to log file - f=open(log_path,"w") - f.write(stdout+stderr) - f.close() - - #Watchdog for timeout process - if self.mswindows: - watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows)) - else: - watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows)) - watch.start() - watch.cancel() # if it's still waiting to run - success = not kill_check.isSet() - if not success: - raise RuntimeError - kill_check.clear() - - if p.returncode!=0: - print('\nAll tests were not successful. Check the test-logs in the jetpack directory.') - result_sdk(home_path) - #sys.exit(1) - raise RuntimeError - else: - ret_code=result_sdk(home_path) - if ret_code==0: - print('\nAll tests were successful. Yay \o/ . Running a sample package test now...') - else: - print ('\nThere were errors during the tests.Take a look at logs') - raise RuntimeError - except RuntimeError: - print "Ending the program" - sys.exit(1) - except: - print "Error during the testall command execution:", sys.exc_info()[0] - raise - - def package(self, example_dir): - try: - timeout = 30 - - print '\nNow Running packaging tests...' - - kill_check = threading.Event() - - # Set the path for the example logs. They will be in the parent directory of the Jetpack SDK. - exlog_path = example_dir + 'test-example.log' - # Subprocess call to test the sample example for packaging. - if self.bin!=None: - if self.mswindows: - p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}" -b \"" + self.bin + "\"' , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout, stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout, stderr) = p.communicate() - elif self.bin==None: - if self.mswindows: - p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}"', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - proc_handle = p._handle - (stdout, stderr) = p.communicate() - else: - p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' ', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - pid = p.pid - (stdout, stderr) = p.communicate() - - #Write the output to log file - f=open(exlog_path,"w") - f.write(stdout+stderr) - f.close() - - #Watch dog for timeout process - if self.mswindows: - watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows)) - else: - watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows)) - watch.start() - watch.cancel() # if it's still waiting to run - success = not kill_check.isSet() - if not success: - raise RuntimeError - kill_check.clear() - - if p.returncode != 0: - print('\nSample tests were not executed correctly. Check the test-example log in jetpack diretory.') - result_example(example_dir) - raise RuntimeError - else: - ret_code=result_example(example_dir) - if ret_code==0: - print('\nAll tests pass. The SDK is working! Yay \o/') - else: - print ('\nTests passed with warning.Take a look at logs') - sys.exit(1) - - except RuntimeError: - print "Ending program" - sys.exit(1) - except: - print "Error during running sample tests:", sys.exc_info()[0] - raise - -def result_sdk(sdk_dir): - log_path = sdk_dir + 'tests.log' - print 'Results are logged at:' + log_path - try: - f = open(log_path,'r') - # Handles file errors - except IOError : - print 'I/O error - Cannot open test log at ' + log_path - raise - - for line in reversed(open(log_path).readlines()): - if line.strip()=='FAIL': - print ('\nOverall result - FAIL. Look at the test log at '+log_path) - return 1 - return 0 - - -def result_example(sdk_dir): - exlog_path = sdk_dir + 'test-example.log' - print 'Sample test results are logged at:' + exlog_path - try: - f = open(exlog_path,'r') - # Handles file errors - except IOError : - print 'I/O error - Cannot open sample test log at ' + exlog_path - raise - - #Read the file in reverse and check for the keyword 'FAIL'. - for line in reversed(open(exlog_path).readlines()): - if line.strip()=='FAIL': - print ('\nOverall result for Sample tests - FAIL. Look at the test log at '+exlog_path) - return 1 - return 0 - -def kill_process(process, kill_check, mswindows): - print '\nProcess Timedout. Killing the process. Please Rerun this script.' - if mswindows: - win32api.TerminateProcess(process, -1) - else: - os.kill(process, signal.SIGKILL) - kill_check.set()# tell the main routine to kill. Used SIGKILL to hard kill the process. - return - -if __name__ == "__main__": - obj = SDK() - obj.download(obj.link,obj.fpath,obj.fname) - obj.extract(obj.base_path,obj.fname) - obj.run_testall(obj.base_path,obj.folder_name) - obj.package(obj.base_path) diff --git a/addon-sdk/source/bin/jpm-test.js b/addon-sdk/source/bin/jpm-test.js deleted file mode 100644 index f22a552ea7..0000000000 --- a/addon-sdk/source/bin/jpm-test.js +++ /dev/null @@ -1,34 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var Promise = require("promise"); -var Mocha = require("mocha"); -var mocha = new Mocha({ - ui: "bdd", - reporter: "spec", - timeout: 900000 -}); - -var isDebug = require("./node-scripts/utils").isDebug; - -exports.run = function(type) { - return new Promise(function(resolve) { - type = type || ""; - [ - (!isDebug && /^(firefox-bin)?$/.test(type)) && require.resolve("../bin/node-scripts/test.firefox-bin"), - (!isDebug && /^(docs)?$/.test(type)) && require.resolve("../bin/node-scripts/test.docs"), - (!isDebug && /^(ini)?$/.test(type)) && require.resolve("../bin/node-scripts/test.ini"), - (/^(examples)?$/.test(type)) && require.resolve("../bin/node-scripts/test.examples"), - (!isDebug && /^(addons)?$/.test(type)) && require.resolve("../bin/node-scripts/test.addons"), - (!isDebug && /^(modules)?$/.test(type)) && require.resolve("../bin/node-scripts/test.modules"), - ].forEach(function(filepath) { - filepath && mocha.addFile(filepath); - }) - - mocha.run(function(failures) { - resolve(failures); - }); - }); -} diff --git a/addon-sdk/source/bin/node-scripts/apply-patch.js b/addon-sdk/source/bin/node-scripts/apply-patch.js deleted file mode 100644 index 31fbf7d31b..0000000000 --- a/addon-sdk/source/bin/node-scripts/apply-patch.js +++ /dev/null @@ -1,64 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var path = require("path"); -var cp = require("child_process"); -var fs = require("fs"); -var Promise = require("promise"); -var patcher = require("patch-editor"); -var readParam = require("./utils").readParam; - -var isKeeper = /\/addon-sdk\/source/; - -function apply(options) { - return clean(options).then(function() { - return new Promise(function(resolve) { - var patch = path.resolve(readParam("patch")); - var proc = cp.spawn("git", ["apply", patch]); - proc.stdout.pipe(process.stdout); - proc.stderr.pipe(process.stderr); - proc.on("close", resolve); - }); - }); -} -exports.apply = apply; - -function clean(options) { - return new Promise(function(resolve) { - var patch = path.resolve(readParam("patch")); - if (!patch) { - throw new Error("no --patch was provided."); - } - console.log("Cleaning patch " + patch); - - patcher.getChunks({ patch: patch }).then(function(chunks) { - var keepers = []; - - for (var i = chunks.length - 1; i >= 0; i--) { - var chunk = chunks[i]; - var files = chunk.getFilesChanged(); - - // check if the file changed is related to the addon-sdk/source directory - var keepIt = files.map(function(file) { - return (isKeeper.test(file)); - }).reduce(function(prev, curr) { - return prev || curr; - }, false); - - if (keepIt) { - keepers.push(chunk); - } - } - - var contents = "\n" + keepers.join("\n") + "\n"; - contents = contents.replace(/\/addon-sdk\/source/g, ""); - - fs.writeFileSync(patch, contents, { encoding: "utf8" }); - - console.log("Done cleaning patch."); - }).then(resolve).catch(console.error); - }); -} -exports.clean = clean; diff --git a/addon-sdk/source/bin/node-scripts/test.addons.js b/addon-sdk/source/bin/node-scripts/test.addons.js deleted file mode 100644 index dc7c6dfcef..0000000000 --- a/addon-sdk/source/bin/node-scripts/test.addons.js +++ /dev/null @@ -1,57 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var utils = require("./utils"); -var path = require("path"); -var fs = require("fs"); -var jpm = utils.run; -var readParam = utils.readParam; -var isDebug = utils.isDebug; - -var addonsPath = path.join(__dirname, "..", "..", "test", "addons"); - -var binary = process.env.JPM_FIREFOX_BINARY || "nightly"; -var filterPattern = readParam("filter"); - -describe("jpm test sdk addons", function () { - fs.readdirSync(addonsPath) - .filter(fileFilter.bind(null, addonsPath)) - .forEach(function (file) { - it(file, function (done) { - var addonPath = path.join(addonsPath, file); - process.chdir(addonPath); - - var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }}; - if (process.env.DISPLAY) { - options.env.DISPLAY = process.env.DISPLAY; - } - if (/^e10s/.test(file)) { - options.e10s = true; - } - - jpm("run", options).then(done).catch(done); - }); - }); -}); - -function fileFilter(root, file) { - var matcher = filterPattern && new RegExp(filterPattern); - if (/^(l10n-properties|simple-prefs|page-mod-debugger)/.test(file)) { - return false; - } - - // filter additional add-ons when using debug builds - if (isDebug) { - if (/^(chrome|e10s)/.test(file)) { - return false; - } - } - - if (matcher && !matcher.test(file)) { - return false; - } - var stat = fs.statSync(path.join(root, file)) - return (stat && stat.isDirectory()); -} diff --git a/addon-sdk/source/bin/node-scripts/test.docs.js b/addon-sdk/source/bin/node-scripts/test.docs.js deleted file mode 100644 index e6aef516d7..0000000000 --- a/addon-sdk/source/bin/node-scripts/test.docs.js +++ /dev/null @@ -1,145 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var createHash = require('crypto').createHash; -var fs = require("fs"); -var fsExtra = require("fs-extra") -var path = require("path"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var teacher = require("teacher"); - -var rootURI = path.join(__dirname, "..", ".."); - -// get a list of words that fail spell check but are still acceptable -var NEW_WORDS = fs.readFileSync(path.join(__dirname, "words.txt")).toString().trim().split("\n"); - -var CACHE_PATH = path.join(__dirname, "..", "..", "cache", "spellchecks.json"); - -var CACHE = {}; - -try { - CACHE = JSON.parse(fs.readFileSync(CACHE_PATH).toString()); -} -catch (e) {} - -function md5(str) { - return createHash("md5").update(str).digest("utf8"); -} - -function addCacheHash(hash) { - CACHE[hash] = true; - fsExtra.ensureFileSync(CACHE_PATH); - fsExtra.writeJSONSync(CACHE_PATH, CACHE); -} - -describe("Spell Checking", function () { - it("Spellcheck CONTRIBUTING.md", function (done) { - var readme = path.join(rootURI, "CONTRIBUTING.md"); - - fs.readFile(readme, function (err, data) { - if (err) { - throw err; - } - var text = data.toString(); - var hash = md5(text); - - // skip this test if we know we have done the - // exact same test with positive results before - if (CACHE[hash]) { - expect(CACHE[hash]).to.be.equal(true); - return done(); - } - - teacher.check(text, function(err, data) { - expect(err).to.be.equal(null); - - var results = data || []; - results = results.filter(function(result) { - if (NEW_WORDS.indexOf(result.string.toLowerCase()) != -1) { - return false; - } - - // ignore anything that starts with a dash - if (result.string[0] == "-") { - return false; - } - - if (!(new RegExp(result.string)).test(text)) { - return false; - } - - return true; - }) - - if (results.length > 0) { - console.log(results); - } - else { - addCacheHash(hash); - } - - expect(results.length).to.be.equal(0); - - setTimeout(done, 500); - }); - }); - }); - - it("Spellcheck README.md", function (done) { - var readme = path.join(rootURI, "README.md"); - - fs.readFile(readme, function (err, data) { - if (err) { - throw err; - } - var text = data.toString(); - var hash = md5(text); - - // skip this test if we know we have done the - // exact same test with positive results before - if (CACHE[hash]) { - expect(CACHE[hash]).to.be.equal(true); - return done(); - } - - teacher.check(text, function(err, data) { - expect(err).to.be.equal(null); - - var results = data || []; - results = results.filter(function(result) { - if (NEW_WORDS.indexOf(result.string.toLowerCase()) != -1) { - return false; - } - - // ignore anything that starts with a dash - if (result.string[0] == "-") { - return false; - } - - // ignore anything that we don't find in the original text, - // for some reason "bootstrap.js" becomes "bootstrapjs". - if (!(new RegExp(result.string)).test(text)) { - return false; - } - - return true; - }) - - if (results.length > 0) { - console.log(results); - } - else { - addCacheHash(hash); - } - - expect(results.length).to.be.equal(0); - - done(); - }); - }); - }); -}); diff --git a/addon-sdk/source/bin/node-scripts/test.examples.js b/addon-sdk/source/bin/node-scripts/test.examples.js deleted file mode 100644 index 71f7ee43c1..0000000000 --- a/addon-sdk/source/bin/node-scripts/test.examples.js +++ /dev/null @@ -1,45 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var utils = require("./utils"); -var path = require("path"); -var fs = require("fs"); -var jpm = utils.run; -var readParam = utils.readParam; - -var examplesPath = path.join(__dirname, "..", "..", "examples"); - -var binary = process.env.JPM_FIREFOX_BINARY || "nightly"; -var filterPattern = readParam("filter"); - -describe("jpm test sdk examples", function () { - fs.readdirSync(examplesPath) - .filter(fileFilter.bind(null, examplesPath)) - .forEach(function (file) { - it(file, function (done) { - var addonPath = path.join(examplesPath, file); - process.chdir(addonPath); - - var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }}; - if (process.env.DISPLAY) { - options.env.DISPLAY = process.env.DISPLAY; - } - - jpm("test", options).then(done); - }); - }); -}); - -function fileFilter(root, file) { - var matcher = filterPattern && new RegExp(filterPattern); - if (/^(reading-data)/.test(file)) { - return false; - } - if (matcher && !matcher.test(file)) { - return false; - } - var stat = fs.statSync(path.join(root, file)) - return (stat && stat.isDirectory()); -} diff --git a/addon-sdk/source/bin/node-scripts/test.firefox-bin.js b/addon-sdk/source/bin/node-scripts/test.firefox-bin.js deleted file mode 100644 index 2570dae20f..0000000000 --- a/addon-sdk/source/bin/node-scripts/test.firefox-bin.js +++ /dev/null @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var fs = require("fs"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var normalizeBinary = require("fx-runner/lib/utils").normalizeBinary; - -//var firefox_binary = process.env["JPM_FIREFOX_BINARY"] || normalizeBinary("nightly"); - -describe("Checking Firefox binary", function () { - - it("using matching fx-runner version with jpm", function () { - var sdkPackageJSON = require("../../package.json"); - var jpmPackageINI = require("jpm/package.json"); - expect(sdkPackageJSON.devDependencies["fx-runner"]).to.be.equal(jpmPackageINI.dependencies["fx-runner"]); - }); - - it("exists", function (done) { - var useEnvVar = new Promise(function(resolve) { - resolve(process.env["JPM_FIREFOX_BINARY"]); - }); - - var firefox_binary = process.env["JPM_FIREFOX_BINARY"] ? useEnvVar : normalizeBinary("nightly"); - firefox_binary.then(function(path) { - expect(path).to.be.ok; - fs.exists(path, function (exists) { - expect(exists).to.be.ok; - done(); - }); - }) - }); - -}); diff --git a/addon-sdk/source/bin/node-scripts/test.ini.js b/addon-sdk/source/bin/node-scripts/test.ini.js deleted file mode 100644 index 07bd15d1fd..0000000000 --- a/addon-sdk/source/bin/node-scripts/test.ini.js +++ /dev/null @@ -1,68 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var fs = require("fs"); -var path = require("path"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var ini = require("./update-ini"); - -var addonINI = path.resolve("./test/addons/jetpack-addon.ini"); -var packageINI = path.resolve("./test/jetpack-package.ini"); - -describe("Checking ini files", function () { - - it("Check test/addons/jetpack-addon.ini", function (done) { - - fs.readFile(addonINI, function (err, data) { - if (err) { - throw err; - } - // filter comments - var text = data.toString().split("\n").filter(function(line) { - return !/^\s*#/.test(line); - }).join("\n"); - var expected = ""; - - ini.makeAddonIniContent() - .then(function(contents) { - expected = contents; - - setTimeout(function end() { - expect(text.trim()).to.be.equal(expected.trim()); - done(); - }); - }); - }); - - }); - - it("Check test/jetpack-package.ini", function (done) { - - fs.readFile(packageINI, function (err, data) { - if (err) { - throw err; - } - // filter comments - var text = data.toString().split("\n").filter(function(line) { - return !/^\s*#/.test(line); - }).join("\n"); - var expected = ""; - - ini.makePackageIniContent() - .then(function(contents) { - expected = contents; - - setTimeout(function end() { - expect(text.trim()).to.be.equal(expected.trim()); - done(); - }); - }); - }); - - }); - -}); diff --git a/addon-sdk/source/bin/node-scripts/test.modules.js b/addon-sdk/source/bin/node-scripts/test.modules.js deleted file mode 100644 index eb400a5f3d..0000000000 --- a/addon-sdk/source/bin/node-scripts/test.modules.js +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var utils = require("./utils"); -var readParam = utils.readParam; -var path = require("path"); -var fs = require("fs"); -var jpm = utils.run; -var sdk = path.join(__dirname, "..", ".."); -var binary = process.env.JPM_FIREFOX_BINARY || "nightly"; - -var filterPattern = readParam("filter"); - -describe("jpm test sdk modules", function () { - it("SDK Modules", function (done) { - process.chdir(sdk); - - var options = { cwd: sdk, env: { JPM_FIREFOX_BINARY: binary } }; - if (process.env.DISPLAY) { - options.env.DISPLAY = process.env.DISPLAY; - } - options.filter = filterPattern; - - jpm("test", options, process).then(done); - }); -}); diff --git a/addon-sdk/source/bin/node-scripts/update-ini.js b/addon-sdk/source/bin/node-scripts/update-ini.js deleted file mode 100644 index 634cbc1de7..0000000000 --- a/addon-sdk/source/bin/node-scripts/update-ini.js +++ /dev/null @@ -1,141 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var path = require("path"); -var cp = require("child_process"); -var fs = require("fs"); -var Promise = require("promise"); -var parser = require("ini-parser"); - -var addonINI = path.resolve("./test/addons/jetpack-addon.ini"); -var addonsDir = path.resolve("./test/addons/"); -var packageINI = path.resolve("./test/jetpack-package.ini"); -var packageDir = path.resolve("./test/"); -var packageIgnorables = [ "addons", "preferences" ]; -var packageSupportFiles = [ - "fixtures.js", - "test-context-menu.html", - "util.js" -] - -function updateAddonINI() { - return new Promise(function(resolve) { - console.log("Start updating " + addonINI); - - makeAddonIniContent(). - then(function(contents) { - fs.writeFileSync(addonINI, contents, { encoding: "utf8" }); - console.log("Done updating " + addonINI); - resolve(); - }); - }) -} -exports.updateAddonINI = updateAddonINI; - -function makeAddonIniContent() { - return new Promise(function(resolve) { - var data = parser.parse(fs.readFileSync(addonINI, { encoding: "utf8" }).toString()); - var result = {}; - - fs.readdir(addonsDir, function(err, files) { - // get a list of folders - var folders = files.filter(function(file) { - return fs.statSync(path.resolve(addonsDir, file)).isDirectory(); - }).sort(); - - // copy any related data from the existing ini - folders.forEach(function(folder) { - var oldData = data[folder + ".xpi"]; - result[folder] = oldData ? oldData : {}; - }); - - // build a new ini file - var contents = []; - Object.keys(result).sort().forEach(function(key) { - contents.push("[" + key + ".xpi]"); - Object.keys(result[key]).forEach(function(dataKey) { - contents.push(dataKey + " = " + result[key][dataKey]); - }); - }); - contents = contents.join("\n") + "\n"; - - return resolve(contents); - }); - }); -} -exports.makeAddonIniContent = makeAddonIniContent; - -function makePackageIniContent() { - return new Promise(function(resolve) { - var data = parser.parse(fs.readFileSync(packageINI, { encoding: "utf8" }).toString()); - var result = {}; - - fs.readdir(packageDir, function(err, files) { - // get a list of folders - var folders = files.filter(function(file) { - var ignore = (packageIgnorables.indexOf(file) >= 0); - var isDir = fs.statSync(path.resolve(packageDir, file)).isDirectory(); - return (isDir && !ignore); - }).sort(); - - // get a list of "test-"" files - var files = files.filter(function(file) { - var ignore = !/^test\-.*\.js$/i.test(file); - var isDir = fs.statSync(path.resolve(packageDir, file)).isDirectory(); - return (!isDir && !ignore); - }).sort(); - - // get a list of the support files - var support_files = packageSupportFiles.map(function(file) { - return " " + file; - }); - folders.forEach(function(folder) { - support_files.push(" " + folder + "/**"); - }); - support_files = support_files.sort(); - - // copy any related data from the existing ini - files.forEach(function(file) { - var oldData = data[file]; - result[file] = oldData ? oldData : {}; - }); - - // build a new ini file - var contents = [ - "[DEFAULT]", - "support-files =" - ]; - support_files.forEach(function(support_file) { - contents.push(support_file); - }); - contents.push(""); - - Object.keys(result).sort().forEach(function(key) { - contents.push("[" + key + "]"); - Object.keys(result[key]).forEach(function(dataKey) { - contents.push(dataKey + " = " + result[key][dataKey]); - }); - }); - contents = contents.join("\n") + "\n"; - - return resolve(contents); - }); - }); -} -exports.makePackageIniContent = makePackageIniContent; - -function updatePackageINI() { - return new Promise(function(resolve) { - console.log("Start updating " + packageINI); - - makeAddonIniContent(). - then(function(contents) { - fs.writeFileSync(packageINI, contents, { encoding: "utf8" }); - console.log("Done updating " + packageINI); - resolve(); - }); - }) -} -exports.updatePackageINI = updatePackageINI; diff --git a/addon-sdk/source/bin/node-scripts/utils.js b/addon-sdk/source/bin/node-scripts/utils.js deleted file mode 100644 index 1d7f94474c..0000000000 --- a/addon-sdk/source/bin/node-scripts/utils.js +++ /dev/null @@ -1,104 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var _ = require("lodash"); -var path = require("path"); -var child_process = require("child_process"); -var jpm = require.resolve("../../node_modules/jpm/bin/jpm"); -var Promise = require("promise"); -var chai = require("chai"); -var expect = chai.expect; -var assert = chai.assert; -var DEFAULT_PROCESS = process; - -var sdk = path.join(__dirname, "..", ".."); -var prefsPath = path.join(sdk, "test", "preferences", "test-preferences.js"); -var e10sPrefsPath = path.join(sdk, "test", "preferences", "test-e10s-preferences.js"); - -var OUTPUT_FILTERS = [ - /[^\n\r]+WARNING\: NS_ENSURE_SUCCESS\(rv, rv\) failed[^\n]+\n\r?/ -]; - -var isDebug = (process.env["JPM_FX_DEBUG"] == "1"); -exports.isDebug = isDebug; - -function spawn (cmd, options) { - options = options || {}; - var env = _.extend({}, options.env, process.env); - - if (isDebug) { - env["MOZ_QUIET"] = 1; - } - - var e10s = options.e10s || false; - - return child_process.spawn("node", [ - jpm, cmd, "-v", "--tbpl", - "--prefs", e10s ? e10sPrefsPath : prefsPath, - "-o", sdk, - "-f", options.filter || "" - ], { - cwd: options.cwd || tmpOutputDir, - env: env - }); -} -exports.spawn = spawn; - -function run (cmd, options, p) { - return new Promise(function(resolve) { - var output = []; - - var proc = spawn(cmd, options); - proc.stderr.pipe(process.stderr); - proc.stdout.on("data", function (data) { - for (var i = OUTPUT_FILTERS.length - 1; i >= 0; i--) { - if (OUTPUT_FILTERS[i].test(data)) { - return null; - } - } - output.push(data); - return null; - }); - - if (p) { - proc.stdout.pipe(p.stdout); - } - else if (!isDebug) { - proc.stdout.pipe(DEFAULT_PROCESS.stdout); - } - else { - proc.stdout.on("data", function (data) { - data = (data || "") + ""; - if (/TEST-/.test(data)) { - DEFAULT_PROCESS.stdout.write(data.replace(/[\s\n]+$/, "") + "\n"); - } - }); - } - - proc.on("close", function(code) { - var out = output.join(""); - var buildDisplayed = /Build \d+/.test(out); - var noTests = /No tests were run/.test(out); - var hasSuccess = /All tests passed!/.test(out); - var hasFailure = /There were test failures\.\.\./.test(out); - if (noTests || hasFailure || !hasSuccess || code != 0) { - DEFAULT_PROCESS.stdout.write(out); - } - expect(code).to.equal(hasFailure ? 1 : 0); - expect(buildDisplayed).to.equal(true); - expect(hasFailure).to.equal(false); - expect(hasSuccess).to.equal(true); - expect(noTests).to.equal(false); - resolve(); - }); - }); -} -exports.run = run; - -function readParam(name) { - var index = process.argv.indexOf("--" + name) - return index >= 0 && process.argv[index + 1] -} -exports.readParam = readParam; diff --git a/addon-sdk/source/bin/node-scripts/words.txt b/addon-sdk/source/bin/node-scripts/words.txt deleted file mode 100644 index b5b29f74b1..0000000000 --- a/addon-sdk/source/bin/node-scripts/words.txt +++ /dev/null @@ -1,11 +0,0 @@ -addon-sdk -github -stackoverflow -bugzilla -irc -jsantell -mossop -gozala -zer0 -autonome -0c0w3 diff --git a/addon-sdk/source/examples/actor-repl/README.md b/addon-sdk/source/examples/actor-repl/README.md deleted file mode 100644 index 5228719e95..0000000000 --- a/addon-sdk/source/examples/actor-repl/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Actor REPL - -Simple REPL for a Firefox debugging protocol. diff --git a/addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js b/addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js deleted file mode 100644 index 6d8e4bf719..0000000000 --- a/addon-sdk/source/examples/actor-repl/data/codemirror-compressed.js +++ /dev/null @@ -1,5 +0,0 @@ -window.CodeMirror=function(){"use strict";function z(a,c){if(!(this instanceof z))return new z(a,c);this.options=c=c||{};for(var d in fd)!c.hasOwnProperty(d)&&fd.hasOwnProperty(d)&&(c[d]=fd[d]);M(c);var e="string"==typeof c.value?0:c.value.first,f=this.display=A(a,e);f.wrapper.CodeMirror=this,J(this),c.autofocus&&!r&&Qb(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,focused:!1,suppressEdits:!1,pasteIncoming:!1,cutIncoming:!1,draggingText:!1,highlight:new jf},H(this),c.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap");var g=c.value;"string"==typeof g&&(g=new te(c.value,c.mode)),Ib(this,xe)(this,g),b&&setTimeout(tf(Pb,this,!0),20),Tb(this);var h;try{h=document.activeElement==f.input}catch(i){}h||c.autofocus&&!r?setTimeout(tf(rc,this),20):sc(this),Ib(this,function(){for(var a in ed)ed.propertyIsEnumerable(a)&&ed[a](this,c[a],hd);for(var b=0;bb.maxLineLength&&(b.maxLineLength=d,b.maxLine=a)})}function M(a){var b=pf(a.gutters,"CodeMirror-linenumbers");-1==b&&a.lineNumbers?a.gutters=a.gutters.concat(["CodeMirror-linenumbers"]):b>-1&&!a.lineNumbers&&(a.gutters=a.gutters.slice(0),a.gutters.splice(b,1))}function N(a){var b=a.display,c=a.doc.height,d=c+jb(b);b.sizer.style.minHeight=b.heightForcer.style.top=d+"px",b.gutters.style.height=Math.max(d,b.scroller.clientHeight-gf)+"px";var e=Math.max(d,b.scroller.scrollHeight),f=b.scroller.scrollWidth>b.scroller.clientWidth+1,g=e>b.scroller.clientHeight+1;g?(b.scrollbarV.style.display="block",b.scrollbarV.style.bottom=f?Hf(b.measure)+"px":"0",b.scrollbarV.firstChild.style.height=Math.max(0,e-b.scroller.clientHeight+b.scrollbarV.clientHeight)+"px"):(b.scrollbarV.style.display="",b.scrollbarV.firstChild.style.height="0"),f?(b.scrollbarH.style.display="block",b.scrollbarH.style.right=g?Hf(b.measure)+"px":"0",b.scrollbarH.firstChild.style.width=b.scroller.scrollWidth-b.scroller.clientWidth+b.scrollbarH.clientWidth+"px"):(b.scrollbarH.style.display="",b.scrollbarH.firstChild.style.width="0"),f&&g?(b.scrollbarFiller.style.display="block",b.scrollbarFiller.style.height=b.scrollbarFiller.style.width=Hf(b.measure)+"px"):b.scrollbarFiller.style.display="",f&&a.options.coverGutterNextToScrollbar&&a.options.fixedGutter?(b.gutterFiller.style.display="block",b.gutterFiller.style.height=Hf(b.measure)+"px",b.gutterFiller.style.width=b.gutters.offsetWidth+"px"):b.gutterFiller.style.display="",n&&0===Hf(b.measure)&&(b.scrollbarV.style.minWidth=b.scrollbarH.style.minHeight=o?"18px":"12px",b.scrollbarV.style.pointerEvents=b.scrollbarH.style.pointerEvents="none")}function O(a,b,c){var d=a.scroller.scrollTop,e=a.wrapper.clientHeight;"number"==typeof c?d=c:c&&(d=c.top,e=c.bottom-c.top),d=Math.floor(d-ib(a));var f=Math.ceil(d+e);return{from:De(b,d),to:De(b,f)}}function P(a){var b=a.display;if(b.alignWidgets||b.gutters.firstChild&&a.options.fixedGutter){for(var c=S(b)-b.scroller.scrollLeft+a.doc.scrollLeft,d=b.gutters.offsetWidth,e=c+"px",f=b.lineDiv.firstChild;f;f=f.nextSibling)if(f.alignable)for(var g=0,h=f.alignable;g=a.display.showingFrom&&h.to<=a.display.showingTo)break}return g&&(bf(a,"update",a),(a.display.showingFrom!=e||a.display.showingTo!=f)&&bf(a,"viewportChange",a,a.display.showingFrom,a.display.showingTo)),g}function U(a,b,c,d){var e=a.display,f=a.doc;if(!e.wrapper.offsetWidth)return e.showingFrom=e.showingTo=f.first,e.viewOffset=0,void 0;if(!(!d&&0==b.length&&c.from>e.showingFrom&&c.tol&&e.showingTo-l<20&&(l=Math.min(j,e.showingTo)),y)for(k=Ce(Rd(f,ye(f,k)));j>l&&Sd(f,ye(f,l));)++l;var m=[{from:Math.max(e.showingFrom,f.first),to:Math.min(e.showingTo,j)}];if(m=m[0].from>=m[0].to?[]:X(m,b),y)for(var i=0;in.from)){m.splice(i--,1);break}n.to=p}for(var q=0,i=0;il&&(n.to=l),n.from>=n.to?m.splice(i--,1):q+=n.to-n.from}if(!d&&q==l-k&&k==e.showingFrom&&l==e.showingTo)return W(a),void 0;m.sort(function(a,b){return a.from-b.from});try{var r=document.activeElement}catch(s){}.7*(l-k)>q&&(e.lineDiv.style.display="none"),Z(a,k,l,m,h),e.lineDiv.style.display="",r&&document.activeElement!=r&&r.offsetHeight&&r.focus();var t=k!=e.showingFrom||l!=e.showingTo||e.lastSizeC!=e.wrapper.clientHeight;return t&&(e.lastSizeC=e.wrapper.clientHeight,eb(a,400)),e.showingFrom=k,e.showingTo=l,e.gutters.style.height="",V(a),W(a),!0}}function V(a){for(var f,b=a.display,d=b.lineDiv.offsetTop,e=b.lineDiv.firstChild;e;e=e.nextSibling)if(e.lineObj){if(c){var g=e.offsetTop+e.offsetHeight;f=g-d,d=g}else{var h=Df(e);f=h.bottom-h.top}var i=e.lineObj.height-f;if(2>f&&(f=Db(b)),i>.001||-.001>i){Be(e.lineObj,f);var j=e.lineObj.widgets;if(j)for(var k=0;kc;++c){for(var e=b[c],f=[],g=e.diff||0,h=0,i=a.length;i>h;++h){var j=a[h];e.to<=j.from&&e.diff?f.push({from:j.from+g,to:j.to+g}):e.to<=j.from||e.from>=j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from}),e.ton){for(;k.lineObj!=b;)k=l(k);i&&n>=e&&k.lineNumber&&Cf(k.lineNumber,R(a.options,n)),k=k.nextSibling}else{if(b.widgets)for(var s,q=0,r=k;r&&20>q;++q,r=r.nextSibling)if(r.lineObj==b&&/div/i.test(r.nodeName)){s=r;break}var t=$(a,b,n,f,s);if(t!=s)j.insertBefore(t,k);else{for(;k!=s;)k=l(k);k=k.nextSibling}t.lineObj=b}++n});k;)k=l(k)}function $(a,b,d,e,f){var k,g=ie(a,b),h=g.pre,i=b.gutterMarkers,j=a.display,l=g.bgClass?g.bgClass+" "+(b.bgClass||""):b.bgClass;if(!(a.options.lineNumbers||i||l||b.wrapClass||b.widgets))return h;if(f){f.alignable=null;for(var q,m=!0,n=0,o=null,p=f.firstChild;p;p=q)if(q=p.nextSibling,/\bCodeMirror-linewidget\b/.test(p.className)){for(var r=0;rb&&(b=0),e.appendChild(zf("div",null,"CodeMirror-selected","position: absolute; left: "+a+"px; top: "+b+"px; width: "+(null==c?h-a:c)+"px; height: "+(d-b)+"px"))}function j(b,d,e){function m(c,d){return xb(a,Gc(b,c),"div",f,d)}var k,l,f=ye(c,b),j=f.text.length;return Of(Fe(f),d||0,null==e?j:e,function(a,b,c){var n,o,p,f=m(a,"left");if(a==b)n=f,o=p=f.left;else{if(n=m(b-1,"right"),"rtl"==c){var q=f;f=n,n=q}o=f.left,p=n.right}null==d&&0==a&&(o=g),n.top-f.top>3&&(i(o,f.top,null,f.bottom),o=g,f.bottoml.bottom||n.bottom==l.bottom&&n.right>l.right)&&(l=n),g+1>o&&(o=g),i(o,n.top,p-o,n.bottom)}),{start:k,end:l}}var b=a.display,c=a.doc,d=a.doc.sel,e=document.createDocumentFragment(),f=kb(a.display),g=f.left,h=b.lineSpace.offsetWidth-f.right;if(d.from.line==d.to.line)j(d.from.line,d.from.ch,d.to.ch);else{var k=ye(c,d.from.line),l=ye(c,d.to.line),m=Rd(c,k)==Rd(c,l),n=j(d.from.line,d.from.ch,m?k.text.length:null).end,o=j(d.to.line,m?0:null,d.to.ch).start;m&&(n.top0&&(b.blinker=setInterval(function(){b.cursor.style.visibility=b.otherCursor.style.visibility=(c=!c)?"":"hidden"},a.options.cursorBlinkRate))}}function eb(a,b){a.doc.mode.startState&&a.doc.frontier=a.display.showingTo)){var f,c=+new Date+a.options.workTime,d=nd(b.mode,hb(a,b.frontier)),e=[];b.iter(b.frontier,Math.min(b.first+b.size,a.display.showingTo+500),function(g){if(b.frontier>=a.display.showingFrom){var h=g.styles;g.styles=ce(a,g,d,!0);for(var i=!h||h.length!=g.styles.length,j=0;!i&&jc?(eb(a,a.options.workDelay),!0):void 0}),e.length&&Ib(a,function(){for(var a=0;ag;--h){if(h<=f.first)return f.first;var i=ye(f,h-1);if(i.stateAfter&&(!c||h<=f.frontier))return h;var j=kf(i.text,null,a.options.tabSize);(null==e||d>j)&&(e=h-1,d=j)}return e}function hb(a,b,c){var d=a.doc,e=a.display;if(!d.mode.startState)return!0;var f=gb(a,b,c),g=f>d.first&&ye(d,f-1).stateAfter;return g=g?nd(d.mode,g):od(d.mode),d.iter(f,b,function(c){ee(a,c.text,g);var h=f==b-1||0==f%5||f>=e.showingFrom&&ff&&0==h&&(f=1)}return e=h>c?"left":c>h?"right":e,"left"==e&&i.leftSide?i=i.leftSide:"right"==e&&i.rightSide&&(i=i.rightSide),{left:c>h?i.right:i.left,right:h>c?i.left:i.right,top:i.top,bottom:i.bottom}}function mb(a,b){for(var c=a.display.measureLineCache,d=0;ds&&(c=s),0>b&&(b=0);for(var d=q.length-2;d>=0;d-=2){var e=q[d],f=q[d+1];if(!(e>c||b>f)&&(b>=e&&f>=c||e>=b&&c>=f||Math.min(c,f)-Math.max(b,e)>=c-b>>1)){q[d]=Math.min(b,e),q[d+1]=Math.max(c,f);break}}return 0>d&&(d=q.length,q.push(b,c)),{left:a.left-p.left,right:a.right-p.left,top:d,bottom:null}}function u(a){a.bottom=q[a.top+1],a.top=q[a.top]}if(!a.options.lineWrapping&&e.text.length>=a.options.crudeMeasuringFrom)return qb(a,e);var f=a.display,g=sf(e.text.length),h=ie(a,e,g,!0).pre;if(b&&!c&&!a.options.lineWrapping&&h.childNodes.length>100){for(var i=document.createDocumentFragment(),j=10,k=h.childNodes.length,l=0,m=Math.ceil(k/j);m>l;++l){for(var n=zf("div",null,null,"display: inline-block"),o=0;j>o&&k;++o)n.appendChild(h.firstChild),--k;i.appendChild(n)}h.appendChild(i)}Bf(f.measure,h);var p=Df(f.lineDiv),q=[],r=sf(e.text.length),s=h.offsetHeight;d&&f.measure.first!=h&&Bf(f.measure,h);for(var v,l=0;l1&&(x=r[l]=t(y[0]),x.rightSide=t(y[y.length-1]))}x||(x=r[l]=t(Df(w))),v.measureRight&&(x.right=Df(v.measureRight).left-p.left),v.leftSide&&(x.leftSide=t(Df(v.leftSide)))}Af(a.display.measure);for(var v,l=0;l=a.options.crudeMeasuringFrom)return lb(a,b,b.text.length,f&&f.measure,"right").right;var g=ie(a,b,null,!0).pre,h=g.appendChild(Jf(a.display.measure));Bf(a.display.measure,g);var i=Df(h);return 0==i.right&&0==i.bottom&&(h=g.appendChild(zf("span","\xa0")),i=Df(h)),i.left-Df(a.display.lineDiv).left}function sb(a){a.display.measureLineCache.length=a.display.measureLineCachePos=0,a.display.cachedCharWidth=a.display.cachedTextHeight=a.display.cachedPaddingH=null,a.options.lineWrapping||(a.display.maxLineChanged=!0),a.display.lineNumChars=null}function tb(){return window.pageXOffset||(document.documentElement||document.body).scrollLeft}function ub(){return window.pageYOffset||(document.documentElement||document.body).scrollTop}function vb(a,b,c,d){if(b.widgets)for(var e=0;ec.from?f(a-1):f(a,d)}d=d||ye(a.doc,b.line),e||(e=ob(a,d));var h=Fe(d),i=b.ch;if(!h)return f(i);var j=Xf(h,i),k=g(i,j);return null!=Wf&&(k.other=g(i,Wf)),k}function zb(a,b,c,d){var e=new Gc(a,b);return e.xRel=d,c&&(e.outside=!0),e}function Ab(a,b,c){var d=a.doc;if(c+=a.display.viewOffset,0>c)return zb(d.first,0,!0,-1);var e=De(d,c),f=d.first+d.size-1;if(e>f)return zb(d.first+d.size-1,ye(d,f).text.length,!0,1);for(0>b&&(b=0);;){var g=ye(d,e),h=Bb(a,g,e,b,c),i=Pd(g),j=i&&i.find();if(!i||!(h.ch>j.from.ch||h.ch==j.from.ch&&h.xRel>0))return h;e=j.to.line}}function Bb(a,b,c,d,e){function j(d){var e=yb(a,Gc(c,d),"line",b,i);return g=!0,f>e.bottom?e.left-h:fq)return zb(c,n,r,1);for(;;){if(k?n==m||n==Zf(b,m,1):1>=n-m){for(var s=o>d||q-d>=d-o?m:n,t=d-(s==m?o:q);yf(b.text.charAt(s));)++s;var u=zb(c,s,s==m?p:r,0>t?-1:t?1:0);return u}var v=Math.ceil(l/2),w=m+v;if(k){w=m;for(var x=0;v>x;++x)w=Zf(b,w,1)}var y=j(w);y>d?(n=w,q=y,(r=g)&&(q+=1e3),l=v):(m=w,o=y,p=g,l-=v)}}function Db(a){if(null!=a.cachedTextHeight)return a.cachedTextHeight;if(null==Cb){Cb=zf("pre");for(var b=0;49>b;++b)Cb.appendChild(document.createTextNode("x")),Cb.appendChild(zf("br"));Cb.appendChild(document.createTextNode("x"))}Bf(a.measure,Cb);var c=Cb.offsetHeight/50;return c>3&&(a.cachedTextHeight=c),Af(a.measure),c||1}function Eb(a){if(null!=a.cachedCharWidth)return a.cachedCharWidth;var b=zf("span","x"),c=zf("pre",[b]);Bf(a.measure,c);var d=b.offsetWidth;return d>2&&(a.cachedCharWidth=d),d||10}function Gb(a){a.curOp={changes:[],forceUpdate:!1,updateInput:null,userSelChange:null,textChanged:null,selectionChanged:!1,cursorActivity:!1,updateMaxLine:!1,updateScrollPos:!1,id:++Fb},af++||(_e=[])}function Hb(a){var b=a.curOp,c=a.doc,d=a.display;if(a.curOp=null,b.updateMaxLine&&L(a),d.maxLineChanged&&!a.options.lineWrapping&&d.maxLine){var e=rb(a,d.maxLine);d.sizer.style.minWidth=Math.max(0,e+3)+"px",d.maxLineChanged=!1;var f=Math.max(0,d.sizer.offsetLeft+d.sizer.offsetWidth-d.scroller.clientWidth);fj&&c.charCodeAt(j)==h.charCodeAt(j);)++j;var l=f.from,m=f.to,n=h.slice(j);j-1){$c(a,f.head.line,"smart");break}}return h.length>1e3||h.indexOf("\n")>-1?b.value=a.display.prevInput="":a.display.prevInput=h,i&&Hb(a),a.state.pasteIncoming=a.state.cutIncoming=!1,!0}function Pb(a,b){var c,e,f=a.doc;if(Hc(f.sel.from,f.sel.to))b&&(a.display.prevInput=a.display.input.value="",g&&!d&&(a.display.inputHasSelection=null));else{a.display.prevInput="",c=Mf&&(f.sel.to.line-f.sel.from.line>100||(e=a.getSelection()).length>1e3);var h=c?"-":e||a.getSelection();a.display.input.value=h,a.state.focused&&of(a.display.input),g&&!d&&(a.display.inputHasSelection=h)}a.display.inaccurateSelection=c}function Qb(a){"nocursor"==a.options.readOnly||r&&document.activeElement==a.display.input||a.display.input.focus()}function Rb(a){a.state.focused||(Qb(a),rc(a))}function Sb(a){return a.options.readOnly||a.doc.cantEdit}function Tb(a){function e(){a.state.focused&&setTimeout(tf(Qb,a),0)}function i(){null==f&&(f=setTimeout(function(){f=null,c.cachedCharWidth=c.cachedTextHeight=c.cachedPaddingH=Gf=null,sb(a),Kb(a,tf(Lb,a))},100))}function j(){for(var a=c.wrapper.parentNode;a&&a!=document.body;a=a.parentNode);a?setTimeout(j,5e3):Ze(window,"resize",i)}function k(b){cf(a,b)||a.options.onDragEvent&&a.options.onDragEvent(a,Re(b))||Ve(b)}function l(b){c.inaccurateSelection&&(c.prevInput="",c.inaccurateSelection=!1,c.input.value=a.getSelection(),of(c.input)),"cut"==b.type&&(a.state.cutIncoming=!0)}var c=a.display;Ye(c.scroller,"mousedown",Ib(a,Yb)),b?Ye(c.scroller,"dblclick",Ib(a,function(b){if(!cf(a,b)){var c=Vb(a,b);if(c&&!_b(a,b)&&!Ub(a.display,b)){Se(b);var d=cd(ye(a.doc,c.line).text,c);Pc(a.doc,d.from,d.to)}}})):Ye(c.scroller,"dblclick",function(b){cf(a,b)||Se(b)}),Ye(c.lineSpace,"selectstart",function(a){Ub(c,a)||Se(a)}),w||Ye(c.scroller,"contextmenu",function(b){uc(a,b)}),Ye(c.scroller,"scroll",function(){c.scroller.clientHeight&&(dc(a,c.scroller.scrollTop),ec(a,c.scroller.scrollLeft,!0),$e(a,"scroll",a))}),Ye(c.scrollbarV,"scroll",function(){c.scroller.clientHeight&&dc(a,c.scrollbarV.scrollTop)}),Ye(c.scrollbarH,"scroll",function(){c.scroller.clientHeight&&ec(a,c.scrollbarH.scrollLeft)}),Ye(c.scroller,"mousewheel",function(b){hc(a,b)}),Ye(c.scroller,"DOMMouseScroll",function(b){hc(a,b)}),Ye(c.scrollbarH,"mousedown",e),Ye(c.scrollbarV,"mousedown",e),Ye(c.wrapper,"scroll",function(){c.wrapper.scrollTop=c.wrapper.scrollLeft=0});var f;Ye(window,"resize",i),setTimeout(j,5e3),Ye(c.input,"keyup",Ib(a,nc)),Ye(c.input,"input",function(){g&&!d&&a.display.inputHasSelection&&(a.display.inputHasSelection=null),Nb(a)}),Ye(c.input,"keydown",Ib(a,pc)),Ye(c.input,"keypress",Ib(a,qc)),Ye(c.input,"focus",tf(rc,a)),Ye(c.input,"blur",tf(sc,a)),a.options.dragDrop&&(Ye(c.scroller,"dragstart",function(b){cc(a,b)}),Ye(c.scroller,"dragenter",k),Ye(c.scroller,"dragover",k),Ye(c.scroller,"drop",Ib(a,bc))),Ye(c.scroller,"paste",function(b){Ub(c,b)||(Qb(a),Nb(a))}),Ye(c.input,"paste",function(){if(h&&!a.state.fakedLastChar&&!(new Date-a.state.lastMiddleDown<200)){var b=c.input.selectionStart,d=c.input.selectionEnd; -c.input.value+="$",c.input.selectionStart=b,c.input.selectionEnd=d,a.state.fakedLastChar=!0}a.state.pasteIncoming=!0,Nb(a)}),Ye(c.input,"cut",l),Ye(c.input,"copy",l),m&&Ye(c.sizer,"mouseup",function(){document.activeElement==c.input&&c.input.blur(),Qb(a)})}function Ub(a,b){for(var c=We(b);c!=a.wrapper;c=c.parentNode)if(!c||c.ignoreEvents||c.parentNode==a.sizer&&c!=a.mover)return!0}function Vb(a,b,c){var d=a.display;if(!c){var e=We(b);if(e==d.scrollbarH||e==d.scrollbarH.firstChild||e==d.scrollbarV||e==d.scrollbarV.firstChild||e==d.scrollbarFiller||e==d.gutterFiller)return null}var f,g,h=Df(d.lineSpace);try{f=b.clientX,g=b.clientY}catch(b){return null}return Ab(a,f-h.left,g-h.top)}function Yb(a){function t(a){if(!Hc(s,a)){if(s=a,"single"==m)return Pc(c.doc,Mc(i,k),a),void 0;if(q=Mc(i,q),r=Mc(i,r),"double"==m){var b=cd(ye(i,a.line).text,a);Ic(a,q)?Pc(c.doc,b.from,r):Pc(c.doc,q,b.to)}else"triple"==m&&(Ic(a,q)?Pc(c.doc,r,Mc(i,Gc(a.line,0))):Pc(c.doc,q,Mc(i,Gc(a.line+1,0))))}}function x(a){var b=++v,d=Vb(c,a,!0);if(d)if(Hc(d,o)){var g=a.clientYu.bottom?20:0;g&&setTimeout(Ib(c,function(){v==b&&(f.scroller.scrollTop+=g,x(a))}),50)}else{Rb(c),o=d,t(d);var e=O(f,i);(d.line>=e.to||d.linel-400&&Hc(Xb.pos,k))m="triple",Se(a),setTimeout(tf(Qb,c),20),dd(c,k.line);else if(Wb&&Wb.time>l-400&&Hc(Wb.pos,k)){m="double",Xb={time:l,pos:k},Se(a);var n=cd(ye(i,k.line).text,k);Pc(c.doc,n.from,n.to)}else Wb={time:l,pos:k};var o=k;if(c.options.dragDrop&&Ef&&!Sb(c)&&!Hc(j.from,j.to)&&!Ic(k,j.from)&&!Ic(j.to,k)&&"single"==m){var p=Ib(c,function(e){h&&(f.scroller.draggable=!1),c.state.draggingText=!1,Ze(document,"mouseup",p),Ze(f.scroller,"drop",p),Math.abs(a.clientX-e.clientX)+Math.abs(a.clientY-e.clientY)<10&&(Se(e),Pc(c.doc,k),Qb(c),b&&!d&&setTimeout(function(){document.body.focus(),Qb(c)},20))});return h&&(f.scroller.draggable=!0),c.state.draggingText=p,f.scroller.dragDrop&&f.scroller.dragDrop(),Ye(document,"mouseup",p),Ye(f.scroller,"drop",p),void 0}Se(a),"single"==m&&Pc(c.doc,Mc(i,k));var q=j.from,r=j.to,s=k,u=Df(f.wrapper),v=0,z=Ib(c,function(a){(g&&!e?a.buttons:Xe(a))?x(a):y(a)}),A=Ib(c,y);Ye(document,"mousemove",z),Ye(document,"mouseup",A)}}}function Zb(a,b,c,d,e){try{var f=b.clientX,g=b.clientY}catch(b){return!1}if(f>=Math.floor(Df(a.display.gutters).right))return!1;d&&Se(b);var h=a.display,i=Df(h.lineDiv);if(g>i.bottom||!ef(a,c))return Ue(b);g-=i.top-h.viewOffset;for(var j=0;j=f){var l=De(a.doc,g),m=a.options.gutters[j];return e(a,c,a,l,m,b),Ue(b)}}}function $b(a,b){return ef(a,"gutterContextMenu")?Zb(a,b,"gutterContextMenu",!1,$e):!1}function _b(a,b){return Zb(a,b,"gutterClick",!0,bf)}function bc(a){var b=this;if(!(cf(b,a)||Ub(b.display,a)||b.options.onDragEvent&&b.options.onDragEvent(b,Re(a)))){Se(a),g&&(ac=+new Date);var c=Vb(b,a,!0),d=a.dataTransfer.files;if(c&&!Sb(b))if(d&&d.length&&window.FileReader&&window.File)for(var e=d.length,f=Array(e),h=0,i=function(a,d){var g=new FileReader;g.onload=function(){f[d]=g.result,++h==e&&(c=Mc(b.doc,c),zc(b.doc,{from:c,to:c,text:Kf(f.join("\n")),origin:"paste"},"around"))},g.readAsText(a)},j=0;e>j;++j)i(d[j],j);else{if(b.state.draggingText&&!Ic(c,b.doc.sel.from)&&!Ic(b.doc.sel.to,c))return b.state.draggingText(a),setTimeout(tf(Qb,b),20),void 0;try{var f=a.dataTransfer.getData("Text");if(f){var k=b.doc.sel.from,l=b.doc.sel.to;Rc(b.doc,c,c),b.state.draggingText&&Fc(b.doc,"",k,l,"paste"),b.replaceSelection(f,null,"paste"),Qb(b)}}catch(a){}}}}function cc(a,b){if(g&&(!a.state.draggingText||+new Date-ac<100))return Ve(b),void 0;if(!cf(a,b)&&!Ub(a.display,b)){var c=a.getSelection();if(b.dataTransfer.setData("Text",c),b.dataTransfer.setDragImage&&!l){var d=zf("img",null,null,"position: fixed; left: 0; top: 0;");d.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",k&&(d.width=d.height=1,a.display.wrapper.appendChild(d),d._top=d.offsetTop),b.dataTransfer.setDragImage(d,0,0),k&&d.parentNode.removeChild(d)}}}function dc(b,c){Math.abs(b.doc.scrollTop-c)<2||(b.doc.scrollTop=c,a||T(b,[],c),b.display.scroller.scrollTop!=c&&(b.display.scroller.scrollTop=c),b.display.scrollbarV.scrollTop!=c&&(b.display.scrollbarV.scrollTop=c),a&&T(b,[]),eb(b,100))}function ec(a,b,c){(c?b==a.doc.scrollLeft:Math.abs(a.doc.scrollLeft-b)<2)||(b=Math.min(b,a.display.scroller.scrollWidth-a.display.scroller.clientWidth),a.doc.scrollLeft=b,P(a),a.display.scroller.scrollLeft!=b&&(a.display.scroller.scrollLeft=b),a.display.scrollbarH.scrollLeft!=b&&(a.display.scrollbarH.scrollLeft=b))}function hc(b,c){var d=c.wheelDeltaX,e=c.wheelDeltaY;null==d&&c.detail&&c.axis==c.HORIZONTAL_AXIS&&(d=c.detail),null==e&&c.detail&&c.axis==c.VERTICAL_AXIS?e=c.detail:null==e&&(e=c.wheelDelta);var f=b.display,g=f.scroller;if(d&&g.scrollWidth>g.clientWidth||e&&g.scrollHeight>g.clientHeight){if(e&&s&&h)for(var i=c.target;i!=g;i=i.parentNode)if(i.lineObj){b.display.currentWheelTarget=i;break}if(d&&!a&&!k&&null!=gc)return e&&dc(b,Math.max(0,Math.min(g.scrollTop+e*gc,g.scrollHeight-g.clientHeight))),ec(b,Math.max(0,Math.min(g.scrollLeft+d*gc,g.scrollWidth-g.clientWidth))),Se(c),f.wheelStartX=null,void 0;if(e&&null!=gc){var j=e*gc,l=b.doc.scrollTop,m=l+f.wrapper.clientHeight;0>j?l=Math.max(0,l+j-50):m=Math.min(b.doc.height,m+j+50),T(b,[],{top:l,bottom:m})}20>fc&&(null==f.wheelStartX?(f.wheelStartX=g.scrollLeft,f.wheelStartY=g.scrollTop,f.wheelDX=d,f.wheelDY=e,setTimeout(function(){if(null!=f.wheelStartX){var a=g.scrollLeft-f.wheelStartX,b=g.scrollTop-f.wheelStartY,c=b&&f.wheelDY&&b/f.wheelDY||a&&f.wheelDX&&a/f.wheelDX;f.wheelStartX=f.wheelStartY=null,c&&(gc=(gc*fc+c)/(fc+1),++fc)}},200)):(f.wheelDX+=d,f.wheelDY+=e))}}function ic(a,b,c){if("string"==typeof b&&(b=pd[b],!b))return!1;a.display.pollingFast&&Ob(a)&&(a.display.pollingFast=!1);var d=a.doc,e=d.sel.shift,f=!1;try{Sb(a)&&(a.state.suppressEdits=!0),c&&(d.sel.shift=!1),f=b(a)!=hf}finally{d.sel.shift=e,a.state.suppressEdits=!1}return f}function jc(a){var b=a.state.keyMaps.slice(0);return a.options.extraKeys&&b.push(a.options.extraKeys),b.push(a.options.keyMap),b}function lc(a,b){var c=rd(a.options.keyMap),e=c.auto;clearTimeout(kc),e&&!td(b)&&(kc=setTimeout(function(){rd(a.options.keyMap)==c&&(a.options.keyMap=e.call?e.call(null,a):e,G(a))},50));var f=ud(b,!0),g=!1;if(!f)return!1;var h=jc(a);return g=b.shiftKey?sd("Shift-"+f,h,function(b){return ic(a,b,!0)})||sd(f,h,function(b){return("string"==typeof b?/^go[A-Z]/.test(b):b.motion)?ic(a,b):void 0}):sd(f,h,function(b){return ic(a,b)}),g&&(Se(b),db(a),d&&(b.oldKeyCode=b.keyCode,b.keyCode=0),bf(a,"keyHandled",a,f,b)),g}function mc(a,b,c){var d=sd("'"+c+"'",jc(a),function(b){return ic(a,b,!0)});return d&&(Se(b),db(a),bf(a,"keyHandled",a,"'"+c+"'",b)),d}function nc(a){var b=this;cf(b,a)||b.options.onKeyEvent&&b.options.onKeyEvent(b,Re(a))||16==a.keyCode&&(b.doc.sel.shift=!1)}function pc(a){var c=this;if(Rb(c),!(cf(c,a)||c.options.onKeyEvent&&c.options.onKeyEvent(c,Re(a)))){b&&27==a.keyCode&&(a.returnValue=!1);var d=a.keyCode;c.doc.sel.shift=16==d||a.shiftKey;var e=lc(c,a);k&&(oc=e?d:null,!e&&88==d&&!Mf&&(s?a.metaKey:a.ctrlKey)&&c.replaceSelection(""))}}function qc(a){var b=this;if(!(cf(b,a)||b.options.onKeyEvent&&b.options.onKeyEvent(b,Re(a)))){var c=a.keyCode,e=a.charCode;if(k&&c==oc)return oc=null,Se(a),void 0;if(!(k&&(!a.which||a.which<10)||m)||!lc(b,a)){var f=String.fromCharCode(null==e?c:e);mc(b,a,f)||(g&&!d&&(b.display.inputHasSelection=null),Nb(b))}}}function rc(a){"nocursor"!=a.options.readOnly&&(a.state.focused||($e(a,"focus",a),a.state.focused=!0,-1==a.display.wrapper.className.search(/\bCodeMirror-focused\b/)&&(a.display.wrapper.className+=" CodeMirror-focused"),a.curOp||(Pb(a,!0),h&&setTimeout(tf(Pb,a,!0),0))),Mb(a),db(a))}function sc(a){a.state.focused&&($e(a,"blur",a),a.state.focused=!1,a.display.wrapper.className=a.display.wrapper.className.replace(" CodeMirror-focused","")),clearInterval(a.display.blinker),setTimeout(function(){a.state.focused||(a.doc.sel.shift=!1)},150)}function uc(a,b){function l(){if(null!=c.input.selectionStart){var a=c.input.value="\u200b"+(Hc(e.from,e.to)?"":c.input.value);c.prevInput="\u200b",c.input.selectionStart=1,c.input.selectionEnd=a.length}}function m(){if(c.inputDiv.style.position="relative",c.input.style.cssText=j,d&&(c.scrollbarV.scrollTop=c.scroller.scrollTop=h),Mb(a),null!=c.input.selectionStart){(!g||d)&&l(),clearTimeout(tc);var b=0,e=function(){"\u200b"==c.prevInput&&0==c.input.selectionStart?Ib(a,pd.selectAll)(a):b++<10?tc=setTimeout(e,500):Pb(a)};tc=setTimeout(e,200)}}if(!cf(a,b,"contextmenu")){var c=a.display,e=a.doc.sel;if(!Ub(c,b)&&!$b(a,b)){var f=Vb(a,b),h=c.scroller.scrollTop;if(f&&!k){var i=a.options.resetSelectionOnContextMenu;i&&(Hc(e.from,e.to)||Ic(f,e.from)||!Ic(f,e.to))&&Ib(a,Rc)(a.doc,f,f);var j=c.input.style.cssText;if(c.inputDiv.style.position="absolute",c.input.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(b.clientY-5)+"px; left: "+(b.clientX-5)+"px; z-index: 1000; background: transparent; outline: none;"+"border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);",Qb(a),Pb(a,!0),Hc(e.from,e.to)&&(c.input.value=c.prevInput=" "),g&&!d&&l(),w){Ve(b);var n=function(){Ze(window,"mouseup",n),setTimeout(m,20)};Ye(window,"mouseup",n)}else setTimeout(m,50)}}}}function wc(a,b,c){if(!Ic(b.from,c))return Mc(a,c);var d=b.text.length-1-(b.to.line-b.from.line);if(c.line>b.to.line+d){var e=c.line-d,f=a.first+a.size-1;return e>f?Gc(f,ye(a,f).text.length):Nc(c,ye(a,e).text.length)}if(c.line==b.to.line+d)return Nc(c,nf(b.text).length+(1==b.text.length?b.from.ch:0)+ye(a,b.to.line).text.length-b.to.ch);var g=c.line-b.from.line;return Nc(c,b.text[g].length+(g?0:b.from.ch))}function xc(a,b,c){if(c&&"object"==typeof c)return{anchor:wc(a,b,c.anchor),head:wc(a,b,c.head)};if("start"==c)return{anchor:b.from,head:b.from};var d=vc(b);if("around"==c)return{anchor:b.from,head:d};if("end"==c)return{anchor:d,head:d};var e=function(a){if(Ic(a,b.from))return a;if(!Ic(b.to,a))return d;var c=a.line+b.text.length-(b.to.line-b.from.line)-1,e=a.ch;return a.line==b.to.line&&(e+=d.ch-b.to.ch),Gc(c,e)};return{anchor:e(a.sel.anchor),head:e(a.sel.head)}}function yc(a,b,c){var d={canceled:!1,from:b.from,to:b.to,text:b.text,origin:b.origin,cancel:function(){this.canceled=!0}};return c&&(d.update=function(b,c,d,e){b&&(this.from=Mc(a,b)),c&&(this.to=Mc(a,c)),d&&(this.text=d),void 0!==e&&(this.origin=e)}),$e(a,"beforeChange",a,d),a.cm&&$e(a.cm,"beforeChange",a.cm,d),d.canceled?null:{from:d.from,to:d.to,text:d.text,origin:d.origin}}function zc(a,b,c,d){if(a.cm){if(!a.cm.curOp)return Ib(a.cm,zc)(a,b,c,d);if(a.cm.state.suppressEdits)return}if(!(ef(a,"beforeChange")||a.cm&&ef(a.cm,"beforeChange"))||(b=yc(a,b,!0))){var e=x&&!d&&Jd(a,b.from,b.to);if(e){for(var f=e.length-1;f>=1;--f)Ac(a,{from:e[f].from,to:e[f].to,text:[""]});e.length&&Ac(a,{from:e[0].from,to:e[0].to,text:b.text},c)}else Ac(a,b,c)}}function Ac(a,b,c){if(1!=b.text.length||""!=b.text[0]||!Hc(b.from,b.to)){var d=xc(a,b,c);Je(a,b,d,a.cm?a.cm.curOp.id:0/0),Dc(a,b,d,Gd(a,b));var e=[];we(a,function(a,c){c||-1!=pf(e,a.history)||(Pe(a.history,b),e.push(a.history)),Dc(a,b,null,Gd(a,b))})}}function Bc(a,b){if(!a.cm||!a.cm.state.suppressEdits){var c=a.history,d=("undo"==b?c.done:c.undone).pop();if(d){var e={changes:[],anchorBefore:d.anchorAfter,headBefore:d.headAfter,anchorAfter:d.anchorBefore,headAfter:d.headBefore,generation:c.generation};("undo"==b?c.undone:c.done).push(e),c.generation=d.generation||++c.maxGeneration;for(var f=ef(a,"beforeChange")||a.cm&&ef(a.cm,"beforeChange"),g=d.changes.length-1;g>=0;--g){var h=d.changes[g];if(h.origin=b,f&&!yc(a,h,!1))return("undo"==b?c.done:c.undone).length=0,void 0;e.changes.push(Ie(a,h));var i=g?xc(a,h,null):{anchor:d.anchorBefore,head:d.headBefore};Dc(a,h,i,Id(a,h));var j=[];we(a,function(a,b){b||-1!=pf(j,a.history)||(Pe(a.history,h),j.push(a.history)),Dc(a,h,null,Id(a,h))})}}}}function Cc(a,b){function c(a){return Gc(a.line+b,a.ch)}a.first+=b,a.cm&&Lb(a.cm,a.first,a.first,b),a.sel.head=c(a.sel.head),a.sel.anchor=c(a.sel.anchor),a.sel.from=c(a.sel.from),a.sel.to=c(a.sel.to)}function Dc(a,b,c,d){if(a.cm&&!a.cm.curOp)return Ib(a.cm,Dc)(a,b,c,d);if(b.to.linea.lastLine())){if(b.from.linef&&(b={from:b.from,to:Gc(f,ye(a,f).text.length),text:[b.text[0]],origin:b.origin}),b.removed=ze(a,b.from,b.to),c||(c=xc(a,b,null)),a.cm?Ec(a.cm,b,d,c):pe(a,b,d,c)}}function Ec(a,b,c,d){var e=a.doc,f=a.display,g=b.from,h=b.to,i=!1,j=g.line;a.options.lineWrapping||(j=Ce(Rd(e,ye(e,g.line))),e.iter(j,h.line+1,function(a){return a==f.maxLine?(i=!0,!0):void 0})),Ic(e.sel.head,b.from)||Ic(b.to,e.sel.head)||(a.curOp.cursorActivity=!0),pe(e,b,c,d,E(a)),a.options.lineWrapping||(e.iter(j,g.line+b.text.length,function(a){var b=K(e,a);b>f.maxLineLength&&(f.maxLine=a,f.maxLineLength=b,f.maxLineChanged=!0,i=!1)}),i&&(a.curOp.updateMaxLine=!0)),e.frontier=Math.min(e.frontier,g.line),eb(a,400);var k=b.text.length-(h.line-g.line)-1;if(Lb(a,g.line,h.line+1,k),ef(a,"change")){var l={from:g,to:h,text:b.text,removed:b.removed,origin:b.origin};if(a.curOp.textChanged){for(var m=a.curOp.textChanged;m.next;m=m.next);m.next=l}else a.curOp.textChanged=l}}function Fc(a,b,c,d,e){if(d||(d=c),Ic(d,c)){var f=d;d=c,c=f}"string"==typeof b&&(b=Kf(b)),zc(a,{from:c,to:d,text:b,origin:e},null)}function Gc(a,b){return this instanceof Gc?(this.line=a,this.ch=b,void 0):new Gc(a,b)}function Hc(a,b){return a.line==b.line&&a.ch==b.ch}function Ic(a,b){return a.linec?Gc(c,ye(a,c).text.length):Nc(b,ye(a,b.line).text.length)}function Nc(a,b){var c=a.ch;return null==c||c>b?Gc(a.line,b):0>c?Gc(a.line,0):a}function Oc(a,b){return b>=a.first&&b=f.ch:j.to>f.ch))){if(d&&($e(k,"beforeCursorEnter"),k.explicitlyCleared)){if(h.markedSpans){--i;continue}break}if(!k.atomic)continue;var l=k.find()[0>g?"from":"to"];if(Hc(l,f)&&(l.ch+=g,l.ch<0?l=l.line>a.first?Mc(a,Gc(l.line-1)):null:l.ch>h.text.length&&(l=l.line(window.innerHeight||document.documentElement.clientHeight)&&(e=!1),null!=e&&!p){var f=zf("div","\u200b",null,"position: absolute; top: "+(b.top-c.viewOffset)+"px; height: "+(b.bottom-b.top+gf)+"px; left: "+b.left+"px; width: 2px;");a.display.lineSpace.appendChild(f),f.scrollIntoView(e),a.display.lineSpace.removeChild(f)}}}function Vc(a,b,c,d){for(null==d&&(d=0);;){var e=!1,f=yb(a,b),g=c&&c!=b?yb(a,c):f,h=Xc(a,Math.min(f.left,g.left),Math.min(f.top,g.top)-d,Math.max(f.left,g.left),Math.max(f.bottom,g.bottom)+d),i=a.doc.scrollTop,j=a.doc.scrollLeft;if(null!=h.scrollTop&&(dc(a,h.scrollTop),Math.abs(a.doc.scrollTop-i)>1&&(e=!0)),null!=h.scrollLeft&&(ec(a,h.scrollLeft),Math.abs(a.doc.scrollLeft-j)>1&&(e=!0)),!e)return f}}function Wc(a,b,c,d,e){var f=Xc(a,b,c,d,e);null!=f.scrollTop&&dc(a,f.scrollTop),null!=f.scrollLeft&&ec(a,f.scrollLeft)}function Xc(a,b,c,d,e){var f=a.display,g=Db(a.display);0>c&&(c=0);var h=f.scroller.clientHeight-gf,i=f.scroller.scrollTop,j={},k=a.doc.height+jb(f),l=g>c,m=e>k-g;if(i>c)j.scrollTop=l?0:c;else if(e>i+h){var n=Math.min(c,(m?k:e)-h);n!=i&&(j.scrollTop=n)}var o=f.scroller.clientWidth-gf,p=f.scroller.scrollLeft;b+=f.gutters.offsetWidth,d+=f.gutters.offsetWidth;var q=f.gutters.offsetWidth,r=q+10>b;return p+q>b||r?(r&&(b=0),j.scrollLeft=Math.max(0,b-10-q)):d>o+p-3&&(j.scrollLeft=d+10-o),j}function Yc(a,b,c){a.curOp.updateScrollPos={scrollLeft:null==b?a.doc.scrollLeft:b,scrollTop:null==c?a.doc.scrollTop:c}}function Zc(a,b,c){var d=a.curOp.updateScrollPos||(a.curOp.updateScrollPos={scrollLeft:a.doc.scrollLeft,scrollTop:a.doc.scrollTop}),e=a.display.scroller;d.scrollTop=Math.max(0,Math.min(e.scrollHeight-e.clientHeight,d.scrollTop+c)),d.scrollLeft=Math.max(0,Math.min(e.scrollWidth-e.clientWidth,d.scrollLeft+b))}function $c(a,b,c,d){var f,e=a.doc;null==c&&(c="add"),"smart"==c&&(a.doc.mode.indent?f=hb(a,b):c="prev");var g=a.options.tabSize,h=ye(e,b),i=kf(h.text,null,g);h.stateAfter&&(h.stateAfter=null);var k,j=h.text.match(/^\s*/)[0];if(d||/\S/.test(h.text)){if("smart"==c&&(k=a.doc.mode.indent(f,h.text.slice(j.length),h.text),k==hf)){if(!d)return;c="prev"}}else k=0,c="not";"prev"==c?k=b>e.first?kf(ye(e,b-1).text,null,g):0:"add"==c?k=i+a.options.indentUnit:"subtract"==c?k=i-a.options.indentUnit:"number"==typeof c&&(k=i+c),k=Math.max(0,k);var l="",m=0;if(a.options.indentWithTabs)for(var n=Math.floor(k/g);n;--n)m+=g,l+=" ";k>m&&(l+=mf(k-m)),l!=j?Fc(a.doc,l,Gc(b,0),Gc(b,j.length),"+input"):e.sel.head.line==b&&e.sel.head.ch=a.first+a.size?j=!1:(f=b,i=ye(a,b))}function l(a){var b=(e?Zf:$f)(i,g,c,!0);if(null==b){if(a||!k())return j=!1;g=e?(0>c?Sf:Rf)(i):0>c?i.text.length:0}else g=b;return!0}var f=b.line,g=b.ch,h=c,i=ye(a,f),j=!0;if("char"==d)l();else if("column"==d)l(!0);else if("word"==d||"group"==d)for(var m=null,n="group"==d,o=!0;!(0>c)||l(!o);o=!1){var p=i.text.charAt(g)||"\n",q=vf(p)?"w":n&&"\n"==p?"n":!n||/\s/.test(p)?null:"p";if(!n||o||q||(q="s"),m&&m!=q){0>c&&(c=1,l());break}if(q&&(m=q),c>0&&!l(!o))break}var r=Tc(a,Gc(f,g),h,!0);return j||(r.hitSide=!0),r}function bd(a,b,c,d){var g,e=a.doc,f=b.left;if("page"==d){var h=Math.min(a.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);g=b.top+c*(h-(0>c?1.5:.5)*Db(a.display))}else"line"==d&&(g=c>0?b.bottom+3:b.top-3);for(;;){var i=Ab(a,f,g);if(!i.outside)break;if(0>c?0>=g:g>=e.height){i.hitSide=!0;break}g+=5*c}return i}function cd(a,b){var c=b.ch,d=b.ch;if(a){(b.xRel<0||d==a.length)&&c?--c:++d;for(var e=a.charAt(c),f=vf(e)?vf:/\s/.test(e)?function(a){return/\s/.test(a)}:function(a){return!/\s/.test(a)&&!vf(a)};c>0&&f(a.charAt(c-1));)--c;for(;dg;++g){var i=d(f[g]);if(i)return i}return!1}for(var e=0;e=b:f.to>b);(e||(e=[])).push({from:f.from,to:i?null:f.to,marker:g})}}return e}function Fd(a,b,c){if(a)for(var e,d=0;d=b:f.to>b);if(h||f.from==b&&"bookmark"==g.type&&(!c||f.marker.insertLeft)){var i=null==f.from||(g.inclusiveLeft?f.from<=b:f.from0&&h)for(var l=0;ll;++l)o.push(q);o.push(i)}return o}function Hd(a){for(var b=0;b=0&&0>=l||0>=k&&l>=0)&&(0>=k&&(Jc(j.to,c)||Ld(i.marker)-Kd(e))>0||k>=0&&(Jc(j.from,d)||Kd(i.marker)-Ld(e))<0))return!0}}}function Rd(a,b){for(var c;c=Od(b);)b=ye(a,c.find().from.line);return b}function Sd(a,b){var c=y&&b.markedSpans;if(c)for(var d,e=0;ea.options.maxHighlightLength?(g=!1,f&&ee(a,b,d,j.pos),j.pos=b.length,k=null):k=c.token(j,d),a.options.addModeClass){var l=z.innerMode(c,d).mode.name;l&&(k="m-"+(k?l+" "+k:l))}g&&i==k||(hi;){var d=e[h];d>a&&e.splice(h,1,a,e[h+1],d),h+=2,i=Math.min(a,d)}if(b)if(g.opaque)e.splice(c,h-c,a,b),h=c+2;else for(;h>c;c+=2){var f=e[c+1];e[c+1]=f?f+" "+b:b}})}return e}function de(a,b){return b.styles&&b.styles[0]==a.state.modeGen||(b.styles=ce(a,b,b.stateAfter=hb(a,Ce(b)))),b.styles}function ee(a,b,c,d){var e=a.doc.mode,f=new vd(b,a.options.tabSize);for(f.start=f.pos=d||0,""==b&&e.blankLine&&e.blankLine(c);!f.eol()&&f.pos<=a.options.maxHighlightLength;)e.token(f,c),f.start=f.pos}function he(a,b){if(!a)return null;for(;;){var c=a.match(/(?:^|\s+)line-(background-)?(\S+)/);if(!c)break;a=a.slice(0,c.index)+a.slice(c.index+c[0].length);var d=c[1]?"bgClass":"textClass";null==b[d]?b[d]=c[2]:new RegExp("(?:^|s)"+c[2]+"(?:$|s)").test(b[d])||(b[d]+=" "+c[2])}if(/^\s*$/.test(a))return null;var e=b.cm.options.addModeClass?ge:fe;return e[a]||(e[a]=a.replace(/\S+/g,"cm-$&"))}function ie(a,b,c,d){for(var e,f=b,i=!0;e=Od(f);)f=ye(a.doc,e.find().from.line);var j={pre:zf("pre"),col:0,pos:0,measure:null,measuredSomething:!1,cm:a,copyWidgets:d};do{f.text&&(i=!1),j.measure=f==b&&c,j.pos=0,j.addToken=j.measure?le:ke,(g||h)&&a.getOption("lineWrapping")&&(j.addToken=me(j.addToken));var k=oe(f,j,de(a,f));c&&f==b&&!j.measuredSomething&&(c[0]=j.pre.appendChild(Jf(a.display.measure)),j.measuredSomething=!0),k&&(f=ye(a.doc,k.to.line))}while(k);!c||j.measuredSomething||c[0]||(c[0]=j.pre.appendChild(i?zf("span","\xa0"):Jf(a.display.measure))),j.pre.firstChild||Sd(a.doc,b)||j.pre.appendChild(document.createTextNode("\xa0"));var l;if(c&&g&&(l=Fe(f))){var m=l.length-1;l[m].from==l[m].to&&--m;var n=l[m],o=l[m-1];if(n.from+1==n.to&&o&&n.leveli)?(null!=t.to&&l>t.to&&(l=t.to,n=""),u.className&&(m+=" "+u.className),u.startStyle&&t.from==i&&(o+=" "+u.startStyle),u.endStyle&&t.to==l&&(n+=" "+u.endStyle),u.title&&!p&&(p=u.title),u.collapsed&&(!q||Md(q.marker,u)<0)&&(q=t)):t.from>i&&l>t.from&&(l=t.from),"bookmark"==u.type&&t.from==i&&u.replacedWith&&r.push(u)}if(q&&(q.from||0)==i&&(ne(b,(null==q.to?h:q.to)-i,q.marker,null==q.from),null==q.to))return q.marker.find();if(!q&&r.length)for(var s=0;s=h)break;for(var v=Math.min(h,l);;){if(j){var w=i+j.length;if(!q){var x=w>v?j.slice(0,v-i):j;b.addToken(b,x,k?k+m:m,o,i+x.length==l?n:"",p)}if(w>=v){j=j.slice(v-i),i=v;break}i=w,o=""}j=e.slice(f,f=c[g++]),k=he(c[g++],b)}}else for(var g=1;gp;++p)r.push(new $d(j[p],f(p),e));r.push(new $d(m+k.text.slice(i.ch),n,e)),g(k,k.text.slice(0,h.ch)+j[0],f(0)),a.insert(h.line+1,r)}else if(1==j.length)g(k,k.text.slice(0,h.ch)+j[0]+l.text.slice(i.ch),f(0)),a.remove(h.line+1,o);else{g(k,k.text.slice(0,h.ch)+j[0],f(0)),g(l,m+l.text.slice(i.ch),n);for(var p=1,q=j.length-1,r=[];q>p;++p)r.push(new $d(j[p],f(p),e));o>1&&a.remove(h.line+1,o-1),a.insert(h.line+1,r)}else{for(var p=0,q=j.length-1,r=[];q>p;++p)r.push(new $d(j[p],f(p),e));g(l,l.text,n),o&&a.remove(h.line,o),r.length&&a.insert(h.line,r)}bf(a,"change",a,b),Rc(a,d.anchor,d.head,null,!0)}function qe(a){this.lines=a,this.parent=null;for(var b=0,c=a.length,d=0;c>b;++b)a[b].parent=this,d+=a[b].height;this.height=d}function re(a){this.children=a;for(var b=0,c=0,d=0,e=a.length;e>d;++d){var f=a[d];b+=f.chunkSize(),c+=f.height,f.parent=this}this.size=b,this.height=c,this.parent=null}function we(a,b,c){function d(a,e,f){if(a.linked)for(var g=0;gb){a=d;break}b-=e}return a.lines[b]}function ze(a,b,c){var d=[],e=b.line;return a.iter(b.line,c.line+1,function(a){var f=a.text;e==c.line&&(f=f.slice(0,c.ch)),e==b.line&&(f=f.slice(b.ch)),d.push(f),++e}),d}function Ae(a,b,c){var d=[];return a.iter(b,c,function(a){d.push(a.text)}),d}function Be(a,b){for(var c=b-a.height,d=a;d;d=d.parent)d.height+=c}function Ce(a){if(null==a.parent)return null;for(var b=a.parent,c=pf(b.lines,a),d=b.parent;d;b=d,d=d.parent)for(var e=0;d.children[e]!=b;++e)c+=d.children[e].chunkSize();return c+b.first}function De(a,b){var c=a.first;a:do{for(var d=0,e=a.children.length;e>d;++d){var f=a.children[d],g=f.height;if(g>b){a=f;continue a}b-=g,c+=f.chunkSize()}return c}while(!a.lines);for(var d=0,e=a.lines.length;e>d;++d){var h=a.lines[d],i=h.height;if(i>b)break;b-=i}return c+d}function Ee(a,b){b=Rd(a.doc,b);for(var c=0,d=b.parent,e=0;ef-a.cm.options.historyEventDelay||"*"==b.origin.charAt(0)))){var h=nf(g.changes);Hc(b.from,b.to)&&Hc(b.from,h.to)?h.to=vc(b):g.changes.push(Ie(a,b)),g.anchorAfter=c.anchor,g.headAfter=c.head}else for(g={changes:[Ie(a,b)],generation:e.generation,anchorBefore:a.sel.anchor,headBefore:a.sel.head,anchorAfter:c.anchor,headAfter:c.head},e.done.push(g);e.done.length>e.undoDepth;)e.done.shift();e.generation=++e.maxGeneration,e.lastTime=f,e.lastOp=d,e.lastOrigin=b.origin,h||$e(a,"historyAdded")}function Ke(a){if(!a)return null;for(var c,b=0;b-1&&(nf(g)[k]=i[k],delete i[k])}}return d}function Ne(a,b,c,d){c0}function ff(a){a.prototype.on=function(a,b){Ye(this,a,b)},a.prototype.off=function(a,b){Ze(this,a,b)}}function jf(){this.id=null}function kf(a,b,c,d,e){null==b&&(b=a.search(/[^\s\u00a0]/),-1==b&&(b=a.length));for(var f=d||0,g=e||0;b>f;++f)" "==a.charAt(f)?g+=c-g%c:++g;return g}function mf(a){for(;lf.length<=a;)lf.push(nf(lf)+" ");return lf[a]}function nf(a){return a[a.length-1]}function of(a){if(q)a.selectionStart=0,a.selectionEnd=a.value.length;else try{a.select()}catch(b){}}function pf(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;++c)if(a[c]==b)return c;return-1}function qf(a,b){function c(){}c.prototype=a;var d=new c;return b&&rf(b,d),d}function rf(a,b){b||(b={});for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function sf(a){for(var b=[],c=0;a>c;++c)b.push(void 0);return b}function tf(a){var b=Array.prototype.slice.call(arguments,1);return function(){return a.apply(null,b)}}function vf(a){return/\w/.test(a)||a>"\x80"&&(a.toUpperCase()!=a.toLowerCase()||uf.test(a))}function wf(a){for(var b in a)if(a.hasOwnProperty(b)&&a[b])return!1;return!0}function yf(a){return a.charCodeAt(0)>=768&&xf.test(a)}function zf(a,b,c,d){var e=document.createElement(a);if(c&&(e.className=c),d&&(e.style.cssText=d),"string"==typeof b)Cf(e,b);else if(b)for(var f=0;f0;--b)a.removeChild(a.firstChild);return a}function Bf(a,b){return Af(a).appendChild(b)}function Cf(a,b){d?(a.innerHTML="",a.appendChild(document.createTextNode(b))):a.textContent=b}function Df(a){return a.getBoundingClientRect()}function Ff(){return!1}function Hf(a){if(null!=Gf)return Gf;var b=zf("div",null,null,"width: 50px; height: 50px; overflow-x: scroll");return Bf(a,b),b.offsetWidth&&(Gf=b.offsetHeight-b.clientHeight),Gf||0}function Jf(a){if(null==If){var b=zf("span","\u200b");Bf(a,zf("span",[b,document.createTextNode("x")])),0!=a.firstChild.offsetHeight&&(If=b.offsetWidth<=1&&b.offsetHeight>2&&!c)}return If?zf("span","\u200b"):zf("span","\xa0",null,"display: inline-block; width: 1px; margin-right: -1px")}function Of(a,b,c,d){if(!a)return d(b,c,"ltr");for(var e=!1,f=0;fb||b==c&&g.to==b)&&(d(Math.max(g.from,b),Math.min(g.to,c),1==g.level?"rtl":"ltr"),e=!0)}e||d(b,c,"ltr")}function Pf(a){return a.level%2?a.to:a.from}function Qf(a){return a.level%2?a.from:a.to}function Rf(a){var b=Fe(a);return b?Pf(b[0]):0}function Sf(a){var b=Fe(a);return b?Qf(nf(b)):a.text.length}function Tf(a,b){var c=ye(a.doc,b),d=Rd(a.doc,c);d!=c&&(b=Ce(d));var e=Fe(d),f=e?e[0].level%2?Sf(d):Rf(d):0;return Gc(b,f)}function Uf(a,b){for(var c,d;c=Pd(d=ye(a.doc,b));)b=c.find().to.line;var e=Fe(d),f=e?e[0].level%2?Rf(d):Sf(d):d.text.length;return Gc(b,f)}function Vf(a,b,c){var d=a[0].level;return b==d?!0:c==d?!1:c>b}function Xf(a,b){Wf=null;for(var d,c=0;cb)return c;if(e.from==b||e.to==b){if(null!=d)return Vf(a,e.level,a[d].level)?(e.from!=e.to&&(Wf=d),c):(e.from!=e.to&&(Wf=c),d);d=c}}return d}function Yf(a,b,c,d){if(!d)return b+c;do b+=c;while(b>0&&yf(a.text.charAt(b)));return b}function Zf(a,b,c,d){var e=Fe(a);if(!e)return $f(a,b,c,d);for(var f=Xf(e,b),g=e[f],h=Yf(a,b,g.level%2?-c:c,d);;){if(h>g.from&&h0==g.level%2?g.to:g.from);if(g=e[f+=c],!g)return null;h=c>0==g.level%2?Yf(a,g.to,-1,d):Yf(a,g.from,1,d)}}function $f(a,b,c,d){var e=b+c;if(d)for(;e>0&&yf(a.text.charAt(e));)e+=c;return 0>e||e>a.text.length?null:e}var a=/gecko\/\d/i.test(navigator.userAgent),b=/MSIE \d/.test(navigator.userAgent),c=b&&(null==document.documentMode||document.documentMode<8),d=b&&(null==document.documentMode||document.documentMode<9),e=b&&(null==document.documentMode||document.documentMode<10),f=/Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent),g=b||f,h=/WebKit\//.test(navigator.userAgent),i=h&&/Qt\/\d+\.\d+/.test(navigator.userAgent),j=/Chrome\//.test(navigator.userAgent),k=/Opera\//.test(navigator.userAgent),l=/Apple Computer/.test(navigator.vendor),m=/KHTML\//.test(navigator.userAgent),n=/Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent),o=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent),p=/PhantomJS/.test(navigator.userAgent),q=/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent),r=q||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent),s=q||/Mac/.test(navigator.platform),t=/win/i.test(navigator.platform),u=k&&navigator.userAgent.match(/Version\/(\d*\.\d*)/);u&&(u=Number(u[1])),u&&u>=15&&(k=!1,h=!0);var Cb,Wb,Xb,v=s&&(i||k&&(null==u||12.11>u)),w=a||g&&!d,x=!1,y=!1,Fb=0,ac=0,fc=0,gc=null;g?gc=-.53:a?gc=15:j?gc=-.7:l&&(gc=-1/3);var kc,tc,oc=null,vc=z.changeEnd=function(a){return a.text?Gc(a.from.line+a.text.length-1,nf(a.text).length+(1==a.text.length?a.from.ch:0)):a.to};z.Pos=Gc,z.prototype={constructor:z,focus:function(){window.focus(),Qb(this),Nb(this)},setOption:function(a,b){var c=this.options,d=c[a];(c[a]!=b||"mode"==a)&&(c[a]=b,ed.hasOwnProperty(a)&&Ib(this,ed[a])(this,b,d))},getOption:function(a){return this.options[a]},getDoc:function(){return this.doc},addKeyMap:function(a,b){this.state.keyMaps[b?"push":"unshift"](a)},removeKeyMap:function(a){for(var b=this.state.keyMaps,c=0;c=d;++d)$c(this,d,a)}),getTokenAt:function(a,b){var c=this.doc;a=Mc(c,a);for(var d=hb(this,a.line,b),e=this.doc.mode,f=ye(c,a.line),g=new vd(f.text,this.options.tabSize);g.pos>1;if((f?b[2*f-1]:0)>=e)d=f;else{if(!(b[2*f+1]d&&(a=d,c=!0);var e=ye(this.doc,a);return vb(this,ye(this.doc,a),{top:0,left:0},b||"page").top+(c?e.height:0)},defaultTextHeight:function(){return Db(this.display)},defaultCharWidth:function(){return Eb(this.display)},setGutterMarker:Ib(null,function(a,b,c){return _c(this,a,function(a){var d=a.gutterMarkers||(a.gutterMarkers={});return d[b]=c,!c&&wf(d)&&(a.gutterMarkers=null),!0})}),clearGutter:Ib(null,function(a){var b=this,c=b.doc,d=c.first;c.iter(function(c){c.gutterMarkers&&c.gutterMarkers[a]&&(c.gutterMarkers[a]=null,Lb(b,d,d+1),wf(c.gutterMarkers)&&(c.gutterMarkers=null)),++d})}),addLineClass:Ib(null,function(a,b,c){return _c(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass";if(a[d]){if(new RegExp("(?:^|\\s)"+c+"(?:$|\\s)").test(a[d]))return!1;a[d]+=" "+c}else a[d]=c;return!0})}),removeLineClass:Ib(null,function(a,b,c){return _c(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass",e=a[d];if(!e)return!1;if(null==c)a[d]=null;else{var f=e.match(new RegExp("(?:^|\\s+)"+c+"(?:$|\\s+)"));if(!f)return!1;var g=f.index+f[0].length;a[d]=e.slice(0,f.index)+(f.index&&g!=e.length?" ":"")+e.slice(g)||null}return!0})}),addLineWidget:Ib(null,function(a,b,c){return Zd(this,a,b,c)}),removeLineWidget:function(a){a.clear()},lineInfo:function(a){if("number"==typeof a){if(!Oc(this.doc,a))return null;var b=a;if(a=ye(this.doc,a),!a)return null}else{var b=Ce(a);if(null==b)return null}return{line:b,handle:a,text:a.text,gutterMarkers:a.gutterMarkers,textClass:a.textClass,bgClass:a.bgClass,wrapClass:a.wrapClass,widgets:a.widgets}},getViewport:function(){return{from:this.display.showingFrom,to:this.display.showingTo}},addWidget:function(a,b,c,d,e){var f=this.display;a=yb(this,Mc(this.doc,a));var g=a.bottom,h=a.left;if(b.style.position="absolute",f.sizer.appendChild(b),"over"==d)g=a.top;else if("above"==d||"near"==d){var i=Math.max(f.wrapper.clientHeight,this.doc.height),j=Math.max(f.sizer.clientWidth,f.lineSpace.clientWidth);("above"==d||a.bottom+b.offsetHeight>i)&&a.top>b.offsetHeight?g=a.top-b.offsetHeight:a.bottom+b.offsetHeight<=i&&(g=a.bottom),h+b.offsetWidth>j&&(h=j-b.offsetWidth)}b.style.top=g+"px",b.style.left=b.style.right="","right"==e?(h=f.sizer.clientWidth-b.offsetWidth,b.style.right="0px"):("left"==e?h=0:"middle"==e&&(h=(f.sizer.clientWidth-b.offsetWidth)/2),b.style.left=h+"px"),c&&Wc(this,h,g,h+b.offsetWidth,g+b.offsetHeight)},triggerOnKeyDown:Ib(null,pc),triggerOnKeyPress:Ib(null,qc),triggerOnKeyUp:Ib(null,nc),execCommand:function(a){return pd.hasOwnProperty(a)?pd[a](this):void 0},findPosH:function(a,b,c,d){var e=1;0>b&&(e=-1,b=-b);for(var f=0,g=Mc(this.doc,a);b>f&&(g=ad(this.doc,g,e,c,d),!g.hitSide);++f);return g},moveH:Ib(null,function(a,b){var d,c=this.doc.sel;d=c.shift||c.extend||Hc(c.from,c.to)?ad(this.doc,c.head,a,b,this.options.rtlMoveVisually):0>a?c.from:c.to,Pc(this.doc,d,d,a)}),deleteH:Ib(null,function(a,b){var c=this.doc.sel;Hc(c.from,c.to)?Fc(this.doc,"",c.from,ad(this.doc,c.head,a,b,!1),"+delete"):Fc(this.doc,"",c.from,c.to,"+delete"),this.curOp.userSelChange=!0}),findPosV:function(a,b,c,d){var e=1,f=d;0>b&&(e=-1,b=-b);for(var g=0,h=Mc(this.doc,a);b>g;++g){var i=yb(this,h,"div");if(null==f?f=i.left:i.left=f,h=bd(this,i,e,c),h.hitSide)break}return h},moveV:Ib(null,function(a,b){var d,e,c=this.doc.sel;if(c.shift||c.extend||Hc(c.from,c.to)){var f=yb(this,c.head,"div");null!=c.goalColumn&&(f.left=c.goalColumn),d=bd(this,f,a,b),"page"==b&&Zc(this,0,xb(this,d,"div").top-f.top),e=f.left}else d=0>a?c.from:c.to;Pc(this.doc,d,d,a),null!=e&&(c.goalColumn=e)}),toggleOverwrite:function(a){(null==a||a!=this.state.overwrite)&&((this.state.overwrite=!this.state.overwrite)?this.display.cursor.className+=" CodeMirror-overwrite":this.display.cursor.className=this.display.cursor.className.replace(" CodeMirror-overwrite",""),$e(this,"overwriteToggle",this,this.state.overwrite))},hasFocus:function(){return document.activeElement==this.display.input},scrollTo:Ib(null,function(a,b){Yc(this,a,b)}),getScrollInfo:function(){var a=this.display.scroller,b=gf;return{left:a.scrollLeft,top:a.scrollTop,height:a.scrollHeight-b,width:a.scrollWidth-b,clientHeight:a.clientHeight-b,clientWidth:a.clientWidth-b}},scrollIntoView:Ib(null,function(a,b){null==a?a={from:this.doc.sel.head,to:null}:"number"==typeof a?a={from:Gc(a,0),to:null}:null==a.from&&(a={from:a,to:null}),a.to||(a.to=a.from),b||(b=0);var c=a;null!=a.from.line&&(this.curOp.scrollToPos={from:a.from,to:a.to,margin:b},c={from:yb(this,a.from),to:yb(this,a.to)});var d=Xc(this,Math.min(c.from.left,c.to.left),Math.min(c.from.top,c.to.top)-b,Math.max(c.from.right,c.to.right),Math.max(c.from.bottom,c.to.bottom)+b);Yc(this,d.scrollLeft,d.scrollTop)}),setSize:Ib(null,function(a,b){function c(a){return"number"==typeof a||/^\d+$/.test(String(a))?a+"px":a}null!=a&&(this.display.wrapper.style.width=c(a)),null!=b&&(this.display.wrapper.style.height=c(b)),this.options.lineWrapping&&(this.display.measureLineCache.length=this.display.measureLineCachePos=0),this.curOp.forceUpdate=!0,$e(this,"refresh",this)}),operation:function(a){return Kb(this,a)},refresh:Ib(null,function(){var a=this.display.cachedTextHeight;sb(this),Yc(this,this.doc.scrollLeft,this.doc.scrollTop),Lb(this),(null==a||Math.abs(a-Db(this.display))>.5)&&F(this),$e(this,"refresh",this)}),swapDoc:Ib(null,function(a){var b=this.doc;return b.cm=null,xe(this,a),sb(this),Pb(this,!0),Yc(this,a.scrollLeft,a.scrollTop),bf(this,"swapDoc",this,b),b}),getInputField:function(){return this.display.input},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},ff(z);var ed=z.optionHandlers={},fd=z.defaults={},hd=z.Init={toString:function(){return"CodeMirror.Init"}};gd("value","",function(a,b){a.setValue(b)},!0),gd("mode",null,function(a,b){a.doc.modeOption=b,B(a)},!0),gd("indentUnit",2,B,!0),gd("indentWithTabs",!1),gd("smartIndent",!0),gd("tabSize",4,function(a){C(a),sb(a),Lb(a)},!0),gd("specialChars",/[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g,function(a,b){a.options.specialChars=new RegExp(b.source+(b.test(" ")?"":"| "),"g"),a.refresh()},!0),gd("specialCharPlaceholder",je,function(a){a.refresh()},!0),gd("electricChars",!0),gd("rtlMoveVisually",!t),gd("wholeLineUpdateBefore",!0),gd("theme","default",function(a){H(a),I(a)},!0),gd("keyMap","default",G),gd("extraKeys",null),gd("onKeyEvent",null),gd("onDragEvent",null),gd("lineWrapping",!1,D,!0),gd("gutters",[],function(a){M(a.options),I(a)},!0),gd("fixedGutter",!0,function(a,b){a.display.gutters.style.left=b?S(a.display)+"px":"0",a.refresh()},!0),gd("coverGutterNextToScrollbar",!1,N,!0),gd("lineNumbers",!1,function(a){M(a.options),I(a)},!0),gd("firstLineNumber",1,I,!0),gd("lineNumberFormatter",function(a){return a},I,!0),gd("showCursorWhenSelecting",!1,ab,!0),gd("resetSelectionOnContextMenu",!0),gd("readOnly",!1,function(a,b){"nocursor"==b?(sc(a),a.display.input.blur(),a.display.disabled=!0):(a.display.disabled=!1,b||Pb(a,!0))}),gd("disableInput",!1,function(a,b){b||Pb(a,!0)},!0),gd("dragDrop",!0),gd("cursorBlinkRate",530),gd("cursorScrollMargin",0),gd("cursorHeight",1),gd("workTime",100),gd("workDelay",100),gd("flattenSpans",!0,C,!0),gd("addModeClass",!1,C,!0),gd("pollInterval",100),gd("undoDepth",40,function(a,b){a.doc.history.undoDepth=b}),gd("historyEventDelay",500),gd("viewportMargin",10,function(a){a.refresh()},!0),gd("maxHighlightLength",1e4,C,!0),gd("crudeMeasuringFrom",1e4),gd("moveInputWithCursor",!0,function(a,b){b||(a.display.inputDiv.style.top=a.display.inputDiv.style.left=0)}),gd("tabindex",null,function(a,b){a.display.input.tabIndex=b||""}),gd("autofocus",null);var id=z.modes={},jd=z.mimeModes={};z.defineMode=function(a,b){if(z.defaults.mode||"null"==a||(z.defaults.mode=a),arguments.length>2){b.dependencies=[];for(var c=2;c0&&b.ch=this.string.length},sol:function(){return this.pos==this.lineStart},peek:function(){return this.string.charAt(this.pos)||void 0},next:function(){return this.posb},eatSpace:function(){for(var a=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);return b>-1?(this.pos=b,!0):void 0},backUp:function(a){this.pos-=a},column:function(){return this.lastColumnPos0?null:(f&&b!==!1&&(this.pos+=f[0].length),f)}var d=function(a){return c?a.toLowerCase():a},e=this.string.substr(this.pos,a.length);return d(e)==d(a)?(b!==!1&&(this.pos+=a.length),!0):void 0},current:function(){return this.string.slice(this.start,this.pos)},hideFirstChars:function(a,b){this.lineStart+=a;try{return b()}finally{this.lineStart-=a}}},z.StringStream=vd,z.TextMarker=wd,ff(wd),wd.prototype.clear=function(){if(!this.explicitlyCleared){var a=this.doc.cm,b=a&&!a.curOp;if(b&&Gb(a),ef(this,"clear")){var c=this.find();c&&bf(this,"clear",c.from,c.to)}for(var d=null,e=null,f=0;fa.display.maxLineLength&&(a.display.maxLine=i,a.display.maxLineLength=j,a.display.maxLineChanged=!0)}null!=d&&a&&Lb(a,d,e+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,a&&Sc(a)),bf(a,"markerCleared",a,this),b&&Hb(a)}},wd.prototype.find=function(a){for(var b,c,d=0;d=b.display.showingFrom&&a.linec;++c){var e=this.lines[c];this.height-=e.height,ae(e),bf(e,"delete")}this.lines.splice(a,b)},collapse:function(a){a.splice.apply(a,[a.length,0].concat(this.lines))},insertInner:function(a,b,c){this.height+=c,this.lines=this.lines.slice(0,a).concat(b).concat(this.lines.slice(a));for(var d=0,e=b.length;e>d;++d)b[d].parent=this},iterN:function(a,b,c){for(var d=a+b;d>a;++a)if(c(this.lines[a]))return!0}},re.prototype={chunkSize:function(){return this.size},removeInner:function(a,b){this.size-=b;for(var c=0;ca){var f=Math.min(b,e-a),g=d.height;if(d.removeInner(a,f),this.height-=g-d.height,e==f&&(this.children.splice(c--,1),d.parent=null),0==(b-=f))break;a=0}else a-=e}if(this.size-b<25){var h=[];this.collapse(h),this.children=[new qe(h)],this.children[0].parent=this}},collapse:function(a){for(var b=0,c=this.children.length;c>b;++b)this.children[b].collapse(a)},insertInner:function(a,b,c){this.size+=b.length,this.height+=c;for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>=a){if(f.insertInner(a,b,c),f.lines&&f.lines.length>50){for(;f.lines.length>50;){var h=f.lines.splice(f.lines.length-25,25),i=new qe(h);f.height-=i.height,this.children.splice(d+1,0,i),i.parent=this}this.maybeSpill()}break}a-=g}},maybeSpill:function(){if(!(this.children.length<=10)){var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new re(b);if(a.parent){a.size-=c.size,a.height-=c.height;var e=pf(a.parent.children,a);a.parent.children.splice(e+1,0,c)}else{var d=new re(a.children);d.parent=a,a.children=[d,c],a=d}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()}},iterN:function(a,b,c){for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>a){var h=Math.min(b,g-a);if(f.iterN(a,h,c))return!0;if(0==(b-=h))break;a=0}else a-=g}}};var se=0,te=z.Doc=function(a,b,c){if(!(this instanceof te))return new te(a,b,c);null==c&&(c=0),re.call(this,[new qe([new $d("",null)])]),this.first=c,this.scrollTop=this.scrollLeft=0,this.cantEdit=!1,this.history=Ge(),this.cleanGeneration=1,this.frontier=c;var d=Gc(c,0);this.sel={from:d,to:d,head:d,anchor:d,shift:!1,extend:!1,goalColumn:null},this.id=++se,this.modeOption=b,"string"==typeof a&&(a=Kf(a)),pe(this,{from:d,to:d,text:a},null,{head:d,anchor:d})};te.prototype=qf(re.prototype,{constructor:te,iter:function(a,b,c){c?this.iterN(a-this.first,b-a,c):this.iterN(this.first,this.first+this.size,a)},insert:function(a,b){for(var c=0,d=0,e=b.length;e>d;++d)c+=b[d].height;this.insertInner(a-this.first,b,c)},remove:function(a,b){this.removeInner(a-this.first,b)},getValue:function(a){var b=Ae(this,this.first,this.first+this.size);return a===!1?b:b.join(a||"\n")},setValue:function(a){var b=Gc(this.first,0),c=this.first+this.size-1;zc(this,{from:b,to:Gc(c,ye(this,c).text.length),text:Kf(a),origin:"setValue"},{head:b,anchor:b},!0)},replaceRange:function(a,b,c,d){b=Mc(this,b),c=c?Mc(this,c):b,Fc(this,a,b,c,d)},getRange:function(a,b,c){var d=ze(this,Mc(this,a),Mc(this,b));return c===!1?d:d.join(c||"\n")},getLine:function(a){var b=this.getLineHandle(a);return b&&b.text},setLine:function(a,b){Oc(this,a)&&Fc(this,b,Gc(a,0),Mc(this,Gc(a)))},removeLine:function(a){a?Fc(this,"",Mc(this,Gc(a-1)),Mc(this,Gc(a))):Fc(this,"",Gc(0,0),Mc(this,Gc(1,0)))},getLineHandle:function(a){return Oc(this,a)?ye(this,a):void 0},getLineNumber:function(a){return Ce(a)},getLineHandleVisualStart:function(a){return"number"==typeof a&&(a=ye(this,a)),Rd(this,a)},lineCount:function(){return this.size},firstLine:function(){return this.first},lastLine:function(){return this.first+this.size-1},clipPos:function(a){return Mc(this,a)},getCursor:function(a){var c,b=this.sel;return c=null==a||"head"==a?b.head:"anchor"==a?b.anchor:"end"==a||a===!1?b.to:b.from,Kc(c)},somethingSelected:function(){return!Hc(this.sel.head,this.sel.anchor)},setCursor:Jb(function(a,b,c){var d=Mc(this,"number"==typeof a?Gc(a,b||0):a);c?Pc(this,d):Rc(this,d,d)}),setSelection:Jb(function(a,b,c){Rc(this,Mc(this,a),Mc(this,b||a),c)}),extendSelection:Jb(function(a,b,c){Pc(this,Mc(this,a),b&&Mc(this,b),c)}),getSelection:function(a){return this.getRange(this.sel.from,this.sel.to,a)},replaceSelection:function(a,b,c){zc(this,{from:this.sel.from,to:this.sel.to,text:Kf(a),origin:c},b||"around")},undo:Jb(function(){Bc(this,"undo")}),redo:Jb(function(){Bc(this,"redo")}),setExtending:function(a){this.sel.extend=a},historySize:function(){var a=this.history;return{undo:a.done.length,redo:a.undone.length}},clearHistory:function(){this.history=Ge(this.history.maxGeneration)},markClean:function(){this.cleanGeneration=this.changeGeneration(!0)},changeGeneration:function(a){return a&&(this.history.lastOp=this.history.lastOrigin=null),this.history.generation},isClean:function(a){return this.history.generation==(a||this.cleanGeneration)},getHistory:function(){return{done:Me(this.history.done),undone:Me(this.history.undone)}},setHistory:function(a){var b=this.history=Ge(this.history.maxGeneration);b.done=a.done.slice(0),b.undone=a.undone.slice(0)},markText:function(a,b,c){return yd(this,Mc(this,a),Mc(this,b),c,"range")},setBookmark:function(a,b){var c={replacedWith:b&&(null==b.nodeType?b.widget:b),insertLeft:b&&b.insertLeft,clearWhenEmpty:!1};return a=Mc(this,a),yd(this,a,a,c,"bookmark")},findMarksAt:function(a){a=Mc(this,a);var b=[],c=ye(this,a.line).markedSpans;if(c)for(var d=0;d=a.ch)&&b.push(e.marker.parent||e.marker)}return b},findMarks:function(a,b){a=Mc(this,a),b=Mc(this,b);var c=[],d=a.line;return this.iter(a.line,b.line+1,function(e){var f=e.markedSpans;if(f)for(var g=0;gh.to||null==h.from&&d!=a.line||d==b.line&&h.from>b.ch||c.push(h.marker.parent||h.marker)}++d}),c},getAllMarks:function(){var a=[];return this.iter(function(b){var c=b.markedSpans;if(c)for(var d=0;da?(b=a,!0):(a-=e,++c,void 0)}),Mc(this,Gc(c,b))},indexFromPos:function(a){a=Mc(this,a);var b=a.ch;return a.lineb&&(b=a.from),null!=a.to&&a.to=8208&&8212>=c}:h&&(Ff=function(a,b){if(b>1&&45==a.charCodeAt(b-1)){if(/\w/.test(a.charAt(b-2))&&/[^\-?\.]/.test(a.charAt(b)))return!0;if(b>2&&/[\d\.,]/.test(a.charAt(b-2))&&/[\d\.,]/.test(a.charAt(b)))return!1}return/[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(a.slice(b-1,b+1))});var Gf,If,Kf=3!="\n\nb".split(/\n/).length?function(a){for(var b=0,c=[],d=a.length;d>=b;){var e=a.indexOf("\n",b);-1==e&&(e=a.length);var f=a.slice(b,"\r"==a.charAt(e-1)?e-1:e),g=f.indexOf("\r");-1!=g?(c.push(f.slice(0,g)),b+=g+1):(c.push(f),b=e+1)}return c}:function(a){return a.split(/\r\n?|\n/)};z.splitLines=Kf;var Lf=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return b&&b.parentElement()==a?0!=b.compareEndPoints("StartToEnd",b):!1},Mf=function(){var a=zf("div");return"oncopy"in a?!0:(a.setAttribute("oncopy","return;"),"function"==typeof a.oncopy)}(),Nf={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",61:"=",91:"Mod",92:"Mod",93:"Mod",107:"=",109:"-",127:"Delete",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63232:"Up",63233:"Down",63234:"Left",63235:"Right",63272:"Delete",63273:"Home",63275:"End",63276:"PageUp",63277:"PageDown",63302:"Insert"};z.keyNames=Nf,function(){for(var a=0;10>a;a++)Nf[a+48]=Nf[a+96]=String(a);for(var a=65;90>=a;a++)Nf[a]=String.fromCharCode(a);for(var a=1;12>=a;a++)Nf[a+111]=Nf[a+63235]="F"+a}();var Wf,_f=function(){function c(c){return 255>=c?a.charAt(c):c>=1424&&1524>=c?"R":c>=1536&&1791>=c?b.charAt(c-1536):c>=1792&&2220>=c?"r":"L"}var a="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL",b="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr",d=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,e=/[stwN]/,f=/[LRr]/,g=/[Lb1n]/,h=/[1n]/,i="L";return function(a){if(!d.test(a))return!1;for(var l,b=a.length,j=[],k=0;b>k;++k)j.push(l=c(a.charCodeAt(k)));for(var k=0,m=i;b>k;++k){var l=j[k];"m"==l?j[k]=m:m=l}for(var k=0,n=i;b>k;++k){var l=j[k];"1"==l&&"r"==n?j[k]="n":f.test(l)&&(n=l,"r"==l&&(j[k]="R"))}for(var k=1,m=j[0];b-1>k;++k){var l=j[k];"+"==l&&"1"==m&&"1"==j[k+1]?j[k]="1":","!=l||m!=j[k+1]||"1"!=m&&"n"!=m||(j[k]=m),m=l}for(var k=0;b>k;++k){var l=j[k];if(","==l)j[k]="N";else if("%"==l){for(var o=k+1;b>o&&"%"==j[o];++o);for(var p=k&&"!"==j[k-1]||b>o&&"1"==j[o]?"1":"N",q=k;o>q;++q)j[q]=p;k=o-1}}for(var k=0,n=i;b>k;++k){var l=j[k];"L"==n&&"1"==l?j[k]="L":f.test(l)&&(n=l)}for(var k=0;b>k;++k)if(e.test(j[k])){for(var o=k+1;b>o&&e.test(j[o]);++o);for(var r="L"==(k?j[k-1]:i),s="L"==(b>o?j[o]:i),p=r||s?"L":"R",q=k;o>q;++q)j[q]=p;k=o-1}for(var u,t=[],k=0;b>k;)if(g.test(j[k])){var v=k;for(++k;b>k&&g.test(j[k]);++k);t.push({from:v,to:k,level:0})}else{var w=k,x=t.length;for(++k;b>k&&"L"!=j[k];++k);for(var q=w;k>q;)if(h.test(j[q])){q>w&&t.splice(x,0,{from:w,to:q,level:1});var y=q;for(++q;k>q&&h.test(j[q]);++q);t.splice(x,0,{from:y,to:q,level:2}),w=q}else++q;k>w&&t.splice(x,0,{from:w,to:k,level:1})}return 1==t[0].level&&(u=a.match(/^\s+/))&&(t[0].from=u[0].length,t.unshift({from:0,to:u[0].length,level:0})),1==nf(t).level&&(u=a.match(/\s+$/))&&(nf(t).to-=u[0].length,t.push({from:b-u[0].length,to:b,level:0})),t[0].level!=nf(t).level&&t.push({from:b,to:b,level:t[0].level}),t}}();return z.version="3.22.1",z}(),CodeMirror.defineMode("javascript",function(a,b){function k(a){for(var c,b=!1,d=!1;null!=(c=a.next());){if(!b){if("/"==c&&!d)return;"["==c?d=!0:d&&"]"==c&&(d=!1)}b=!b&&"\\"==c}}function n(a,b,c){return l=a,m=c,b}function o(a,b){var c=a.next();if('"'==c||"'"==c)return b.tokenize=p(c),b.tokenize(a,b);if("."==c&&a.match(/^\d+(?:[eE][+\-]?\d+)?/))return n("number","number");if("."==c&&a.match(".."))return n("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(c))return n(c);if("="==c&&a.eat(">"))return n("=>","operator");if("0"==c&&a.eat(/x/i))return a.eatWhile(/[\da-f]/i),n("number","number");if(/\d/.test(c))return a.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/),n("number","number");if("/"==c)return a.eat("*")?(b.tokenize=q,q(a,b)):a.eat("/")?(a.skipToEnd(),n("comment","comment")):"operator"==b.lastType||"keyword c"==b.lastType||"sof"==b.lastType||/^[\[{}\(,;:]$/.test(b.lastType)?(k(a),a.eatWhile(/[gimy]/),n("regexp","string-2")):(a.eatWhile(i),n("operator","operator",a.current()));if("`"==c)return b.tokenize=r,r(a,b);if("#"==c)return a.skipToEnd(),n("error","error");if(i.test(c))return a.eatWhile(i),n("operator","operator",a.current());a.eatWhile(/[\w\$_]/);var d=a.current(),e=h.propertyIsEnumerable(d)&&h[d];return e&&"."!=b.lastType?n(e.type,e.style,d):n("variable","variable",d)}function p(a){return function(b,c){var f,d=!1;if(e&&"@"==b.peek()&&b.match(j))return c.tokenize=o,n("jsonld-keyword","meta");for(;null!=(f=b.next())&&(f!=a||d);)d=!d&&"\\"==f;return d||(c.tokenize=o),n("string","string")}}function q(a,b){for(var d,c=!1;d=a.next();){if("/"==d&&c){b.tokenize=o;break}c="*"==d}return n("comment","comment")}function r(a,b){for(var d,c=!1;null!=(d=a.next());){if(!c&&("`"==d||"$"==d&&a.eat("{"))){b.tokenize=o;break}c=!c&&"\\"==d}return n("quasi","string-2",a.current())}function t(a,b){b.fatArrowAt&&(b.fatArrowAt=null);var c=a.string.indexOf("=>",a.start);if(!(0>c)){for(var d=0,e=!1,f=c-1;f>=0;--f){var g=a.string.charAt(f),h=s.indexOf(g);if(h>=0&&3>h){if(!d){++f;break}if(0==--d)break}else if(h>=3&&6>h)++d;else if(/[$\w]/.test(g))e=!0;else if(e&&!d){++f;break}}e&&!d&&(b.fatArrowAt=f)}}function v(a,b,c,d,e,f){this.indented=a,this.column=b,this.type=c,this.prev=e,this.info=f,null!=d&&(this.align=d)}function w(a,b){for(var c=a.localVars;c;c=c.next)if(c.name==b)return!0;for(var d=a.context;d;d=d.prev)for(var c=d.vars;c;c=c.next)if(c.name==b)return!0}function x(a,b,c,d,e){var g=a.cc;for(y.state=a,y.stream=e,y.marked=null,y.cc=g,a.lexical.hasOwnProperty("align")||(a.lexical.align=!0);;){var h=g.length?g.pop():f?J:I;if(h(c,d)){for(;g.length&&g[g.length-1].lex;)g.pop()();return y.marked?y.marked:"variable"==c&&w(a,d)?"variable-2":b}}}function z(){for(var a=arguments.length-1;a>=0;a--)y.cc.push(arguments[a])}function A(){return z.apply(null,arguments),!0}function B(a){function c(b){for(var c=b;c;c=c.next)if(c.name==a)return!0;return!1}var d=y.state;if(d.context){if(y.marked="def",c(d.localVars))return;d.localVars={name:a,next:d.localVars}}else{if(c(d.globalVars))return;b.globalVars&&(d.globalVars={name:a,next:d.globalVars})}}function D(){y.state.context={prev:y.state.context,vars:y.state.localVars},y.state.localVars=C}function E(){y.state.localVars=y.state.context.vars,y.state.context=y.state.context.prev}function F(a,b){var c=function(){var c=y.state,d=c.indented;"stat"==c.lexical.type&&(d=c.lexical.indented),c.lexical=new v(d,y.stream.column(),a,null,c.lexical,b)};return c.lex=!0,c}function G(){var a=y.state;a.lexical.prev&&(")"==a.lexical.type&&(a.indented=a.lexical.indented),a.lexical=a.lexical.prev)}function H(a){return function(b){return b==a?A():";"==a?z():A(arguments.callee)}}function I(a,b){return"var"==a?A(F("vardef",b.length),cb,H(";"),G):"keyword a"==a?A(F("form"),J,I,G):"keyword b"==a?A(F("form"),I,G):"{"==a?A(F("}"),_,G):";"==a?A():"if"==a?A(F("form"),J,I,G,hb):"function"==a?A(nb):"for"==a?A(F("form"),ib,I,G):"variable"==a?A(F("stat"),U):"switch"==a?A(F("form"),J,F("}","switch"),H("{"),_,G,G):"case"==a?A(J,H(":")):"default"==a?A(H(":")):"catch"==a?A(F("form"),D,H("("),ob,H(")"),I,G,E):"module"==a?A(F("form"),D,sb,E,G):"class"==a?A(F("form"),pb,rb,G):"export"==a?A(F("form"),tb,G):"import"==a?A(F("form"),ub,G):z(F("stat"),J,H(";"),G)}function J(a){return L(a,!1)}function K(a){return L(a,!0)}function L(a,b){if(y.state.fatArrowAt==y.stream.start){var c=b?T:S;if("("==a)return A(D,F(")"),Z(db,")"),G,H("=>"),c,E);if("variable"==a)return z(D,db,H("=>"),c,E)}var d=b?P:O;return u.hasOwnProperty(a)?A(d):"function"==a?A(nb):"keyword c"==a?A(b?N:M):"("==a?A(F(")"),M,zb,H(")"),G,d):"operator"==a||"spread"==a?A(b?K:J):"["==a?A(F("]"),xb,G,d):"{"==a?$(W,"}",null,d):A()}function M(a){return a.match(/[;\}\)\],]/)?z():z(J)}function N(a){return a.match(/[;\}\)\],]/)?z():z(K)}function O(a,b){return","==a?A(J):P(a,b,!1)}function P(a,b,c){var d=0==c?O:P,e=0==c?J:K;return"=>"==b?A(D,c?T:S,E):"operator"==a?/\+\+|--/.test(b)?A(d):"?"==b?A(J,H(":"),e):A(e):"quasi"==a?(y.cc.push(d),Q(b)):";"!=a?"("==a?$(K,")","call",d):"."==a?A(V,d):"["==a?A(F("]"),M,H("]"),G,d):void 0:void 0}function Q(a){return"${"!=a.slice(a.length-2)?A():A(J,R)}function R(a){return"}"==a?(y.marked="string-2",y.state.tokenize=r,A()):void 0}function S(a){return t(y.stream,y.state),"{"==a?z(I):z(J)}function T(a){return t(y.stream,y.state),"{"==a?z(I):z(K)}function U(a){return":"==a?A(G,I):z(O,H(";"),G)}function V(a){return"variable"==a?(y.marked="property",A()):void 0}function W(a,b){if("variable"==a){if(y.marked="property","get"==b||"set"==b)return A(X)}else if("number"==a||"string"==a)y.marked=e?"property":a+" property";else if("["==a)return A(J,H("]"),Y);return u.hasOwnProperty(a)?A(Y):void 0}function X(a){return"variable"!=a?z(Y):(y.marked="property",A(nb))}function Y(a){return":"==a?A(K):"("==a?z(nb):void 0}function Z(a,b){function c(d){if(","==d){var e=y.state.lexical;return"call"==e.info&&(e.pos=(e.pos||0)+1),A(a,c)}return d==b?A():A(H(b))}return function(d){return d==b?A():z(a,c)}}function $(a,b,c){for(var d=3;d!?|~^]/,j=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/,s="([{}])",u={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,"this":!0,"jsonld-keyword":!0},y={state:null,column:null,marked:null,cc:null},C={name:"this",next:{name:"arguments"}};return G.lex=!0,{startState:function(a){var d={tokenize:o,lastType:"sof",cc:[],lexical:new v((a||0)-c,0,"block",!1),localVars:b.localVars,context:b.localVars&&{vars:b.localVars},indented:0};return b.globalVars&&(d.globalVars=b.globalVars),d},token:function(a,b){if(a.sol()&&(b.lexical.hasOwnProperty("align")||(b.lexical.align=!1),b.indented=a.indentation(),t(a,b)),b.tokenize!=q&&a.eatSpace())return null;var c=b.tokenize(a,b);return"comment"==l?c:(b.lastType="operator"!=l||"++"!=m&&"--"!=m?l:"incdec",x(b,c,l,m,a))},indent:function(a,e){if(a.tokenize==q)return CodeMirror.Pass;if(a.tokenize!=o)return 0;for(var f=e&&e.charAt(0),g=a.lexical,h=a.cc.length-1;h>=0;--h){var i=a.cc[h];if(i==G)g=g.prev;else if(i!=hb)break}"stat"==g.type&&"}"==f&&(g=g.prev),d&&")"==g.type&&"stat"==g.prev.type&&(g=g.prev);var j=g.type,k=f==j;return"vardef"==j?g.indented+("operator"==a.lastType||","==a.lastType?g.info+1:0):"form"==j&&"{"==f?g.indented:"form"==j?g.indented+c:"stat"==j?g.indented+("operator"==a.lastType||","==a.lastType?d||c:0):"switch"!=g.info||k||0==b.doubleIndentSwitch?g.align?g.column+(k?0:1):g.indented+(k?0:c):g.indented+(/^(?:case|default)\b/.test(e)?c:2*c)},electricChars:":{}",blockCommentStart:f?null:"/*",blockCommentEnd:f?null:"*/",lineComment:f?null:"//",fold:"brace",helperType:f?"json":"javascript",jsonldMode:e,jsonMode:f}}),CodeMirror.defineMIME("text/javascript","javascript"),CodeMirror.defineMIME("text/ecmascript","javascript"),CodeMirror.defineMIME("application/javascript","javascript"),CodeMirror.defineMIME("application/ecmascript","javascript"),CodeMirror.defineMIME("application/json",{name:"javascript",json:!0}),CodeMirror.defineMIME("application/x-json",{name:"javascript",json:!0}),CodeMirror.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),CodeMirror.defineMIME("text/typescript",{name:"javascript",typescript:!0}),CodeMirror.defineMIME("application/typescript",{name:"javascript",typescript:!0}),function(){function d(a,d,e){function r(d,e,f){if(d.text){var h=m?0:d.text.length-1,i=m?d.text.length:-1;if(d.text.length>g)return null;for(null!=f&&(h=f+n);h!=i;h+=n){var j=d.text.charAt(h);if(q.test(j)&&a.getTokenTypeAt(b(e,h+1))==o){var k=c[j];if(">"==k.charAt(1)==m)p.push(j);else{if(p.pop()!=k.charAt(0))return{pos:h,match:!1};if(!p.length)return{pos:h,match:!0}}}}}}var f=a.state.matchBrackets,g=f&&f.maxScanLineLength||1e4,h=f&&f.maxScanLines||100,i=d||a.getCursor(),j=a.getLineHandle(i.line),k=i.ch-1,l=k>=0&&c[j.text.charAt(k)]||c[j.text.charAt(++k)];if(!l)return null;var m=">"==l.charAt(1),n=m?1:-1;if(e&&m!=(k==i.ch))return null;for(var t,o=a.getTokenTypeAt(b(i.line,k+1)),p=[j.text.charAt(k)],q=/[(){}[\]]/,s=i.line,u=m?Math.min(s+h,a.lineCount()):Math.max(-1,s-h);s!=u&&!(t=s==i.line?r(j,s,k):r(a.getLineHandle(s),s));s+=n);return{from:b(i.line,k),to:t&&b(s,t.pos),match:t&&t.match,forward:m}}function e(c,e){var f=c.state.matchBrackets.maxHighlightLineLength||1e3,g=d(c);if(!(!g||c.getLine(g.from.line).length>f||g.to&&c.getLine(g.to.line).length>f)){var h=g.match?"CodeMirror-matchingbracket":"CodeMirror-nonmatchingbracket",i=c.markText(g.from,b(g.from.line,g.from.ch+1),{className:h}),j=g.to&&c.markText(g.to,b(g.to.line,g.to.ch+1),{className:h});a&&c.state.focused&&c.display.input.focus();var k=function(){c.operation(function(){i.clear(),j&&j.clear()})};return e?(setTimeout(k,800),void 0):k -}}function g(a){a.operation(function(){f&&(f(),f=null),a.somethingSelected()||(f=e(a,!1))})}var a=/MSIE \d/.test(navigator.userAgent)&&(null==document.documentMode||document.documentMode<8),b=CodeMirror.Pos,c={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},f=null;CodeMirror.defineOption("matchBrackets",!1,function(a,b,c){c&&c!=CodeMirror.Init&&a.off("cursorActivity",g),b&&(a.state.matchBrackets="object"==typeof b?b:{},a.on("cursorActivity",g))}),CodeMirror.defineExtension("matchBrackets",function(){e(this,!0)}),CodeMirror.defineExtension("findMatchingBracket",function(a,b){return d(this,a,b)})}(),CodeMirror.runMode=function(a,b,c,d){var e=CodeMirror.getMode(CodeMirror.defaults,b),f=/MSIE \d/.test(navigator.userAgent),g=f&&(null==document.documentMode||document.documentMode<9);if(1==c.nodeType){var h=d&&d.tabSize||CodeMirror.defaults.tabSize,i=c,j=0;i.innerHTML="",c=function(a,b){if("\n"==a)return i.appendChild(document.createTextNode(g?"\r":a)),j=0,void 0;for(var c="",d=0;;){var e=a.indexOf(" ",d);if(-1==e){c+=a.slice(d),j+=a.length-d;break}j+=e-d,c+=a.slice(d,e);var f=h-j%h;j+=f;for(var k=0;f>k;++k)c+=" ";d=e+1}if(b){var l=i.appendChild(document.createElement("span"));l.className="cm-"+b.replace(/ +/g," cm-"),l.appendChild(document.createTextNode(c))}else i.appendChild(document.createTextNode(c))}}for(var k=CodeMirror.splitLines(a),l=d&&d.state||CodeMirror.startState(e),m=0,n=k.length;n>m;++m){m&&c("\n");for(var o=new CodeMirror.StringStream(k[m]);!o.eol();){var p=e.token(o,l);c(o.current(),p,m,o.start,l),o.start=o.pos}}}; \ No newline at end of file diff --git a/addon-sdk/source/examples/actor-repl/data/codemirror.css b/addon-sdk/source/examples/actor-repl/data/codemirror.css deleted file mode 100644 index 2b050e19f2..0000000000 --- a/addon-sdk/source/examples/actor-repl/data/codemirror.css +++ /dev/null @@ -1,264 +0,0 @@ -/* BASICS */ - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; -} -.CodeMirror-scroll { - /* Set scrolling behaviour here */ - overflow: auto; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/* CURSOR */ - -.CodeMirror div.CodeMirror-cursor { - border-left: 1px solid black; - z-index: 3; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { - width: auto; - border: 0; - background: #7e7; - z-index: 1; -} -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} - -.cm-tab { display: inline-block; } - -.CodeMirror-ruler { - border-left: 1px solid #ccc; - position: absolute; -} - -/* DEFAULT THEME */ - -.cm-s-default .cm-keyword {color: #708;} -.cm-s-default .cm-atom {color: #219;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable {color: black;} -.cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-property {color: black;} -.cm-s-default .cm-operator {color: black;} -.cm-s-default .cm-comment {color: #a50;} -.cm-s-default .cm-string {color: #a11;} -.cm-s-default .cm-string-2 {color: #f50;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} - -.cm-s-default .cm-error {color: #f00;} -.cm-invalidchar {color: #f00;} - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - line-height: 1; - position: relative; - overflow: hidden; - background: white; - color: black; -} - -.CodeMirror-scroll { - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -.CodeMirror-sizer { - position: relative; - border-right: 30px solid transparent; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - padding-bottom: 30px; - z-index: 3; -} -.CodeMirror-gutter { - white-space: normal; - height: 100%; - -moz-box-sizing: content-box; - box-sizing: content-box; - padding-bottom: 30px; - margin-bottom: -32px; - display: inline-block; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} - -.CodeMirror-lines { - cursor: text; -} -.CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; - overflow: auto; -} - -.CodeMirror-widget {} - -.CodeMirror-wrap .CodeMirror-scroll { - overflow-x: hidden; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} -.CodeMirror-measure pre { position: static; } - -.CodeMirror div.CodeMirror-cursor { - position: absolute; - visibility: hidden; - border-right: none; - width: 0; -} -.CodeMirror-focused div.CodeMirror-cursor { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } - -.cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursor { - visibility: hidden; - } -} diff --git a/addon-sdk/source/examples/actor-repl/data/index.html b/addon-sdk/source/examples/actor-repl/data/index.html deleted file mode 100644 index 250ece2495..0000000000 --- a/addon-sdk/source/examples/actor-repl/data/index.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - -
-

-            
-
-
- - -

-  
-  
-  
-
diff --git a/addon-sdk/source/examples/actor-repl/data/main.css b/addon-sdk/source/examples/actor-repl/data/main.css
deleted file mode 100644
index 03499944b0..0000000000
--- a/addon-sdk/source/examples/actor-repl/data/main.css
+++ /dev/null
@@ -1,117 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-body
-{
-  position: absolute;
-  width: 100%;
-  margin: 0;
-  padding: 0;
-  background: white;
-}
-
-
-pre
-{
-  margin: 0;
-}
-
-section
-{
-  border-top: 1px solid rgba(150, 150, 150, 0.5);
-}
-
-.CodeMirror {
-  height: auto;
-}
-.CodeMirror-scroll {
-  overflow-y: hidden;
-  overflow-x: auto;
-}
-
-.request,
-.response,
-.input
-{
-  border-left: 5px solid;
-  padding-left: 10px;
-}
-
-.request:not(:empty),
-.response.pending
-{
-  padding: 5px;
-}
-
-.input
-{
-  padding-left: 6px;
-  border-color: lightgreen;
-}
-.input.invalid
-{
-  border-color: orange;
-}
-
-.request
-{
-  border-color: lightgrey;
-}
-
-.response
-{
-  border-color: grey;
-}
-.response.error
-{
-  border-color: red;
-}
-
-.response.message
-{
-    border-color: lightblue;
-}
-
-.response .one,
-.response .two,
-.response .three
-{
-  width: 0;
-  height: auto;
-}
-
-
-
-.response.pending .one,
-.response.pending .two,
-.response.pending .three
-{
-  width: 10px;
-  height: 10px;
-  background-color: rgba(150, 150, 150, 0.5);
-
-  border-radius: 100%;
-  display: inline-block;
-  animation: bouncedelay 1.4s infinite ease-in-out;
-  /* Prevent first frame from flickering when animation starts */
-  animation-fill-mode: both;
-}
-
-.response.pending .one
-{
-  animation-delay: -0.32s;
-}
-
-.response.pending .two
-{
-  animation-delay: -0.16s;
-}
-
-@keyframes bouncedelay {
-  0%, 80%, 100% {
-    transform: scale(0.0);
-  } 40% {
-    transform: scale(1.0);
-  }
-}
diff --git a/addon-sdk/source/examples/actor-repl/data/robot.png b/addon-sdk/source/examples/actor-repl/data/robot.png
deleted file mode 100644
index 983516f981..0000000000
Binary files a/addon-sdk/source/examples/actor-repl/data/robot.png and /dev/null differ
diff --git a/addon-sdk/source/examples/actor-repl/index.js b/addon-sdk/source/examples/actor-repl/index.js
deleted file mode 100644
index b99aaf8a21..0000000000
--- a/addon-sdk/source/examples/actor-repl/index.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const { Panel } = require("dev/panel");
-const { Tool } = require("dev/toolbox");
-const { Class } = require("sdk/core/heritage");
-
-
-const REPLPanel = Class({
-  extends: Panel,
-  label: "Actor REPL",
-  tooltip: "Firefox debugging protocol REPL",
-  icon: "./robot.png",
-  url: "./index.html",
-  setup: function({debuggee}) {
-    this.debuggee = debuggee;
-  },
-  dispose: function() {
-    this.debuggee = null;
-  },
-  onReady: function() {
-    console.log("repl panel document is interactive");
-    this.debuggee.start();
-    this.postMessage("RDP", [this.debuggee]);
-  },
-  onLoad: function() {
-    console.log("repl panel document is fully loaded");
-  }
-});
-exports.REPLPanel = REPLPanel;
-
-
-const replTool = new Tool({
-  panels: { repl: REPLPanel }
-});
diff --git a/addon-sdk/source/examples/actor-repl/package.json b/addon-sdk/source/examples/actor-repl/package.json
deleted file mode 100644
index 9dc1347e84..0000000000
--- a/addon-sdk/source/examples/actor-repl/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "actor-repl",
-  "id": "@actor-repl",
-  "title": "Actor REPL",
-  "description": "Actor REPL",
-  "version": "0.0.1",
-  "author": "Irakli Gozalishvili",
-  "main": "./index.js",
-  "license": "MPL-2.0"
-}
diff --git a/addon-sdk/source/examples/actor-repl/test/test-main.js b/addon-sdk/source/examples/actor-repl/test/test-main.js
deleted file mode 100644
index 9862fc20b7..0000000000
--- a/addon-sdk/source/examples/actor-repl/test/test-main.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
-
-exports.testMain = function(assert) {
-  assert.pass("TODO: Write some tests.");
-};
-
-require("sdk/test").run(exports);
diff --git a/addon-sdk/source/examples/debug-client/data/client.js b/addon-sdk/source/examples/debug-client/data/client.js
deleted file mode 100644
index 022f9a1c35..0000000000
--- a/addon-sdk/source/examples/debug-client/data/client.js
+++ /dev/null
@@ -1,816 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-(function(exports) {
-"use strict";
-
-
-var describe = Object.getOwnPropertyDescriptor;
-var Class = fields => {
-  var constructor = fields.constructor || function() {};
-  var ancestor = fields.extends || Object;
-
-
-
-  var descriptor = {};
-  for (var key of Object.keys(fields))
-    descriptor[key] = describe(fields, key);
-
-  var prototype = Object.create(ancestor.prototype, descriptor);
-
-  constructor.prototype = prototype;
-  prototype.constructor = constructor;
-
-  return constructor;
-};
-
-
-var bus = function Bus() {
-  var parser = new DOMParser();
-  return parser.parseFromString("", "application/xml").documentElement;
-}();
-
-var GUID = new WeakMap();
-GUID.id = 0;
-var guid = x => GUID.get(x);
-var setGUID = x => {
-  GUID.set(x, ++ GUID.id);
-};
-
-var Emitter = Class({
-  extends: EventTarget,
-  constructor: function() {
-   this.setupEmitter();
-  },
-  setupEmitter: function() {
-    setGUID(this);
-  },
-  addEventListener: function(type, listener, capture) {
-    bus.addEventListener(type + "@" + guid(this),
-                         listener, capture);
-  },
-  removeEventListener: function(type, listener, capture) {
-    bus.removeEventListener(type + "@" + guid(this),
-                            listener, capture);
-  }
-});
-
-function dispatch(target, type, data) {
-  var event = new MessageEvent(type + "@" + guid(target), {
-    bubbles: true,
-    cancelable: false,
-    data: data
-  });
-  bus.dispatchEvent(event);
-}
-
-var supervisedWorkers = new WeakMap();
-var supervised = supervisor => {
-  if (!supervisedWorkers.has(supervisor)) {
-    supervisedWorkers.set(supervisor, new Map());
-    supervisor.connection.addActorPool(supervisor);
-  }
-  return supervisedWorkers.get(supervisor);
-};
-
-var Supervisor = Class({
-  extends: Emitter,
-  constructor: function(...params) {
-    this.setupEmitter(...params);
-    this.setupSupervisor(...params);
-  },
-  Supervisor: function(connection) {
-    this.connection = connection;
-  },
-  /**
-   * Return the parent pool for this client.
-   */
-  supervisor: function() {
-    return this.connection.poolFor(this.actorID);
-  },
-  /**
-   * Override this if you want actors returned by this actor
-   * to belong to a different actor by default.
-   */
-  marshallPool: function() { return this; },
-    /**
-   * Add an actor as a child of this pool.
-   */
-  supervise: function(actor) {
-    if (!actor.actorID)
-      actor.actorID = this.connection.allocID(actor.actorPrefix ||
-                                              actor.typeName);
-
-    supervised(this).set(actor.actorID, actor);
-    return actor;
-  },
-  /**
-   * Remove an actor as a child of this pool.
-   */
-  abandon: function(actor) {
-    supervised(this).delete(actor.actorID);
-  },
-  // true if the given actor ID exists in the pool.
-  has: function(actorID) {
-    return supervised(this).has(actorID);
-  },
-  // Same as actor, should update debugger connection to use 'actor'
-  // and then remove this.
-  get: function(actorID) {
-    return supervised(this).get(actorID);
-  },
-  actor: function(actorID) {
-    return supervised(this).get(actorID);
-  },
-  isEmpty: function() {
-    return supervised(this).size === 0;
-  },
-  /**
-   * For getting along with the debugger server pools, should be removable
-   * eventually.
-   */
-  cleanup: function() {
-    this.destroy();
-  },
-  destroy: function() {
-    var supervisor = this.supervisor();
-    if (supervisor)
-      supervisor.abandon(this);
-
-    for (var actor of supervised(this).values()) {
-      if (actor !== this) {
-        var destroy = actor.destroy;
-        // Disconnect destroy while we're destroying in case of (misbehaving)
-        // circular ownership.
-        if (destroy) {
-          actor.destroy = null;
-          destroy.call(actor);
-          actor.destroy = destroy;
-        }
-      }
-    }
-
-    this.connection.removeActorPool(this);
-    supervised(this).clear();
-  }
-
-});
-
-
-
-
-var mailbox = new WeakMap();
-var clientRequests = new WeakMap();
-
-var inbox = client => mailbox.get(client).inbox;
-var outbox = client => mailbox.get(client).outbox;
-var requests = client => clientRequests.get(client);
-
-
-var Receiver = Class({
-  receive: function(packet) {
-    if (packet.error)
-      this.reject(packet.error);
-    else
-      this.resolve(this.read(packet));
-  }
-});
-
-var Connection = Class({
-  constructor: function() {
-    // Queue of the outgoing messages.
-    this.outbox = [];
-    // Map of pending requests.
-    this.pending = new Map();
-    this.pools = new Set();
-  },
-  isConnected: function() {
-    return !!this.port
-  },
-  connect: function(port) {
-    this.port = port;
-    port.addEventListener("message", this);
-    port.start();
-
-    this.flush();
-  },
-  addPool: function(pool) {
-    this.pools.add(pool);
-  },
-  removePool: function(pool) {
-    this.pools.delete(pool);
-  },
-  poolFor: function(id) {
-    for (let pool of this.pools.values()) {
-      if (pool.has(id))
-        return pool;
-    }
-  },
-  get: function(id) {
-    var pool = this.poolFor(id);
-    return pool && pool.get(id);
-  },
-  disconnect: function() {
-    this.port.stop();
-    this.port = null;
-    for (var request of this.pending.values()) {
-      request.catch(new Error("Connection closed"));
-    }
-    this.pending.clear();
-
-    var requests = this.outbox.splice(0);
-    for (var request of request) {
-      requests.catch(new Error("Connection closed"));
-    }
-  },
-  handleEvent: function(event) {
-    this.receive(event.data);
-  },
-  flush: function() {
-    if (this.isConnected()) {
-      for (var request of this.outbox) {
-        if (!this.pending.has(request.to)) {
-          this.outbox.splice(this.outbox.indexOf(request), 1);
-          this.pending.set(request.to, request);
-          this.send(request.packet);
-        }
-      }
-    }
-  },
-  send: function(packet) {
-    this.port.postMessage(packet);
-  },
-  request: function(packet) {
-    return new Promise(function(resolve, reject) {
-      this.outbox.push({
-        to: packet.to,
-        packet: packet,
-        receive: resolve,
-        catch: reject
-      });
-      this.flush();
-    });
-  },
-  receive: function(packet) {
-    var { from, type, why } = packet;
-    var receiver = this.pending.get(from);
-    if (!receiver) {
-      console.warn("Unable to handle received packet", data);
-    } else {
-      this.pending.delete(from);
-      if (packet.error)
-        receiver.catch(packet.error);
-      else
-        receiver.receive(packet);
-    }
-    this.flush();
-  },
-});
-
-/**
- * Base class for client-side actor fronts.
- */
-var Client = Class({
-  extends: Supervisor,
-  constructor: function(from=null, detail=null, connection=null) {
-    this.Client(from, detail, connection);
-  },
-  Client: function(form, detail, connection) {
-    this.Supervisor(connection);
-
-    if (form) {
-      this.actorID = form.actor;
-      this.from(form, detail);
-    }
-  },
-  connect: function(port) {
-    this.connection = new Connection(port);
-  },
-  actorID: null,
-  actor: function() {
-    return this.actorID;
-  },
-  /**
-   * Update the actor from its representation.
-   * Subclasses should override this.
-   */
-  form: function(form) {
-  },
-  /**
-   * Method is invokeid when packet received constitutes an
-   * event. By default such packets are demarshalled and
-   * dispatched on the client instance.
-   */
-  dispatch: function(packet) {
-  },
-  /**
-   * Method is invoked when packet is returned in response to
-   * a request. By default respond delivers response to a first
-   * request in a queue.
-   */
-  read: function(input) {
-    throw new TypeError("Subclass must implement read method");
-  },
-  write: function(input) {
-    throw new TypeError("Subclass must implement write method");
-  },
-  respond: function(packet) {
-    var [resolve, reject] = requests(this).shift();
-    if (packet.error)
-      reject(packet.error);
-    else
-      resolve(this.read(packet));
-  },
-  receive: function(packet) {
-    if (this.isEventPacket(packet)) {
-      this.dispatch(packet);
-    }
-    else if (requests(this).length) {
-      this.respond(packet);
-    }
-    else {
-      this.catch(packet);
-    }
-  },
-  send: function(packet) {
-    Promise.cast(packet.to || this.actor()).then(id => {
-      packet.to = id;
-      this.connection.send(packet);
-    })
-  },
-  request: function(packet) {
-    return this.connection.request(packet);
-  }
-});
-
-
-var Destructor = method => {
-  return function(...args) {
-    return method.apply(this, args).then(result => {
-      this.destroy();
-      return result;
-    });
-  };
-};
-
-var Profiled = (method, id) => {
-  return function(...args) {
-    var start = new Date();
-    return method.apply(this, args).then(result => {
-      var end = new Date();
-      this.telemetry.add(id, +end - start);
-      return result;
-    });
-  };
-};
-
-var Method = (request, response) => {
-  return response ? new BidirectionalMethod(request, response) :
-         new UnidirecationalMethod(request);
-};
-
-var UnidirecationalMethod = request => {
-  return function(...args) {
-    var packet = request.write(args, this);
-    this.connection.send(packet);
-    return Promise.resolve(void(0));
-  };
-};
-
-var BidirectionalMethod = (request, response) => {
-  return function(...args) {
-    var packet = request.write(args, this);
-    return this.connection.request(packet).then(packet => {
-      return response.read(packet, this);
-    });
-  };
-};
-
-
-Client.from = ({category, typeName, methods, events}) => {
-  var proto = {
-    constructor: function(...args) {
-      this.Client(...args);
-    },
-    extends: Client,
-    name: typeName
-  };
-
-  methods.forEach(({telemetry, request, response, name, oneway, release}) => {
-    var [reader, writer] = oneway ? [, new Request(request)] :
-                           [new Request(request), new Response(response)];
-    var method = new Method(request, response);
-    var profiler = telemetry ? new Profiler(method) : method;
-    var destructor = release ? new Destructor(profiler) : profiler;
-    proto[name] = destructor;
-  });
-
-  return Class(proto);
-};
-
-
-var defineType = (client, descriptor) => {
-  var type = void(0)
-  if (typeof(descriptor) === "string") {
-    if (name.indexOf(":") > 0)
-      type = makeCompoundType(descriptor);
-    else if (name.indexOf("#") > 0)
-      type = new ActorDetail(descriptor);
-    else if (client.specification[descriptor])
-      type = makeCategoryType(client.specification[descriptor]);
-  } else {
-    type = makeCategoryType(descriptor);
-  }
-
-  if (type)
-    client.types.set(type.name, type);
-  else
-    throw TypeError("Invalid type: " + descriptor);
-};
-
-
-var makeCompoundType = name => {
-  var index = name.indexOf(":");
-  var [baseType, subType] = [name.slice(0, index), parts.slice(1)];
-  return baseType === "array" ? new ArrayOf(subType) :
-         baseType === "nullable" ? new Maybe(subType) :
-         null;
-};
-
-var makeCategoryType = (descriptor) => {
-  var { category } = descriptor;
-  return category === "dict" ? new Dictionary(descriptor) :
-         category === "actor" ? new Actor(descriptor) :
-         null;
-};
-
-
-var typeFor = (client, type="primitive") => {
-  if (!client.types.has(type))
-    defineType(client, type);
-
-  return client.types.get(type);
-};
-
-
-var Client = Class({
-  constructor: function() {
-  },
-  setupTypes: function(specification) {
-    this.specification = specification;
-    this.types = new Map();
-  },
-  read: function(input, type) {
-    return typeFor(this, type).read(input, this);
-  },
-  write: function(input, type) {
-    return typeFor(this, type).write(input, this);
-  }
-});
-
-
-var Type = Class({
-  get name() {
-    return this.category ? this.category + ":" + this.type :
-           this.type;
-  },
-  read: function(input, client) {
-    throw new TypeError("`Type` subclass must implement `read`");
-  },
-  write: function(input, client) {
-    throw new TypeError("`Type` subclass must implement `write`");
-  }
-});
-
-
-var Primitve = Class({
-  extends: Type,
-  constuctor: function(type) {
-    this.type = type;
-  },
-  read: function(input, client) {
-    return input;
-  },
-  write: function(input, client) {
-    return input;
-  }
-});
-
-var Maybe = Class({
-  extends: Type,
-  category: "nullable",
-  constructor: function(type) {
-    this.type = type;
-  },
-  read: function(input, client) {
-    return input === null ? null :
-           input === void(0) ? void(0) :
-           client.read(input, this.type);
-  },
-  write: function(input, client) {
-    return input === null ? null :
-           input === void(0) ? void(0) :
-           client.write(input, this.type);
-  }
-});
-
-var ArrayOf = Class({
-  extends: Type,
-  category: "array",
-  constructor: function(type) {
-    this.type = type;
-  },
-  read: function(input, client) {
-    return input.map($ => client.read($, this.type));
-  },
-  write: function(input, client) {
-    return input.map($ => client.write($, this.type));
-  }
-});
-
-var Dictionary = Class({
-  exteds: Type,
-  category: "dict",
-  get name() { return this.type; },
-  constructor: function({typeName, specializations}) {
-    this.type = typeName;
-    this.types = specifications;
-  },
-  read: function(input, client) {
-    var output = {};
-    for (var key in input) {
-      output[key] = client.read(input[key], this.types[key]);
-    }
-    return output;
-  },
-  write: function(input, client) {
-    var output = {};
-    for (var key in input) {
-      output[key] = client.write(value, this.types[key]);
-    }
-    return output;
-  }
-});
-
-var Actor = Class({
-  exteds: Type,
-  category: "actor",
-  get name() { return this.type; },
-  constructor: function({typeName}) {
-    this.type = typeName;
-  },
-  read: function(input, client, detail) {
-    var id = value.actor;
-    var actor = void(0);
-    if (client.connection.has(id)) {
-      return client.connection.get(id).form(input, detail, client);
-    } else {
-      actor = Client.from(detail, client);
-      actor.actorID = id;
-      client.supervise(actor);
-    }
-  },
-  write: function(input, client, detail) {
-    if (input instanceof Actor) {
-      if (!input.actorID) {
-        client.supervise(input);
-      }
-      return input.from(detail);
-    }
-    return input.actorID;
-  }
-});
-
-var Root = Client.from({
-  "category": "actor",
-  "typeName": "root",
-  "methods": [
-    {"name": "listTabs",
-     "request": {},
-     "response": {
-     }
-    },
-    {"name": "listAddons"
-    },
-    {"name": "echo",
-
-    },
-    {"name": "protocolDescription",
-
-    }
-  ]
-});
-
-
-var ActorDetail = Class({
-  extends: Actor,
-  constructor: function(name, actor, detail) {
-    this.detail = detail;
-    this.actor = actor;
-  },
-  read: function(input, client) {
-    this.actor.read(input, client, this.detail);
-  },
-  write: function(input, client) {
-    this.actor.write(input, client, this.detail);
-  }
-});
-
-var registeredLifetimes = new Map();
-var LifeTime = Class({
-  extends: Type,
-  category: "lifetime",
-  constructor: function(lifetime, type) {
-    this.name = lifetime + ":" + type.name;
-    this.field = registeredLifetimes.get(lifetime);
-  },
-  read: function(input, client) {
-    return this.type.read(input, client[this.field]);
-  },
-  write: function(input, client) {
-    return this.type.write(input, client[this.field]);
-  }
-});
-
-var primitive = new Primitve("primitive");
-var string = new Primitve("string");
-var number = new Primitve("number");
-var boolean = new Primitve("boolean");
-var json = new Primitve("json");
-var array = new Primitve("array");
-
-
-var TypedValue = Class({
-  extends: Type,
-  constructor: function(name, type) {
-    this.TypedValue(name, type);
-  },
-  TypedValue: function(name, type) {
-    this.name = name;
-    this.type = type;
-  },
-  read: function(input, client) {
-    return this.client.read(input, this.type);
-  },
-  write: function(input, client) {
-    return this.client.write(input, this.type);
-  }
-});
-
-var Return = Class({
-  extends: TypedValue,
-  constructor: function(type) {
-    this.type = type
-  }
-});
-
-var Argument = Class({
-  extends: TypedValue,
-  constructor: function(...args) {
-    this.Argument(...args);
-  },
-  Argument: function(index, type) {
-    this.index = index;
-    this.TypedValue("argument[" + index + "]", type);
-  },
-  read: function(input, client, target) {
-    return target[this.index] = client.read(input, this.type);
-  }
-});
-
-var Option = Class({
-  extends: Argument,
-  constructor: function(...args) {
-    return this.Argument(...args);
-  },
-  read: function(input, client, target, name) {
-    var param = target[this.index] || (target[this.index] = {});
-    param[name] = input === void(0) ? input : client.read(input, this.type);
-  },
-  write: function(input, client, name) {
-    var value = input && input[name];
-    return value === void(0) ? value : client.write(value, this.type);
-  }
-});
-
-var Request = Class({
-  extends: Type,
-  constructor: function(template={}) {
-    this.type = template.type;
-    this.template = template;
-    this.params = findPlaceholders(template, Argument);
-  },
-  read: function(packet, client) {
-    var args = [];
-    for (var param of this.params) {
-      var {placeholder, path} = param;
-      var name = path[path.length - 1];
-      placeholder.read(getPath(packet, path), client, args, name);
-      // TODO:
-      // args[placeholder.index] = placeholder.read(query(packet, path), client);
-    }
-    return args;
-  },
-  write: function(input, client) {
-    return JSON.parse(JSON.stringify(this.template, (key, value) => {
-      return value instanceof Argument ? value.write(input[value.index],
-                                                     client, key) :
-             value;
-    }));
-  }
-});
-
-var Response = Class({
-  extends: Type,
-  constructor: function(template={}) {
-    this.template = template;
-    var [x] = findPlaceholders(template, Return);
-    var {placeholder, path} = x;
-    this.return = placeholder;
-    this.path = path;
-  },
-  read: function(packet, client) {
-    var value = query(packet, this.path);
-    return this.return.read(value, client);
-  },
-  write: function(input, client) {
-    return JSON.parse(JSON.stringify(this.template, (key, value) => {
-      return value instanceof Return ? value.write(input) :
-             input
-    }));
-  }
-});
-
-// Returns array of values for the given object.
-var values = object => Object.keys(object).map(key => object[key]);
-// Returns [key, value] pairs for the given object.
-var pairs = object => Object.keys(object).map(key => [key, object[key]]);
-// Queries an object for the field nested with in it.
-var query = (object, path) => path.reduce((object, entry) => object && object[entry],
-                                          object);
-
-
-var Root = Client.from({
-  "category": "actor",
-  "typeName": "root",
-  "methods": [
-    {
-      "name": "echo",
-      "request": {
-        "string": { "_arg": 0, "type": "string" }
-      },
-      "response": {
-        "string": { "_retval": "string" }
-      }
-    },
-    {
-      "name": "listTabs",
-      "request": {},
-      "response": { "_retval": "tablist" }
-    },
-    {
-      "name": "actorDescriptions",
-      "request": {},
-      "response": { "_retval": "json" }
-    }
-  ],
-  "events": {
-    "tabListChanged": {}
-  }
-});
-
-var Tab = Client.from({
-  "category": "dict",
-  "typeName": "tab",
-  "specifications": {
-    "title": "string",
-    "url": "string",
-    "outerWindowID": "number",
-    "console": "console",
-    "inspectorActor": "inspector",
-    "callWatcherActor": "call-watcher",
-    "canvasActor": "canvas",
-    "webglActor": "webgl",
-    "webaudioActor": "webaudio",
-    "styleSheetsActor": "stylesheets",
-    "styleEditorActor": "styleeditor",
-    "storageActor": "storage",
-    "gcliActor": "gcli",
-    "memoryActor": "memory",
-    "eventLoopLag": "eventLoopLag",
-
-    "trace": "trace", // missing
-  }
-});
-
-var tablist = Client.from({
-  "category": "dict",
-  "typeName": "tablist",
-  "specializations": {
-    "selected": "number",
-    "tabs": "array:tab"
-  }
-});
-
-})(this);
-
diff --git a/addon-sdk/source/examples/debug-client/data/index.html b/addon-sdk/source/examples/debug-client/data/index.html
deleted file mode 100644
index 7788e3580f..0000000000
--- a/addon-sdk/source/examples/debug-client/data/index.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-  
-      
-      
-  
-  
-  
-  
-
diff --git a/addon-sdk/source/examples/debug-client/data/plugin.png b/addon-sdk/source/examples/debug-client/data/plugin.png
deleted file mode 100644
index 6a364a30a8..0000000000
Binary files a/addon-sdk/source/examples/debug-client/data/plugin.png and /dev/null differ
diff --git a/addon-sdk/source/examples/debug-client/data/task.js b/addon-sdk/source/examples/debug-client/data/task.js
deleted file mode 100644
index b46feb10e9..0000000000
--- a/addon-sdk/source/examples/debug-client/data/task.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-(function(exports) {
-"use strict";
-
-const spawn = (task, ...args) => {
-  return new Promise((resolve, reject) => {
-    try {
-      const routine = task(...args);
-      const raise = error => routine.throw(error);
-      const step = data => {
-        const { done, value } = routine.next(data);
-        if (done)
-          resolve(value);
-        else
-          Promise.resolve(value).then(step, raise);
-      }
-      step();
-    } catch(error) {
-      reject(error);
-    }
-  });
-}
-exports.spawn = spawn;
-
-})(Task = {});
diff --git a/addon-sdk/source/examples/debug-client/index.js b/addon-sdk/source/examples/debug-client/index.js
deleted file mode 100644
index ff91ff8cdc..0000000000
--- a/addon-sdk/source/examples/debug-client/index.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const { Panel } = require("dev/panel");
-const { Tool } = require("dev/toolbox");
-const { Class } = require("sdk/core/heritage");
-
-
-const LadybugPanel = Class({
-  extends: Panel,
-  label: "Ladybug",
-  tooltip: "Debug client example",
-  icon: "./plugin.png",
-  url: "./index.html",
-  setup: function({debuggee}) {
-    this.debuggee = debuggee;
-  },
-  dispose: function() {
-    delete this.debuggee;
-  },
-  onReady: function() {
-    this.debuggee.start();
-    this.postMessage("RDP", [this.debuggee]);
-  },
-});
-exports.LadybugPanel = LadybugPanel;
-
-
-const ladybug = new Tool({
-  panels: { ladybug: LadybugPanel }
-});
diff --git a/addon-sdk/source/examples/debug-client/package.json b/addon-sdk/source/examples/debug-client/package.json
deleted file mode 100644
index 058fa97afa..0000000000
--- a/addon-sdk/source/examples/debug-client/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "debug-client",
-  "id": "@debug-client",
-  "title": "Debug client",
-  "description": "Example debug client",
-  "version": "0.0.1",
-  "author": "Irakli Gozalishvili",
-  "main": "./index.js",
-  "license": "MPL-2.0"
-}
diff --git a/addon-sdk/source/examples/debug-client/test/test-main.js b/addon-sdk/source/examples/debug-client/test/test-main.js
deleted file mode 100644
index 9862fc20b7..0000000000
--- a/addon-sdk/source/examples/debug-client/test/test-main.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
-
-exports.testMain = function(assert) {
-  assert.pass("TODO: Write some tests.");
-};
-
-require("sdk/test").run(exports);
diff --git a/addon-sdk/source/examples/reading-data/data/mom.png b/addon-sdk/source/examples/reading-data/data/mom.png
deleted file mode 100644
index 4ba89a2c1e..0000000000
Binary files a/addon-sdk/source/examples/reading-data/data/mom.png and /dev/null differ
diff --git a/addon-sdk/source/examples/reading-data/data/sample.html b/addon-sdk/source/examples/reading-data/data/sample.html
deleted file mode 100644
index c7c09cb987..0000000000
--- a/addon-sdk/source/examples/reading-data/data/sample.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-

Hello World

- diff --git a/addon-sdk/source/examples/reading-data/lib/main.js b/addon-sdk/source/examples/reading-data/lib/main.js deleted file mode 100644 index 468a497b12..0000000000 --- a/addon-sdk/source/examples/reading-data/lib/main.js +++ /dev/null @@ -1,53 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var self = require("sdk/self"); -var { Panel } = require("sdk/panel"); -var { ToggleButton } = require("sdk/ui"); - -function replaceMom(html) { - return html.replace("World", "Mom"); -} -exports.replaceMom = replaceMom; - -exports.main = function(options, callbacks) { - console.log("My ID is " + self.id); - - // Load the sample HTML into a string. - var helloHTML = self.data.load("sample.html"); - - // Let's now modify it... - helloHTML = replaceMom(helloHTML); - - // ... and then create a panel that displays it. - var myPanel = Panel({ - contentURL: "data:text/html," + helloHTML, - onHide: handleHide - }); - - // Create a widget that displays the image. We'll attach the panel to it. - // When you click the widget, the panel will pop up. - var button = ToggleButton({ - id: "test-widget", - label: "Mom", - icon: './mom.png', - onChange: handleChange - }); - - // If you run cfx with --static-args='{"quitWhenDone":true}' this program - // will automatically quit Firefox when it's done. - if (options.staticArgs.quitWhenDone) - callbacks.quit(); -} - -function handleChange(state) { - if (state.checked) { - myPanel.show({ position: button }); - } -} - -function handleHide() { - button.state('window', { checked: false }); -} diff --git a/addon-sdk/source/examples/reading-data/package.json b/addon-sdk/source/examples/reading-data/package.json deleted file mode 100644 index 8bdce64235..0000000000 --- a/addon-sdk/source/examples/reading-data/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "reading-data", - "description": "A demonstration of reading bundled data.", - "keywords": [], - "author": "Brian Warner", - "contributors": [], - "license": "MPL-2.0", - "id": "reading-data-example@jetpack.mozillalabs.com" -} diff --git a/addon-sdk/source/examples/reading-data/tests/test-main.js b/addon-sdk/source/examples/reading-data/tests/test-main.js deleted file mode 100644 index 4e85f49de4..0000000000 --- a/addon-sdk/source/examples/reading-data/tests/test-main.js +++ /dev/null @@ -1,25 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var m = require("main"); -var self = require("sdk/self"); - -exports.testReplace = function(test) { - var input = "Hello World"; - var output = m.replaceMom(input); - test.assertEqual(output, "Hello Mom"); - var callbacks = { quit: function() {} }; - - // Make sure it doesn't crash... - m.main({ staticArgs: {} }, callbacks); -}; - -exports.testID = function(test) { - // The ID is randomly generated during tests, so we cannot compare it against - // anything in particular. Just assert that it is not empty. - test.assert(self.id.length > 0); - test.assertEqual(self.data.url("sample.html"), - "resource://reading-data-example-at-jetpack-dot-mozillalabs-dot-com/reading-data/data/sample.html"); -}; diff --git a/addon-sdk/source/examples/theme/data/icon-16.png b/addon-sdk/source/examples/theme/data/icon-16.png deleted file mode 100644 index 72327f77b6..0000000000 Binary files a/addon-sdk/source/examples/theme/data/icon-16.png and /dev/null differ diff --git a/addon-sdk/source/examples/theme/data/index.html b/addon-sdk/source/examples/theme/data/index.html deleted file mode 100644 index 24ffc1a049..0000000000 --- a/addon-sdk/source/examples/theme/data/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/addon-sdk/source/examples/theme/data/theme.css b/addon-sdk/source/examples/theme/data/theme.css deleted file mode 100644 index c837960d96..0000000000 --- a/addon-sdk/source/examples/theme/data/theme.css +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#devtools-theme-box { - background-color: red !important; -} diff --git a/addon-sdk/source/examples/theme/lib/main.js b/addon-sdk/source/examples/theme/lib/main.js deleted file mode 100644 index 3b71376a59..0000000000 --- a/addon-sdk/source/examples/theme/lib/main.js +++ /dev/null @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Tool } = require("dev/toolbox"); -const { Class } = require("sdk/core/heritage"); -const { onEnable, onDisable } = require("dev/theme/hooks"); -const { Theme, LightTheme } = require("dev/theme"); - -/** - * This object represents a new theme registered within the Toolbox. - * You can activate it by clicking on "My Light Theme" theme option - * in the Options panel. - * Note that the new theme derives styles from built-in Light theme. - */ -const MyTheme = Theme({ - name: "mytheme", - label: "My Light Theme", - styles: [LightTheme, "./theme.css"], - - onEnable: function(window, oldTheme) { - console.log("myTheme.onEnable; method override " + - window.location.href); - }, - onDisable: function(window, newTheme) { - console.log("myTheme.onDisable; method override " + - window.location.href); - }, -}); - -// Registration - -const mytheme = new Tool({ - name: "My Tool", - themes: { mytheme: MyTheme } -}); diff --git a/addon-sdk/source/examples/theme/package.json b/addon-sdk/source/examples/theme/package.json deleted file mode 100644 index cf761b6e3c..0000000000 --- a/addon-sdk/source/examples/theme/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "theme", - "title": "theme", - "id": "theme@jetpack", - "description": "How to create new theme for devtools", - "author": "Jan Odvarko", - "license": "MPL-2.0", - "version": "0.1.0", - "main": "lib/main" -} diff --git a/addon-sdk/source/examples/theme/test/test-main.js b/addon-sdk/source/examples/theme/test/test-main.js deleted file mode 100644 index 9862fc20b7..0000000000 --- a/addon-sdk/source/examples/theme/test/test-main.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -exports.testMain = function(assert) { - assert.pass("TODO: Write some tests."); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/examples/toolbar-api/data/favicon.ico b/addon-sdk/source/examples/toolbar-api/data/favicon.ico deleted file mode 100644 index ae5084bc03..0000000000 Binary files a/addon-sdk/source/examples/toolbar-api/data/favicon.ico and /dev/null differ diff --git a/addon-sdk/source/examples/toolbar-api/data/index.html b/addon-sdk/source/examples/toolbar-api/data/index.html deleted file mode 100644 index 4c91a63ae7..0000000000 --- a/addon-sdk/source/examples/toolbar-api/data/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/addon-sdk/source/examples/toolbar-api/lib/main.js b/addon-sdk/source/examples/toolbar-api/lib/main.js deleted file mode 100644 index a538c8cd63..0000000000 --- a/addon-sdk/source/examples/toolbar-api/lib/main.js +++ /dev/null @@ -1,48 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Toolbar } = require("sdk/ui/toolbar"); -const { Frame } = require("sdk/ui/frame"); -const { ActionButton } = require("sdk/ui/button/action"); - -var button = new ActionButton({ - id: "button", - label: "send!", - icon: "./favicon.ico", - onClick: () => { - frame.postMessage({ - hello: "content" - }); - } -}); - -var frame = new Frame({ - url: "./index.html", - onAttach: () => { - console.log("frame was attached"); - }, - onReady: () => { - console.log("frame document was loaded"); - }, - onLoad: () => { - console.log("frame load complete"); - }, - onMessage: (event) => { - console.log("got message from frame content", event); - if (event.data === "ping!") - event.source.postMessage("pong!", event.source.origin); - } -}); -var toolbar = new Toolbar({ - items: [frame], - title: "Addon Demo", - hidden: false, - onShow: () => { - console.log("toolbar was shown"); - }, - onHide: () => { - console.log("toolbar was hidden"); - } -}); diff --git a/addon-sdk/source/examples/toolbar-api/package.json b/addon-sdk/source/examples/toolbar-api/package.json deleted file mode 100644 index 62af7f7ff0..0000000000 --- a/addon-sdk/source/examples/toolbar-api/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "toolbar-api", - "title": "Toolbar API", - "main": "./lib/main.js", - "description": "a toolbar api example", - "author": "", - "license": "MPL-2.0", - "version": "0.1.1", - "engines": { - "firefox": ">=27.0 <=30.0" - } -} diff --git a/addon-sdk/source/examples/toolbar-api/test/test-main.js b/addon-sdk/source/examples/toolbar-api/test/test-main.js deleted file mode 100644 index 9862fc20b7..0000000000 --- a/addon-sdk/source/examples/toolbar-api/test/test-main.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -exports.testMain = function(assert) { - assert.pass("TODO: Write some tests."); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/examples/ui-button-apis/lib/main.js b/addon-sdk/source/examples/ui-button-apis/lib/main.js deleted file mode 100644 index f0ae3dd6cf..0000000000 --- a/addon-sdk/source/examples/ui-button-apis/lib/main.js +++ /dev/null @@ -1,39 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var data = require('sdk/self').data; -var tabs = require('sdk/tabs'); -var { notify } = require('sdk/notifications'); -var { ActionButton, ToggleButton } = require('sdk/ui'); - -var icon = 'chrome://mozapps/skin/extensions/extensionGeneric.svg'; -exports.icon = icon; - -// your basic action button -var action = ActionButton({ - id: 'test-action-button', - label: 'Action Button', - icon: icon, - onClick: function (state) { - notify({ - title: "Action!", - text: "This notification was triggered from an action button!", - }); - } -}); -exports.actionButton = action; - -var toggle = ToggleButton({ - id: 'test-toggle-button', - label: 'Toggle Button', - icon: icon, - onClick: function (state) { - notify({ - title: "Toggled!", - text: "The current state of the button is " + state.checked, - }); - } -}); -exports.toggleButton = toggle; diff --git a/addon-sdk/source/examples/ui-button-apis/package.json b/addon-sdk/source/examples/ui-button-apis/package.json deleted file mode 100644 index 83cf7f6ffa..0000000000 --- a/addon-sdk/source/examples/ui-button-apis/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ui-button-apis", - "title": "Australis Button API Examples", - "id": "ui-button-apis@mozilla.org", - "description": "A Button API example", - "author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)", - "license": "MPL-2.0", - "version": "0.1.1", - "main": "./lib/main.js" -} diff --git a/addon-sdk/source/examples/ui-button-apis/tests/test-main.js b/addon-sdk/source/examples/ui-button-apis/tests/test-main.js deleted file mode 100644 index 49bdc863a8..0000000000 --- a/addon-sdk/source/examples/ui-button-apis/tests/test-main.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -try { - // CFX use case.. - var { actionButton, toggleButton, icon } = require("main"); -} -catch (e) { - // JPM use case.. - let mainURI = "../lib/main"; - var { actionButton, toggleButton, icon } = require(mainURI); -} -var self = require("sdk/self"); - -exports.testActionButton = function(assert) { - assert.equal(actionButton.id, "test-action-button", "action button id is correct"); - assert.equal(actionButton.label, "Action Button", "action button label is correct"); - assert.equal(actionButton.icon, icon, "action button icon is correct"); -} - -exports.testToggleButton = function(assert) { - assert.equal(toggleButton.id, "test-toggle-button", "toggle button id is correct"); - assert.equal(toggleButton.label, "Toggle Button", "toggle button label is correct"); - assert.equal(toggleButton.icon, icon, "toggle button icon is correct"); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/gulpfile.js b/addon-sdk/source/gulpfile.js deleted file mode 100644 index 4020dd9d49..0000000000 --- a/addon-sdk/source/gulpfile.js +++ /dev/null @@ -1,44 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var gulp = require('gulp'); -var patch = require("./bin/node-scripts/apply-patch"); -var ini = require("./bin/node-scripts/update-ini"); - -gulp.task('test', function(done) { - require("./bin/jpm-test").run().then(done); -}); - -gulp.task('test:addons', function(done) { - require("./bin/jpm-test").run("addons").catch(console.error).then(done); -}); - -gulp.task('test:docs', function(done) { - require("./bin/jpm-test").run("docs").catch(console.error).then(done); -}); - -gulp.task('test:examples', function(done) { - require("./bin/jpm-test").run("examples").catch(console.error).then(done); -}); - -gulp.task('test:modules', function(done) { - require("./bin/jpm-test").run("modules").catch(console.error).then(done); -}); - -gulp.task('test:ini', function(done) { - require("./bin/jpm-test").run("ini").catch(console.error).then(done); -}); - -gulp.task('test:firefox-bin', function(done) { - require("./bin/jpm-test").run("firefox-bin").catch(console.error).then(done); -}); - -gulp.task('patch:clean', function(done) { - patch.clean().catch(console.error).then(done); -}); - -gulp.task('patch:apply', function(done) { - patch.apply().catch(console.error).then(done); -}); diff --git a/addon-sdk/source/mapping.json b/addon-sdk/source/mapping.json deleted file mode 100644 index 0052e820b0..0000000000 --- a/addon-sdk/source/mapping.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "api-utils": "sdk/deprecated/api-utils", - "base64": "sdk/base64", - "content": "sdk/content/content", - "deprecate": "sdk/util/deprecate", - "event/core": "sdk/event/core", - "events": "sdk/deprecated/events", - "functional": "sdk/core/functional", - "l10n/core": "sdk/l10n/json/core", - "l10n/html": "sdk/l10n/html", - "l10n/loader": "sdk/l10n/loader", - "l10n/locale": "sdk/l10n/locale", - "l10n/prefs": "sdk/l10n/prefs", - "list": "sdk/util/list", - "loader": "sdk/loader/loader", - "namespace": "sdk/core/namespace", - "preferences-service": "sdk/preferences/service", - "promise": "sdk/core/promise", - "system": "sdk/system", - "system/events": "sdk/system/events-shimmed", - "tabs/tab": "sdk/tabs/tab", - "tabs/utils": "sdk/tabs/utils", - "timer": "sdk/timers", - "traits": "sdk/deprecated/traits", - "unload": "sdk/system/unload", - "window-utils": "sdk/deprecated/window-utils", - "window/utils": "sdk/window/utils", - "windows/dom": "sdk/windows/dom", - "windows/loader": "sdk/windows/loader", - "xul-app": "sdk/system/xul-app", - "url": "sdk/url", - "traceback": "sdk/console/traceback", - "xhr": "sdk/net/xhr", - "match-pattern": "sdk/util/match-pattern", - "file": "sdk/io/file", - "runtime": "sdk/system/runtime", - "xpcom": "sdk/platform/xpcom", - "querystring": "sdk/querystring", - "text-streams": "sdk/io/text-streams", - "app-strings": "sdk/deprecated/app-strings", - "environment": "sdk/system/environment", - "keyboard/utils": "sdk/keyboard/utils", - "dom/events": "sdk/dom/events-shimmed", - "utils/data": "sdk/io/data", - "test/assert": "sdk/test/assert", - "hidden-frame": "sdk/frame/hidden-frame", - "collection": "sdk/util/collection", - "array": "sdk/util/array", - "clipboard": "sdk/clipboard", - "context-menu": "sdk/context-menu", - "hotkeys": "sdk/hotkeys", - "indexed-db": "sdk/indexed-db", - "l10n": "sdk/l10n", - "notifications": "sdk/notifications", - "page-mod": "sdk/page-mod", - "page-worker": "sdk/page-worker", - "panel": "sdk/panel", - "passwords": "sdk/passwords", - "private-browsing": "sdk/private-browsing", - "request": "sdk/request", - "selection": "sdk/selection", - "self": "sdk/self", - "simple-prefs": "sdk/simple-prefs", - "simple-storage": "sdk/simple-storage", - "tabs": "sdk/tabs", - "timers": "sdk/timers", - "windows": "sdk/windows", - "harness": "sdk/test/harness", - "run-tests": "sdk/test/runner", - "test": "sdk/test" -} diff --git a/addon-sdk/source/package.json b/addon-sdk/source/package.json deleted file mode 100644 index 5a9707afb8..0000000000 --- a/addon-sdk/source/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "addon-sdk", - "description": "Add-on development made easy.", - "keywords": [ - "javascript", "engine", "addon", "extension", - "xulrunner", "firefox", "browser" - ], - "license": "MPL-2.0", - "unpack": true, - "scripts": { - "test": "gulp test" - }, - "homepage": "https://github.com/mozilla/addon-sdk", - "repository": { - "type": "git", - "url": "git://github.com/mozilla/addon-sdk.git" - }, - "version": "0.1.18", - "main": "./lib/index.js", - "loader": "lib/sdk/loader/cuddlefish.js", - "devDependencies": { - "async": "0.9.0", - "chai": "2.1.1", - "fs-extra": "0.18.2", - "fx-runner": "0.0.7", - "glob": "4.4.2", - "gulp": "3.8.11", - "ini-parser": "0.0.2", - "jpm": "0.0.29", - "lodash": "3.3.1", - "mocha": "2.1.0", - "patch-editor": "0.0.1", - "promise": "6.1.0", - "rimraf": "2.3.1", - "teacher": "0.0.1", - "unzip": "0.1.11", - "xmldom": "0.1.19" - } -} diff --git a/addon-sdk/source/python-lib/cuddlefish/__init__.py b/addon-sdk/source/python-lib/cuddlefish/__init__.py deleted file mode 100644 index 365d96c5ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ /dev/null @@ -1,959 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import sys -import os -import optparse -import time - -from copy import copy -import simplejson as json -from cuddlefish import packaging -from cuddlefish._version import get_versions - -MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary' -MOZRUNNER_BIN_NOT_FOUND_HELP = """ -I can't find the application binary in any of its default locations -on your system. Please specify one using the -b/--binary option. -""" - -UPDATE_RDF_FILENAME = "%s.update.rdf" -XPI_FILENAME = "%s.xpi" - -usage = """ -%prog [options] command [command-specific options] - -Supported Commands: - init - create a sample addon in an empty directory - test - run tests - run - run program - xpi - generate an xpi - -Internal Commands: - testcfx - test the cfx tool - testex - test all example code - testpkgs - test all installed packages - testall - test whole environment - -Experimental and internal commands and options are not supported and may be -changed or removed in the future. -""" - -global_options = [ - (("-v", "--verbose",), dict(dest="verbose", - help="enable lots of output", - action="store_true", - default=False)), - ] - -parser_groups = ( - ("Supported Command-Specific Options", [ - (("", "--update-url",), dict(dest="update_url", - help="update URL in install.rdf", - metavar=None, - default=None, - cmds=['xpi'])), - (("", "--update-link",), dict(dest="update_link", - help="generate update.rdf", - metavar=None, - default=None, - cmds=['xpi'])), - (("-p", "--profiledir",), dict(dest="profiledir", - help=("profile directory to pass to " - "app"), - metavar=None, - default=None, - cmds=['test', 'run', 'testex', - 'testpkgs', 'testall'])), - (("-b", "--binary",), dict(dest="binary", - help="path to app binary", - metavar=None, - default=None, - cmds=['test', 'run', 'testex', 'testpkgs', - 'testall'])), - (("", "--binary-args",), dict(dest="cmdargs", - help=("additional arguments passed to the " - "binary"), - metavar=None, - default=None, - cmds=['run', 'test'])), - (("", "--dependencies",), dict(dest="dep_tests", - help="include tests for all deps", - action="store_true", - default=False, - cmds=['test', 'testex', 'testpkgs', - 'testall'])), - (("", "--times",), dict(dest="iterations", - type="int", - help="number of times to run tests", - default=1, - cmds=['test', 'testex', 'testpkgs', - 'testall'])), - (("-f", "--filter",), dict(dest="filter", - help=("only run tests whose filenames " - "match FILENAME and optionally " - "match TESTNAME, both regexps"), - metavar="FILENAME[:TESTNAME]", - default='', - cmds=['test', 'testex', 'testaddons', 'testpkgs', - 'testall'])), - (("-g", "--use-config",), dict(dest="config", - help="use named config from local.json", - metavar=None, - default="default", - cmds=['test', 'run', 'xpi', 'testex', - 'testpkgs', 'testall'])), - (("", "--templatedir",), dict(dest="templatedir", - help="XULRunner app/ext. template", - metavar=None, - default=None, - cmds=['run', 'xpi'])), - (("", "--package-path",), dict(dest="packagepath", action="append", - help="extra directories for package search", - metavar=None, - default=[], - cmds=['run', 'xpi', 'test'])), - (("", "--extra-packages",), dict(dest="extra_packages", - help=("extra packages to include, " - "comma-separated. Default is " - "'addon-sdk'."), - metavar=None, - default="addon-sdk", - cmds=['run', 'xpi', 'test', 'testex', - 'testpkgs', 'testall', - 'testcfx'])), - (("", "--pkgdir",), dict(dest="pkgdir", - help=("package dir containing " - "package.json; default is " - "current directory"), - metavar=None, - default=None, - cmds=['run', 'xpi', 'test'])), - (("", "--static-args",), dict(dest="static_args", - help="extra harness options as JSON", - type="json", - metavar=None, - default="{}", - cmds=['run', 'xpi'])), - (("", "--parseable",), dict(dest="parseable", - help="display test output in a parseable format", - action="store_true", - default=False, - cmds=['run', 'test', 'testex', 'testpkgs', - 'testaddons', 'testall'])), - ] - ), - - ("Experimental Command-Specific Options", [ - (("-a", "--app",), dict(dest="app", - help=("app to run: firefox (default), fennec, " - "fennec-on-device, xulrunner or " - "thunderbird"), - metavar=None, - type="choice", - choices=["firefox", - "fennec-on-device", "thunderbird", - "xulrunner"], - default="firefox", - cmds=['test', 'run', 'testex', 'testpkgs', - 'testall'])), - (("-o", "--overload-modules",), dict(dest="overload_modules", - help=("Overload JS modules integrated into" - " Firefox with the one from your SDK" - " repository"), - action="store_true", - default=False, - cmds=['run', 'test', 'testex', 'testpkgs', - 'testall'])), - (("", "--strip-sdk",), dict(dest="bundle_sdk", - help=("Do not ship SDK modules in the xpi"), - action="store_false", - default=False, - cmds=['run', 'test', 'testex', 'testpkgs', - 'testall', 'xpi'])), - (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk", - help=("When --strip-sdk isn't passed, " - "force using sdk modules shipped in " - "the xpi instead of firefox ones"), - action="store_true", - default=False, - cmds=['run', 'test', 'testex', 'testpkgs', - 'testall', 'xpi'])), - (("", "--no-run",), dict(dest="no_run", - help=("Instead of launching the " - "application, just show the command " - "for doing so. Use this to launch " - "the application in a debugger like " - "gdb."), - action="store_true", - default=False, - cmds=['run', 'test'])), - (("", "--no-quit",), dict(dest="no_quit", - help=("Prevent from killing Firefox when" - "running tests"), - action="store_true", - default=False, - cmds=['run', 'test'])), - (("", "--no-strip-xpi",), dict(dest="no_strip_xpi", - help="retain unused modules in XPI", - action="store_true", - default=False, - cmds=['xpi'])), - (("", "--force-mobile",), dict(dest="enable_mobile", - help="Force compatibility with Firefox Mobile", - action="store_true", - default=False, - cmds=['run', 'test', 'xpi', 'testall'])), - (("", "--mobile-app",), dict(dest="mobile_app_name", - help=("Name of your Android application to " - "use. Possible values: 'firefox', " - "'firefox_beta', 'fennec_aurora', " - "'fennec' (for nightly)."), - metavar=None, - default=None, - cmds=['run', 'test', 'testall'])), - (("", "--harness-option",), dict(dest="extra_harness_option_args", - help=("Extra properties added to " - "harness-options.json"), - action="append", - metavar="KEY=VALUE", - default=[], - cmds=['xpi'])), - (("", "--stop-on-error",), dict(dest="stopOnError", - help="Stop running tests after the first failure", - action="store_true", - metavar=None, - default=False, - cmds=['test', 'testex', 'testpkgs'])), - (("", "--check-memory",), dict(dest="check_memory", - help="attempts to detect leaked compartments after a test run", - action="store_true", - default=False, - cmds=['test', 'testpkgs', 'testaddons', - 'testall'])), - (("", "--output-file",), dict(dest="output_file", - help="Where to put the finished .xpi", - default=None, - cmds=['xpi'])), - (("", "--abort-on-missing-module",), dict(dest="abort_on_missing", - help="Abort if required module is missing", - action="store_true", - default=False, - cmds=['test', 'run', 'xpi', 'testpkgs'])), - (("", "--no-connections",), dict(dest="no_connections", - help="disable/enable remote connections (on for cfx run only by default)", - type="choice", - choices=["on", "off", "default"], - default="default", - cmds=['test', 'run', 'testpkgs', - 'testall', 'testaddons', 'testex'])), - ] - ), - - ("Internal Command-Specific Options", [ - (("", "--addons",), dict(dest="addons", - help=("paths of addons to install, " - "comma-separated"), - metavar=None, - default=None, - cmds=['test', 'run', 'testex', 'testpkgs', - 'testall'])), - (("", "--test-runner-pkg",), dict(dest="test_runner_pkg", - help=("name of package " - "containing test runner " - "program (default is " - "test-harness)"), - default="addon-sdk", - cmds=['test', 'testex', 'testpkgs', - 'testall'])), - # --keydir was removed in 1.0b5, but we keep it around in the options - # parser to make life easier for frontends like FlightDeck which - # might still pass it. It can go away once the frontends are updated. - (("", "--keydir",), dict(dest="keydir", - help=("obsolete, ignored"), - metavar=None, - default=None, - cmds=['test', 'run', 'xpi', 'testex', - 'testpkgs', 'testall'])), - (("", "--e10s",), dict(dest="enable_e10s", - help="enable remote windows", - action="store_true", - default=False, - cmds=['test', 'run', 'testex', 'testpkgs', - 'testaddons', 'testcfx', 'testall'])), - (("", "--logfile",), dict(dest="logfile", - help="log console output to file", - metavar=None, - default=None, - cmds=['run', 'test', 'testex', 'testpkgs'])), - # TODO: This should default to true once our memory debugging - # issues are resolved; see bug 592774. - (("", "--profile-memory",), dict(dest="profileMemory", - help=("profile memory usage " - "(default is false)"), - type="int", - action="store", - default=0, - cmds=['test', 'testex', 'testpkgs', - 'testall'])), - ] - ), - ) - -def find_parent_package(cur_dir): - tail = True - while tail: - if os.path.exists(os.path.join(cur_dir, 'package.json')): - return cur_dir - cur_dir, tail = os.path.split(cur_dir) - return None - -def check_json(option, opt, value): - # We return the parsed JSON here; see bug 610816 for background on why. - try: - return json.loads(value) - except ValueError: - raise optparse.OptionValueError("Option %s must be JSON." % opt) - -class CfxOption(optparse.Option): - TYPES = optparse.Option.TYPES + ('json',) - TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) - TYPE_CHECKER['json'] = check_json - -def parse_args(arguments, global_options, usage, version, parser_groups, - defaults=None): - parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption, - version=version) - - def name_cmp(a, b): - # a[0] = name sequence - # a[0][0] = short name (possibly empty string) - # a[0][1] = long name - names = [] - for seq in (a, b): - names.append(seq[0][0][1:] if seq[0][0] else seq[0][1][2:]) - return cmp(*names) - - global_options.sort(name_cmp) - for names, opts in global_options: - parser.add_option(*names, **opts) - - for group_name, options in parser_groups: - group = optparse.OptionGroup(parser, group_name) - options.sort(name_cmp) - for names, opts in options: - if 'cmds' in opts: - cmds = opts['cmds'] - del opts['cmds'] - cmds.sort() - if not 'help' in opts: - opts['help'] = "" - opts['help'] += " (%s)" % ", ".join(cmds) - group.add_option(*names, **opts) - parser.add_option_group(group) - - if defaults: - parser.set_defaults(**defaults) - - (options, args) = parser.parse_args(args=arguments) - - if not args: - parser.print_help() - parser.exit() - - return (options, args) - -# all tests emit progress messages to stderr, not stdout. (the mozrunner -# console output goes to stderr and is hard to change, and -# unittest.TextTestRunner prefers stderr, so we send everything else there -# too, to keep all the messages in order) - -def test_all(env_root, defaults): - fail = False - - starttime = time.time() - - if not defaults['filter']: - print >>sys.stderr, "Testing cfx..." - sys.stderr.flush() - result = test_cfx(env_root, defaults['verbose']) - if result.failures or result.errors: - fail = True - - if not fail or not defaults.get("stopOnError"): - print >>sys.stderr, "Testing all examples..." - sys.stderr.flush() - - try: - test_all_examples(env_root, defaults) - except SystemExit, e: - fail = (e.code != 0) or fail - - if not fail or not defaults.get("stopOnError"): - print >>sys.stderr, "Testing all unit-test addons..." - sys.stderr.flush() - - try: - test_all_testaddons(env_root, defaults) - except SystemExit, e: - fail = (e.code != 0) or fail - - if not fail or not defaults.get("stopOnError"): - print >>sys.stderr, "Testing all packages..." - sys.stderr.flush() - try: - test_all_packages(env_root, defaults) - except SystemExit, e: - fail = (e.code != 0) or fail - - print >>sys.stderr, "Total time for all tests: %f seconds" % (time.time() - starttime) - - if fail: - print >>sys.stderr, "Some tests were unsuccessful." - sys.exit(1) - print >>sys.stderr, "All tests were successful. Ship it!" - sys.exit(0) - -def test_cfx(env_root, verbose): - import cuddlefish.tests - - # tests write to stderr. flush everything before and after to avoid - # confusion later. - sys.stdout.flush(); sys.stderr.flush() - olddir = os.getcwd() - os.chdir(env_root) - retval = cuddlefish.tests.run(verbose) - os.chdir(olddir) - sys.stdout.flush(); sys.stderr.flush() - return retval - -def test_all_testaddons(env_root, defaults): - addons_dir = os.path.join(env_root, "test", "addons") - addons = [dirname for dirname in os.listdir(addons_dir) - if os.path.isdir(os.path.join(addons_dir, dirname))] - addons.sort() - fail = False - for dirname in addons: - # apply the filter - if (not defaults['filter'].split(":")[0] in dirname): - continue - - print >>sys.stderr, "Testing %s..." % dirname - sys.stderr.flush() - try: - run(arguments=["testrun", - "--pkgdir", - os.path.join(addons_dir, dirname)], - defaults=defaults, - env_root=env_root) - except SystemExit, e: - fail = (e.code != 0) or fail - if fail and defaults.get("stopOnError"): - break - - if fail: - print >>sys.stderr, "Some test addons tests were unsuccessful." - sys.exit(-1) - -def test_all_examples(env_root, defaults): - examples_dir = os.path.join(env_root, "examples") - examples = [dirname for dirname in os.listdir(examples_dir) - if os.path.isdir(os.path.join(examples_dir, dirname))] - examples.sort() - fail = False - for dirname in examples: - if (not defaults['filter'].split(":")[0] in dirname): - continue - - print >>sys.stderr, "Testing %s..." % dirname - sys.stderr.flush() - try: - run(arguments=["test", - "--pkgdir", - os.path.join(examples_dir, dirname)], - defaults=defaults, - env_root=env_root) - except SystemExit, e: - fail = (e.code != 0) or fail - if fail and defaults.get("stopOnError"): - break - - if fail: - print >>sys.stderr, "Some examples tests were unsuccessful." - sys.exit(-1) - -def test_all_packages(env_root, defaults): - packages_dir = os.path.join(env_root, "packages") - if os.path.isdir(packages_dir): - packages = [dirname for dirname in os.listdir(packages_dir) - if os.path.isdir(os.path.join(packages_dir, dirname))] - else: - packages = [] - packages.append(env_root) - packages.sort() - print >>sys.stderr, "Testing all available packages: %s." % (", ".join(packages)) - sys.stderr.flush() - fail = False - for dirname in packages: - print >>sys.stderr, "Testing %s..." % dirname - sys.stderr.flush() - try: - run(arguments=["test", - "--pkgdir", - os.path.join(packages_dir, dirname)], - defaults=defaults, - env_root=env_root) - except SystemExit, e: - fail = (e.code != 0) or fail - if fail and defaults.get('stopOnError'): - break - if fail: - print >>sys.stderr, "Some package tests were unsuccessful." - sys.exit(-1) - -def get_config_args(name, env_root): - local_json = os.path.join(env_root, "local.json") - if not (os.path.exists(local_json) and - os.path.isfile(local_json)): - if name == "default": - return [] - else: - print >>sys.stderr, "File does not exist: %s" % local_json - sys.exit(1) - local_json = packaging.load_json_file(local_json) - if 'configs' not in local_json: - print >>sys.stderr, "'configs' key not found in local.json." - sys.exit(1) - if name not in local_json.configs: - if name == "default": - return [] - else: - print >>sys.stderr, "No config found for '%s'." % name - sys.exit(1) - config = local_json.configs[name] - if type(config) != list: - print >>sys.stderr, "Config for '%s' must be a list of strings." % name - sys.exit(1) - return config - -def initializer(env_root, args, out=sys.stdout, err=sys.stderr): - from templates import PACKAGE_JSON, TEST_MAIN_JS - from preflight import create_jid - path = os.getcwd() - addon = os.path.basename(path) - # if more than two arguments - if len(args) > 2: - print >>err, 'Too many arguments.' - return {"result":1} - if len(args) == 2: - path = os.path.join(path,args[1]) - try: - os.mkdir(path) - print >>out, '*', args[1], 'package directory created' - except OSError: - print >>out, '*', args[1], 'already exists, testing if directory is empty' - # avoid clobbering existing files, but we tolerate things like .git - existing = [fn for fn in os.listdir(path) if not fn.startswith(".")] - if existing: - print >>err, 'This command must be run in an empty directory.' - return {"result":1} - for d in ['lib','data','test']: - os.mkdir(os.path.join(path,d)) - print >>out, '*', d, 'directory created' - jid = create_jid() - print >>out, '* generated jID automatically:', jid - open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(), - 'title':addon, - 'id':jid }) - print >>out, '* package.json written' - open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS) - print >>out, '* test/test-main.js written' - open(os.path.join(path,'lib','main.js'),'w').write('') - print >>out, '* lib/main.js written' - if len(args) == 1: - print >>out, '\nYour sample add-on is now ready.' - print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' - else: - print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' - print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' - return {"result":0, "jid":jid} - -def buildJID(target_cfg): - if "id" in target_cfg: - jid = target_cfg["id"] - else: - import uuid - jid = str(uuid.uuid4()) - if not ("@" in jid or jid.startswith("{")): - jid = jid + "@jetpack" - return jid - -def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, - defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'), - stdout=sys.stdout): - versions = get_versions() - sdk_version = versions["version"] - display_version = "Add-on SDK %s (%s)" % (sdk_version, versions["full"]) - parser_kwargs = dict(arguments=arguments, - global_options=global_options, - parser_groups=parser_groups, - usage=usage, - version=display_version, - defaults=defaults) - - (options, args) = parse_args(**parser_kwargs) - - config_args = get_config_args(options.config, env_root); - - # reparse configs with arguments from local.json - if config_args: - parser_kwargs['arguments'] += config_args - (options, args) = parse_args(**parser_kwargs) - - command = args[0] - - if command == "init": - initializer(env_root, args) - return - if command == "testpkgs": - test_all_packages(env_root, defaults=options.__dict__) - return - elif command == "testaddons": - test_all_testaddons(env_root, defaults=options.__dict__) - return - elif command == "testex": - test_all_examples(env_root, defaults=options.__dict__) - return - elif command == "testall": - test_all(env_root, defaults=options.__dict__) - return - elif command == "testcfx": - if options.filter: - print >>sys.stderr, "The filter option is not valid with the testcfx command" - return - test_cfx(env_root, options.verbose) - return - elif command not in ["xpi", "test", "run", "testrun"]: - print >>sys.stderr, "Unknown command: %s" % command - print >>sys.stderr, "Try using '--help' for assistance." - sys.exit(1) - - target_cfg_json = None - if not target_cfg: - if not options.pkgdir: - options.pkgdir = find_parent_package(os.getcwd()) - if not options.pkgdir: - print >>sys.stderr, ("cannot find 'package.json' in the" - " current directory or any parent.") - sys.exit(1) - else: - options.pkgdir = os.path.abspath(options.pkgdir) - if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): - print >>sys.stderr, ("cannot find 'package.json' in" - " %s." % options.pkgdir) - sys.exit(1) - - target_cfg_json = os.path.join(options.pkgdir, 'package.json') - target_cfg = packaging.get_config_in_dir(options.pkgdir) - - # At this point, we're either building an XPI or running Jetpack code in - # a Mozilla application (which includes running tests). - - use_main = False - inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory', - 'no_quit', 'abort_on_missing'] - enforce_timeouts = False - - if command == "xpi": - use_main = True - elif command == "test": - if 'tests' not in target_cfg: - target_cfg['tests'] = [] - inherited_options.extend(['iterations', 'filter', 'profileMemory', - 'stopOnError']) - enforce_timeouts = True - elif command == "run": - use_main = True - elif command == "testrun": - use_main = True - enforce_timeouts = True - else: - assert 0, "shouldn't get here" - - if use_main and 'main' not in target_cfg: - # If the user supplies a template dir, then the main - # program may be contained in the template. - if not options.templatedir: - print >>sys.stderr, "package.json does not have a 'main' entry." - sys.exit(1) - - if not pkg_cfg: - pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath) - - target = target_cfg.name - - # TODO: Consider keeping a cache of dynamic UUIDs, based - # on absolute filesystem pathname, in the root directory - # or something. - if command in ('xpi', 'run', 'testrun'): - from cuddlefish.preflight import preflight_config - if target_cfg_json: - config_was_ok, modified = preflight_config(target_cfg, - target_cfg_json) - if not config_was_ok: - if modified: - # we need to re-read package.json . The safest approach - # is to re-run the "cfx xpi"/"cfx run" command. - print >>sys.stderr, ("package.json modified: please re-run" - " 'cfx %s'" % command) - else: - print >>sys.stderr, ("package.json needs modification:" - " please update it and then re-run" - " 'cfx %s'" % command) - sys.exit(1) - # if we make it this far, we have a JID - else: - assert command == "test" - - jid = buildJID(target_cfg) - - targets = [target] - if command == "test": - targets.append(options.test_runner_pkg) - - extra_packages = [] - if options.extra_packages: - extra_packages = options.extra_packages.split(",") - if extra_packages: - targets.extend(extra_packages) - target_cfg.extra_dependencies = extra_packages - - deps = packaging.get_deps_for_targets(pkg_cfg, targets) - - from cuddlefish.manifest import build_manifest, ModuleNotFoundError, \ - BadChromeMarkerError - # Figure out what loader files should be scanned. This is normally - # computed inside packaging.generate_build_for_target(), by the first - # dependent package that defines a "loader" property in its package.json. - # This property is interpreted as a filename relative to the top of that - # file, and stored as a path in build.loader . generate_build_for_target() - # cannot be called yet (it needs the list of used_deps that - # build_manifest() computes, but build_manifest() needs the list of - # loader files that it computes). We could duplicate or factor out this - # build.loader logic, but that would be messy, so instead we hard-code - # the choice of loader for manifest-generation purposes. In practice, - # this means that alternative loaders probably won't work with - # --strip-xpi. - assert packaging.DEFAULT_LOADER == "addon-sdk" - assert pkg_cfg.packages["addon-sdk"].loader == "lib/sdk/loader/cuddlefish.js" - cuddlefish_js_path = os.path.join(pkg_cfg.packages["addon-sdk"].root_dir, - "lib", "sdk", "loader", "cuddlefish.js") - loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)] - scan_tests = command == "test" - - try: - manifest = build_manifest(target_cfg, pkg_cfg, deps, scan_tests, - None, loader_modules, - abort_on_missing=options.abort_on_missing) - except ModuleNotFoundError, e: - print str(e) - sys.exit(1) - except BadChromeMarkerError, e: - # An error had already been displayed on stderr in manifest code - sys.exit(1) - used_deps = manifest.get_used_packages() - if command == "test": - # The test runner doesn't appear to link against any actual packages, - # because it loads everything at runtime (invisible to the linker). - # If we believe that, we won't set up URI mappings for anything, and - # tests won't be able to run. - used_deps = deps - for xp in extra_packages: - if xp not in used_deps: - used_deps.append(xp) - - build = packaging.generate_build_for_target( - pkg_cfg, target, used_deps, - include_dep_tests=options.dep_tests, - is_running_tests=(command == "test") - ) - - harness_options = { - 'jetpackID': jid, - 'staticArgs': options.static_args, - 'name': target, - } - - harness_options.update(build) - - # When cfx is run from sdk root directory, we will strip sdk modules and - # override them with local modules. - # So that integration tools will continue to work and use local modules - if os.getcwd() == env_root: - options.bundle_sdk = True - options.force_use_bundled_sdk = False - options.overload_modules = True - - if options.pkgdir == env_root: - options.bundle_sdk = True - options.overload_modules = True - - extra_environment = {} - if command == "test": - # This should be contained in the test runner package. - harness_options['main'] = 'sdk/test/runner' - harness_options['mainPath'] = 'sdk/test/runner' - else: - harness_options['main'] = target_cfg.get('main') - harness_options['mainPath'] = manifest.top_path - extra_environment["CFX_COMMAND"] = command - - for option in inherited_options: - harness_options[option] = getattr(options, option) - - harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps) - - harness_options['sdkVersion'] = sdk_version - - packaging.call_plugins(pkg_cfg, used_deps) - - retval = 0 - - if options.templatedir: - app_extension_dir = os.path.abspath(options.templatedir) - elif os.path.exists(os.path.join(options.pkgdir, "app-extension")): - app_extension_dir = os.path.join(options.pkgdir, "app-extension") - else: - mydir = os.path.dirname(os.path.abspath(__file__)) - app_extension_dir = os.path.join(mydir, "../../app-extension") - - # Do not add entries for SDK modules - harness_options['manifest'] = manifest.get_harness_options_manifest(False) - - # Gives an hint to tell if sdk modules are bundled or not - harness_options['is-sdk-bundled'] = options.bundle_sdk or options.no_strip_xpi - - if options.force_use_bundled_sdk: - if not harness_options['is-sdk-bundled']: - print >>sys.stderr, ("--force-use-bundled-sdk " - "can't be used if sdk isn't bundled.") - sys.exit(1) - if options.overload_modules: - print >>sys.stderr, ("--force-use-bundled-sdk and --overload-modules " - "can't be used at the same time.") - sys.exit(1) - # Pass a flag in order to force using sdk modules shipped in the xpi - harness_options['force-use-bundled-sdk'] = True - - from cuddlefish.rdf import gen_manifest, RDFUpdate - - manifest_rdf = gen_manifest(template_root_dir=app_extension_dir, - target_cfg=target_cfg, - jid=jid, - update_url=options.update_url, - bootstrap=True, - enable_mobile=options.enable_mobile) - - if command == "xpi" and options.update_link: - if not options.update_link.startswith("https"): - raise optparse.OptionValueError("--update-link must start with 'https': %s" % options.update_link) - rdf_name = UPDATE_RDF_FILENAME % target_cfg.name - print >>stdout, "Exporting update description to %s." % rdf_name - update = RDFUpdate() - update.add(manifest_rdf, options.update_link) - open(rdf_name, "w").write(str(update)) - - # ask the manifest what files were used, so we can construct an XPI - # without the rest. This will include the loader (and everything it - # uses) because of the "loader_modules" starting points we passed to - # build_manifest earlier - used_files = None - if command == "xpi": - used_files = set(manifest.get_used_files(options.bundle_sdk)) - - if options.no_strip_xpi: - used_files = None # disables the filter, includes all files - - if command == 'xpi': - from cuddlefish.xpi import build_xpi - # Generate extra options - extra_harness_options = {} - for kv in options.extra_harness_option_args: - key,value = kv.split("=", 1) - extra_harness_options[key] = value - # Generate xpi filepath - if options.output_file: - xpi_path = options.output_file - else: - xpi_path = XPI_FILENAME % target_cfg.name - - print >>stdout, "Exporting extension to %s." % xpi_path - build_xpi(template_root_dir=app_extension_dir, - manifest=manifest_rdf, - xpi_path=xpi_path, - harness_options=harness_options, - limit_to=used_files, - extra_harness_options=extra_harness_options, - bundle_sdk=True, - pkgdir=options.pkgdir) - else: - from cuddlefish.runner import run_app - - if options.no_connections == "default": - if command == "run": - no_connections = False - else: - no_connections = True - elif options.no_connections == "on": - no_connections = True - else: - no_connections = False - - if options.profiledir: - options.profiledir = os.path.expanduser(options.profiledir) - options.profiledir = os.path.abspath(options.profiledir) - - if options.addons is not None: - options.addons = options.addons.split(",") - - enable_e10s = options.enable_e10s or target_cfg.get('e10s', False) - - try: - retval = run_app(harness_root_dir=app_extension_dir, - manifest_rdf=manifest_rdf, - harness_options=harness_options, - app_type=options.app, - binary=options.binary, - profiledir=options.profiledir, - verbose=options.verbose, - parseable=options.parseable, - enforce_timeouts=enforce_timeouts, - logfile=options.logfile, - addons=options.addons, - args=options.cmdargs, - extra_environment=extra_environment, - norun=options.no_run, - noquit=options.no_quit, - used_files=used_files, - enable_mobile=options.enable_mobile, - mobile_app_name=options.mobile_app_name, - env_root=env_root, - is_running_tests=(command == "test"), - overload_modules=options.overload_modules, - bundle_sdk=options.bundle_sdk, - pkgdir=options.pkgdir, - enable_e10s=enable_e10s, - no_connections=no_connections) - except ValueError, e: - print "" - print "A given cfx option has an inappropriate value:" - print >>sys.stderr, " " + " \n ".join(str(e).split("\n")) - retval = -1 - except Exception, e: - if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): - print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() - retval = -1 - else: - raise - sys.exit(retval) diff --git a/addon-sdk/source/python-lib/cuddlefish/_version.py b/addon-sdk/source/python-lib/cuddlefish/_version.py deleted file mode 100644 index 6f12874d21..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/_version.py +++ /dev/null @@ -1,174 +0,0 @@ - -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by versioneer-0.6 -# (https://github.com/warner/python-versioneer) - -# these strings will be replaced by git during git-archive -git_refnames = "$Format:%d$" -git_full = "$Format:%H$" - - -import subprocess - -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError, e: - if verbose: - print "unable to run %s" % args[0] - print e - return None - stdout = p.communicate()[0].strip() - if p.returncode != 0: - if verbose: - print "unable to run %s (error)" % args[0] - return None - return stdout - - -import sys -import re -import os.path - -def get_expanded_variables(versionfile_source): - """ - the code embedded in _version.py can just fetch the value of these - variables. When used from setup.py, we don't want to import - _version.py, so we do it with a regexp instead. This function is not - used from _version.py. - """ - variables = {} - try: - for line in open(versionfile_source,"r").readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - variables["full"] = mo.group(1) - except EnvironmentError: - pass - return variables - -def versions_from_expanded_variables(variables, tag_prefix): - refnames = variables["refnames"].strip() - if refnames.startswith("$Format"): - return {} # unexpanded, so not in an unpacked git-archive tarball - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - refs.discard(ref) - # Assume all version tags have a digit. git's %d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". - for ref in sorted(refs): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - return { "version": r, - "full": variables["full"].strip() } - # no suitable tags, so we use the full revision id - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - """ - this runs 'git' from the root of the source tree. That either means - someone ran a setup.py command (and this code is in versioneer.py, thus - the containing directory is the root of the source tree), or someone - ran a project-specific entry point (and this code is in _version.py, - thus the containing directory is somewhere deeper in the source tree). - This only gets called if the git-archive 'subst' variables were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - if not os.path.exists(os.path.join(root, ".git")): - return {} - - GIT = "git" - if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], - cwd=root) - if stdout is None: - return {} - if not stdout.startswith(tag_prefix): - if verbose: - print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix) - return {} - tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) - if stdout is None: - return {} - full = stdout.strip() - if tag.endswith("-dirty"): - full += "-dirty" - return {"version": tag, "full": full} - - -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): - try: - here = os.path.abspath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to _version.py, when - # this is used by the runtime. Invert this to find the root from - # __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - except NameError: - # try a couple different things to handle py2exe, bbfreeze, and - # non-CPython implementations which don't do __file__. This code - # either lives in versioneer.py (used by setup.py) or _version.py - # (used by the runtime). In the versioneer.py case, sys.argv[0] will - # be setup.py, in the root of the source tree. In the _version.py - # case, we have no idea what sys.argv[0] is (some - # application-specific runner). - root = os.path.dirname(os.path.abspath(sys.argv[0])) - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print "dirname '%s' doesn't start with prefix '%s'" % (dirname, parentdir_prefix) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "" -parentdir_prefix = "addon-sdk-" -versionfile_source = "python-lib/cuddlefish/_version.py" - -def get_versions(): - variables = { "refnames": git_refnames, "full": git_full } - ver = versions_from_expanded_variables(variables, tag_prefix) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source) - if not ver: - ver = {"version": "unknown", "full": ""} - return ver - diff --git a/addon-sdk/source/python-lib/cuddlefish/bunch.py b/addon-sdk/source/python-lib/cuddlefish/bunch.py deleted file mode 100644 index 5efa79f16c..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/bunch.py +++ /dev/null @@ -1,34 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Taken from Paver's paver.options module. - -class Bunch(dict): - """A dictionary that provides attribute-style access.""" - - def __repr__(self): - keys = self.keys() - keys.sort() - args = ', '.join(['%s=%r' % (key, self[key]) for key in keys]) - return '%s(%s)' % (self.__class__.__name__, args) - - def __getitem__(self, key): - item = dict.__getitem__(self, key) - if callable(item): - return item() - return item - - def __getattr__(self, name): - try: - return self[name] - except KeyError: - raise AttributeError(name) - - __setattr__ = dict.__setitem__ - - def __delattr__(self, name): - try: - del self[name] - except KeyError: - raise AttributeError(name) diff --git a/addon-sdk/source/python-lib/cuddlefish/manifest.py b/addon-sdk/source/python-lib/cuddlefish/manifest.py deleted file mode 100644 index e9913be7bb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/manifest.py +++ /dev/null @@ -1,807 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -import os, sys, re, hashlib -import simplejson as json -SEP = os.path.sep -from cuddlefish.util import filter_filenames, filter_dirnames - -# Load new layout mapping hashtable -path = os.path.join(os.environ.get('CUDDLEFISH_ROOT'), "mapping.json") -data = open(path, 'r').read() -NEW_LAYOUT_MAPPING = json.loads(data) - -def js_zipname(packagename, modulename): - return "%s-lib/%s.js" % (packagename, modulename) -def docs_zipname(packagename, modulename): - return "%s-docs/%s.md" % (packagename, modulename) -def datamap_zipname(packagename): - return "%s-data.json" % packagename -def datafile_zipname(packagename, datapath): - return "%s-data/%s" % (packagename, datapath) - -def to_json(o): - return json.dumps(o, indent=1).encode("utf-8")+"\n" - -class ModuleNotFoundError(Exception): - def __init__(self, requirement_type, requirement_name, - used_by, line_number, looked_in): - Exception.__init__(self) - self.requirement_type = requirement_type # "require" or "define" - self.requirement_name = requirement_name # string, what they require()d - self.used_by = used_by # string, full path to module which did require() - self.line_number = line_number # int, 1-indexed line number of first require() - self.looked_in = looked_in # list of full paths to potential .js files - def __str__(self): - what = "%s(%s)" % (self.requirement_type, self.requirement_name) - where = self.used_by - if self.line_number is not None: - where = "%s:%d" % (self.used_by, self.line_number) - searched = "Looked for it in:\n %s\n" % "\n ".join(self.looked_in) - return ("ModuleNotFoundError: unable to satisfy: %s from\n" - " %s:\n" % (what, where)) + searched - -class BadModuleIdentifier(Exception): - pass -class BadSection(Exception): - pass -class UnreachablePrefixError(Exception): - pass - -class ManifestEntry: - def __init__(self): - self.docs_filename = None - self.docs_hash = None - self.requirements = {} - self.datamap = None - - def get_path(self): - name = self.moduleName - - if name.endswith(".js"): - name = name[:-3] - items = [] - # Only add package name for addons, so that system module paths match - # the path from the commonjs root directory and also match the loader - # mappings. - if self.packageName != "addon-sdk": - items.append(self.packageName) - # And for the same reason, do not append `lib/`. - if self.sectionName == "tests": - items.append(self.sectionName) - items.append(name) - - return "/".join(items) - - def get_entry_for_manifest(self): - entry = { "packageName": self.packageName, - "sectionName": self.sectionName, - "moduleName": self.moduleName, - "jsSHA256": self.js_hash, - "docsSHA256": self.docs_hash, - "requirements": {}, - } - for req in self.requirements: - if isinstance(self.requirements[req], ManifestEntry): - them = self.requirements[req] # this is another ManifestEntry - entry["requirements"][req] = them.get_path() - else: - # something magic. The manifest entry indicates that they're - # allowed to require() it - entry["requirements"][req] = self.requirements[req] - assert isinstance(entry["requirements"][req], unicode) or \ - isinstance(entry["requirements"][req], str) - return entry - - def add_js(self, js_filename): - self.js_filename = js_filename - self.js_hash = hash_file(js_filename) - def add_docs(self, docs_filename): - self.docs_filename = docs_filename - self.docs_hash = hash_file(docs_filename) - def add_requirement(self, reqname, reqdata): - self.requirements[reqname] = reqdata - def add_data(self, datamap): - self.datamap = datamap - - def get_js_zipname(self): - return js_zipname(self.packagename, self.modulename) - def get_docs_zipname(self): - if self.docs_hash: - return docs_zipname(self.packagename, self.modulename) - return None - # self.js_filename - # self.docs_filename - - -def hash_file(fn): - return hashlib.sha256(open(fn,"rb").read()).hexdigest() - -def get_datafiles(datadir): - """ - yields pathnames relative to DATADIR, ignoring some files - """ - for dirpath, dirnames, filenames in os.walk(datadir): - filenames = list(filter_filenames(filenames)) - # this tells os.walk to prune the search - dirnames[:] = filter_dirnames(dirnames) - for filename in filenames: - fullname = os.path.join(dirpath, filename) - assert fullname.startswith(datadir+SEP), "%s%s not in %s" % (datadir, SEP, fullname) - yield fullname[len(datadir+SEP):] - - -class DataMap: - # one per package - def __init__(self, pkg): - self.pkg = pkg - self.name = pkg.name - self.files_to_copy = [] - datamap = {} - datadir = os.path.join(pkg.root_dir, "data") - for dataname in get_datafiles(datadir): - absname = os.path.join(datadir, dataname) - zipname = datafile_zipname(pkg.name, dataname) - datamap[dataname] = hash_file(absname) - self.files_to_copy.append( (zipname, absname) ) - self.data_manifest = to_json(datamap) - self.data_manifest_hash = hashlib.sha256(self.data_manifest).hexdigest() - self.data_manifest_zipname = datamap_zipname(pkg.name) - self.data_uri_prefix = "%s/data/" % (self.name) - -class BadChromeMarkerError(Exception): - pass - -class ModuleInfo: - def __init__(self, package, section, name, js, docs): - self.package = package - self.section = section - self.name = name - self.js = js - self.docs = docs - - def __hash__(self): - return hash( (self.package.name, self.section, self.name, - self.js, self.docs) ) - def __eq__(self, them): - if them.__class__ is not self.__class__: - return False - if ((them.package.name, them.section, them.name, them.js, them.docs) != - (self.package.name, self.section, self.name, self.js, self.docs) ): - return False - return True - - def __repr__(self): - return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name, - self.section, - self.name, - self.js, self.docs) - -class ManifestBuilder: - def __init__(self, target_cfg, pkg_cfg, deps, extra_modules, - stderr=sys.stderr, abort_on_missing=False): - self.manifest = {} # maps (package,section,module) to ManifestEntry - self.target_cfg = target_cfg # the entry point - self.pkg_cfg = pkg_cfg # all known packages - self.deps = deps # list of package names to search - self.used_packagenames = set() - self.stderr = stderr - self.extra_modules = extra_modules - self.modules = {} # maps ModuleInfo to URI in self.manifest - self.datamaps = {} # maps package name to DataMap instance - self.files = [] # maps manifest index to (absfn,absfn) js/docs pair - self.test_modules = [] # for runtime - self.abort_on_missing = abort_on_missing # cfx eol - - def build(self, scan_tests, test_filter_re): - """ - process the top module, which recurses to process everything it reaches - """ - if "main" in self.target_cfg: - top_mi = self.find_top(self.target_cfg) - top_me = self.process_module(top_mi) - self.top_path = top_me.get_path() - self.datamaps[self.target_cfg.name] = DataMap(self.target_cfg) - if scan_tests: - mi = self._find_module_in_package("addon-sdk", "lib", "sdk/test/runner", []) - self.process_module(mi) - # also scan all test files in all packages that we use. By making - # a copy of self.used_packagenames first, we refrain from - # processing tests in packages that our own tests depend upon. If - # we're running tests for package A, and either modules in A or - # tests in A depend upon modules from package B, we *don't* want - # to run tests for package B. - test_modules = [] - dirnames = self.target_cfg["tests"] - if isinstance(dirnames, basestring): - dirnames = [dirnames] - dirnames = [os.path.join(self.target_cfg.root_dir, d) - for d in dirnames] - for d in dirnames: - for filename in os.listdir(d): - if filename.startswith("test-") and filename.endswith(".js"): - testname = filename[:-3] # require(testname) - if test_filter_re: - if not re.search(test_filter_re, testname): - continue - tmi = ModuleInfo(self.target_cfg, "tests", testname, - os.path.join(d, filename), None) - # scan the test's dependencies - tme = self.process_module(tmi) - test_modules.append( (testname, tme) ) - # also add it as an artificial dependency of unit-test-finder, so - # the runtime dynamic load can work. - test_finder = self.get_manifest_entry("addon-sdk", "lib", - "sdk/deprecated/unit-test-finder") - for (testname,tme) in test_modules: - test_finder.add_requirement(testname, tme) - # finally, tell the runtime about it, so they won't have to - # search for all tests. self.test_modules will be passed - # through the harness-options.json file in the - # .allTestModules property. - # Pass the absolute module path. - self.test_modules.append(tme.get_path()) - - # include files used by the loader - for em in self.extra_modules: - (pkgname, section, modname, js) = em - mi = ModuleInfo(self.pkg_cfg.packages[pkgname], section, modname, - js, None) - self.process_module(mi) - - - def get_module_entries(self): - return frozenset(self.manifest.values()) - def get_data_entries(self): - return frozenset(self.datamaps.values()) - - def get_used_packages(self): - used = set() - for index in self.manifest: - (package, section, module) = index - used.add(package) - return sorted(used) - - def get_used_files(self, bundle_sdk_modules): - """ - returns all .js files that we reference, plus data/ files. You will - need to add the loader, off-manifest files that it needs, and - generated metadata. - """ - for datamap in self.datamaps.values(): - for (zipname, absname) in datamap.files_to_copy: - yield absname - - for me in self.get_module_entries(): - # Only ship SDK files if we are told to do so - if me.packageName != "addon-sdk" or bundle_sdk_modules: - yield me.js_filename - - def get_harness_options_manifest(self, bundle_sdk_modules): - manifest = {} - for me in self.get_module_entries(): - path = me.get_path() - # Do not add manifest entries for system modules. - # Doesn't prevent from shipping modules. - # Shipping modules is decided in `get_used_files`. - if me.packageName != "addon-sdk" or bundle_sdk_modules: - manifest[path] = me.get_entry_for_manifest() - return manifest - - def get_manifest_entry(self, package, section, module): - index = (package, section, module) - if index not in self.manifest: - m = self.manifest[index] = ManifestEntry() - m.packageName = package - m.sectionName = section - m.moduleName = module - self.used_packagenames.add(package) - return self.manifest[index] - - def uri_name_from_path(self, pkg, fn): - # given a filename like .../pkg1/lib/bar/foo.js, and a package - # specification (with a .root_dir like ".../pkg1" and a .lib list of - # paths where .lib[0] is like "lib"), return the appropriate NAME - # that can be put into a URI like resource://JID-pkg1-lib/NAME . This - # will throw an exception if the file is outside of the lib/ - # directory, since that means we can't construct a URI that points to - # it. - # - # This should be a lot easier, and shouldn't fail when the file is in - # the root of the package. Both should become possible when the XPI - # is rearranged and our URI scheme is simplified. - fn = os.path.abspath(fn) - pkglib = pkg.lib[0] - libdir = os.path.abspath(os.path.join(pkg.root_dir, pkglib)) - # AARGH, section and name! we need to reverse-engineer a - # ModuleInfo instance that will produce a URI (in the form - # PREFIX/PKGNAME-SECTION/JS) that will map to the existing file. - # Until we fix URI generation to get rid of "sections", this is - # limited to files in the same .directories.lib as the rest of - # the package uses. So if the package's main files are in lib/, - # but the main.js is in the package root, there is no URI we can - # construct that will point to it, and we must fail. - # - # This will become much easier (and the failure case removed) - # when we get rid of sections and change the URIs to look like - # (PREFIX/PKGNAME/PATH-TO-JS). - - # AARGH 2, allowing .lib to be a list is really getting in the - # way. That needs to go away eventually too. - if not fn.startswith(libdir): - raise UnreachablePrefixError("Sorry, but the 'main' file (%s) in package %s is outside that package's 'lib' directory (%s), so I cannot construct a URI to reach it." - % (fn, pkg.name, pkglib)) - name = fn[len(libdir):].lstrip(SEP)[:-len(".js")] - return name - - - def parse_main(self, root_dir, main, check_lib_dir=None): - # 'main' can be like one of the following: - # a: ./lib/main.js b: ./lib/main c: lib/main - # we require it to be a path to the file, though, and ignore the - # .directories stuff. So just "main" is insufficient if you really - # want something in a "lib/" subdirectory. - if main.endswith(".js"): - main = main[:-len(".js")] - if main.startswith("./"): - main = main[len("./"):] - # package.json must always use "/", but on windows we'll replace that - # with "\" before using it as an actual filename - main = os.sep.join(main.split("/")) - paths = [os.path.join(root_dir, main+".js")] - if check_lib_dir is not None: - paths.append(os.path.join(root_dir, check_lib_dir, main+".js")) - return paths - - def find_top_js(self, target_cfg): - for libdir in target_cfg.lib: - for n in self.parse_main(target_cfg.root_dir, target_cfg.main, - libdir): - if os.path.exists(n): - return n - raise KeyError("unable to find main module '%s.js' in top-level package" % target_cfg.main) - - def find_top(self, target_cfg): - top_js = self.find_top_js(target_cfg) - n = os.path.join(target_cfg.root_dir, "README.md") - if os.path.exists(n): - top_docs = n - else: - top_docs = None - name = self.uri_name_from_path(target_cfg, top_js) - return ModuleInfo(target_cfg, "lib", name, top_js, top_docs) - - def process_module(self, mi): - pkg = mi.package - #print "ENTERING", pkg.name, mi.name - # mi.name must be fully-qualified - assert (not mi.name.startswith("./") and - not mi.name.startswith("../")) - # create and claim the manifest row first - me = self.get_manifest_entry(pkg.name, mi.section, mi.name) - - me.add_js(mi.js) - if mi.docs: - me.add_docs(mi.docs) - - js_lines = open(mi.js,"r").readlines() - requires, problems, locations = scan_module(mi.js,js_lines,self.stderr) - if problems: - # the relevant instructions have already been written to stderr - raise BadChromeMarkerError() - - # We update our requirements on the way out of the depth-first - # traversal of the module graph - - for reqname in sorted(requires.keys()): - # If requirement is chrome or a pseudo-module (starts with @) make - # path a requirement name. - if reqname == "chrome" or reqname.startswith("@"): - me.add_requirement(reqname, reqname) - else: - # when two modules require() the same name, do they get a - # shared instance? This is a deep question. For now say yes. - - # find_req_for() returns an entry to put in our - # 'requirements' dict, and will recursively process - # everything transitively required from here. It will also - # populate the self.modules[] cache. Note that we must - # tolerate cycles in the reference graph. - looked_in = [] # populated by subroutines - them_me = self.find_req_for(mi, reqname, looked_in, locations) - if them_me is None: - if mi.section == "tests": - # tolerate missing modules in tests, because - # test-securable-module.js, and the modules/red.js - # that it imports, both do that intentionally - continue - if reqname.endswith(".jsm"): - # ignore JSM modules - continue - if not self.abort_on_missing: - # print a warning, but tolerate missing modules - # unless cfx --abort-on-missing-module flag was set - print >>self.stderr, "Warning: missing module: %s" % reqname - me.add_requirement(reqname, reqname) - continue - lineno = locations.get(reqname) # None means define() - if lineno is None: - reqtype = "define" - else: - reqtype = "require" - err = ModuleNotFoundError(reqtype, reqname, - mi.js, lineno, looked_in) - raise err - else: - me.add_requirement(reqname, them_me) - - return me - #print "LEAVING", pkg.name, mi.name - - def find_req_for(self, from_module, reqname, looked_in, locations): - # handle a single require(reqname) statement from from_module . - # Return a uri that exists in self.manifest - # Populate looked_in with places we looked. - def BAD(msg): - return BadModuleIdentifier(msg + " in require(%s) from %s" % - (reqname, from_module)) - - if not reqname: - raise BAD("no actual modulename") - - # Allow things in tests/*.js to require both test code and real code. - # But things in lib/*.js can only require real code. - if from_module.section == "tests": - lookfor_sections = ["tests", "lib"] - elif from_module.section == "lib": - lookfor_sections = ["lib"] - else: - raise BadSection(from_module.section) - modulename = from_module.name - - #print " %s require(%s))" % (from_module, reqname) - - if reqname.startswith("./") or reqname.startswith("../"): - # 1: they want something relative to themselves, always from - # their own package - them = modulename.split("/")[:-1] - bits = reqname.split("/") - while bits[0] in (".", ".."): - if not bits: - raise BAD("no actual modulename") - if bits[0] == "..": - if not them: - raise BAD("too many ..") - them.pop() - bits.pop(0) - bits = them+bits - lookfor_pkg = from_module.package.name - lookfor_mod = "/".join(bits) - return self._get_module_from_package(lookfor_pkg, - lookfor_sections, lookfor_mod, - looked_in) - - # non-relative import. Might be a short name (requiring a search - # through "library" packages), or a fully-qualified one. - - if "/" in reqname: - # 2: PKG/MOD: find PKG, look inside for MOD - bits = reqname.split("/") - lookfor_pkg = bits[0] - lookfor_mod = "/".join(bits[1:]) - mi = self._get_module_from_package(lookfor_pkg, - lookfor_sections, lookfor_mod, - looked_in) - if mi: # caution, 0==None - return mi - else: - # 3: try finding PKG, if found, use its main.js entry point - lookfor_pkg = reqname - mi = self._get_entrypoint_from_package(lookfor_pkg, looked_in) - if mi: - return mi - - # 4: search packages for MOD or MODPARENT/MODCHILD. We always search - # their own package first, then the list of packages defined by their - # .dependencies list - from_pkg = from_module.package.name - mi = self._search_packages_for_module(from_pkg, - lookfor_sections, reqname, - looked_in) - if mi: - return mi - - # Only after we look for module in the addon itself, search for a module - # in new layout. - # First normalize require argument in order to easily find a mapping - normalized = reqname - if normalized.endswith(".js"): - normalized = normalized[:-len(".js")] - if normalized.startswith("addon-kit/"): - normalized = normalized[len("addon-kit/"):] - if normalized.startswith("api-utils/"): - normalized = normalized[len("api-utils/"):] - if normalized in NEW_LAYOUT_MAPPING: - # get the new absolute path for this module - original_reqname = reqname - reqname = NEW_LAYOUT_MAPPING[normalized] - from_pkg = from_module.package.name - - # If the addon didn't explicitely told us to ignore deprecated - # require path, warn the developer: - # (target_cfg is the package.json file) - if not "ignore-deprecated-path" in self.target_cfg: - lineno = locations.get(original_reqname) - print >>self.stderr, "Warning: Use of deprecated require path:" - print >>self.stderr, " In %s:%d:" % (from_module.js, lineno) - print >>self.stderr, " require('%s')." % original_reqname - print >>self.stderr, " New path should be:" - print >>self.stderr, " require('%s')" % reqname - - return self._search_packages_for_module(from_pkg, - lookfor_sections, reqname, - looked_in) - else: - # We weren't able to find this module, really. - return None - - def _handle_module(self, mi): - if not mi: - return None - - # we tolerate cycles in the reference graph, which means we need to - # populate the self.modules cache before recursing into - # process_module() . We must also check the cache first, so recursion - # can terminate. - if mi in self.modules: - return self.modules[mi] - - # this creates the entry - new_entry = self.get_manifest_entry(mi.package.name, mi.section, mi.name) - # and populates the cache - self.modules[mi] = new_entry - self.process_module(mi) - return new_entry - - def _get_module_from_package(self, pkgname, sections, modname, looked_in): - if pkgname not in self.pkg_cfg.packages: - return None - mi = self._find_module_in_package(pkgname, sections, modname, - looked_in) - return self._handle_module(mi) - - def _get_entrypoint_from_package(self, pkgname, looked_in): - if pkgname not in self.pkg_cfg.packages: - return None - pkg = self.pkg_cfg.packages[pkgname] - main = pkg.get("main", None) - if not main: - return None - for js in self.parse_main(pkg.root_dir, main): - looked_in.append(js) - if os.path.exists(js): - section = "lib" - name = self.uri_name_from_path(pkg, js) - docs = None - mi = ModuleInfo(pkg, section, name, js, docs) - return self._handle_module(mi) - return None - - def _search_packages_for_module(self, from_pkg, sections, reqname, - looked_in): - searchpath = [] # list of package names - searchpath.append(from_pkg) # search self first - us = self.pkg_cfg.packages[from_pkg] - if 'dependencies' in us: - # only look in dependencies - searchpath.extend(us['dependencies']) - else: - # they didn't declare any dependencies (or they declared an empty - # list, but we'll treat that as not declaring one, because it's - # easier), so look in all deps, sorted alphabetically, so - # addon-kit comes first. Note that self.deps includes all - # packages found by traversing the ".dependencies" lists in each - # package.json, starting from the main addon package, plus - # everything added by --extra-packages - searchpath.extend(sorted(self.deps)) - for pkgname in searchpath: - mi = self._find_module_in_package(pkgname, sections, reqname, - looked_in) - if mi: - return self._handle_module(mi) - return None - - def _find_module_in_package(self, pkgname, sections, name, looked_in): - # require("a/b/c") should look at ...\a\b\c.js on windows - filename = os.sep.join(name.split("/")) - # normalize filename, make sure that we do not add .js if it already has - # it. - if not filename.endswith(".js") and not filename.endswith(".json"): - filename += ".js" - - if filename.endswith(".js"): - basename = filename[:-3] - if filename.endswith(".json"): - basename = filename[:-5] - - pkg = self.pkg_cfg.packages[pkgname] - if isinstance(sections, basestring): - sections = [sections] - for section in sections: - for sdir in pkg.get(section, []): - js = os.path.join(pkg.root_dir, sdir, filename) - looked_in.append(js) - if os.path.exists(js): - docs = None - maybe_docs = os.path.join(pkg.root_dir, "docs", - basename+".md") - if section == "lib" and os.path.exists(maybe_docs): - docs = maybe_docs - return ModuleInfo(pkg, section, name, js, docs) - return None - -def build_manifest(target_cfg, pkg_cfg, deps, scan_tests, - test_filter_re=None, extra_modules=[], abort_on_missing=False): - """ - Perform recursive dependency analysis starting from entry_point, - building up a manifest of modules that need to be included in the XPI. - Each entry will map require() names to the URL of the module that will - be used to satisfy that dependency. The manifest will be used by the - runtime's require() code. - - This returns a ManifestBuilder object, with two public methods. The - first, get_module_entries(), returns a set of ManifestEntry objects, each - of which can be asked for the following: - - * its contribution to the harness-options.json '.manifest' - * the local disk name - * the name in the XPI at which it should be placed - - The second is get_data_entries(), which returns a set of DataEntry - objects, each of which has: - - * local disk name - * name in the XPI - - note: we don't build the XPI here, but our manifest is passed to the - code which does, so it knows what to copy into the XPI. - """ - - mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules, - abort_on_missing=abort_on_missing) - mxt.build(scan_tests, test_filter_re) - return mxt - - - -COMMENT_PREFIXES = ["//", "/*", "*", "dump("] - -REQUIRE_RE = r"(?>stderr, """ -The following lines from file %(fn)s: -%(lines)s -use 'Components' to access chrome authority. To do so, you need to add a -line somewhat like the following: - - const {%(needs)s} = require("chrome"); - -Then you can use any shortcuts to its properties that you import from the -'chrome' module ('Cc', 'Ci', 'Cm', 'Cr', and 'Cu' for the 'classes', -'interfaces', 'manager', 'results', and 'utils' properties, respectively. And -`components` for `Components` object itself). -""" % { "fn": fn, "needs": ",".join(sorted(old_chrome)), - "lines": "\n".join([" %3d: %s" % (lineno,line) - for (lineno, line) in old_chrome_lines]), - } - problems = True - return problems - -def scan_module(fn, lines, stderr=sys.stderr): - filename = os.path.basename(fn) - requires, locations = scan_requirements_with_grep(fn, lines) - if filename == "cuddlefish.js": - # this is the loader: don't scan for chrome - problems = False - else: - problems = scan_for_bad_chrome(fn, lines, stderr) - return requires, problems, locations - - - -if __name__ == '__main__': - for fn in sys.argv[1:]: - requires, problems, locations = scan_module(fn, open(fn).readlines()) - print - print "---", fn - if problems: - print "PROBLEMS" - sys.exit(1) - print "requires: %s" % (",".join(sorted(requires.keys()))) - print "locations: %s" % locations diff --git a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js deleted file mode 100644 index a0120b0be1..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js +++ /dev/null @@ -1,48 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); - -var { log } = console; - -function startup(data, reason) { - // This code allow to make all stdIO work - try { - Cu.import("resource://gre/modules/ctypes.jsm"); - let libdvm = ctypes.open("libdvm.so"); - let dvmStdioConverterStartup; - // Starting with Android ICS, dalvik uses C++. - // So that the symbol isn't a simple C one - try { - dvmStdioConverterStartup = libdvm.declare("_Z24dvmStdioConverterStartupv", ctypes.default_abi, ctypes.bool); - } - catch(e) { - // Otherwise, before ICS, it was a pure C library - dvmStdioConverterStartup = libdvm.declare("dvmStdioConverterStartup", ctypes.default_abi, ctypes.void_t); - } - dvmStdioConverterStartup(); - log("MU: console redirected to adb logcat."); - } catch(e) { - Cu.reportError("MU: unable to execute jsctype hack: "+e); - } - - try { - let QuitObserver = { - observe: function (aSubject, aTopic, aData) { - Services.obs.removeObserver(QuitObserver, "quit-application"); - dump("MU: APPLICATION-QUIT\n"); - } - }; - Services.obs.addObserver(QuitObserver, "quit-application", false); - log("MU: ready to watch firefox exit."); - } catch(e) { - log("MU: unable to register quit-application observer: " + e); - } -} - -function install() {} -function shutdown() {} diff --git a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf deleted file mode 100644 index 0b81a0e5f8..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - mobile-utils@mozilla.com - 1.0 - 2 - true - - - - - {a23983c0-fd0e-11dc-95ff-0800200c9a66} - 1 - * - - - - - - - {aa3c5121-dab2-40e2-81ca-7ea25febc110} - 1 - * - - - - - Mobile Addon-SDK utility addon - Allow better integration with cfx tool. - Mozilla Corporation - - - diff --git a/addon-sdk/source/python-lib/cuddlefish/packaging.py b/addon-sdk/source/python-lib/cuddlefish/packaging.py deleted file mode 100644 index 0c5357e8e9..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/packaging.py +++ /dev/null @@ -1,463 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import sys -import re -import copy - -import simplejson as json -from cuddlefish.bunch import Bunch - -MANIFEST_NAME = 'package.json' -DEFAULT_LOADER = 'addon-sdk' - -# Is different from root_dir when running tests -env_root = os.environ.get('CUDDLEFISH_ROOT') - -DEFAULT_PROGRAM_MODULE = 'main' - -DEFAULT_ICON = 'icon.png' -DEFAULT_ICON64 = 'icon64.png' - -METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version', - 'developers', 'translators', 'contributors', 'license', 'homepage', - 'icon', 'icon64', 'main', 'directories', 'permissions', 'preferences'] - -RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$') - -class Error(Exception): - pass - -class MalformedPackageError(Error): - pass - -class MalformedJsonFileError(Error): - pass - -class DuplicatePackageError(Error): - pass - -class PackageNotFoundError(Error): - def __init__(self, missing_package, reason): - self.missing_package = missing_package - self.reason = reason - def __str__(self): - return "%s (%s)" % (self.missing_package, self.reason) - -class BadChromeMarkerError(Error): - pass - -def validate_resource_hostname(name): - """ - Validates the given hostname for a resource: URI. - - For more information, see: - - https://bugzilla.mozilla.org/show_bug.cgi?id=566812#c13 - - Examples: - - >>> validate_resource_hostname('blarg') - - >>> validate_resource_hostname('bl arg') - Traceback (most recent call last): - ... - ValueError: Error: the name of your package contains an invalid character. - Package names can contain only lower-case letters, numbers, underscores, and dashes. - Current package name: bl arg - - >>> validate_resource_hostname('BLARG') - Traceback (most recent call last): - ... - ValueError: Error: the name of your package contains upper-case letters. - Package names can contain only lower-case letters, numbers, underscores, and dashes. - Current package name: BLARG - - >>> validate_resource_hostname('foo@bar') - Traceback (most recent call last): - ... - ValueError: Error: the name of your package contains an invalid character. - Package names can contain only lower-case letters, numbers, underscores, and dashes. - Current package name: foo@bar - """ - - # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details. - if not name.lower() == name: - raise ValueError("""Error: the name of your package contains upper-case letters. -Package names can contain only lower-case letters, numbers, underscores, and dashes. -Current package name: %s""" % name) - - if not RESOURCE_HOSTNAME_RE.match(name): - raise ValueError("""Error: the name of your package contains an invalid character. -Package names can contain only lower-case letters, numbers, underscores, and dashes. -Current package name: %s""" % name) - -def find_packages_with_module(pkg_cfg, name): - # TODO: Make this support more than just top-level modules. - filename = "%s.js" % name - packages = [] - for cfg in pkg_cfg.packages.itervalues(): - if 'lib' in cfg: - matches = [dirname for dirname in resolve_dirs(cfg, cfg.lib) - if os.path.exists(os.path.join(dirname, filename))] - if matches: - packages.append(cfg.name) - return packages - -def resolve_dirs(pkg_cfg, dirnames): - for dirname in dirnames: - yield resolve_dir(pkg_cfg, dirname) - -def resolve_dir(pkg_cfg, dirname): - return os.path.join(pkg_cfg.root_dir, dirname) - -def validate_permissions(perms): - if (perms.get('cross-domain-content') and - not isinstance(perms.get('cross-domain-content'), list)): - raise ValueError("Error: `cross-domain-content` permissions in \ - package.json file must be an array of strings:\n %s" % perms) - -def get_metadata(pkg_cfg, deps): - metadata = Bunch() - for pkg_name in deps: - cfg = pkg_cfg.packages[pkg_name] - metadata[pkg_name] = Bunch() - for prop in METADATA_PROPS: - if cfg.get(prop): - if prop == 'permissions': - validate_permissions(cfg[prop]) - metadata[pkg_name][prop] = cfg[prop] - return metadata - -def set_section_dir(base_json, name, base_path, dirnames, allow_root=False): - resolved = compute_section_dir(base_json, base_path, dirnames, allow_root) - if resolved: - base_json[name] = os.path.abspath(resolved) - -def compute_section_dir(base_json, base_path, dirnames, allow_root): - # PACKAGE_JSON.lib is highest priority - # then PACKAGE_JSON.directories.lib - # then lib/ (if it exists) - # then . (but only if allow_root=True) - for dirname in dirnames: - if base_json.get(dirname): - return os.path.join(base_path, base_json[dirname]) - if "directories" in base_json: - for dirname in dirnames: - if dirname in base_json.directories: - return os.path.join(base_path, base_json.directories[dirname]) - for dirname in dirnames: - if os.path.isdir(os.path.join(base_path, dirname)): - return os.path.join(base_path, dirname) - if allow_root: - return os.path.abspath(base_path) - return None - -def normalize_string_or_array(base_json, key): - if base_json.get(key): - if isinstance(base_json[key], basestring): - base_json[key] = [base_json[key]] - -def load_json_file(path): - data = open(path, 'r').read() - try: - return Bunch(json.loads(data)) - except ValueError, e: - raise MalformedJsonFileError('%s when reading "%s"' % (str(e), - path)) - -def get_config_in_dir(path): - package_json = os.path.join(path, MANIFEST_NAME) - if not (os.path.exists(package_json) and - os.path.isfile(package_json)): - raise MalformedPackageError('%s not found in "%s"' % (MANIFEST_NAME, - path)) - base_json = load_json_file(package_json) - - if 'name' not in base_json: - base_json.name = os.path.basename(path) - - # later processing steps will expect to see the following keys in the - # base_json that we return: - # - # name: name of the package - # lib: list of directories with .js files - # test: list of directories with test-*.js files - # doc: list of directories with documentation .md files - # data: list of directories with bundled arbitrary data files - # packages: ? - - if (not base_json.get('tests') and - os.path.isdir(os.path.join(path, 'test'))): - base_json['tests'] = 'test' - - set_section_dir(base_json, 'lib', path, ['lib'], True) - set_section_dir(base_json, 'tests', path, ['test', 'tests'], False) - set_section_dir(base_json, 'doc', path, ['doc', 'docs']) - set_section_dir(base_json, 'data', path, ['data']) - set_section_dir(base_json, 'packages', path, ['packages']) - set_section_dir(base_json, 'locale', path, ['locale']) - - if (not base_json.get('icon') and - os.path.isfile(os.path.join(path, DEFAULT_ICON))): - base_json['icon'] = DEFAULT_ICON - - if (not base_json.get('icon64') and - os.path.isfile(os.path.join(path, DEFAULT_ICON64))): - base_json['icon64'] = DEFAULT_ICON64 - - for key in ['lib', 'tests', 'dependencies', 'packages']: - # TODO: lib/tests can be an array?? consider interaction with - # compute_section_dir above - normalize_string_or_array(base_json, key) - - if 'main' not in base_json and 'lib' in base_json: - for dirname in base_json['lib']: - program = os.path.join(path, dirname, - '%s.js' % DEFAULT_PROGRAM_MODULE) - if os.path.exists(program): - base_json['main'] = DEFAULT_PROGRAM_MODULE - break - - base_json.root_dir = path - - if "dependencies" in base_json: - deps = base_json["dependencies"] - deps = [x for x in deps if x not in ["addon-kit", "api-utils"]] - deps.append("addon-sdk") - base_json["dependencies"] = deps - - return base_json - -def _is_same_file(a, b): - if hasattr(os.path, 'samefile'): - return os.path.samefile(a, b) - return a == b - -def build_config(root_dir, target_cfg, packagepath=[]): - dirs_to_scan = [env_root] # root is addon-sdk dir, diff from root_dir in tests - - def add_packages_from_config(pkgconfig): - if 'packages' in pkgconfig: - for package_dir in resolve_dirs(pkgconfig, pkgconfig.packages): - dirs_to_scan.append(package_dir) - - add_packages_from_config(target_cfg) - - packages_dir = os.path.join(root_dir, 'packages') - if os.path.exists(packages_dir) and os.path.isdir(packages_dir): - dirs_to_scan.append(packages_dir) - dirs_to_scan.extend(packagepath) - - packages = Bunch({target_cfg.name: target_cfg}) - - while dirs_to_scan: - packages_dir = dirs_to_scan.pop() - if os.path.exists(os.path.join(packages_dir, "package.json")): - package_paths = [packages_dir] - else: - package_paths = [os.path.join(packages_dir, dirname) - for dirname in os.listdir(packages_dir) - if not dirname.startswith('.')] - package_paths = [dirname for dirname in package_paths - if os.path.isdir(dirname)] - - for path in package_paths: - pkgconfig = get_config_in_dir(path) - if pkgconfig.name in packages: - otherpkg = packages[pkgconfig.name] - if not _is_same_file(otherpkg.root_dir, path): - raise DuplicatePackageError(path, otherpkg.root_dir) - else: - packages[pkgconfig.name] = pkgconfig - add_packages_from_config(pkgconfig) - - return Bunch(packages=packages) - -def get_deps_for_targets(pkg_cfg, targets): - visited = [] - deps_left = [[dep, None] for dep in list(targets)] - - while deps_left: - [dep, required_by] = deps_left.pop() - if dep not in visited: - visited.append(dep) - if dep not in pkg_cfg.packages: - required_reason = ("required by '%s'" % (required_by)) \ - if required_by is not None \ - else "specified as target" - raise PackageNotFoundError(dep, required_reason) - dep_cfg = pkg_cfg.packages[dep] - deps_left.extend([[i, dep] for i in dep_cfg.get('dependencies', [])]) - deps_left.extend([[i, dep] for i in dep_cfg.get('extra_dependencies', [])]) - - return visited - -def generate_build_for_target(pkg_cfg, target, deps, - include_tests=True, - include_dep_tests=False, - is_running_tests=False, - default_loader=DEFAULT_LOADER): - - build = Bunch(# Contains section directories for all packages: - packages=Bunch(), - locale=Bunch() - ) - - def add_section_to_build(cfg, section, is_code=False, - is_data=False): - if section in cfg: - dirnames = cfg[section] - if isinstance(dirnames, basestring): - # This is just for internal consistency within this - # function, it has nothing to do w/ a non-canonical - # configuration dict. - dirnames = [dirnames] - for dirname in resolve_dirs(cfg, dirnames): - # ensure that package name is valid - try: - validate_resource_hostname(cfg.name) - except ValueError, err: - print err - sys.exit(1) - # ensure that this package has an entry - if not cfg.name in build.packages: - build.packages[cfg.name] = Bunch() - # detect duplicated sections - if section in build.packages[cfg.name]: - raise KeyError("package's section already defined", - cfg.name, section) - # Register this section (lib, data, tests) - build.packages[cfg.name][section] = dirname - - def add_locale_to_build(cfg): - # Bug 730776: Ignore locales for addon-kit, that are only for unit tests - if not is_running_tests and cfg.name == "addon-sdk": - return - - path = resolve_dir(cfg, cfg['locale']) - files = os.listdir(path) - for filename in files: - fullpath = os.path.join(path, filename) - if os.path.isfile(fullpath) and filename.endswith('.properties'): - language = filename[:-len('.properties')] - - from property_parser import parse_file, MalformedLocaleFileError - try: - content = parse_file(fullpath) - except MalformedLocaleFileError, msg: - print msg[0] - sys.exit(1) - - # Merge current locales into global locale hashtable. - # Locale files only contains one big JSON object - # that act as an hastable of: - # "keys to translate" => "translated keys" - if language in build.locale: - merge = (build.locale[language].items() + - content.items()) - build.locale[language] = Bunch(merge) - else: - build.locale[language] = content - - def add_dep_to_build(dep): - dep_cfg = pkg_cfg.packages[dep] - add_section_to_build(dep_cfg, "lib", is_code=True) - add_section_to_build(dep_cfg, "data", is_data=True) - if include_tests and include_dep_tests: - add_section_to_build(dep_cfg, "tests", is_code=True) - if 'locale' in dep_cfg: - add_locale_to_build(dep_cfg) - if ("loader" in dep_cfg) and ("loader" not in build): - build.loader = "%s/%s" % (dep, - dep_cfg.loader) - - target_cfg = pkg_cfg.packages[target] - - if include_tests and not include_dep_tests: - add_section_to_build(target_cfg, "tests", is_code=True) - - for dep in deps: - add_dep_to_build(dep) - - if 'loader' not in build: - add_dep_to_build(DEFAULT_LOADER) - - if 'icon' in target_cfg: - build['icon'] = os.path.join(target_cfg.root_dir, target_cfg.icon) - del target_cfg['icon'] - - if 'icon64' in target_cfg: - build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64) - del target_cfg['icon64'] - - if 'id' in target_cfg: - # NOTE: logic duplicated from buildJID() - jid = target_cfg['id'] - if not ('@' in jid or jid.startswith('{')): - jid += '@jetpack' - build['preferencesBranch'] = jid - - if 'preferences-branch' in target_cfg: - build['preferencesBranch'] = target_cfg['preferences-branch'] - - return build - -def _get_files_in_dir(path): - data = {} - files = os.listdir(path) - for filename in files: - fullpath = os.path.join(path, filename) - if os.path.isdir(fullpath): - data[filename] = _get_files_in_dir(fullpath) - else: - try: - info = os.stat(fullpath) - data[filename] = ("file", dict(size=info.st_size)) - except OSError: - pass - return ("directory", data) - -def build_pkg_index(pkg_cfg): - pkg_cfg = copy.deepcopy(pkg_cfg) - for pkg in pkg_cfg.packages: - root_dir = pkg_cfg.packages[pkg].root_dir - files = _get_files_in_dir(root_dir) - pkg_cfg.packages[pkg].files = files - try: - readme = open(root_dir + '/README.md').read() - pkg_cfg.packages[pkg].readme = readme - except IOError: - pass - del pkg_cfg.packages[pkg].root_dir - return pkg_cfg.packages - -def build_pkg_cfg(root): - pkg_cfg = build_config(root, Bunch(name='dummy')) - del pkg_cfg.packages['dummy'] - return pkg_cfg - -def call_plugins(pkg_cfg, deps): - for dep in deps: - dep_cfg = pkg_cfg.packages[dep] - dirnames = dep_cfg.get('python-lib', []) - for dirname in resolve_dirs(dep_cfg, dirnames): - sys.path.append(dirname) - module_names = dep_cfg.get('python-plugins', []) - for module_name in module_names: - module = __import__(module_name) - module.init(root_dir=dep_cfg.root_dir) - -def call_cmdline_tool(env_root, pkg_name): - pkg_cfg = build_config(env_root, Bunch(name='dummy')) - if pkg_name not in pkg_cfg.packages: - print "This tool requires the '%s' package." % pkg_name - sys.exit(1) - cfg = pkg_cfg.packages[pkg_name] - for dirname in resolve_dirs(cfg, cfg['python-lib']): - sys.path.append(dirname) - module_name = cfg.get('python-cmdline-tool') - module = __import__(module_name) - module.run() diff --git a/addon-sdk/source/python-lib/cuddlefish/preflight.py b/addon-sdk/source/python-lib/cuddlefish/preflight.py deleted file mode 100644 index c92a0890d9..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/preflight.py +++ /dev/null @@ -1,77 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os, sys -import base64 -import simplejson as json - -def create_jid(): - """Return 'jid1-XYZ', where 'XYZ' is a randomly-generated string. (in the - previous jid0- series, the string securely identified a specific public - key). To get a suitable add-on ID, append '@jetpack' to this string. - """ - # per https://developer.mozilla.org/en/Install_Manifests#id all XPI id - # values must either be in the form of a 128-bit GUID (crazy braces - # and all) or in the form of an email address (crazy @ and all). - # Firefox will refuse to install an add-on with an id that doesn't - # match one of these forms. The actual regexp is at: - # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#130 - # So the JID needs an @-suffix, and the only legal punctuation is - # "-._". So we start with a base64 encoding, and replace the - # punctuation (+/) with letters (AB), losing a few bits of integrity. - - # even better: windows has a maximum path length limitation of 256 - # characters: - # http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx - # (unless all paths are prefixed with "\\?\", I kid you not). The - # typical install will put add-on code in a directory like: - # C:\Documents and Settings\\Application Data\Mozilla\Firefox\Profiles\232353483.default\extensions\$JID\... - # (which is 108 chars long without the $JID). - # Then the unpacked XPI contains packaged resources like: - # resources/$JID-api-utils-lib/main.js (35 chars plus the $JID) - # - # We create a random 80 bit string, base64 encode that (with - # AB instead of +/ to be path-safe), then bundle it into - # "jid1-XYZ@jetpack". This gives us 27 characters. The resulting - # main.js will have a path length of 211 characters, leaving us 45 - # characters of margin. - # - # 80 bits is enough to generate one billion JIDs and still maintain lower - # than a one-in-a-million chance of accidental collision. (1e9 JIDs is 30 - # bits, square for the "birthday-paradox" to get 60 bits, add 20 bits for - # the one-in-a-million margin to get 80 bits) - - # if length were no issue, we'd prefer to use this: - h = os.urandom(80/8) - s = base64.b64encode(h, "AB").strip("=") - jid = "jid1-" + s - return jid - -def preflight_config(target_cfg, filename, stderr=sys.stderr): - modified = False - config = json.load(open(filename, 'r')) - - if "id" not in config: - print >>stderr, ("No 'id' in package.json: creating a new ID for you.") - jid = create_jid() - config["id"] = jid - modified = True - - if modified: - i = 0 - backup = filename + ".backup" - while os.path.exists(backup): - if i > 1000: - raise ValueError("I'm having problems finding a good name" - " for the backup file. Please move %s out" - " of the way and try again." - % (filename + ".backup")) - backup = filename + ".backup-%d" % i - i += 1 - os.rename(filename, backup) - new_json = json.dumps(config, indent=4) - open(filename, 'w').write(new_json+"\n") - return False, True - - return True, False diff --git a/addon-sdk/source/python-lib/cuddlefish/prefs.py b/addon-sdk/source/python-lib/cuddlefish/prefs.py deleted file mode 100644 index 9192eb72c1..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/prefs.py +++ /dev/null @@ -1,239 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEFAULT_COMMON_PREFS = { - # allow debug output via dump to be printed to the system console - # (setting it here just in case, even though PlainTextConsole also - # sets this preference) - 'browser.dom.window.dump.enabled': True, - # warn about possibly incorrect code - 'javascript.options.showInConsole': True, - - # Allow remote connections to the debugger - 'devtools.debugger.remote-enabled' : True, - - 'extensions.sdk.console.logLevel': 'info', - - 'extensions.checkCompatibility.nightly' : False, - - # Disable extension updates and notifications. - 'extensions.update.enabled' : False, - 'lightweightThemes.update.enabled' : False, - 'extensions.update.notifyUser' : False, - - # From: - # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l372 - # Only load extensions from the application and user profile. - # AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION - 'extensions.enabledScopes' : 5, - # Disable metadata caching for installed add-ons by default - 'extensions.getAddons.cache.enabled' : False, - # Disable intalling any distribution add-ons - 'extensions.installDistroAddons' : False, - # Allow installing extensions dropped into the profile folder - 'extensions.autoDisableScopes' : 10, - - # shut up some warnings on `about:` page - 'app.releaseNotesURL': 'http://localhost/app-dummy/', - 'app.vendorURL': 'http://localhost/app-dummy/', -} - -DEFAULT_NO_CONNECTIONS_PREFS = { - 'toolkit.telemetry.enabled': False, - 'toolkit.telemetry.server': 'https://localhost/telemetry-dummy/', - 'app.update.auto' : False, - 'app.update.url': 'http://localhost/app-dummy/update', - # Make sure GMPInstallManager won't hit the network. - 'media.gmp-gmpopenh264.autoupdate' : False, - 'media.gmp-manager.cert.checkAttributes' : False, - 'media.gmp-manager.cert.requireBuiltIn' : False, - 'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager', - 'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml', - 'media.gmp-manager.updateEnabled': False, - 'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy', - 'browser.newtab.url' : 'about:blank', - 'browser.search.update': False, - 'browser.search.suggest.enabled' : False, - 'browser.safebrowsing.phishing.enabled' : False, - 'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update', - 'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', - 'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport', - 'browser.selfsupport.url': 'https://localhost/selfsupport-dummy', - 'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', - 'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update', - - # Disable app update - 'app.update.enabled' : False, - 'app.update.staging.enabled': False, - - # Disable about:newtab content fetch and ping - 'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}', - 'browser.newtabpage.directory.ping': '', - - # Point update checks to a nonexistent local URL for fast failures. - 'extensions.update.url' : 'http://localhost/extensions-dummy/updateURL', - 'extensions.update.background.url': 'http://localhost/extensions-dummy/updateBackgroundURL', - 'extensions.blocklist.url' : 'http://localhost/extensions-dummy/blocklistURL', - # Make sure opening about:addons won't hit the network. - 'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL', - 'extensions.getAddons.maxResults': 0, - - # Disable webapp updates. Yes, it is supposed to be an integer. - 'browser.webapps.checkForUpdates': 0, - - # Location services - 'geo.wifi.uri': 'http://localhost/location-dummy/locationURL', - 'browser.search.geoip.url': 'http://localhost/location-dummy/locationURL', - - # Tell the search service we are running in the US. This also has the - # desired side-effect of preventing our geoip lookup. - 'browser.search.isUS' : True, - 'browser.search.countryCode' : 'US', - - 'geo.wifi.uri' : 'http://localhost/extensions-dummy/geowifiURL', - 'geo.wifi.scan' : False, - - # We don't want to hit the real Firefox Accounts server for tests. We don't - # actually need a functioning FxA server, so just set it to something that - # resolves and accepts requests, even if they all fail. - 'identity.fxaccounts.auth.uri': 'http://localhost/fxa-dummy/' -} - -DEFAULT_FENNEC_PREFS = { - 'browser.console.showInPanel': True, - 'browser.firstrun.show.uidiscovery': False -} - -# When launching a temporary new Firefox profile, use these preferences. -DEFAULT_FIREFOX_PREFS = { - 'browser.startup.homepage' : 'about:blank', - 'startup.homepage_welcome_url' : 'about:blank', - 'devtools.browsertoolbox.panel': 'jsdebugger', - 'devtools.chrome.enabled' : True, - - # From: - # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388 - # Make url-classifier updates so rare that they won't affect tests. - 'urlclassifier.updateinterval' : 172800, - # Point the url-classifier to a nonexistent local URL for fast failures. - 'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash', - 'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update', - 'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', - 'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update', -} - -# When launching a temporary new Thunderbird profile, use these preferences. -# Note that these were taken from: -# http://dxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py -DEFAULT_THUNDERBIRD_PREFS = { - # say no to slow script warnings - 'dom.max_chrome_script_run_time': 200, - 'dom.max_script_run_time': 0, - # do not ask about being the default mail client - 'mail.shell.checkDefaultClient': False, - # disable non-gloda indexing daemons - 'mail.winsearch.enable': False, - 'mail.winsearch.firstRunDone': True, - 'mail.spotlight.enable': False, - 'mail.spotlight.firstRunDone': True, - # disable address books for undisclosed reasons - 'ldap_2.servers.osx.position': 0, - 'ldap_2.servers.oe.position': 0, - # disable the first use junk dialog - 'mailnews.ui.junk.firstuse': False, - # other unknown voodoo - # -- dummied up local accounts to stop the account wizard - 'mail.account.account1.server' : "server1", - 'mail.account.account2.identities' : "id1", - 'mail.account.account2.server' : "server2", - 'mail.accountmanager.accounts' : "account1,account2", - 'mail.accountmanager.defaultaccount' : "account2", - 'mail.accountmanager.localfoldersserver' : "server1", - 'mail.identity.id1.fullName' : "Tinderbox", - 'mail.identity.id1.smtpServer' : "smtp1", - 'mail.identity.id1.useremail' : "tinderbox@invalid.com", - 'mail.identity.id1.valid' : True, - 'mail.root.none-rel' : "[ProfD]Mail", - 'mail.root.pop3-rel' : "[ProfD]Mail", - 'mail.server.server1.directory-rel' : "[ProfD]Mail/Local Folders", - 'mail.server.server1.hostname' : "Local Folders", - 'mail.server.server1.name' : "Local Folders", - 'mail.server.server1.type' : "none", - 'mail.server.server1.userName' : "nobody", - 'mail.server.server2.check_new_mail' : False, - 'mail.server.server2.directory-rel' : "[ProfD]Mail/tinderbox", - 'mail.server.server2.download_on_biff' : True, - 'mail.server.server2.hostname' : "tinderbox", - 'mail.server.server2.login_at_startup' : False, - 'mail.server.server2.name' : "tinderbox@invalid.com", - 'mail.server.server2.type' : "pop3", - 'mail.server.server2.userName' : "tinderbox", - 'mail.smtp.defaultserver' : "smtp1", - 'mail.smtpserver.smtp1.hostname' : "tinderbox", - 'mail.smtpserver.smtp1.username' : "tinderbox", - 'mail.smtpservers' : "smtp1", - 'mail.startup.enabledMailCheckOnce' : True, - 'mailnews.start_page_override.mstone' : "ignore", -} - -DEFAULT_TEST_PREFS = { - 'browser.console.showInPanel': True, - 'browser.startup.page': 0, - 'browser.firstrun.show.localepicker': False, - 'browser.firstrun.show.uidiscovery': False, - 'browser.ui.layout.tablet': 0, - 'dom.disable_open_during_load': False, - 'dom.experimental_forms': True, - 'dom.forms.number': True, - 'dom.forms.color': True, - 'dom.max_script_run_time': 0, - 'hangmonitor.timeout': 0, - 'dom.max_chrome_script_run_time': 0, - 'dom.popup_maximum': -1, - 'dom.send_after_paint_to_content': True, - 'dom.successive_dialog_time_limit': 0, - 'browser.shell.checkDefaultBrowser': False, - 'shell.checkDefaultClient': False, - 'browser.warnOnQuit': False, - 'accessibility.typeaheadfind.autostart': False, - 'browser.EULA.override': True, - 'gfx.color_management.force_srgb': True, - 'network.manage-offline-status': False, - # Disable speculative connections so they aren't reported as leaking when they're hanging around. - 'network.http.speculative-parallel-limit': 0, - 'test.mousescroll': True, - # Need to client auth test be w/o any dialogs - 'security.default_personal_cert': 'Select Automatically', - 'network.http.prompt-temp-redirect': False, - 'security.warn_viewing_mixed': False, - 'extensions.defaultProviders.enabled': True, - 'datareporting.policy.dataSubmissionPolicyBypassNotification': True, - 'layout.css.report_errors': True, - 'layout.css.grid.enabled': True, - 'layout.spammy_warnings.enabled': False, - 'dom.mozSettings.enabled': True, - # Make sure the disk cache doesn't get auto disabled - 'network.http.bypass-cachelock-threshold': 200000, - # Always use network provider for geolocation tests - # so we bypass the OSX dialog raised by the corelocation provider - 'geo.provider.testing': True, - # Background thumbnails in particular cause grief, and disabling thumbnails - # in general can't hurt - we re-enable them when tests need them. - 'browser.pagethumbnails.capturing_disabled': True, - # Indicate that the download panel has been shown once so that whichever - # download test runs first doesn't show the popup inconsistently. - 'browser.download.panel.shown': True, - # Assume the about:newtab page's intro panels have been shown to not depend on - # which test runs first and happens to open about:newtab - 'browser.newtabpage.introShown': True, - # Disable useragent updates. - 'general.useragent.updates.enabled': False, - 'media.eme.enabled': True, - 'media.eme.apiVisible': True, - # Don't forceably kill content processes after a timeout - 'dom.ipc.tabs.shutdownTimeoutSecs': 0, - 'general.useragent.locale': "en-US", - 'intl.locale.matchOS': "en-US", - 'dom.indexedDB.experimental': True -} diff --git a/addon-sdk/source/python-lib/cuddlefish/property_parser.py b/addon-sdk/source/python-lib/cuddlefish/property_parser.py deleted file mode 100644 index 20068a2646..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/property_parser.py +++ /dev/null @@ -1,111 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import re -import codecs - -class MalformedLocaleFileError(Exception): - pass - -def parse_file(path): - return parse(read_file(path), path) - -def read_file(path): - try: - return codecs.open( path, "r", "utf-8" ).readlines() - except UnicodeDecodeError, e: - raise MalformedLocaleFileError( - 'Following locale file is not a valid ' + - 'UTF-8 file: %s\n%s"' % (path, str(e))) - -COMMENT = re.compile(r'\s*#') -EMPTY = re.compile(r'^\s+$') -KEYVALUE = re.compile(r"\s*([^=:]+)(=|:)\s*(.*)") - -def parse(lines, path=None): - lines = iter(lines) - lineNo = 1 - pairs = dict() - for line in lines: - if COMMENT.match(line) or EMPTY.match(line) or len(line) == 0: - continue - m = KEYVALUE.match(line) - if not m: - raise MalformedLocaleFileError( - 'Following locale file is not a valid .properties file: %s\n' - 'Line %d is incorrect:\n%s' % (path, lineNo, line)) - - # All spaces are strip. Spaces at the beginning are stripped - # by the regular expression. We have to strip spaces at the end. - key = m.group(1).rstrip() - val = m.group(3).rstrip() - val = val.encode('raw-unicode-escape').decode('raw-unicode-escape') - - # `key` can be empty when key is only made of spaces - if not key: - raise MalformedLocaleFileError( - 'Following locale file is not a valid .properties file: %s\n' - 'Key is invalid on line %d is incorrect:\n%s' % - (path, lineNo, line)) - - # Multiline value: keep reading lines, while lines end with backslash - # and strip spaces at the beginning of lines except the last line - # that doesn't end up with backslash, we strip all spaces for this one. - if val.endswith("\\"): - val = val[:-1] - try: - # remove spaces before/after and especially the \n at EOL - line = lines.next().strip() - while line.endswith("\\"): - val += line[:-1].lstrip() - line = lines.next() - lineNo += 1 - val += line.strip() - except StopIteration: - raise MalformedLocaleFileError( - 'Following locale file is not a valid .properties file: %s\n' - 'Unexpected EOF in multiline sequence at line %d:\n%s' % - (path, lineNo, line)) - # Save this new pair - pairs[key] = val - lineNo += 1 - - normalize_plural(path, pairs) - return pairs - -# Plural forms in properties files are defined like this: -# key = other form -# key[one] = one form -# key[...] = ... -# Parse them and merge each key into one object containing all forms: -# key: { -# other: "other form", -# one: "one form", -# ...: ... -# } -PLURAL_FORM = re.compile(r'^(.*)\[(zero|one|two|few|many|other)\]$') -def normalize_plural(path, pairs): - for key in list(pairs.keys()): - m = PLURAL_FORM.match(key) - if not m: - continue - main_key = m.group(1) - plural_form = m.group(2) - # Allows not specifying a generic key (i.e a key without [form]) - if not main_key in pairs: - pairs[main_key] = {} - # Ensure that we always have the [other] form - if not main_key + "[other]" in pairs: - raise MalformedLocaleFileError( - 'Following locale file is not a valid UTF-8 file: %s\n' - 'This plural form doesn\'t have a matching `%s[other]` form:\n' - '%s\n' - 'You have to defined following key:\n%s' - % (path, main_key, key, main_key)) - # convert generic form into an object if it is still a string - if isinstance(pairs[main_key], unicode): - pairs[main_key] = {"other": pairs[main_key]} - # then, add this new plural form - pairs[main_key][plural_form] = pairs[key] - del pairs[key] diff --git a/addon-sdk/source/python-lib/cuddlefish/rdf.py b/addon-sdk/source/python-lib/cuddlefish/rdf.py deleted file mode 100644 index 2964897c19..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/rdf.py +++ /dev/null @@ -1,214 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import xml.dom.minidom -import StringIO - -RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" -EM_NS = "http://www.mozilla.org/2004/em-rdf#" - -class RDF(object): - def __str__(self): - # real files have an .encoding attribute and use it when you - # write() unicode into them: they read()/write() unicode and - # put encoded bytes in the backend file. StringIO objects - # read()/write() unicode and put unicode in the backing store, - # so we must encode the output of getvalue() to get a - # bytestring. (cStringIO objects are weirder: they effectively - # have .encoding hardwired to "ascii" and put only bytes in - # the backing store, so we can't use them here). - # - # The encoding= argument to dom.writexml() merely sets the XML header's - # encoding= attribute. It still writes unencoded unicode to the output file, - # so we have to encode it for real afterwards. - # - # Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660 - - buf = StringIO.StringIO() - self.dom.writexml(buf, encoding="utf-8") - return buf.getvalue().encode('utf-8') - -class RDFUpdate(RDF): - def __init__(self): - impl = xml.dom.minidom.getDOMImplementation() - self.dom = impl.createDocument(RDF_NS, "RDF", None) - self.dom.documentElement.setAttribute("xmlns", RDF_NS) - self.dom.documentElement.setAttribute("xmlns:em", EM_NS) - - def _make_node(self, name, value, parent): - elem = self.dom.createElement(name) - elem.appendChild(self.dom.createTextNode(value)) - parent.appendChild(elem) - return elem - - def add(self, manifest, update_link): - desc = self.dom.createElement("Description") - desc.setAttribute( - "about", - "urn:mozilla:extension:%s" % manifest.get("em:id") - ) - self.dom.documentElement.appendChild(desc) - - updates = self.dom.createElement("em:updates") - desc.appendChild(updates) - - seq = self.dom.createElement("Seq") - updates.appendChild(seq) - - li = self.dom.createElement("li") - seq.appendChild(li) - - li_desc = self.dom.createElement("Description") - li.appendChild(li_desc) - - self._make_node("em:version", manifest.get("em:version"), - li_desc) - - apps = manifest.dom.documentElement.getElementsByTagName( - "em:targetApplication" - ) - - for app in apps: - target_app = self.dom.createElement("em:targetApplication") - li_desc.appendChild(target_app) - - ta_desc = self.dom.createElement("Description") - target_app.appendChild(ta_desc) - - for name in ["em:id", "em:minVersion", "em:maxVersion"]: - elem = app.getElementsByTagName(name)[0] - self._make_node(name, elem.firstChild.nodeValue, ta_desc) - - self._make_node("em:updateLink", update_link, ta_desc) - -class RDFManifest(RDF): - def __init__(self, path): - self.dom = xml.dom.minidom.parse(path) - - def set(self, property, value): - elements = self.dom.documentElement.getElementsByTagName(property) - if not elements: - raise ValueError("Element with value not found: %s" % property) - if not elements[0].firstChild: - elements[0].appendChild(self.dom.createTextNode(value)) - else: - elements[0].firstChild.nodeValue = value - - def get(self, property, default=None): - elements = self.dom.documentElement.getElementsByTagName(property) - if not elements: - return default - return elements[0].firstChild.nodeValue - - def remove(self, property): - elements = self.dom.documentElement.getElementsByTagName(property) - if not elements: - return True - else: - for i in elements: - i.parentNode.removeChild(i); - - return True; - -def gen_manifest(template_root_dir, target_cfg, jid, - update_url=None, bootstrap=True, enable_mobile=False): - install_rdf = os.path.join(template_root_dir, "install.rdf") - manifest = RDFManifest(install_rdf) - dom = manifest.dom - - manifest.set("em:id", jid) - manifest.set("em:version", - target_cfg.get('version', '1.0')) - manifest.set("em:name", - target_cfg.get('title', target_cfg.get('fullName', target_cfg['name']))) - manifest.set("em:description", - target_cfg.get("description", "")) - manifest.set("em:creator", - target_cfg.get("author", "")) - manifest.set("em:bootstrap", str(bootstrap).lower()) - # XPIs remain packed by default, but package.json can override that. The - # RDF format accepts "true" as True, anything else as False. We expect - # booleans in the .json file, not strings. - manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false") - - if target_cfg.get('hasEmbeddedWebExtension', False): - elem = dom.createElement("em:hasEmbeddedWebExtension"); - elem.appendChild(dom.createTextNode("true")) - dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) - - for translator in target_cfg.get("translators", [ ]): - elem = dom.createElement("em:translator"); - elem.appendChild(dom.createTextNode(translator)) - dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) - - for developer in target_cfg.get("developers", [ ]): - elem = dom.createElement("em:developer"); - elem.appendChild(dom.createTextNode(developer)) - dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) - - for contributor in target_cfg.get("contributors", [ ]): - elem = dom.createElement("em:contributor"); - elem.appendChild(dom.createTextNode(contributor)) - dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) - - if update_url: - manifest.set("em:updateURL", update_url) - else: - manifest.remove("em:updateURL") - - if target_cfg.get("preferences"): - manifest.set("em:optionsType", "2") - - # workaround until bug 971249 is fixed - # https://bugzilla.mozilla.org/show_bug.cgi?id=971249 - manifest.set("em:optionsURL", "data:text/xml,") - - # workaround for workaround, for testing simple-prefs-regression - if (os.path.exists(os.path.join(template_root_dir, "options.xul"))): - manifest.remove("em:optionsURL") - else: - manifest.remove("em:optionsType") - manifest.remove("em:optionsURL") - - if enable_mobile: - target_app = dom.createElement("em:targetApplication") - dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app) - - ta_desc = dom.createElement("Description") - target_app.appendChild(ta_desc) - - elem = dom.createElement("em:id") - elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}")) - ta_desc.appendChild(elem) - - elem = dom.createElement("em:minVersion") - elem.appendChild(dom.createTextNode("26.0")) - ta_desc.appendChild(elem) - - elem = dom.createElement("em:maxVersion") - elem.appendChild(dom.createTextNode("30.0a1")) - ta_desc.appendChild(elem) - - if target_cfg.get("homepage"): - manifest.set("em:homepageURL", target_cfg.get("homepage")) - else: - manifest.remove("em:homepageURL") - - return manifest - -if __name__ == "__main__": - print "Running smoke test." - root = os.path.join(os.path.dirname(__file__), '../../app-extension') - manifest = gen_manifest(root, {'name': 'test extension'}, - 'fakeid', 'http://foo.com/update.rdf') - update = RDFUpdate() - update.add(manifest, "https://foo.com/foo.xpi") - exercise_str = str(manifest) + str(update) - for tagname in ["em:targetApplication", "em:version", "em:id"]: - if not len(update.dom.getElementsByTagName(tagname)): - raise Exception("tag does not exist: %s" % tagname) - if not update.dom.getElementsByTagName(tagname)[0].firstChild: - raise Exception("tag has no children: %s" % tagname) - print "Success!" diff --git a/addon-sdk/source/python-lib/cuddlefish/runner.py b/addon-sdk/source/python-lib/cuddlefish/runner.py deleted file mode 100644 index 7f337cc038..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/runner.py +++ /dev/null @@ -1,767 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import sys -import time -import tempfile -import atexit -import shlex -import subprocess -import re -import shutil - -import mozrunner -from cuddlefish.prefs import DEFAULT_COMMON_PREFS -from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS -from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS -from cuddlefish.prefs import DEFAULT_FENNEC_PREFS -from cuddlefish.prefs import DEFAULT_NO_CONNECTIONS_PREFS -from cuddlefish.prefs import DEFAULT_TEST_PREFS - -# Used to remove noise from ADB output -CLEANUP_ADB = re.compile(r'^(I|E)/(stdout|stderr|GeckoConsole)\s*\(\s*\d+\):\s*(.*)$') -# Used to filter only messages send by `console` module -FILTER_ONLY_CONSOLE_FROM_ADB = re.compile(r'^I/(stdout|stderr)\s*\(\s*\d+\):\s*((info|warning|error|debug): .*)$') - -# Used to detect the currently running test -PARSEABLE_TEST_NAME = re.compile(r'TEST-START \| ([^\n]+)\n') - -# Maximum time we'll wait for tests to finish, in seconds. -# The purpose of this timeout is to recover from infinite loops. It should be -# longer than the amount of time any test run takes, including those on slow -# machines running slow (debug) versions of Firefox. -RUN_TIMEOUT = 5400 #1.5 hours (1.5 * 60 * 60 sec) - -# Maximum time we'll wait for tests to emit output, in seconds. -# The purpose of this timeout is to recover from hangs. It should be longer -# than the amount of time any test takes to report results. -OUTPUT_TIMEOUT = 300 #five minutes (60 * 5 sec) - -def follow_file(filename): - """ - Generator that yields the latest unread content from the given - file, or None if no new content is available. - - For example: - - >>> f = open('temp.txt', 'w') - >>> f.write('hello') - >>> f.flush() - >>> tail = follow_file('temp.txt') - >>> tail.next() - 'hello' - >>> tail.next() is None - True - >>> f.write('there') - >>> f.flush() - >>> tail.next() - 'there' - >>> f.close() - >>> os.remove('temp.txt') - """ - - last_pos = 0 - last_size = 0 - while True: - newstuff = None - if os.path.exists(filename): - size = os.stat(filename).st_size - if size > last_size: - last_size = size - f = open(filename, 'r') - f.seek(last_pos) - newstuff = f.read() - last_pos = f.tell() - f.close() - yield newstuff - -# subprocess.check_output only appeared in python2.7, so this code is taken -# from python source code for compatibility with py2.5/2.6 -class CalledProcessError(Exception): - def __init__(self, returncode, cmd, output=None): - self.returncode = returncode - self.cmd = cmd - self.output = output - def __str__(self): - return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) - -def check_output(*popenargs, **kwargs): - if 'stdout' in kwargs: - raise ValueError('stdout argument not allowed, it will be overridden.') - process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) - output, unused_err = process.communicate() - retcode = process.poll() - if retcode: - cmd = kwargs.get("args") - if cmd is None: - cmd = popenargs[0] - raise CalledProcessError(retcode, cmd, output=output) - return output - - -class FennecProfile(mozrunner.Profile): - preferences = {} - names = ['fennec'] - -FENNEC_REMOTE_PATH = '/mnt/sdcard/jetpack-profile' - -class RemoteFennecRunner(mozrunner.Runner): - profile_class = FennecProfile - - names = ['fennec'] - - _INTENT_PREFIX = 'org.mozilla.' - - _adb_path = None - - def __init__(self, binary=None, **kwargs): - # Check that we have a binary set - if not binary: - raise ValueError("You have to define `--binary` option set to the " - "path to your ADB executable.") - # Ensure that binary refer to a valid ADB executable - output = subprocess.Popen([binary], stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate() - output = "".join(output) - if not ("Android Debug Bridge" in output): - raise ValueError("`--binary` option should be the path to your " - "ADB executable.") - self.binary = binary - - mobile_app_name = kwargs['cmdargs'][0] - self.profile = kwargs['profile'] - self._adb_path = binary - - # This pref has to be set to `false` otherwise, we do not receive - # output of adb commands! - subprocess.call([self._adb_path, "shell", - "setprop log.redirect-stdio false"]) - - # Android apps are launched by their "intent" name, - # Automatically detect already installed firefox by using `pm` program - # or use name given as cfx `--mobile-app` argument. - intents = self.getIntentNames() - if not intents: - raise ValueError("Unable to find any Firefox " - "application on your device.") - elif mobile_app_name: - if not mobile_app_name in intents: - raise ValueError("Unable to find Firefox application " - "with intent name '%s'\n" - "Available ones are: %s" % - (mobile_app_name, ", ".join(intents))) - self._intent_name = self._INTENT_PREFIX + mobile_app_name - else: - if "firefox" in intents: - self._intent_name = self._INTENT_PREFIX + "firefox" - elif "firefox_beta" in intents: - self._intent_name = self._INTENT_PREFIX + "firefox_beta" - elif "firefox_nightly" in intents: - self._intent_name = self._INTENT_PREFIX + "firefox_nightly" - else: - self._intent_name = self._INTENT_PREFIX + intents[0] - - print "Launching mobile application with intent name " + self._intent_name - - # First try to kill firefox if it is already running - pid = self.getProcessPID(self._intent_name) - if pid != None: - print "Killing running Firefox instance ..." - subprocess.call([self._adb_path, "shell", - "am force-stop " + self._intent_name]) - time.sleep(7) - # It appears recently that the PID still exists even after - # Fennec closes, so removing this error still allows the tests - # to pass as the new Fennec instance is able to start. - # Leaving error in but commented out for now. - # - #if self.getProcessPID(self._intent_name) != None: - # raise Exception("Unable to automatically kill running Firefox" + - # " instance. Please close it manually before " + - # "executing cfx.") - - print "Pushing the addon to your device" - - # Create a clean empty profile on the sd card - subprocess.call([self._adb_path, "shell", "rm -r " + FENNEC_REMOTE_PATH]) - subprocess.call([self._adb_path, "shell", "mkdir " + FENNEC_REMOTE_PATH]) - - # Push the profile folder created by mozrunner to the device - # (we can't simply use `adb push` as it doesn't copy empty folders) - localDir = self.profile.profile - remoteDir = FENNEC_REMOTE_PATH - for root, dirs, files in os.walk(localDir, followlinks='true'): - relRoot = os.path.relpath(root, localDir) - # Note about os.path usage below: - # Local files may be using Windows `\` separators but - # remote are always `/`, so we need to convert local ones to `/` - for file in files: - localFile = os.path.join(root, file) - remoteFile = remoteDir.replace("/", os.sep) - if relRoot != ".": - remoteFile = os.path.join(remoteFile, relRoot) - remoteFile = os.path.join(remoteFile, file) - remoteFile = "/".join(remoteFile.split(os.sep)) - subprocess.Popen([self._adb_path, "push", localFile, remoteFile], - stderr=subprocess.PIPE).wait() - for dir in dirs: - targetDir = remoteDir.replace("/", os.sep) - if relRoot != ".": - targetDir = os.path.join(targetDir, relRoot) - targetDir = os.path.join(targetDir, dir) - targetDir = "/".join(targetDir.split(os.sep)) - # `-p` option is not supported on all devices! - subprocess.call([self._adb_path, "shell", "mkdir " + targetDir]) - - @property - def command(self): - """Returns the command list to run.""" - return [self._adb_path, - "shell", - "am start " + - "-a android.activity.MAIN " + - "-n " + self._intent_name + "/" + self._intent_name + ".App " + - "--es args \"-profile " + FENNEC_REMOTE_PATH + "\"" - ] - - def start(self): - subprocess.call(self.command) - - def getProcessPID(self, processName): - p = subprocess.Popen([self._adb_path, "shell", "ps"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - line = p.stdout.readline() - while line: - columns = line.split() - pid = columns[1] - name = columns[-1] - line = p.stdout.readline() - if processName in name: - return pid - return None - - def getIntentNames(self): - p = subprocess.Popen([self._adb_path, "shell", "pm list packages"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - names = [] - for line in p.stdout.readlines(): - line = re.sub("(^package:)|\s", "", line) - if self._INTENT_PREFIX in line: - names.append(line.replace(self._INTENT_PREFIX, "")) - return names - - -class XulrunnerAppProfile(mozrunner.Profile): - preferences = {} - names = [] - -class XulrunnerAppRunner(mozrunner.Runner): - """ - Runner for any XULRunner app. Can use a Firefox binary in XULRunner - mode to execute the app, or can use XULRunner itself. Expects the - app's application.ini to be passed in as one of the items in - 'cmdargs' in the constructor. - - This class relies a lot on the particulars of mozrunner.Runner's - implementation, and does some unfortunate acrobatics to get around - some of the class' limitations/assumptions. - """ - - profile_class = XulrunnerAppProfile - - # This is a default, and will be overridden in the instance if - # Firefox is used in XULRunner mode. - names = ['xulrunner'] - - # Default location of XULRunner on OS X. - __DARWIN_PATH = "/Library/Frameworks/XUL.framework/xulrunner-bin" - __LINUX_PATH = "/usr/bin/xulrunner" - - # What our application.ini's path looks like if it's part of - # an "installed" XULRunner app on OS X. - __DARWIN_APP_INI_SUFFIX = '.app/Contents/Resources/application.ini' - - def __init__(self, binary=None, **kwargs): - if sys.platform == 'darwin' and binary and binary.endswith('.app'): - # Assume it's a Firefox app dir. - binary = os.path.join(binary, 'Contents/MacOS/firefox-bin') - - self.__app_ini = None - self.__real_binary = binary - - mozrunner.Runner.__init__(self, **kwargs) - - # See if we're using a genuine xulrunner-bin from the XULRunner SDK, - # or if we're being asked to use Firefox in XULRunner mode. - self.__is_xulrunner_sdk = 'xulrunner' in self.binary - - if sys.platform == 'linux2' and not self.env.get('LD_LIBRARY_PATH'): - self.env['LD_LIBRARY_PATH'] = os.path.dirname(self.binary) - - newargs = [] - for item in self.cmdargs: - if 'application.ini' in item: - self.__app_ini = item - else: - newargs.append(item) - self.cmdargs = newargs - - if not self.__app_ini: - raise ValueError('application.ini not found in cmdargs') - if not os.path.exists(self.__app_ini): - raise ValueError("file does not exist: '%s'" % self.__app_ini) - - if (sys.platform == 'darwin' and - self.binary == self.__DARWIN_PATH and - self.__app_ini.endswith(self.__DARWIN_APP_INI_SUFFIX)): - # If the application.ini is in an app bundle, then - # it could be inside an "installed" XULRunner app. - # If this is the case, use the app's actual - # binary instead of the XUL framework's, so we get - # a proper app icon, etc. - new_binary = '/'.join(self.__app_ini.split('/')[:-2] + - ['MacOS', 'xulrunner']) - if os.path.exists(new_binary): - self.binary = new_binary - - @property - def command(self): - """Returns the command list to run.""" - - if self.__is_xulrunner_sdk: - return [self.binary, self.__app_ini, '-profile', - self.profile.profile] - else: - return [self.binary, '-app', self.__app_ini, '-profile', - self.profile.profile] - - def __find_xulrunner_binary(self): - if sys.platform == 'darwin': - if os.path.exists(self.__DARWIN_PATH): - return self.__DARWIN_PATH - if sys.platform == 'linux2': - if os.path.exists(self.__LINUX_PATH): - return self.__LINUX_PATH - return None - - def find_binary(self): - # This gets called by the superclass constructor. It will - # always get called, even if a binary was passed into the - # constructor, because we want to have full control over - # what the exact setting of self.binary is. - - if not self.__real_binary: - self.__real_binary = self.__find_xulrunner_binary() - if not self.__real_binary: - dummy_profile = {} - runner = mozrunner.FirefoxRunner(profile=dummy_profile) - self.__real_binary = runner.find_binary() - self.names = runner.names - return self.__real_binary - -def set_overloaded_modules(env_root, app_type, addon_id, preferences, overloads): - # win32 file scheme needs 3 slashes - desktop_file_scheme = "file://" - if not env_root.startswith("/"): - desktop_file_scheme = desktop_file_scheme + "/" - - pref_prefix = "extensions.modules." + addon_id + ".path" - - # Set preferences that will map require prefix to a given path - for name, path in overloads.items(): - if len(name) == 0: - prefName = pref_prefix - else: - prefName = pref_prefix + "." + name - if app_type == "fennec-on-device": - # For testing on device, we have to copy overloaded files from fs - # to the device and use device path instead of local fs path. - # Actual copy of files if done after the call to Profile constructor - preferences[prefName] = "file://" + \ - FENNEC_REMOTE_PATH + "/overloads/" + name - else: - preferences[prefName] = desktop_file_scheme + \ - path.replace("\\", "/") + "/" - -def run_app(harness_root_dir, manifest_rdf, harness_options, - app_type, binary=None, profiledir=None, verbose=False, - parseable=False, enforce_timeouts=False, - logfile=None, addons=None, args=None, extra_environment={}, - norun=None, noquit=None, - used_files=None, enable_mobile=False, - mobile_app_name=None, - env_root=None, - is_running_tests=False, - overload_modules=False, - bundle_sdk=True, - pkgdir="", - enable_e10s=False, - no_connections=False): - if binary: - binary = os.path.expanduser(binary) - - if addons is None: - addons = [] - else: - addons = list(addons) - - cmdargs = [] - preferences = dict(DEFAULT_COMMON_PREFS) - - if is_running_tests: - preferences.update(DEFAULT_TEST_PREFS) - - if no_connections: - preferences.update(DEFAULT_NO_CONNECTIONS_PREFS) - - if enable_e10s: - preferences['browser.tabs.remote.autostart'] = True - else: - preferences['browser.tabs.remote.autostart'] = False - preferences['browser.tabs.remote.autostart.1'] = False - preferences['browser.tabs.remote.autostart.2'] = False - - # For now, only allow running on Mobile with --force-mobile argument - if app_type in ["fennec-on-device"] and not enable_mobile: - print """ - WARNING: Firefox Mobile support is still experimental. - If you would like to run an addon on this platform, use --force-mobile flag: - - cfx --force-mobile""" - return 0 - - if app_type == "fennec-on-device": - profile_class = FennecProfile - preferences.update(DEFAULT_FENNEC_PREFS) - runner_class = RemoteFennecRunner - # We pass the intent name through command arguments - cmdargs.append(mobile_app_name) - elif app_type == "xulrunner": - profile_class = XulrunnerAppProfile - runner_class = XulrunnerAppRunner - cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) - elif app_type == "firefox": - profile_class = mozrunner.FirefoxProfile - preferences.update(DEFAULT_FIREFOX_PREFS) - runner_class = mozrunner.FirefoxRunner - elif app_type == "thunderbird": - profile_class = mozrunner.ThunderbirdProfile - preferences.update(DEFAULT_THUNDERBIRD_PREFS) - runner_class = mozrunner.ThunderbirdRunner - else: - raise ValueError("Unknown app: %s" % app_type) - if sys.platform == 'darwin' and app_type != 'xulrunner': - cmdargs.append('-foreground') - - if args: - cmdargs.extend(shlex.split(args)) - - # TODO: handle logs on remote device - if app_type != "fennec-on-device": - # tempfile.gettempdir() was constant, preventing two simultaneous "cfx - # run"/"cfx test" on the same host. On unix it points at /tmp (which is - # world-writeable), enabling a symlink attack (e.g. imagine some bad guy - # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile - # gives us a unique filename that fixes both problems. We leave the - # (0-byte) file in place until the browser-side code starts writing to - # it, otherwise the symlink attack becomes possible again. - fileno,resultfile = tempfile.mkstemp(prefix="harness-result-") - os.close(fileno) - harness_options['resultFile'] = resultfile - - def maybe_remove_logfile(): - if os.path.exists(logfile): - os.remove(logfile) - - logfile_tail = None - - # We always buffer output through a logfile for two reasons: - # 1. On Windows, it's the only way to print console output to stdout/err. - # 2. It enables us to keep track of the last time output was emitted, - # so we can raise an exception if the test runner hangs. - if not logfile: - fileno,logfile = tempfile.mkstemp(prefix="harness-log-") - os.close(fileno) - logfile_tail = follow_file(logfile) - atexit.register(maybe_remove_logfile) - - logfile = os.path.abspath(os.path.expanduser(logfile)) - maybe_remove_logfile() - - env = {} - env.update(os.environ) - if no_connections: - env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1' - env['MOZ_NO_REMOTE'] = '1' - env['XPCOM_DEBUG_BREAK'] = 'stack' - env.update(extra_environment) - if norun: - cmdargs.append("-no-remote") - - # Create the addon XPI so mozrunner will copy it to the profile it creates. - # We delete it below after getting mozrunner to create the profile. - from cuddlefish.xpi import build_xpi - xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') - build_xpi(template_root_dir=harness_root_dir, - manifest=manifest_rdf, - xpi_path=xpi_path, - harness_options=harness_options, - limit_to=used_files, - bundle_sdk=bundle_sdk, - pkgdir=pkgdir) - addons.append(xpi_path) - - starttime = last_output_time = time.time() - - # Redirect runner output to a file so we can catch output not generated - # by us. - # In theory, we could do this using simple redirection on all platforms - # other than Windows, but this way we only have a single codepath to - # maintain. - fileno,outfile = tempfile.mkstemp(prefix="harness-stdout-") - os.close(fileno) - outfile_tail = follow_file(outfile) - def maybe_remove_outfile(): - if os.path.exists(outfile): - try: - os.remove(outfile) - except Exception, e: - print "Error Cleaning up: " + str(e) - atexit.register(maybe_remove_outfile) - outf = open(outfile, "w") - popen_kwargs = { 'stdout': outf, 'stderr': outf} - - profile = None - - if app_type == "fennec-on-device": - # Install a special addon when we run firefox on mobile device - # in order to be able to kill it - mydir = os.path.dirname(os.path.abspath(__file__)) - addon_dir = os.path.join(mydir, "mobile-utils") - addons.append(addon_dir) - - # Overload addon-specific commonjs modules path with lib/ folder - overloads = dict() - if overload_modules: - overloads[""] = os.path.join(env_root, "lib") - - # Overload tests/ mapping with test/ folder, only when running test - if is_running_tests: - overloads["tests"] = os.path.join(env_root, "test") - - set_overloaded_modules(env_root, app_type, harness_options["jetpackID"], \ - preferences, overloads) - - # the XPI file is copied into the profile here - profile = profile_class(addons=addons, - profile=profiledir, - preferences=preferences) - - # Delete the temporary xpi file - os.remove(xpi_path) - - # Copy overloaded files registered in set_overloaded_modules - # For testing on device, we have to copy overloaded files from fs - # to the device and use device path instead of local fs path. - # (has to be done after the call to profile_class() which eventualy creates - # profile folder) - if app_type == "fennec-on-device": - profile_path = profile.profile - for name, path in overloads.items(): - shutil.copytree(path, \ - os.path.join(profile_path, "overloads", name)) - - runner = runner_class(profile=profile, - binary=binary, - env=env, - cmdargs=cmdargs, - kp_kwargs=popen_kwargs) - - sys.stdout.flush(); sys.stderr.flush() - - if app_type == "fennec-on-device": - if not enable_mobile: - print >>sys.stderr, """ - WARNING: Firefox Mobile support is still experimental. - If you would like to run an addon on this platform, use --force-mobile flag: - - cfx --force-mobile""" - return 0 - - # In case of mobile device, we need to get stdio from `adb logcat` cmd: - - # First flush logs in order to avoid catching previous ones - subprocess.call([binary, "logcat", "-c"]) - - # Launch adb command - runner.start() - - # We can immediatly remove temporary profile folder - # as it has been uploaded to the device - profile.cleanup() - # We are not going to use the output log file - outf.close() - - # Then we simply display stdout of `adb logcat` - p = subprocess.Popen([binary, "logcat", "stderr:V stdout:V GeckoConsole:V *:S"], stdout=subprocess.PIPE) - while True: - line = p.stdout.readline() - if line == '': - break - # mobile-utils addon contains an application quit event observer - # that will print this string: - if "APPLICATION-QUIT" in line: - break - - if verbose: - # if --verbose is given, we display everything: - # All JS Console messages, stdout and stderr. - m = CLEANUP_ADB.match(line) - if not m: - print line.rstrip() - continue - print m.group(3) - else: - # Otherwise, display addons messages dispatched through - # console.[info, log, debug, warning, error](msg) - m = FILTER_ONLY_CONSOLE_FROM_ADB.match(line) - if m: - print m.group(2) - - print >>sys.stderr, "Program terminated successfully." - return 0 - - - print >>sys.stderr, "Using binary at '%s'." % runner.binary - - # Ensure cfx is being used with Firefox 4.0+. - # TODO: instead of dying when Firefox is < 4, warn when Firefox is outside - # the minVersion/maxVersion boundaries. - version_output = check_output(runner.command + ["-v"]) - # Note: this regex doesn't handle all valid versions in the Toolkit Version - # Format , just the - # common subset that we expect Mozilla apps to use. - mo = re.search(r"Mozilla (Firefox|Iceweasel|Fennec)\b[^ ]* ((\d+)\.\S*)", - version_output) - if not mo: - # cfx may be used with Thunderbird, SeaMonkey or an exotic Firefox - # version. - print """ - WARNING: cannot determine Firefox version; please ensure you are running - a Mozilla application equivalent to Firefox 4.0 or greater. - """ - elif mo.group(1) == "Fennec": - # For now, only allow running on Mobile with --force-mobile argument - if not enable_mobile: - print """ - WARNING: Firefox Mobile support is still experimental. - If you would like to run an addon on this platform, use --force-mobile flag: - - cfx --force-mobile""" - return - else: - version = mo.group(3) - if int(version) < 4: - print """ - cfx requires Firefox 4 or greater and is unable to find a compatible - binary. Please install a newer version of Firefox or provide the path to - your existing compatible version with the --binary flag: - - cfx --binary=PATH_TO_FIREFOX_BINARY""" - return - - # Set the appropriate extensions.checkCompatibility preference to false, - # so the tests run even if the SDK is not marked as compatible with the - # version of Firefox on which they are running, and we don't have to - # ensure we update the maxVersion before the version of Firefox changes - # every six weeks. - # - # The regex we use here is effectively the same as BRANCH_REGEX from - # /toolkit/mozapps/extensions/content/extensions.js, which toolkit apps - # use to determine whether or not to load an incompatible addon. - # - br = re.search(r"^([^\.]+\.[0-9]+[a-z]*).*", mo.group(2), re.I) - if br: - prefname = 'extensions.checkCompatibility.' + br.group(1) - profile.preferences[prefname] = False - # Calling profile.set_preferences here duplicates the list of prefs - # in prefs.js, since the profile calls self.set_preferences in its - # constructor, but that is ok, because it doesn't change the set of - # preferences that are ultimately registered in Firefox. - profile.set_preferences(profile.preferences) - - print >>sys.stderr, "Using profile at '%s'." % profile.profile - sys.stderr.flush() - - if norun: - print "To launch the application, enter the following command:" - print " ".join(runner.command) + " " + (" ".join(runner.cmdargs)) - return 0 - - runner.start() - - done = False - result = None - test_name = "Jetpack startup" - - def Timeout(message, test_name, parseable): - if parseable: - sys.stderr.write("TEST-UNEXPECTED-FAIL | %s | %s\n" % (test_name, message)) - sys.stderr.flush() - return Exception(message) - - try: - while not done: - time.sleep(0.05) - for tail in (logfile_tail, outfile_tail): - if tail: - new_chars = tail.next() - if new_chars: - last_output_time = time.time() - sys.stderr.write(new_chars) - sys.stderr.flush() - if is_running_tests and parseable: - match = PARSEABLE_TEST_NAME.search(new_chars) - if match: - test_name = match.group(1) - if os.path.exists(resultfile): - result = open(resultfile).read() - if result: - if result in ['OK', 'FAIL']: - done = True - else: - sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result))) - sys.stderr.write("'"+result+"'\n") - if enforce_timeouts: - if time.time() - last_output_time > OUTPUT_TIMEOUT: - raise Timeout("Test output exceeded timeout (%ds)." % - OUTPUT_TIMEOUT, test_name, parseable) - if time.time() - starttime > RUN_TIMEOUT: - raise Timeout("Test run exceeded timeout (%ds)." % - RUN_TIMEOUT, test_name, parseable) - except: - if not noquit: - runner.stop() - raise - else: - runner.wait(10) - # double kill - hack for bugs 942111, 1006043.. - try: - runner.stop() - except: - pass - finally: - outf.close() - if profile: - profile.cleanup() - - print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime) - - if result == 'OK': - print >>sys.stderr, "Program terminated successfully." - return 0 - else: - print >>sys.stderr, "Program terminated unsuccessfully." - return -1 diff --git a/addon-sdk/source/python-lib/cuddlefish/templates.py b/addon-sdk/source/python-lib/cuddlefish/templates.py deleted file mode 100644 index be6110e7a4..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/templates.py +++ /dev/null @@ -1,32 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#Template used by test-main.js -TEST_MAIN_JS = '''\ -var main = require("./main"); - -exports["test main"] = function(assert) { - assert.pass("Unit test running!"); -}; - -exports["test main async"] = function(assert, done) { - assert.pass("async Unit test running!"); - done(); -}; - -require("sdk/test").run(exports); -''' - -#Template used by package.json -PACKAGE_JSON = '''\ -{ - "name": "%(name)s", - "title": "%(title)s", - "id": "%(id)s", - "description": "a basic add-on", - "author": "", - "license": "MPL-2.0", - "version": "0.1" -} -''' diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/__init__.py b/addon-sdk/source/python-lib/cuddlefish/tests/__init__.py deleted file mode 100644 index 60e7e88b3c..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import unittest -import doctest -import glob - -env_root = os.environ['CUDDLEFISH_ROOT'] - -def get_tests(): - import cuddlefish - import cuddlefish.tests - - tests = [] - packages = [cuddlefish, cuddlefish.tests] - for package in packages: - path = os.path.abspath(package.__path__[0]) - pynames = glob.glob(os.path.join(path, '*.py')) - for filename in pynames: - basename = os.path.basename(filename) - module_name = os.path.splitext(basename)[0] - full_name = "%s.%s" % (package.__name__, module_name) - module = __import__(full_name, fromlist=[package.__name__]) - - loader = unittest.TestLoader() - suite = loader.loadTestsFromModule(module) - for test in suite: - tests.append(test) - - finder = doctest.DocTestFinder() - doctests = finder.find(module) - for test in doctests: - if len(test.examples) > 0: - tests.append(doctest.DocTestCase(test)) - - return tests - -def run(verbose=False): - if verbose: - verbosity = 2 - else: - verbosity = 1 - - tests = get_tests() - suite = unittest.TestSuite(tests) - runner = unittest.TextTestRunner(verbosity=verbosity) - return runner.run(suite) - -if __name__ == '__main__': - run() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js deleted file mode 100644 index b7e0a1d5ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js +++ /dev/null @@ -1,4 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json deleted file mode 100644 index 8d56d74baf..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "loader": "lib/main.js", - "icon": "explicit-icon.png", - "icon64": "explicit-icon64.png" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js deleted file mode 100644 index b7e0a1d5ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js +++ /dev/null @@ -1,4 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json deleted file mode 100644 index 3f0e2419f3..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "lib/main.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js deleted file mode 100644 index b7e0a1d5ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js +++ /dev/null @@ -1,4 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json deleted file mode 100644 index 3f0e2419f3..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "lib/main.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js deleted file mode 100644 index b7e0a1d5ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js +++ /dev/null @@ -1,4 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json deleted file mode 100644 index e83a9d4224..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "lib/bar-loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js deleted file mode 100644 index b7e0a1d5ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js +++ /dev/null @@ -1,4 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json deleted file mode 100644 index 4648df67e0..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "loader": "lib/foo-loader.js", - "dependencies": ["bar"] -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md deleted file mode 100644 index 54518d38db..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md +++ /dev/null @@ -1,5 +0,0 @@ - - -minimal docs diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js deleted file mode 100644 index aeda0e7fbd..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = function(options, callbacks) { - console.log("minimal"); - callbacks.quit(); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json deleted file mode 100644 index 45d409a74c..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "jspath-one", - "author": "Jon Smith", - "description": "A package w/ a main module; can be built into an extension." -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md deleted file mode 100644 index c92ddb880e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md +++ /dev/null @@ -1,5 +0,0 @@ - - -I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json deleted file mode 100644 index ccc61b29dd..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "lib/foo-loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js deleted file mode 100644 index bd0cfa96fb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testThing = function(test) { - test.assertEqual(2, 1 + 1); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md deleted file mode 100644 index c92ddb880e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md +++ /dev/null @@ -1,5 +0,0 @@ - - -I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json deleted file mode 100644 index ccc61b29dd..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "lib/foo-loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js deleted file mode 100644 index bd0cfa96fb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testThing = function(test) { - test.assertEqual(2, 1 + 1); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md deleted file mode 100644 index c92ddb880e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md +++ /dev/null @@ -1,5 +0,0 @@ - - -I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json deleted file mode 100644 index c2d22aa6f5..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "lib/loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js deleted file mode 100644 index bd0cfa96fb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testThing = function(test) { - test.assertEqual(2, 1 + 1); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md deleted file mode 100644 index c92ddb880e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md +++ /dev/null @@ -1,5 +0,0 @@ - - -I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json deleted file mode 100644 index 100249fedf..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "loader": "loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js deleted file mode 100644 index bd0cfa96fb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testThing = function(test) { - test.assertEqual(2, 1 + 1); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md deleted file mode 100644 index c92ddb880e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md +++ /dev/null @@ -1,5 +0,0 @@ - - -I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json deleted file mode 100644 index f79250e6e7..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "directories": { "lib": "./alt-lib" }, - "loader": "alt-lib/loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js deleted file mode 100644 index bd0cfa96fb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testThing = function(test) { - test.assertEqual(2, 1 + 1); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js deleted file mode 100644 index 2daeb35126..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md deleted file mode 100644 index c92ddb880e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md +++ /dev/null @@ -1,5 +0,0 @@ - - -I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json deleted file mode 100644 index b017baa865..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "lib": "./alt2-lib", - "loader": "alt2-lib/loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js deleted file mode 100644 index bd0cfa96fb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testThing = function(test) { - test.assertEqual(2, 1 + 1); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md deleted file mode 100644 index 54518d38db..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md +++ /dev/null @@ -1,5 +0,0 @@ - - -minimal docs diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js deleted file mode 100644 index aeda0e7fbd..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = function(options, callbacks) { - console.log("minimal"); - callbacks.quit(); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json deleted file mode 100644 index de868f7769..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "extra-options", - "author": "Jon Smith", - "description": "A package w/ a main module; can be built into an extension.", - "loader": "lib/main.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json deleted file mode 100644 index afa13e9e59..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "empty", - "license": "MPL-2.0", - "author": "", - "version": "0.1", - "title": "empty", - "id": "jid1-80fr8b6qeRlQSQ", - "unpack": false, - "description": "test unpack= support" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json deleted file mode 100644 index b1f76edcdb..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "empty", - "license": "MPL-2.0", - "author": "", - "version": "0.1", - "title": "empty", - "id": "jid1-80fr8b6qeRlQSQ", - "unpack": true, - "description": "test unpack= support" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json deleted file mode 100644 index e3dd9fb72c..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "empty", - "license": "MPL-2.0", - "author": "", - "version": "0.1", - "title": "empty", - "id": "jid1-80fr8b6qeRlQSQ", - "description": "test unpack= support" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json deleted file mode 100644 index 2525a14979..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "empty", - "license": "MPL-2.0", - "author": "", - "version": "0.1", - "fullName": "a long fullName", - "id": "jid1-80123", - "description": "test addon name fallback to 'fullName' key" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json deleted file mode 100644 index b05025c721..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "a long none", - "license": "MPL-2.0", - "author": "", - "version": "0.1", - - "id": "jid1-80123", - "description": "test addon name falls back all the way to the 'name' key" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json deleted file mode 100644 index 634817b7a2..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "empty", - "license": "MPL-2.0", - "author": "", - "version": "0.1", - "title": "a long title", - "id": "jid1-80123", - "description": "test addon name comes from the 'title' key" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js deleted file mode 100644 index ad54ae76da..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -if (this.sendMessage) { -} else { - require('bar'); - - exports.register = function(process) { - }; -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js deleted file mode 100644 index fe9e4fb72d..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -require('chrome'); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js deleted file mode 100644 index 55633d1677..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -require('bar'); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js deleted file mode 100644 index e32a30f9c3..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = "'main' mainly reigns in main(.js)"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json deleted file mode 100644 index 98e4b85aab..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "five", - "main": "./lib/main" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js deleted file mode 100644 index 7e1ce7ea00..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json deleted file mode 100644 index 3010fae71b..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ "name": "four-a", - "directories": {"lib": "lib"}, - "main": "./topfiles/main.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js deleted file mode 100644 index 7e1ce7ea00..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js deleted file mode 100644 index b95f7bd539..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var a = require("four-a"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json deleted file mode 100644 index 53180b9f6d..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "four", - "main": "main" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js deleted file mode 100644 index 08b9245c55..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var panel = require("sdk/panel"); -var two = require("two.js"); -var a = require("./two"); -var b = require("sdk/tabs.js"); -var c = require("./subdir/three"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js deleted file mode 100644 index b594f3c68c..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js +++ /dev/null @@ -1,6 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.foo = 1; -var main = require("../main"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js deleted file mode 100644 index 9765219589..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - -// this ought to find our sibling, not packages/development-mode/lib/main.js -var main = require("main"); -exports.foo = 1; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json deleted file mode 100644 index edd2b17c8d..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ "name": "one", - "id": "jid1@jetpack", - "main": "main" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data deleted file mode 100644 index 1269488f7f..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data +++ /dev/null @@ -1 +0,0 @@ -data diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js deleted file mode 100644 index b1d68d3a03..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js +++ /dev/null @@ -1,6 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var self = require("sdk/self"); // trigger inclusion of data -exports.main = function () { console.log("main"); }; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js deleted file mode 100644 index a7b1c1449c..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.unused = "just pretend I'm not here"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json deleted file mode 100644 index 922c77dfc7..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "seven", - "id": "jid7" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js deleted file mode 100644 index ada31ef8c6..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.unused = "I am."; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json deleted file mode 100644 index 906b249277..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "six", - "main": "./unreachable" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js deleted file mode 100644 index e8b229cde1..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = "I am outside lib/ and cannot be reached, yet"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js deleted file mode 100644 index 54e4b7efb4..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = 42; -require("./subdir/subfile"); -require("sdk/self"); // trigger inclusion of our data/ directory - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js deleted file mode 100644 index aec24d01b3..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = "I should be included in a subdir"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js deleted file mode 100644 index 36c4a4e2bd..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = "unused, linker should not include me in the XPI"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties deleted file mode 100644 index 980ac46c66..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties +++ /dev/null @@ -1,5 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -Yes= Oui diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json deleted file mode 100644 index 6b796fc33d..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "three-a", - "main": "./lib/main.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js deleted file mode 100644 index 7e1ce7ea00..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties deleted file mode 100644 index c1bf1465f7..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties +++ /dev/null @@ -1,6 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -No= Non -one= un diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json deleted file mode 100644 index c0ff5ee0ef..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "three-b", - "main": "./lib/main" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js deleted file mode 100644 index 7e1ce7ea00..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js deleted file mode 100644 index 587849615a..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js +++ /dev/null @@ -1,6 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.foo = "you found me down here"; - diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties deleted file mode 100644 index dac3f133b2..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties +++ /dev/null @@ -1,9 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -No= Nein -What?= Quoi? -plural=other -plural[one]=one -uft8_value=é diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json deleted file mode 100644 index 169c9146ba..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "three-c", - "main": "lib/main" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt deleted file mode 100644 index 3b18e512db..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt +++ /dev/null @@ -1 +0,0 @@ -hello world diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt deleted file mode 100644 index d2cfe80de3..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt +++ /dev/null @@ -1 +0,0 @@ -hello subdir diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js deleted file mode 100644 index 4f59443740..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var a = require("three-a"); -var b = require("three-b"); -var c = require("three-c"); -var c3 = require("three-c/sub/foo"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json deleted file mode 100644 index cbfbc5bb29..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ "name": "three", - "main": "main" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js deleted file mode 100644 index edbc08ef2e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// dummy diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js deleted file mode 100644 index edbc08ef2e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// dummy diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js deleted file mode 100644 index edbc08ef2e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// dummy diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me deleted file mode 100644 index 014242c92d..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me +++ /dev/null @@ -1,3 +0,0 @@ -The docs processor should tolerate (by ignoring) random non-.js files in lib -directories, such as those left around by editors, version-control systems, -or OS metadata like .DS_Store . This file exercises that tolerance. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js deleted file mode 100644 index 0264872571..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = function(options, callbacks) { - console.log("1 + 1 =", require("bar-module").add(1, 1)); - callbacks.quit(); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too deleted file mode 100644 index 066f9b55d0..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too +++ /dev/null @@ -1,2 +0,0 @@ -The docs processor should also ignore directories named *.js, and their -contents. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json deleted file mode 100644 index afb5698e9e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "author": "Jon Smith", - "description": "A package w/ a main module; can be built into an extension.", - "keywords": ["potato"], - "version": "1.0", - "dependencies": ["addon-sdk", "barbeque"] -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js deleted file mode 100644 index 0264872571..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = function(options, callbacks) { - console.log("1 + 1 =", require("bar-module").add(1, 1)); - callbacks.quit(); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json deleted file mode 100644 index 9684581da0..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "anteater", - "author": "Jon Smith", - "description": "A package w/ a main module; can be built into an extension.", - "keywords": ["potato"], - "version": "1.0", - "dependencies": ["addon-sdk", "barbeque"] -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js deleted file mode 100644 index 361846d277..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// This module will be imported by the XPCOM harness/boostrapper -// via Components.utils.import() and is responsible for creating a -// CommonJS module loader. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json deleted file mode 100644 index 64eb065be3..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "description": "A foundational package that provides a CommonJS module loader implementation.", - "keywords": ["potato", "jetpack-low-level"], - "loader": "lib/loader.js" -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js deleted file mode 100644 index ff982ae20b..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.add = function add(a, b) { - return a + b; -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json deleted file mode 100644 index 62e3c12d75..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "keywords": ["potato", "jetpack-low-level"], - "description": "A package used by 'aardvark' as a library." -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js deleted file mode 100644 index aeda0e7fbd..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = function(options, callbacks) { - console.log("minimal"); - callbacks.quit(); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json deleted file mode 100644 index 530f3c2477..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "author": "Jon Smith", - "description": "A package w/ a main module; can be built into an extension." -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md deleted file mode 100644 index 35152f6012..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md +++ /dev/null @@ -1 +0,0 @@ -hello, I'm a third party. \ No newline at end of file diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js deleted file mode 100644 index 0264872571..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.main = function(options, callbacks) { - console.log("1 + 1 =", require("bar-module").add(1, 1)); - callbacks.quit(); -}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json deleted file mode 100644 index afb5698e9e..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "author": "Jon Smith", - "description": "A package w/ a main module; can be built into an extension.", - "keywords": ["potato"], - "version": "1.0", - "dependencies": ["addon-sdk", "barbeque"] -} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js deleted file mode 100644 index a20bf3fe66..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// This file contains XPCOM code that bootstraps an SDK-based add-on -// by loading its harness-options.json, registering all its resource -// directories, executing its loader, and then executing its program's -// main() function. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py deleted file mode 100644 index 5ececfea60..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py +++ /dev/null @@ -1,211 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os, unittest, shutil -import zipfile -from StringIO import StringIO -from cuddlefish import initializer -from cuddlefish.templates import TEST_MAIN_JS, PACKAGE_JSON - -tests_path = os.path.abspath(os.path.dirname(__file__)) - -class TestInit(unittest.TestCase): - - def run_init_in_subdir(self, dirname, f, *args, **kwargs): - top = os.path.abspath(os.getcwd()) - basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname)) - if os.path.isdir(basedir): - assert basedir.startswith(top) - shutil.rmtree(basedir) - os.makedirs(basedir) - try: - os.chdir(basedir) - return f(basedir, *args, **kwargs) - finally: - os.chdir(top) - - def do_test_init(self,basedir): - # Let's init the addon, no error admitted - f = open(".ignoreme","w") - f.write("stuff") - f.close() - - out, err = StringIO(), StringIO() - init_run = initializer(None, ["init"], out, err) - out, err = out.getvalue(), err.getvalue() - self.assertEqual(init_run["result"], 0) - self.assertTrue("* lib directory created" in out) - self.assertTrue("* data directory created" in out) - self.assertTrue("Have fun!" in out) - self.assertEqual(err,"") - self.assertTrue(len(os.listdir(basedir))>0) - main_js = os.path.join(basedir,"lib","main.js") - package_json = os.path.join(basedir,"package.json") - test_main_js = os.path.join(basedir,"test","test-main.js") - self.assertTrue(os.path.exists(main_js)) - self.assertTrue(os.path.exists(package_json)) - self.assertTrue(os.path.exists(test_main_js)) - self.assertEqual(open(main_js,"r").read(),"") - self.assertEqual(open(package_json,"r").read() % {"id":"tmp_addon_id" }, - PACKAGE_JSON % {"name":"tmp_addon_sample", - "title": "tmp_addon_SAMPLE", - "id":init_run["jid"] }) - self.assertEqual(open(test_main_js,"r").read(),TEST_MAIN_JS) - - # Let's check that the addon is initialized - out, err = StringIO(), StringIO() - init_run = initializer(None, ["init"], out, err) - out, err = out.getvalue(), err.getvalue() - self.failIfEqual(init_run["result"],0) - self.assertTrue("This command must be run in an empty directory." in err) - - def test_initializer(self): - self.run_init_in_subdir("tmp_addon_SAMPLE",self.do_test_init) - - def do_test_args(self, basedir): - # check that running it with spurious arguments will fail - out,err = StringIO(), StringIO() - init_run = initializer(None, ["init", "specified-dirname", "extra-arg"], out, err) - out, err = out.getvalue(), err.getvalue() - self.failIfEqual(init_run["result"], 0) - self.assertTrue("Too many arguments" in err) - - def test_args(self): - self.run_init_in_subdir("tmp_addon_sample", self.do_test_args) - - def _test_existing_files(self, basedir): - f = open("pay_attention_to_me","w") - f.write("stuff") - f.close() - out,err = StringIO(), StringIO() - rc = initializer(None, ["init"], out, err) - out, err = out.getvalue(), err.getvalue() - self.assertEqual(rc["result"], 1) - self.failUnless("This command must be run in an empty directory" in err, - err) - self.failIf(os.path.exists("lib")) - - def test_existing_files(self): - self.run_init_in_subdir("existing_files", self._test_existing_files) - - def test_init_subdir(self): - parent = os.path.abspath(os.path.join(".test_tmp", self.id())) - basedir = os.path.join(parent, "init-basedir") - if os.path.exists(parent): - shutil.rmtree(parent) - os.makedirs(parent) - - # if the basedir exists and is not empty, init should refuse - os.makedirs(basedir) - f = open(os.path.join(basedir, "boo"), "w") - f.write("stuff") - f.close() - out, err = StringIO(), StringIO() - rc = initializer(None, ["init", basedir], out, err) - out, err = out.getvalue(), err.getvalue() - self.assertEqual(rc["result"], 1) - self.assertTrue("testing if directory is empty" in out, out) - self.assertTrue("This command must be run in an empty directory." in err, - err) - - # a .dotfile should be tolerated - os.rename(os.path.join(basedir, "boo"), os.path.join(basedir, ".phew")) - out, err = StringIO(), StringIO() - rc = initializer(None, ["init", basedir], out, err) - out, err = out.getvalue(), err.getvalue() - self.assertEqual(rc["result"], 0) - self.assertTrue("* data directory created" in out, out) - self.assertTrue("Have fun!" in out) - self.assertEqual(err,"") - self.assertTrue(os.listdir(basedir)) - main_js = os.path.join(basedir,"lib","main.js") - package_json = os.path.join(basedir,"package.json") - self.assertTrue(os.path.exists(main_js)) - self.assertTrue(os.path.exists(package_json)) - shutil.rmtree(basedir) - - # init should create directories that don't exist already - out, err = StringIO(), StringIO() - rc = initializer(None, ["init", basedir], out, err) - out, err = out.getvalue(), err.getvalue() - self.assertEqual(rc["result"], 0) - self.assertTrue("* data directory created" in out) - self.assertTrue("Have fun!" in out) - self.assertEqual(err,"") - self.assertTrue(os.listdir(basedir)) - main_js = os.path.join(basedir,"lib","main.js") - package_json = os.path.join(basedir,"package.json") - self.assertTrue(os.path.exists(main_js)) - self.assertTrue(os.path.exists(package_json)) - - -class TestCfxQuits(unittest.TestCase): - - def run_cfx(self, addon_path, command): - old_cwd = os.getcwd() - os.chdir(addon_path) - import sys - old_stdout = sys.stdout - old_stderr = sys.stderr - sys.stdout = out = StringIO() - sys.stderr = err = StringIO() - rc = 0 - try: - import cuddlefish - args = list(command) - # Pass arguments given to cfx so that cfx can find firefox path - # if --binary option is given: - args.extend(sys.argv[1:]) - cuddlefish.run(arguments=args) - except SystemExit, e: - if "code" in e: - rc = e.code - elif "args" in e and len(e.args)>0: - rc = e.args[0] - else: - rc = 0 - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - os.chdir(old_cwd) - out.flush() - err.flush() - return rc, out.getvalue(), err.getvalue() - - # this method doesn't exists in python 2.5, - # implements our own - def assertIn(self, member, container): - """Just like self.assertTrue(a in b), but with a nicer default message.""" - if member not in container: - standardMsg = '"%s" not found in "%s"' % (member, - container) - self.fail(standardMsg) - - def test_cfx_init(self): - # Create an empty test directory - addon_path = os.path.abspath(os.path.join(".test_tmp", "test-cfx-init")) - if os.path.isdir(addon_path): - shutil.rmtree(addon_path) - os.makedirs(addon_path) - - # Fake a call to cfx init - old_cwd = os.getcwd() - os.chdir(addon_path) - out, err = StringIO(), StringIO() - rc = initializer(None, ["init"], out, err) - os.chdir(old_cwd) - out, err = out.getvalue(), err.getvalue() - self.assertEqual(rc["result"], 0) - self.assertTrue("Have fun!" in out) - self.assertEqual(err,"") - - # run cfx test - rc, out, err = self.run_cfx(addon_path, ["test"]) - self.assertEqual(rc, 0) - self.assertIn("6 of 6 tests passed.", err) - self.assertIn("Program terminated successfully.", err) - - -if __name__ == "__main__": - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py deleted file mode 100644 index 574812ada4..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py +++ /dev/null @@ -1,100 +0,0 @@ - -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import unittest -import os.path - -parent = os.path.dirname -test_dir = parent(os.path.abspath(__file__)) -sdk_root = parent(parent(parent(test_dir))) - -def from_sdk_top(fn): - return os.path.abspath(os.path.join(sdk_root, fn)) - -MPL2_URL = "http://mozilla.org/MPL/2.0/" - -# These files all come with their own license headers -skip = [ - "python-lib/cuddlefish/_version.py", # generated, public domain - "doc/static-files/js/jquery.js", # MIT/GPL dual - "examples/annotator/data/jquery-1.4.2.min.js", # MIT/GPL dual - "examples/reddit-panel/data/jquery-1.4.4.min.js", # MIT/GPL dual - "examples/library-detector/data/library-detector.js", # MIT - "python-lib/mozrunner/killableprocess.py", # MIT? BSDish? - "python-lib/mozrunner/winprocess.py", # MIT - "packages/api-utils/tests/test-querystring.js", # MIT - "packages/api-utils/lib/promise.js", # MIT - "packages/api-utils/tests/test-promise.js", # MIT - "examples/actor-repl/README.md", # It's damn readme file - "examples/actor-repl/data/codemirror-compressed.js", # MIT - "examples/actor-repl/data/codemirror.css", # MIT - ] -absskip = [from_sdk_top(os.path.join(*fn.split("/"))) for fn in skip] - -class Licenses(unittest.TestCase): - def test(self): - # Examine most SDK files to check if they've got an MPL2 license - # header. We exclude some files that are known to include different - # licenses. - self.missing = [] - self.scan_file(from_sdk_top(os.path.join("python-lib", "jetpack_sdk_env.py"))) - self.scan(os.path.join("python-lib", "cuddlefish"), [".js", ".py"], - skipdirs=["sdk-docs"], # test_generate.py makes this - ) - self.scan(os.path.join("python-lib", "mozrunner"), [".py"]) - self.scan("lib", [".js", ".jsm", ".css"], - skipdirs=[ - "diffpatcher", # MIT - "method", # MIT - "child_process", # MPL 1.1/GPL 2.0/LGPL 2.1 - "fs" # MIT - ]) - self.scan("test", [".js", ".jsm", ".css", ".html"], - skipdirs=[ - "buffers", # MIT - "querystring", # MIT - "path" # MIT - ]) - self.scan("modules", [".js", ".jsm"]) - self.scan("examples", [".js", ".css", ".html", ".md"]) - self.scan("bin", [".bat", ".ps1", ".js"]) - for fn in [os.path.join("bin", "activate"), - os.path.join("bin", "cfx"), - os.path.join("bin", "integration-scripts", "buildbot-run-cfx-helper"), - os.path.join("bin", "integration-scripts", "integration-check"), - ]: - self.scan_file(from_sdk_top(fn)) - - if self.missing: - print - print "The following files are missing an MPL2 header:" - for fn in sorted(self.missing): - print " "+fn - self.fail("%d files are missing an MPL2 header" % len(self.missing)) - - def scan(self, start, extensions=[], skipdirs=[]): - # scan a whole subdirectory - start = from_sdk_top(start) - for root, dirs, files in os.walk(start): - for d in skipdirs: - if d in dirs: - dirs.remove(d) - for fn in files: - ext = os.path.splitext(fn)[1] - if extensions and ext not in extensions: - continue - absfn = os.path.join(root, fn) - if absfn in absskip: - continue - self.scan_file(absfn) - - def scan_file(self, fn): - # scan a single file - if not MPL2_URL in open(fn, "r").read(): - relfile = fn[len(sdk_root)+1:] - self.missing.append(relfile) - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py deleted file mode 100644 index 7a6fd12e48..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py +++ /dev/null @@ -1,247 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os.path -import shutil -import zipfile -from StringIO import StringIO -import simplejson as json -import unittest -import cuddlefish -from cuddlefish import packaging, manifest - -def up(path, generations=1): - for i in range(generations): - path = os.path.dirname(path) - return path - -ROOT = up(os.path.abspath(__file__), 4) -def get_linker_files_dir(name): - return os.path.join(up(os.path.abspath(__file__)), "linker-files", name) - -class Basic(unittest.TestCase): - def get_pkg(self, name): - d = get_linker_files_dir(name) - return packaging.get_config_in_dir(d) - - def test_deps(self): - target_cfg = self.get_pkg("one") - pkg_cfg = packaging.build_config(ROOT, target_cfg) - deps = packaging.get_deps_for_targets(pkg_cfg, ["one"]) - self.failUnlessEqual(deps, ["one"]) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - self.failUnlessEqual(deps, ["addon-sdk", "one"]) - - def test_manifest(self): - target_cfg = self.get_pkg("one") - pkg_cfg = packaging.build_config(ROOT, target_cfg) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - self.failUnlessEqual(deps, ["addon-sdk", "one"]) - # target_cfg.dependencies is not provided, so we'll search through - # all known packages (everything in 'deps'). - m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) - m = m.get_harness_options_manifest(False) - - def assertReqIs(modname, reqname, path): - reqs = m["one/%s" % modname]["requirements"] - self.failUnlessEqual(reqs[reqname], path) - - assertReqIs("main", "sdk/panel", "sdk/panel") - assertReqIs("main", "two.js", "one/two") - assertReqIs("main", "./two", "one/two") - assertReqIs("main", "sdk/tabs.js", "sdk/tabs") - assertReqIs("main", "./subdir/three", "one/subdir/three") - assertReqIs("two", "main", "one/main") - assertReqIs("subdir/three", "../main", "one/main") - - target_cfg.dependencies = [] - - try: - # this should now work, as we ignore missing modules by default - m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) - m = m.get_harness_options_manifest(False) - - assertReqIs("main", "sdk/panel", "sdk/panel") - # note that with "addon-sdk" dependency present, - # "sdk/tabs.js" mapped to "sdk/tabs", but without, - # we just get the default (identity) mapping - assertReqIs("main", "sdk/tabs.js", "sdk/tabs.js") - except Exception, e: - self.fail("Must not throw from build_manifest() if modules are missing") - - # now, because .dependencies *is* provided, we won't search 'deps', - # and stop_on_missing is True, we'll get a link error - self.assertRaises(manifest.ModuleNotFoundError, - manifest.build_manifest, - target_cfg, pkg_cfg, deps, scan_tests=False, - abort_on_missing=True) - - def test_main_in_deps(self): - target_cfg = self.get_pkg("three") - package_path = [get_linker_files_dir("three-deps")] - pkg_cfg = packaging.build_config(ROOT, target_cfg, - packagepath=package_path) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - self.failUnlessEqual(deps, ["addon-sdk", "three"]) - m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) - m = m.get_harness_options_manifest(False) - def assertReqIs(modname, reqname, path): - reqs = m["three/%s" % modname]["requirements"] - self.failUnlessEqual(reqs[reqname], path) - assertReqIs("main", "three-a", "three-a/main") - assertReqIs("main", "three-b", "three-b/main") - assertReqIs("main", "three-c", "three-c/main") - - def test_relative_main_in_top(self): - target_cfg = self.get_pkg("five") - package_path = [] - pkg_cfg = packaging.build_config(ROOT, target_cfg, - packagepath=package_path) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - self.failUnlessEqual(deps, ["addon-sdk", "five"]) - # all we care about is that this next call doesn't raise an exception - m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) - m = m.get_harness_options_manifest(False) - reqs = m["five/main"]["requirements"] - self.failUnlessEqual(reqs, {}); - - def test_unreachable_relative_main_in_top(self): - target_cfg = self.get_pkg("six") - package_path = [] - pkg_cfg = packaging.build_config(ROOT, target_cfg, - packagepath=package_path) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - self.failUnlessEqual(deps, ["addon-sdk", "six"]) - self.assertRaises(manifest.UnreachablePrefixError, - manifest.build_manifest, - target_cfg, pkg_cfg, deps, scan_tests=False) - - def test_unreachable_in_deps(self): - target_cfg = self.get_pkg("four") - package_path = [get_linker_files_dir("four-deps")] - pkg_cfg = packaging.build_config(ROOT, target_cfg, - packagepath=package_path) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - self.failUnlessEqual(deps, ["addon-sdk", "four"]) - self.assertRaises(manifest.UnreachablePrefixError, - manifest.build_manifest, - target_cfg, pkg_cfg, deps, scan_tests=False) - -class Contents(unittest.TestCase): - - def run_in_subdir(self, dirname, f, *args, **kwargs): - top = os.path.abspath(os.getcwd()) - basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname)) - if os.path.isdir(basedir): - assert basedir.startswith(top) - shutil.rmtree(basedir) - os.makedirs(basedir) - try: - os.chdir(basedir) - return f(basedir, *args, **kwargs) - finally: - os.chdir(top) - - def assertIn(self, what, inside_what): - self.failUnless(what in inside_what, inside_what) - - def test_jetpackID(self): - # this uses "id": "jid7", to which a @jetpack should be appended - seven = get_linker_files_dir("seven") - def _test(basedir): - stdout = StringIO() - shutil.copytree(seven, "seven") - os.chdir("seven") - try: - # regrettably, run() always finishes with sys.exit() - cuddlefish.run(["xpi", "--no-strip-xpi"], - stdout=stdout) - except SystemExit, e: - self.failUnlessEqual(e.args[0], 0) - zf = zipfile.ZipFile("seven.xpi", "r") - hopts = json.loads(zf.read("harness-options.json")) - self.failUnlessEqual(hopts["jetpackID"], "jid7@jetpack") - self.run_in_subdir("x", _test) - - def test_jetpackID_suffix(self): - # this uses "id": "jid1@jetpack", so no suffix should be appended - one = get_linker_files_dir("one") - def _test(basedir): - stdout = StringIO() - shutil.copytree(one, "one") - os.chdir("one") - try: - # regrettably, run() always finishes with sys.exit() - cuddlefish.run(["xpi", "--no-strip-xpi"], - stdout=stdout) - except SystemExit, e: - self.failUnlessEqual(e.args[0], 0) - zf = zipfile.ZipFile("one.xpi", "r") - hopts = json.loads(zf.read("harness-options.json")) - self.failUnlessEqual(hopts["jetpackID"], "jid1@jetpack") - self.run_in_subdir("x", _test) - - def test_strip_default(self): - seven = get_linker_files_dir("seven") - # now run 'cfx xpi' in that directory, except put the generated .xpi - # elsewhere - def _test(basedir): - stdout = StringIO() - shutil.copytree(seven, "seven") - os.chdir("seven") - try: - # regrettably, run() always finishes with sys.exit() - cuddlefish.run(["xpi"], # --strip-xpi is now the default - stdout=stdout) - except SystemExit, e: - self.failUnlessEqual(e.args[0], 0) - zf = zipfile.ZipFile("seven.xpi", "r") - names = zf.namelist() - # problem found in bug 664840 was that an addon - # without an explicit tests/ directory would copy all files from - # the package into a bogus JID-PKGNAME-tests/ directory, so check - # for that - testfiles = [fn for fn in names if "seven/tests" in fn] - self.failUnlessEqual([], testfiles) - # another problem was that data files were being stripped from - # the XPI. Note that data/ is only supposed to be included if a - # module that actually gets used does a require("self") . - self.assertIn("resources/seven/data/text.data", - names) - self.failIf("seven/lib/unused.js" - in names, names) - self.run_in_subdir("x", _test) - - def test_no_strip(self): - seven = get_linker_files_dir("seven") - def _test(basedir): - stdout = StringIO() - shutil.copytree(seven, "seven") - os.chdir("seven") - try: - # regrettably, run() always finishes with sys.exit() - cuddlefish.run(["xpi", "--no-strip-xpi"], - stdout=stdout) - except SystemExit, e: - self.failUnlessEqual(e.args[0], 0) - zf = zipfile.ZipFile("seven.xpi", "r") - names = zf.namelist() - self.assertIn("resources/addon-sdk/lib/sdk/loader/cuddlefish.js", names) - testfiles = [fn for fn in names if "seven/tests" in fn] - self.failUnlessEqual([], testfiles) - self.assertIn("resources/seven/data/text.data", - names) - self.failUnless("resources/seven/lib/unused.js" - in names, names) - self.run_in_subdir("x", _test) - - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py deleted file mode 100644 index 1bced12343..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py +++ /dev/null @@ -1,257 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -import unittest -from StringIO import StringIO -from cuddlefish.manifest import scan_module - -class Extra: - def failUnlessKeysAre(self, d, keys): - self.failUnlessEqual(sorted(d.keys()), sorted(keys)) - -class Require(unittest.TestCase, Extra): - def scan(self, text): - lines = StringIO(text).readlines() - requires, problems, locations = scan_module("fake.js", lines) - self.failUnlessEqual(problems, False) - return requires - - def scan_locations(self, text): - lines = StringIO(text).readlines() - requires, problems, locations = scan_module("fake.js", lines) - self.failUnlessEqual(problems, False) - return requires, locations - - def test_modules(self): - mod = """var foo = require('one');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one"]) - - mod = """var foo = require(\"one\");""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one"]) - - mod = """var foo=require( 'one' ) ; """ - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one"]) - - mod = """var foo = require('o'+'ne'); // tricky, denied""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, []) - - mod = """require('one').immediately.do().stuff();""" - requires, locations = self.scan_locations(mod) - self.failUnlessKeysAre(requires, ["one"]) - self.failUnlessEqual(locations, {"one": 1}) - - # these forms are commented out, and thus ignored - - mod = """// var foo = require('one');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, []) - - mod = """/* var foo = require('one');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, []) - - mod = """ * var foo = require('one');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, []) - - mod = """ ' var foo = require('one');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one"]) - - mod = """ \" var foo = require('one');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one"]) - - # multiple requires - - mod = """const foo = require('one'); - const foo = require('two');""" - requires, locations = self.scan_locations(mod) - self.failUnlessKeysAre(requires, ["one", "two"]) - self.failUnlessEqual(locations["one"], 1) - self.failUnlessEqual(locations["two"], 2) - - mod = """const foo = require('repeated'); - const bar = require('repeated'); - const baz = require('repeated');""" - requires, locations = self.scan_locations(mod) - self.failUnlessKeysAre(requires, ["repeated"]) - self.failUnlessEqual(locations["repeated"], 1) # first occurrence - - mod = """const foo = require('one'); const foo = require('two');""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one", "two"]) - - # define calls - - mod = """define('one', ['two', 'numbers/three'], function(t, th) {});""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["two", "numbers/three"]) - - mod = """define( - ['odd', - "numbers/four"], function() {});""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["odd", "numbers/four"]) - - mod = """define(function(require, exports, module) { - var a = require("some/module/a"), - b = require('b/v1'); - exports.a = a; - //This is a fakeout: require('bad'); - /* And another var bad = require('bad2'); */ - require('foo').goFoo(); - });""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["some/module/a", "b/v1", "foo"]) - - mod = """define ( - "foo", - ["bar"], function (bar) { - var me = require("me"); - } - )""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["bar", "me"]) - - mod = """define(['se' + 'ven', 'eight', nine], function () {});""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["eight"]) - - # async require calls - - mod = """require(['one'], function(one) {var o = require("one");});""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one"]) - - mod = """require([ 'one' ], function(one) {var t = require("two");});""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["one", "two"]) - - mod = """require ( ['two', 'numbers/three'], function(t, th) {});""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["two", "numbers/three"]) - - mod = """require ( - ["bar", "fa" + 'ke' ], function (bar) { - var me = require("me"); - // require("bad").doBad(); - } - )""" - requires = self.scan(mod) - self.failUnlessKeysAre(requires, ["bar", "me"]) - -def scan2(text, fn="fake.js"): - stderr = StringIO() - lines = StringIO(text).readlines() - requires, problems, locations = scan_module(fn, lines, stderr) - stderr.seek(0) - return requires, problems, stderr.readlines() - -class Chrome(unittest.TestCase, Extra): - - def test_ignore_loader(self): - # we specifically ignore the loader itself - mod = """let {Cc,Ci} = require('chrome');""" - requires, problems, err = scan2(mod, "blah/cuddlefish.js") - self.failUnlessKeysAre(requires, ["chrome"]) - self.failUnlessEqual(problems, False) - self.failUnlessEqual(err, []) - - def test_chrome(self): - mod = """let {Cc,Ci} = require('chrome');""" - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, ["chrome"]) - self.failUnlessEqual(problems, False) - self.failUnlessEqual(err, []) - - mod = """var foo = require('foo'); - let {Cc,Ci} = require('chrome');""" - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, ["foo", "chrome"]) - self.failUnlessEqual(problems, False) - self.failUnlessEqual(err, []) - - mod = """let c = require('chrome');""" - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, ["chrome"]) - self.failUnlessEqual(problems, False) - self.failUnlessEqual(err, []) - - mod = """var foo = require('foo'); - let c = require('chrome');""" - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, ["foo", "chrome"]) - self.failUnlessEqual(problems, False) - self.failUnlessEqual(err, []) - - def test_not_chrome(self): - # from bug 596595 - mod = r'soughtLines: new RegExp("^\\s*(\\[[0-9 .]*\\])?\\s*\\(\\((EE|WW)\\)|.* [Cc]hipsets?: \\)|\\s*Backtrace")' - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, []) - self.failUnlessEqual((problems,err), (False, [])) - - def test_not_chrome2(self): - # from bug 655788 - mod = r"var foo = 'some stuff Cr';" - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, []) - self.failUnlessEqual((problems,err), (False, [])) - -class BadChrome(unittest.TestCase, Extra): - def test_bad_alias(self): - # using Components.* gets you an error, with a message that teaches - # you the correct approach. - mod = """let Cc = Components.classes; - let Cu = Components.utils; - """ - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, []) - self.failUnlessEqual(problems, True) - self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") - self.failUnlessEqual(err[2], " 1: let Cc = Components.classes;\n") - self.failUnlessEqual(err[3], " 2: let Cu = Components.utils;\n") - self.failUnlessEqual(err[4], "use 'Components' to access chrome authority. To do so, you need to add a\n") - self.failUnlessEqual(err[5], "line somewhat like the following:\n") - self.failUnlessEqual(err[7], ' const {Cc,Cu} = require("chrome");\n') - self.failUnlessEqual(err[9], "Then you can use any shortcuts to its properties that you import from the\n") - - def test_bad_misc(self): - # If it looks like you're using something that doesn't have an alias, - # the warning also suggests a better way. - mod = """if (Components.isSuccessCode(foo)) - """ - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, []) - self.failUnlessEqual(problems, True) - self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") - self.failUnlessEqual(err[2], " 1: if (Components.isSuccessCode(foo))\n") - self.failUnlessEqual(err[3], "use 'Components' to access chrome authority. To do so, you need to add a\n") - self.failUnlessEqual(err[4], "line somewhat like the following:\n") - self.failUnlessEqual(err[6], ' const {components} = require("chrome");\n') - self.failUnlessEqual(err[8], "Then you can use any shortcuts to its properties that you import from the\n") - - def test_chrome_components(self): - # Bug 636145/774636: We no longer tolerate usages of "Components", - # even when adding `require("chrome")` to your module. - mod = """require("chrome"); - var ios = Components.classes['@mozilla.org/network/io-service;1'];""" - requires, problems, err = scan2(mod) - self.failUnlessKeysAre(requires, ["chrome"]) - self.failUnlessEqual(problems, True) - self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") - self.failUnlessEqual(err[2], " 2: var ios = Components.classes['@mozilla.org/network/io-service;1'];\n") - self.failUnlessEqual(err[3], "use 'Components' to access chrome authority. To do so, you need to add a\n") - self.failUnlessEqual(err[4], "line somewhat like the following:\n") - self.failUnlessEqual(err[6], ' const {Cc} = require("chrome");\n') - self.failUnlessEqual(err[8], "Then you can use any shortcuts to its properties that you import from the\n") - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py deleted file mode 100644 index 6944dc3942..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py +++ /dev/null @@ -1,117 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import unittest - -from cuddlefish import packaging -from cuddlefish.bunch import Bunch - -tests_path = os.path.abspath(os.path.dirname(__file__)) -static_files_path = os.path.join(tests_path, 'static-files') - -def get_configs(pkg_name, dirname='static-files'): - root_path = os.path.join(tests_path, dirname) - pkg_path = os.path.join(root_path, 'packages', pkg_name) - if not (os.path.exists(pkg_path) and os.path.isdir(pkg_path)): - raise Exception('path does not exist: %s' % pkg_path) - target_cfg = packaging.get_config_in_dir(pkg_path) - pkg_cfg = packaging.build_config(root_path, target_cfg) - deps = packaging.get_deps_for_targets(pkg_cfg, [pkg_name]) - build = packaging.generate_build_for_target( - pkg_cfg=pkg_cfg, - target=pkg_name, - deps=deps, - is_running_tests=True, - ) - return Bunch(target_cfg=target_cfg, pkg_cfg=pkg_cfg, build=build) - -class PackagingTests(unittest.TestCase): - def test_bug_588661(self): - configs = get_configs('foo', 'bug-588661-files') - self.assertEqual(configs.build.loader, - 'foo/lib/foo-loader.js') - - def test_bug_614712(self): - configs = get_configs('commonjs-naming', 'bug-614712-files') - packages = configs.pkg_cfg.packages - base = os.path.join(tests_path, 'bug-614712-files', 'packages') - self.assertEqual(packages['original-naming'].tests, - [os.path.join(base, 'original-naming', 'tests')]) - self.assertEqual(packages['commonjs-naming'].tests, - [os.path.join(base, 'commonjs-naming', 'test')]) - - def test_basic(self): - configs = get_configs('aardvark') - packages = configs.pkg_cfg.packages - - self.assertTrue('addon-sdk' in packages) - self.assertTrue('aardvark' in packages) - self.assertTrue('addon-sdk' in packages.aardvark.dependencies) - self.assertEqual(packages['addon-sdk'].loader, 'lib/sdk/loader/cuddlefish.js') - self.assertTrue(packages.aardvark.main == 'main') - self.assertTrue(packages.aardvark.version == "1.0") - -class PackagePath(unittest.TestCase): - def test_packagepath(self): - root_path = os.path.join(tests_path, 'static-files') - pkg_path = os.path.join(root_path, 'packages', 'minimal') - target_cfg = packaging.get_config_in_dir(pkg_path) - pkg_cfg = packaging.build_config(root_path, target_cfg) - base_packages = set(pkg_cfg.packages.keys()) - ppath = [os.path.join(tests_path, 'bug-611495-files')] - pkg_cfg2 = packaging.build_config(root_path, target_cfg, packagepath=ppath) - all_packages = set(pkg_cfg2.packages.keys()) - self.assertEqual(sorted(["jspath-one"]), - sorted(all_packages - base_packages)) - -class Directories(unittest.TestCase): - # for bug 652227 - packages_path = os.path.join(tests_path, "bug-652227-files", "packages") - def get_config(self, pkg_name): - pkg_path = os.path.join(tests_path, "bug-652227-files", "packages", - pkg_name) - return packaging.get_config_in_dir(pkg_path) - - def test_explicit_lib(self): - # package.json provides .lib - p = self.get_config('explicit-lib') - self.assertEqual(os.path.abspath(p.lib[0]), - os.path.abspath(os.path.join(self.packages_path, - "explicit-lib", - "alt2-lib"))) - - def test_directories_lib(self): - # package.json provides .directories.lib - p = self.get_config('explicit-dir-lib') - self.assertEqual(os.path.abspath(p.lib[0]), - os.path.abspath(os.path.join(self.packages_path, - "explicit-dir-lib", - "alt-lib"))) - - def test_lib(self): - # package.json is empty, but lib/ exists - p = self.get_config("default-lib") - self.assertEqual(os.path.abspath(p.lib[0]), - os.path.abspath(os.path.join(self.packages_path, - "default-lib", - "lib"))) - - def test_root(self): - # package.json is empty, no lib/, so files are in root - p = self.get_config('default-root') - self.assertEqual(os.path.abspath(p.lib[0]), - os.path.abspath(os.path.join(self.packages_path, - "default-root"))) - - def test_locale(self): - # package.json is empty, but locale/ exists and should be used - p = self.get_config("default-locale") - self.assertEqual(os.path.abspath(p.locale), - os.path.abspath(os.path.join(self.packages_path, - "default-locale", - "locale"))) - -if __name__ == "__main__": - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py deleted file mode 100644 index 571b7911b7..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py +++ /dev/null @@ -1,147 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -import os, shutil -import simplejson as json -import unittest -import hashlib -import base64 -from cuddlefish import preflight -from StringIO import StringIO - -class Util(unittest.TestCase): - def get_basedir(self): - return os.path.join(".test_tmp", self.id()) - def make_basedir(self): - basedir = self.get_basedir() - if os.path.isdir(basedir): - here = os.path.abspath(os.getcwd()) - assert os.path.abspath(basedir).startswith(here) # safety - shutil.rmtree(basedir) - os.makedirs(basedir) - return basedir - - def test_base62(self): - for i in range(1000): - h = hashlib.sha1(str(i)).digest() - s1 = base64.b64encode(h, "AB").strip("=") - s2 = base64.b64encode(h).strip("=").replace("+","A").replace("/","B") - self.failUnlessEqual(s1, s2) - - def write(self, config): - basedir = self.get_basedir() - fn = os.path.join(basedir, "package.json") - open(fn,"w").write(config) - def read(self): - basedir = self.get_basedir() - fn = os.path.join(basedir, "package.json") - return open(fn,"r").read() - - def get_cfg(self): - cfg = json.loads(self.read()) - if "name" not in cfg: - # the cfx parser always provides a name, even if package.json - # doesn't contain one - cfg["name"] = "pretend name" - return cfg - - def parse(self, keydata): - fields = {} - fieldnames = [] - for line in keydata.split("\n"): - if line.strip(): - k,v = line.split(":", 1) - k = k.strip() ; v = v.strip() - fields[k] = v - fieldnames.append(k) - return fields, fieldnames - - def test_preflight(self): - basedir = self.make_basedir() - fn = os.path.join(basedir, "package.json") - - # empty config is not ok: need id (name is automatically supplied) - config_orig = "{}" - self.write(config_orig) - out = StringIO() - cfg = self.get_cfg() - config_was_ok, modified = preflight.preflight_config(cfg, fn, - stderr=out) - self.failUnlessEqual(config_was_ok, False) - self.failUnlessEqual(modified, True) - backup_fn = os.path.join(basedir, "package.json.backup") - config_backup = open(backup_fn,"r").read() - self.failUnlessEqual(config_backup, config_orig) - config = json.loads(self.read()) - self.failIf("name" in config) - self.failUnless("id" in config) - self.failUnless(config["id"].startswith("jid1-"), config["id"]) - self.failUnlessEqual(out.getvalue().strip(), - "No 'id' in package.json: creating a new ID for you.") - os.unlink(backup_fn) - - # just a name? we add the id - config_orig = '{"name": "my-awesome-package"}' - self.write(config_orig) - out = StringIO() - cfg = self.get_cfg() - config_was_ok, modified = preflight.preflight_config(cfg, fn, - stderr=out) - self.failUnlessEqual(config_was_ok, False) - self.failUnlessEqual(modified, True) - backup_fn = os.path.join(basedir, "package.json.backup") - config_backup = open(backup_fn,"r").read() - self.failUnlessEqual(config_backup, config_orig) - config = json.loads(self.read()) - self.failUnlessEqual(config["name"], "my-awesome-package") - self.failUnless("id" in config) - self.failUnless(config["id"].startswith("jid1-"), config["id"]) - jid = str(config["id"]) - self.failUnlessEqual(out.getvalue().strip(), - "No 'id' in package.json: creating a new ID for you.") - os.unlink(backup_fn) - - # name and valid id? great! ship it! - config2 = '{"name": "my-awesome-package", "id": "%s"}' % jid - self.write(config2) - out = StringIO() - cfg = self.get_cfg() - config_was_ok, modified = preflight.preflight_config(cfg, fn, - stderr=out) - self.failUnlessEqual(config_was_ok, True) - self.failUnlessEqual(modified, False) - config2a = self.read() - self.failUnlessEqual(config2a, config2) - self.failUnlessEqual(out.getvalue().strip(), "") - - # name and anonymous ID? without asking to see its papers, ship it - config3 = '{"name": "my-old-skool-package", "id": "anonid0-deadbeef"}' - self.write(config3) - out = StringIO() - cfg = self.get_cfg() - config_was_ok, modified = preflight.preflight_config(cfg, fn, - stderr=out) - self.failUnlessEqual(config_was_ok, True) - self.failUnlessEqual(modified, False) - config3a = self.read() - self.failUnlessEqual(config3a, config3) - self.failUnlessEqual(out.getvalue().strip(), "") - - # name and old-style ID? with nostalgic trepidation, ship it - config4 = '{"name": "my-old-skool-package", "id": "foo@bar.baz"}' - self.write(config4) - out = StringIO() - cfg = self.get_cfg() - config_was_ok, modified = preflight.preflight_config(cfg, fn, - stderr=out) - self.failUnlessEqual(config_was_ok, True) - self.failUnlessEqual(modified, False) - config4a = self.read() - self.failUnlessEqual(config4a, config4) - self.failUnlessEqual(out.getvalue().strip(), "") - - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py deleted file mode 100644 index 4988f8ef54..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py +++ /dev/null @@ -1,93 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import unittest - -from cuddlefish.property_parser import parse, MalformedLocaleFileError - -class TestParser(unittest.TestCase): - - def test_parse(self): - lines = [ - # Comments are striped only if `#` is the first non-space character - "sharp=#can be in value", - "# comment", - "#key=value", - " # comment2", - - "keyWithNoValue=", - "valueWithSpaces= ", - "valueWithMultilineSpaces= \\", - " \\", - " ", - - # All spaces before/after are striped - " key = value ", - "key2=value2", - # Keys can contain '%' - "%s key=%s value", - - # Accept empty lines - "", - " ", - - # Multiline string must use backslash at end of lines - "multi=line\\", "value", - # With multiline string, left spaces are stripped ... - "some= spaces\\", " are\\ ", " stripped ", - # ... but not right spaces, except the last line! - "but=not \\", "all of \\", " them ", - - # Explicit [other] plural definition - "explicitPlural[one] = one", - "explicitPlural[other] = other", - - # Implicit [other] plural definition - "implicitPlural[one] = one", - "implicitPlural = other", # This key is the [other] one - ] - # Ensure that all lines end with a `\n` - # And that strings are unicode ones (parser code relies on it) - lines = [unicode(l + "\n") for l in lines] - pairs = parse(lines) - expected = { - "sharp": "#can be in value", - - "key": "value", - "key2": "value2", - "%s key": "%s value", - - "keyWithNoValue": "", - "valueWithSpaces": "", - "valueWithMultilineSpaces": "", - - "multi": "linevalue", - "some": "spacesarestripped", - "but": "not all of them", - - "implicitPlural": { - "one": "one", - "other": "other" - }, - "explicitPlural": { - "one": "one", - "other": "other" - }, - } - self.assertEqual(pairs, expected) - - def test_exceptions(self): - self.failUnlessRaises(MalformedLocaleFileError, parse, - ["invalid line with no key value"]) - self.failUnlessRaises(MalformedLocaleFileError, parse, - ["plural[one]=plural with no [other] value"]) - self.failUnlessRaises(MalformedLocaleFileError, parse, - ["multiline with no last empty line=\\"]) - self.failUnlessRaises(MalformedLocaleFileError, parse, - ["=no key"]) - self.failUnlessRaises(MalformedLocaleFileError, parse, - [" =only spaces in key"]) - -if __name__ == "__main__": - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py deleted file mode 100644 index 5d9254e4e0..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py +++ /dev/null @@ -1,54 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import unittest -import xml.dom.minidom -import os.path - -from cuddlefish import rdf, packaging - -parent = os.path.dirname -test_dir = parent(os.path.abspath(__file__)) -template_dir = os.path.join(parent(test_dir), "../../app-extension") - -class RDFTests(unittest.TestCase): - def testBug567660(self): - obj = rdf.RDF() - data = u'\u2026'.encode('utf-8') - x = '%s' % data - obj.dom = xml.dom.minidom.parseString(x) - self.assertEqual(obj.dom.documentElement.firstChild.nodeValue, - u'\u2026') - self.assertEqual(str(obj).replace("\n",""), x.replace("\n","")) - - def failUnlessIn(self, substring, s, msg=""): - if substring not in s: - self.fail("(%s) substring '%s' not in string '%s'" - % (msg, substring, s)) - - def testUnpack(self): - basedir = os.path.join(test_dir, "bug-715340-files") - for n in ["pkg-1-pack", "pkg-2-unpack", "pkg-3-pack"]: - cfg = packaging.get_config_in_dir(os.path.join(basedir, n)) - m = rdf.gen_manifest(template_dir, cfg, jid="JID") - if n.endswith("-pack"): - # these ones should remain packed - self.failUnlessEqual(m.get("em:unpack"), "false") - self.failUnlessIn("false", str(m), n) - else: - # and these should be unpacked - self.failUnlessEqual(m.get("em:unpack"), "true") - self.failUnlessIn("true", str(m), n) - - def testTitle(self): - basedir = os.path.join(test_dir, 'bug-906359-files') - for n in ['title', 'fullName', 'none']: - cfg = packaging.get_config_in_dir(os.path.join(basedir, n)) - m = rdf.gen_manifest(template_dir, cfg, jid='JID') - self.failUnlessEqual(m.get('em:name'), 'a long ' + n) - self.failUnlessIn('a long ' + n + '', str(m), n) - - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py deleted file mode 100644 index 26583abd19..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py +++ /dev/null @@ -1,27 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -def xulrunner_app_runner_doctests(): - """ - >>> import sys - >>> from cuddlefish import runner - >>> runner.XulrunnerAppRunner(binary='foo') - Traceback (most recent call last): - ... - Exception: Binary path does not exist foo - - >>> runner.XulrunnerAppRunner(binary=sys.executable) - Traceback (most recent call last): - ... - ValueError: application.ini not found in cmdargs - - >>> runner.XulrunnerAppRunner(binary=sys.executable, - ... cmdargs=['application.ini']) - Traceback (most recent call last): - ... - ValueError: file does not exist: 'application.ini' - """ - - pass diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_util.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_util.py deleted file mode 100644 index aa636a48c0..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_util.py +++ /dev/null @@ -1,22 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -import unittest -from cuddlefish.manifest import filter_filenames, filter_dirnames - -class Filter(unittest.TestCase): - def test_filter_filenames(self): - names = ["foo", "bar.js", "image.png", - ".hidden", "foo~", ".foo.swp", "bar.js.swp"] - self.failUnlessEqual(sorted(filter_filenames(names)), - sorted(["foo", "bar.js", "image.png"])) - - def test_filter_dirnames(self): - names = ["subdir", "data", ".git", ".hg", ".svn", "defaults"] - self.failUnlessEqual(sorted(filter_dirnames(names)), - sorted(["subdir", "data", "defaults"])) - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_version.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_version.py deleted file mode 100644 index 814c57c897..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_version.py +++ /dev/null @@ -1,28 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import unittest -import shutil - -from cuddlefish._version import get_versions - -class Version(unittest.TestCase): - def get_basedir(self): - return os.path.join(".test_tmp", self.id()) - def make_basedir(self): - basedir = self.get_basedir() - if os.path.isdir(basedir): - here = os.path.abspath(os.getcwd()) - assert os.path.abspath(basedir).startswith(here) # safety - shutil.rmtree(basedir) - os.makedirs(basedir) - return basedir - - def test_current_version(self): - # the SDK should be able to determine its own version. We don't care - # what it is, merely that it can be computed. - version = get_versions()["version"] - self.failUnless(isinstance(version, str), (version, type(version))) - self.failUnless(len(version) > 0, version) diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py deleted file mode 100644 index bbefee3b2f..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py +++ /dev/null @@ -1,310 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import unittest -import zipfile -import pprint -import shutil - -import simplejson as json -from cuddlefish import xpi, packaging, manifest, buildJID -from cuddlefish.tests import test_packaging -from test_linker import up - -import xml.etree.ElementTree as ElementTree - -xpi_template_path = os.path.join(test_packaging.static_files_path, - 'xpi-template') - -fake_manifest = '' - - -class Bug588119Tests(unittest.TestCase): - def makexpi(self, pkg_name): - self.xpiname = "%s.xpi" % pkg_name - create_xpi(self.xpiname, pkg_name, 'bug-588119-files') - self.xpi = zipfile.ZipFile(self.xpiname, 'r') - options = self.xpi.read('harness-options.json') - self.xpi_harness_options = json.loads(options) - - def setUp(self): - self.xpiname = None - self.xpi = None - - def tearDown(self): - if self.xpi: - self.xpi.close() - if self.xpiname and os.path.exists(self.xpiname): - os.remove(self.xpiname) - - def testPackageWithImplicitIcon(self): - self.makexpi('implicit-icon') - assert 'icon.png' in self.xpi.namelist() - - def testPackageWithImplicitIcon64(self): - self.makexpi('implicit-icon') - assert 'icon64.png' in self.xpi.namelist() - - def testPackageWithExplicitIcon(self): - self.makexpi('explicit-icon') - assert 'icon.png' in self.xpi.namelist() - - def testPackageWithExplicitIcon64(self): - self.makexpi('explicit-icon') - assert 'icon64.png' in self.xpi.namelist() - - def testPackageWithNoIcon(self): - self.makexpi('no-icon') - assert 'icon.png' not in self.xpi.namelist() - - def testIconPathNotInHarnessOptions(self): - self.makexpi('implicit-icon') - assert 'icon' not in self.xpi_harness_options - - def testIcon64PathNotInHarnessOptions(self): - self.makexpi('implicit-icon') - assert 'icon64' not in self.xpi_harness_options - -class ExtraHarnessOptions(unittest.TestCase): - def setUp(self): - self.xpiname = None - self.xpi = None - - def tearDown(self): - if self.xpi: - self.xpi.close() - if self.xpiname and os.path.exists(self.xpiname): - os.remove(self.xpiname) - - def testOptions(self): - pkg_name = "extra-options" - self.xpiname = "%s.xpi" % pkg_name - create_xpi(self.xpiname, pkg_name, "bug-669274-files", - extra_harness_options={"builderVersion": "futuristic"}) - self.xpi = zipfile.ZipFile(self.xpiname, 'r') - options = self.xpi.read('harness-options.json') - hopts = json.loads(options) - self.failUnless("builderVersion" in hopts) - self.failUnlessEqual(hopts["builderVersion"], "futuristic") - - def testBadOptionName(self): - pkg_name = "extra-options" - self.xpiname = "%s.xpi" % pkg_name - self.failUnlessRaises(xpi.HarnessOptionAlreadyDefinedError, - create_xpi, - self.xpiname, pkg_name, "bug-669274-files", - extra_harness_options={"main": "already in use"}) - -class SmallXPI(unittest.TestCase): - def setUp(self): - self.root = up(os.path.abspath(__file__), 4) - def get_linker_files_dir(self, name): - return os.path.join(up(os.path.abspath(__file__)), "linker-files", name) - def get_pkg(self, name): - d = self.get_linker_files_dir(name) - return packaging.get_config_in_dir(d) - - def get_basedir(self): - return os.path.join(".test_tmp", self.id()) - def make_basedir(self): - basedir = self.get_basedir() - if os.path.isdir(basedir): - here = os.path.abspath(os.getcwd()) - assert os.path.abspath(basedir).startswith(here) # safety - shutil.rmtree(basedir) - os.makedirs(basedir) - return basedir - - def test_contents(self): - target_cfg = self.get_pkg("three") - package_path = [self.get_linker_files_dir("three-deps")] - pkg_cfg = packaging.build_config(self.root, target_cfg, - packagepath=package_path) - deps = packaging.get_deps_for_targets(pkg_cfg, - [target_cfg.name, "addon-sdk"]) - addon_sdk_dir = pkg_cfg.packages["addon-sdk"].lib[0] - m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) - used_files = list(m.get_used_files(True)) - here = up(os.path.abspath(__file__)) - def absify(*parts): - fn = os.path.join(here, "linker-files", *parts) - return os.path.abspath(fn) - expected = [absify(*parts) for parts in - [("three", "lib", "main.js"), - ("three-deps", "three-a", "lib", "main.js"), - ("three-deps", "three-a", "lib", "subdir", "subfile.js"), - ("three", "data", "msg.txt"), - ("three", "data", "subdir", "submsg.txt"), - ("three-deps", "three-b", "lib", "main.js"), - ("three-deps", "three-c", "lib", "main.js"), - ("three-deps", "three-c", "lib", "sub", "foo.js") - ]] - - add_addon_sdk= lambda path: os.path.join(addon_sdk_dir, path) - expected.extend([add_addon_sdk(module) for module in [ - os.path.join("sdk", "self.js"), - os.path.join("sdk", "core", "promise.js"), - os.path.join("sdk", "net", "url.js"), - os.path.join("sdk", "util", "object.js"), - os.path.join("sdk", "util", "array.js"), - os.path.join("sdk", "preferences", "service.js") - ]]) - - missing = set(expected) - set(used_files) - extra = set(used_files) - set(expected) - self.failUnlessEqual(list(missing), []) - self.failUnlessEqual(list(extra), []) - used_deps = m.get_used_packages() - - build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, - used_deps, - include_tests=False) - options = {'main': target_cfg.main} - options.update(build) - basedir = self.make_basedir() - xpi_name = os.path.join(basedir, "contents.xpi") - xpi.build_xpi(template_root_dir=xpi_template_path, - manifest=fake_manifest, - xpi_path=xpi_name, - harness_options=options, - limit_to=used_files) - x = zipfile.ZipFile(xpi_name, "r") - names = x.namelist() - expected = ["components/", - "components/harness.js", - # the real template also has 'bootstrap.js', but the fake - # one in tests/static-files/xpi-template doesn't - "harness-options.json", - "install.rdf", - "resources/", - "resources/addon-sdk/", - "resources/addon-sdk/lib/", - "resources/addon-sdk/lib/sdk/", - "resources/addon-sdk/lib/sdk/self.js", - "resources/addon-sdk/lib/sdk/core/", - "resources/addon-sdk/lib/sdk/util/", - "resources/addon-sdk/lib/sdk/net/", - "resources/addon-sdk/lib/sdk/core/promise.js", - "resources/addon-sdk/lib/sdk/util/object.js", - "resources/addon-sdk/lib/sdk/util/array.js", - "resources/addon-sdk/lib/sdk/net/url.js", - "resources/addon-sdk/lib/sdk/preferences/", - "resources/addon-sdk/lib/sdk/preferences/service.js", - "resources/three/", - "resources/three/lib/", - "resources/three/lib/main.js", - "resources/three/data/", - "resources/three/data/msg.txt", - "resources/three/data/subdir/", - "resources/three/data/subdir/submsg.txt", - "resources/three-a/", - "resources/three-a/lib/", - "resources/three-a/lib/main.js", - "resources/three-a/lib/subdir/", - "resources/three-a/lib/subdir/subfile.js", - "resources/three-b/", - "resources/three-b/lib/", - "resources/three-b/lib/main.js", - "resources/three-c/", - "resources/three-c/lib/", - "resources/three-c/lib/main.js", - "resources/three-c/lib/sub/", - "resources/three-c/lib/sub/foo.js", - # notably absent: three-a/lib/unused.js - "locale/", - "locale/fr-FR.json", - "locales.json", - ] - # showing deltas makes failures easier to investigate - missing = set(expected) - set(names) - extra = set(names) - set(expected) - self.failUnlessEqual((list(missing), list(extra)), ([], [])) - self.failUnlessEqual(sorted(names), sorted(expected)) - - # check locale files - localedata = json.loads(x.read("locales.json")) - self.failUnlessEqual(sorted(localedata["locales"]), sorted(["fr-FR"])) - content = x.read("locale/fr-FR.json") - locales = json.loads(content) - # Locale files are merged into one. - # Conflicts are silently resolved by taking last package translation, - # so that we get "No" translation from three-c instead of three-b one. - self.failUnlessEqual(locales, json.loads(u''' - { - "No": "Nein", - "one": "un", - "What?": "Quoi?", - "Yes": "Oui", - "plural": { - "other": "other", - "one": "one" - }, - "uft8_value": "\u00e9" - }''')) - - -def document_dir(name): - if name in ['packages', 'xpi-template']: - dirname = os.path.join(test_packaging.static_files_path, name) - document_dir_files(dirname) - elif name == 'xpi-output': - create_xpi('test-xpi.xpi') - document_zip_file('test-xpi.xpi') - os.remove('test-xpi.xpi') - else: - raise Exception('unknown dir: %s' % name) - -def normpath(path): - """ - Make a platform-specific relative path use '/' as a separator. - """ - - return path.replace(os.path.sep, '/') - -def document_zip_file(path): - zip = zipfile.ZipFile(path, 'r') - for name in sorted(zip.namelist()): - contents = zip.read(name) - lines = contents.splitlines() - if len(lines) == 1 and name.endswith('.json') and len(lines[0]) > 75: - # Ideally we would json-decode this, but it results - # in an annoying 'u' before every string literal, - # since json decoding makes all strings unicode. - contents = eval(contents) - contents = pprint.pformat(contents) - lines = contents.splitlines() - contents = "\n ".join(lines) - print "%s:\n %s" % (normpath(name), contents) - zip.close() - -def document_dir_files(path): - filename_contents_tuples = [] - for dirpath, dirnames, filenames in os.walk(path): - relpath = dirpath[len(path)+1:] - for filename in filenames: - abspath = os.path.join(dirpath, filename) - contents = open(abspath, 'r').read() - contents = "\n ".join(contents.splitlines()) - relfilename = os.path.join(relpath, filename) - filename_contents_tuples.append((normpath(relfilename), contents)) - filename_contents_tuples.sort() - for filename, contents in filename_contents_tuples: - print "%s:" % filename - print " %s" % contents - -def create_xpi(xpiname, pkg_name='aardvark', dirname='static-files', - extra_harness_options={}): - configs = test_packaging.get_configs(pkg_name, dirname) - options = {'main': configs.target_cfg.main, - 'jetpackID': buildJID(configs.target_cfg), } - options.update(configs.build) - xpi.build_xpi(template_root_dir=xpi_template_path, - manifest=fake_manifest, - xpi_path=xpiname, - harness_options=options, - extra_harness_options=extra_harness_options) - -if __name__ == '__main__': - unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/util.py b/addon-sdk/source/python-lib/cuddlefish/util.py deleted file mode 100644 index 513495a698..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/util.py +++ /dev/null @@ -1,23 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -IGNORED_FILE_PREFIXES = ["."] -IGNORED_FILE_SUFFIXES = ["~", ".swp"] -IGNORED_DIRS = [".git", ".svn", ".hg"] - -def filter_filenames(filenames, ignored_files=[".hgignore"]): - for filename in filenames: - if filename in ignored_files: - continue - if any([filename.startswith(suffix) - for suffix in IGNORED_FILE_PREFIXES]): - continue - if any([filename.endswith(suffix) - for suffix in IGNORED_FILE_SUFFIXES]): - continue - yield filename - -def filter_dirnames(dirnames): - return [dirname for dirname in dirnames if dirname not in IGNORED_DIRS] diff --git a/addon-sdk/source/python-lib/cuddlefish/version_comparator.py b/addon-sdk/source/python-lib/cuddlefish/version_comparator.py deleted file mode 100644 index fee1341fd7..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/version_comparator.py +++ /dev/null @@ -1,206 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -''' - This is a really crummy, slow Python implementation of the Mozilla - platform's nsIVersionComparator interface: - - https://developer.mozilla.org/En/NsIVersionComparator - - For more information, also see: - - http://dxr.mozilla.org/mozilla-central/source/xpcom/glue/nsVersionComparator.cpp -''' - -import re -import sys - -class VersionPart(object): - ''' - Examples: - - >>> VersionPart('1') - (1, None, 0, None) - - >>> VersionPart('1pre') - (1, 'pre', 0, None) - - >>> VersionPart('1pre10') - (1, 'pre', 10, None) - - >>> VersionPart('1pre10a') - (1, 'pre', 10, 'a') - - >>> VersionPart('1+') - (2, 'pre', 0, None) - - >>> VersionPart('*').numA == sys.maxint - True - - >>> VersionPart('1') < VersionPart('2') - True - - >>> VersionPart('2') > VersionPart('1') - True - - >>> VersionPart('1') == VersionPart('1') - True - - >>> VersionPart('1pre') > VersionPart('1') - False - - >>> VersionPart('1') < VersionPart('1pre') - False - - >>> VersionPart('1pre1') < VersionPart('1pre2') - True - - >>> VersionPart('1pre10b') > VersionPart('1pre10a') - True - - >>> VersionPart('1pre10b') == VersionPart('1pre10b') - True - - >>> VersionPart('1pre10a') < VersionPart('1pre10b') - True - - >>> VersionPart('1') > VersionPart('') - True - ''' - - _int_part = re.compile('[+-]?(\d*)(.*)') - _num_chars = '0123456789+-' - - def __init__(self, part): - self.numA = 0 - self.strB = None - self.numC = 0 - self.extraD = None - - if not part: - return - - if part == '*': - self.numA = sys.maxint - else: - match = self._int_part.match(part) - self.numA = int(match.group(1)) - self.strB = match.group(2) or None - if self.strB == '+': - self.strB = 'pre' - self.numA += 1 - elif self.strB: - i = 0 - num_found = -1 - for char in self.strB: - if char in self._num_chars: - num_found = i - break - i += 1 - if num_found != -1: - match = self._int_part.match(self.strB[num_found:]) - self.numC = int(match.group(1)) - self.extraD = match.group(2) or None - self.strB = self.strB[:num_found] - - def _strcmp(self, str1, str2): - # Any string is *before* no string. - if str1 is None: - if str2 is None: - return 0 - else: - return 1 - - if str2 is None: - return -1 - - return cmp(str1, str2) - - def __cmp__(self, other): - r = cmp(self.numA, other.numA) - if r: - return r - - r = self._strcmp(self.strB, other.strB) - if r: - return r - - r = cmp(self.numC, other.numC) - if r: - return r - - return self._strcmp(self.extraD, other.extraD) - - def __repr__(self): - return repr((self.numA, self.strB, self.numC, self.extraD)) - -def compare(a, b): - ''' - Examples: - - >>> compare('1', '2') - -1 - - >>> compare('1', '1') - 0 - - >>> compare('2', '1') - 1 - - >>> compare('1.0pre1', '1.0pre2') - -1 - - >>> compare('1.0pre2', '1.0') - -1 - - >>> compare('1.0', '1.0.0') - 0 - - >>> compare('1.0.0', '1.0.0.0') - 0 - - >>> compare('1.0.0.0', '1.1pre') - -1 - - >>> compare('1.1pre', '1.1pre0') - 0 - - >>> compare('1.1pre0', '1.0+') - 0 - - >>> compare('1.0+', '1.1pre1a') - -1 - - >>> compare('1.1pre1a', '1.1pre1') - -1 - - >>> compare('1.1pre1', '1.1pre10a') - -1 - - >>> compare('1.1pre10a', '1.1pre10') - -1 - - >>> compare('1.1pre10a', '1.*') - -1 - ''' - - a_parts = a.split('.') - b_parts = b.split('.') - - if len(a_parts) < len(b_parts): - a_parts.extend([''] * (len(b_parts) - len(a_parts))) - else: - b_parts.extend([''] * (len(a_parts) - len(b_parts))) - - for a_part, b_part in zip(a_parts, b_parts): - r = cmp(VersionPart(a_part), VersionPart(b_part)) - if r: - return r - - return 0 - -if __name__ == '__main__': - import doctest - - doctest.testmod(verbose=True) diff --git a/addon-sdk/source/python-lib/cuddlefish/xpi.py b/addon-sdk/source/python-lib/cuddlefish/xpi.py deleted file mode 100644 index 4ac497e89a..0000000000 --- a/addon-sdk/source/python-lib/cuddlefish/xpi.py +++ /dev/null @@ -1,169 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import zipfile -import simplejson as json -from cuddlefish.util import filter_filenames, filter_dirnames - -class HarnessOptionAlreadyDefinedError(Exception): - """You cannot use --harness-option on keys that already exist in - harness-options.json""" - -ZIPSEP = "/" # always use "/" in zipfiles - -def make_zipfile_path(localroot, localpath): - return ZIPSEP.join(localpath[len(localroot)+1:].split(os.sep)) - -def mkzipdir(zf, path): - dirinfo = zipfile.ZipInfo(path) - dirinfo.external_attr = int("040755", 8) << 16L - zf.writestr(dirinfo, "") - -def build_xpi(template_root_dir, manifest, xpi_path, - harness_options, limit_to=None, extra_harness_options={}, - bundle_sdk=True, pkgdir=""): - IGNORED_FILES = [".hgignore", ".DS_Store", - "application.ini", xpi_path] - IGNORED_TOP_LVL_FILES = ["install.rdf"] - - files_to_copy = {} # maps zipfile path to local-disk abspath - dirs_to_create = set() # zipfile paths, no trailing slash - - zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED) - - zf.writestr('install.rdf', str(manifest)) - - # Handle add-on icon - if 'icon' in harness_options: - zf.write(os.path.join(str(harness_options['icon'])), 'icon.png') - del harness_options['icon'] - - if 'icon64' in harness_options: - zf.write(os.path.join(str(harness_options['icon64'])), 'icon64.png') - del harness_options['icon64'] - - # chrome.manifest - if os.path.isfile(os.path.join(pkgdir, 'chrome.manifest')): - files_to_copy['chrome.manifest'] = os.path.join(pkgdir, 'chrome.manifest') - - def add_special_dir(folder): - if os.path.exists(os.path.join(pkgdir, folder)): - dirs_to_create.add(folder) - # cp -r folder - abs_dirname = os.path.join(pkgdir, folder) - for dirpath, dirnames, filenames in os.walk(abs_dirname): - goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) - dirnames[:] = filter_dirnames(dirnames) - for dirname in dirnames: - arcpath = make_zipfile_path(template_root_dir, - os.path.join(dirpath, dirname)) - dirs_to_create.add(arcpath) - for filename in goodfiles: - abspath = os.path.join(dirpath, filename) - arcpath = ZIPSEP.join( - [folder, - make_zipfile_path(abs_dirname, os.path.join(dirpath, filename)), - ]) - files_to_copy[str(arcpath)] = str(abspath) - - - # chrome folder (would contain content, skin, and locale folders typically) - add_special_dir('chrome') - # optionally include a `webextension/` dir from the add-on dir. - add_special_dir('webextension') - - for dirpath, dirnames, filenames in os.walk(template_root_dir): - if template_root_dir == dirpath: - filenames = list(filter_filenames(filenames, IGNORED_TOP_LVL_FILES)) - filenames = list(filter_filenames(filenames, IGNORED_FILES)) - dirnames[:] = filter_dirnames(dirnames) - for dirname in dirnames: - arcpath = make_zipfile_path(template_root_dir, - os.path.join(dirpath, dirname)) - dirs_to_create.add(arcpath) - for filename in filenames: - abspath = os.path.join(dirpath, filename) - arcpath = make_zipfile_path(template_root_dir, abspath) - files_to_copy[arcpath] = abspath - - # `packages` attribute contains a dictionnary of dictionnary - # of all packages sections directories - for packageName in harness_options['packages']: - base_arcpath = ZIPSEP.join(['resources', packageName]) - # Eventually strip sdk files. - if not bundle_sdk and packageName == 'addon-sdk': - continue - # Always write the top directory, even if it contains no files, since - # the harness will try to access it. - dirs_to_create.add(base_arcpath) - for sectionName in harness_options['packages'][packageName]: - abs_dirname = harness_options['packages'][packageName][sectionName] - base_arcpath = ZIPSEP.join(['resources', packageName, sectionName]) - # Always write the top directory, even if it contains no files, since - # the harness will try to access it. - dirs_to_create.add(base_arcpath) - # cp -r stuff from abs_dirname/ into ZIP/resources/RESOURCEBASE/ - for dirpath, dirnames, filenames in os.walk(abs_dirname): - goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) - dirnames[:] = filter_dirnames(dirnames) - for filename in goodfiles: - abspath = os.path.join(dirpath, filename) - if limit_to is not None and abspath not in limit_to: - continue # strip unused files - arcpath = ZIPSEP.join( - ['resources', - packageName, - sectionName, - make_zipfile_path(abs_dirname, - os.path.join(dirpath, filename)), - ]) - files_to_copy[str(arcpath)] = str(abspath) - del harness_options['packages'] - - locales_json_data = {"locales": []} - mkzipdir(zf, "locale/") - for language in sorted(harness_options['locale']): - locales_json_data["locales"].append(language) - locale = harness_options['locale'][language] - # Be carefull about strings, we need to always ensure working with UTF-8 - jsonStr = json.dumps(locale, indent=1, sort_keys=True, ensure_ascii=False) - info = zipfile.ZipInfo('locale/' + language + '.json') - info.external_attr = 0644 << 16L - zf.writestr(info, jsonStr.encode( "utf-8" )) - del harness_options['locale'] - - jsonStr = json.dumps(locales_json_data, ensure_ascii=True) +"\n" - info = zipfile.ZipInfo('locales.json') - info.external_attr = 0644 << 16L - zf.writestr(info, jsonStr.encode("utf-8")) - - # now figure out which directories we need: all retained files parents - for arcpath in files_to_copy: - bits = arcpath.split("/") - for i in range(1,len(bits)): - parentpath = ZIPSEP.join(bits[0:i]) - dirs_to_create.add(parentpath) - - # Create zipfile in alphabetical order, with each directory before its - # files - for name in sorted(dirs_to_create.union(set(files_to_copy))): - if name in dirs_to_create: - mkzipdir(zf, name+"/") - if name in files_to_copy: - zf.write(files_to_copy[name], name) - - # Add extra harness options - harness_options = harness_options.copy() - for key,value in extra_harness_options.items(): - if key in harness_options: - msg = "Can't use --harness-option for existing key '%s'" % key - raise HarnessOptionAlreadyDefinedError(msg) - harness_options[key] = value - - # Write harness-options.json - zf.writestr('harness-options.json', json.dumps(harness_options, indent=1, - sort_keys=True)) - - zf.close() diff --git a/addon-sdk/source/python-lib/jetpack_sdk_env.py b/addon-sdk/source/python-lib/jetpack_sdk_env.py deleted file mode 100644 index fb8238bdea..0000000000 --- a/addon-sdk/source/python-lib/jetpack_sdk_env.py +++ /dev/null @@ -1,66 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import sys -import os - -def welcome(): - """ - Perform a bunch of sanity tests to make sure the Add-on SDK - environment is sane, and then display a welcome message. - """ - - try: - if sys.version_info[0] > 2: - print ("Error: You appear to be using Python %d, but " - "the Add-on SDK only supports the Python 2.x line." % - (sys.version_info[0])) - return - - import mozrunner - - if 'CUDDLEFISH_ROOT' not in os.environ: - print ("Error: CUDDLEFISH_ROOT environment variable does " - "not exist! It should point to the root of the " - "Add-on SDK repository.") - return - - env_root = os.environ['CUDDLEFISH_ROOT'] - - bin_dir = os.path.join(env_root, 'bin') - python_lib_dir = os.path.join(env_root, 'python-lib') - path = os.environ['PATH'].split(os.path.pathsep) - - if bin_dir not in path: - print ("Warning: the Add-on SDK binary directory %s " - "does not appear to be in your PATH. You may " - "not be able to run 'cfx' or other SDK tools." % - bin_dir) - - if python_lib_dir not in sys.path: - print ("Warning: the Add-on SDK python-lib directory %s " - "does not appear to be in your sys.path, which " - "is odd because I'm running from it." % python_lib_dir) - - if not mozrunner.__path__[0].startswith(env_root): - print ("Warning: your mozrunner package is installed at %s, " - "which does not seem to be located inside the Jetpack " - "SDK. This may cause problems, and you may want to " - "uninstall the other version. See bug 556562 for " - "more information." % mozrunner.__path__[0]) - except Exception: - # Apparently we can't get the actual exception object in the - # 'except' clause in a way that's syntax-compatible for both - # Python 2.x and 3.x, so we'll have to use the traceback module. - - import traceback - _, e, _ = sys.exc_info() - print ("Verification of Add-on SDK environment failed (%s)." % e) - print ("Your SDK may not work properly.") - return - - print ("Welcome to the Add-on SDK. For the docs, visit https://developer.mozilla.org/en-US/Add-ons/SDK") - -if __name__ == '__main__': - welcome() diff --git a/addon-sdk/source/python-lib/mozrunner/__init__.py b/addon-sdk/source/python-lib/mozrunner/__init__.py deleted file mode 100644 index 87c2c320fc..0000000000 --- a/addon-sdk/source/python-lib/mozrunner/__init__.py +++ /dev/null @@ -1,694 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import sys -import copy -import tempfile -import signal -import commands -import zipfile -import optparse -import killableprocess -import subprocess -import platform -import shutil -from StringIO import StringIO -from xml.dom import minidom - -from distutils import dir_util -from time import sleep - -# conditional (version-dependent) imports -try: - import simplejson -except ImportError: - import json as simplejson - -import logging -logger = logging.getLogger(__name__) - -# Use dir_util for copy/rm operations because shutil is all kinds of broken -copytree = dir_util.copy_tree -rmtree = dir_util.remove_tree - -def findInPath(fileName, path=os.environ['PATH']): - dirs = path.split(os.pathsep) - for dir in dirs: - if os.path.isfile(os.path.join(dir, fileName)): - return os.path.join(dir, fileName) - if os.name == 'nt' or sys.platform == 'cygwin': - if os.path.isfile(os.path.join(dir, fileName + ".exe")): - return os.path.join(dir, fileName + ".exe") - return None - -stdout = sys.stdout -stderr = sys.stderr -stdin = sys.stdin - -def run_command(cmd, env=None, **kwargs): - """Run the given command in killable process.""" - killable_kwargs = {'stdout':stdout ,'stderr':stderr, 'stdin':stdin} - killable_kwargs.update(kwargs) - - if sys.platform != "win32": - return killableprocess.Popen(cmd, preexec_fn=lambda : os.setpgid(0, 0), - env=env, **killable_kwargs) - else: - return killableprocess.Popen(cmd, env=env, **killable_kwargs) - -def getoutput(l): - tmp = tempfile.mktemp() - x = open(tmp, 'w') - subprocess.call(l, stdout=x, stderr=x) - x.close(); x = open(tmp, 'r') - r = x.read() ; x.close() - os.remove(tmp) - return r - -def get_pids(name, minimun_pid=0): - """Get all the pids matching name, exclude any pids below minimum_pid.""" - if os.name == 'nt' or sys.platform == 'cygwin': - import wpk - - pids = wpk.get_pids(name) - - else: - data = getoutput(['ps', 'ax']).splitlines() - pids = [int(line.split()[0]) for line in data if line.find(name) is not -1] - - matching_pids = [m for m in pids if m > minimun_pid] - return matching_pids - -def makedirs(name): - - head, tail = os.path.split(name) - if not tail: - head, tail = os.path.split(head) - if head and tail and not os.path.exists(head): - try: - makedirs(head) - except OSError, e: - pass - if tail == os.curdir: # xxx/newdir/. exists if xxx/newdir exists - return - try: - os.mkdir(name) - except: - pass - -# addon_details() copied from mozprofile -def addon_details(install_rdf_fh): - """ - returns a dictionary of details about the addon - - addon_path : path to the addon directory - Returns: - {'id': u'rainbow@colors.org', # id of the addon - 'version': u'1.4', # version of the addon - 'name': u'Rainbow', # name of the addon - 'unpack': # whether to unpack the addon - """ - - details = { - 'id': None, - 'unpack': False, - 'name': None, - 'version': None - } - - def get_namespace_id(doc, url): - attributes = doc.documentElement.attributes - namespace = "" - for i in range(attributes.length): - if attributes.item(i).value == url: - if ":" in attributes.item(i).name: - # If the namespace is not the default one remove 'xlmns:' - namespace = attributes.item(i).name.split(':')[1] + ":" - break - return namespace - - def get_text(element): - """Retrieve the text value of a given node""" - rc = [] - for node in element.childNodes: - if node.nodeType == node.TEXT_NODE: - rc.append(node.data) - return ''.join(rc).strip() - - doc = minidom.parse(install_rdf_fh) - - # Get the namespaces abbreviations - em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#") - rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#") - - description = doc.getElementsByTagName(rdf + "Description").item(0) - for node in description.childNodes: - # Remove the namespace prefix from the tag for comparison - entry = node.nodeName.replace(em, "") - if entry in details.keys(): - details.update({ entry: get_text(node) }) - - # turn unpack into a true/false value - if isinstance(details['unpack'], basestring): - details['unpack'] = details['unpack'].lower() == 'true' - - return details - -class Profile(object): - """Handles all operations regarding profile. Created new profiles, installs extensions, - sets preferences and handles cleanup.""" - - def __init__(self, binary=None, profile=None, addons=None, - preferences=None): - - self.binary = binary - - self.create_new = not(bool(profile)) - if profile: - self.profile = profile - else: - self.profile = self.create_new_profile(self.binary) - - self.addons_installed = [] - self.addons = addons or [] - - ### set preferences from class preferences - preferences = preferences or {} - if hasattr(self.__class__, 'preferences'): - self.preferences = self.__class__.preferences.copy() - else: - self.preferences = {} - self.preferences.update(preferences) - - for addon in self.addons: - self.install_addon(addon) - - self.set_preferences(self.preferences) - - def create_new_profile(self, binary): - """Create a new clean profile in tmp which is a simple empty folder""" - profile = tempfile.mkdtemp(suffix='.mozrunner') - return profile - - def unpack_addon(self, xpi_zipfile, addon_path): - for name in xpi_zipfile.namelist(): - if name.endswith('/'): - makedirs(os.path.join(addon_path, name)) - else: - if not os.path.isdir(os.path.dirname(os.path.join(addon_path, name))): - makedirs(os.path.dirname(os.path.join(addon_path, name))) - data = xpi_zipfile.read(name) - f = open(os.path.join(addon_path, name), 'wb') - f.write(data) ; f.close() - zi = xpi_zipfile.getinfo(name) - os.chmod(os.path.join(addon_path,name), (zi.external_attr>>16)) - - def install_addon(self, path): - """Installs the given addon or directory of addons in the profile.""" - - extensions_path = os.path.join(self.profile, 'extensions') - if not os.path.exists(extensions_path): - os.makedirs(extensions_path) - - addons = [path] - if not path.endswith('.xpi') and not os.path.exists(os.path.join(path, 'install.rdf')): - addons = [os.path.join(path, x) for x in os.listdir(path)] - - for addon in addons: - if addon.endswith('.xpi'): - xpi_zipfile = zipfile.ZipFile(addon, "r") - details = addon_details(StringIO(xpi_zipfile.read('install.rdf'))) - addon_path = os.path.join(extensions_path, details["id"]) - if details.get("unpack", True): - self.unpack_addon(xpi_zipfile, addon_path) - self.addons_installed.append(addon_path) - else: - shutil.copy(addon, addon_path + '.xpi') - else: - # it's already unpacked, but we need to extract the id so we - # can copy it - details = addon_details(open(os.path.join(addon, "install.rdf"), "rb")) - addon_path = os.path.join(extensions_path, details["id"]) - shutil.copytree(addon, addon_path, symlinks=True) - - def set_preferences(self, preferences): - """Adds preferences dict to profile preferences""" - prefs_file = os.path.join(self.profile, 'user.js') - # Ensure that the file exists first otherwise create an empty file - if os.path.isfile(prefs_file): - f = open(prefs_file, 'a+') - else: - f = open(prefs_file, 'w') - - f.write('\n#MozRunner Prefs Start\n') - - pref_lines = ['user_pref(%s, %s);' % - (simplejson.dumps(k), simplejson.dumps(v) ) for k, v in - preferences.items()] - for line in pref_lines: - f.write(line+'\n') - f.write('#MozRunner Prefs End\n') - f.flush() ; f.close() - - def pop_preferences(self): - """ - pop the last set of preferences added - returns True if popped - """ - - # our magic markers - delimeters = ('#MozRunner Prefs Start', '#MozRunner Prefs End') - - lines = file(os.path.join(self.profile, 'user.js')).read().splitlines() - def last_index(_list, value): - """ - returns the last index of an item; - this should actually be part of python code but it isn't - """ - for index in reversed(range(len(_list))): - if _list[index] == value: - return index - s = last_index(lines, delimeters[0]) - e = last_index(lines, delimeters[1]) - - # ensure both markers are found - if s is None: - assert e is None, '%s found without %s' % (delimeters[1], delimeters[0]) - return False # no preferences found - elif e is None: - assert e is None, '%s found without %s' % (delimeters[0], delimeters[1]) - - # ensure the markers are in the proper order - assert e > s, '%s found at %s, while %s found at %s' (delimeter[1], e, delimeter[0], s) - - # write the prefs - cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) - f = file(os.path.join(self.profile, 'user.js'), 'w') - f.write(cleaned_prefs) - f.close() - return True - - def clean_preferences(self): - """Removed preferences added by mozrunner.""" - while True: - if not self.pop_preferences(): - break - - def clean_addons(self): - """Cleans up addons in the profile.""" - for addon in self.addons_installed: - if os.path.isdir(addon): - rmtree(addon) - - def cleanup(self): - """Cleanup operations on the profile.""" - def oncleanup_error(function, path, excinfo): - #TODO: How should we handle this? - print "Error Cleaning up: " + str(excinfo[1]) - if self.create_new: - shutil.rmtree(self.profile, False, oncleanup_error) - else: - self.clean_preferences() - self.clean_addons() - -class FirefoxProfile(Profile): - """Specialized Profile subclass for Firefox""" - preferences = {# Don't automatically update the application - 'app.update.enabled' : False, - # Don't restore the last open set of tabs if the browser has crashed - 'browser.sessionstore.resume_from_crash': False, - # Don't check for the default web browser - 'browser.shell.checkDefaultBrowser' : False, - # Don't warn on exit when multiple tabs are open - 'browser.tabs.warnOnClose' : False, - # Don't warn when exiting the browser - 'browser.warnOnQuit': False, - # Only install add-ons from the profile and the app folder - 'extensions.enabledScopes' : 5, - # Don't automatically update add-ons - 'extensions.update.enabled' : False, - # Don't open a dialog to show available add-on updates - 'extensions.update.notifyUser' : False, - } - - # The possible names of application bundles on Mac OS X, in order of - # preference from most to least preferred. - # Note: Nightly is obsolete, as it has been renamed to FirefoxNightly, - # but it will still be present if users update an older nightly build - # via the app update service. - bundle_names = ['Firefox', 'FirefoxNightly', 'Nightly'] - - # The possible names of binaries, in order of preference from most to least - # preferred. - @property - def names(self): - if sys.platform == 'darwin': - return ['firefox', 'nightly', 'shiretoko'] - if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): - return ['firefox', 'mozilla-firefox', 'iceweasel'] - if os.name == 'nt' or sys.platform == 'cygwin': - return ['firefox'] - -class ThunderbirdProfile(Profile): - preferences = {'extensions.update.enabled' : False, - 'extensions.update.notifyUser' : False, - 'browser.shell.checkDefaultBrowser' : False, - 'browser.tabs.warnOnClose' : False, - 'browser.warnOnQuit': False, - 'browser.sessionstore.resume_from_crash': False, - } - - # The possible names of application bundles on Mac OS X, in order of - # preference from most to least preferred. - bundle_names = ["Thunderbird", "Shredder"] - - # The possible names of binaries, in order of preference from most to least - # preferred. - names = ["thunderbird", "shredder"] - - -class Runner(object): - """Handles all running operations. Finds bins, runs and kills the process.""" - - def __init__(self, binary=None, profile=None, cmdargs=[], env=None, - kp_kwargs={}): - if binary is None: - self.binary = self.find_binary() - elif sys.platform == 'darwin' and binary.find('Contents/MacOS/') == -1: - self.binary = os.path.join(binary, 'Contents/MacOS/%s-bin' % self.names[0]) - else: - self.binary = binary - - if not os.path.exists(self.binary): - raise Exception("Binary path does not exist "+self.binary) - - if sys.platform == 'linux2' and self.binary.endswith('-bin'): - dirname = os.path.dirname(self.binary) - if os.environ.get('LD_LIBRARY_PATH', None): - os.environ['LD_LIBRARY_PATH'] = '%s:%s' % (os.environ['LD_LIBRARY_PATH'], dirname) - else: - os.environ['LD_LIBRARY_PATH'] = dirname - - # Disable the crash reporter by default - os.environ['MOZ_CRASHREPORTER_NO_REPORT'] = '1' - - self.profile = profile - - self.cmdargs = cmdargs - if env is None: - self.env = copy.copy(os.environ) - self.env.update({'MOZ_NO_REMOTE':"1",}) - else: - self.env = env - self.kp_kwargs = kp_kwargs or {} - - def find_binary(self): - """Finds the binary for self.names if one was not provided.""" - binary = None - if sys.platform in ('linux2', 'sunos5', 'solaris') \ - or sys.platform.startswith('freebsd'): - for name in reversed(self.names): - binary = findInPath(name) - elif os.name == 'nt' or sys.platform == 'cygwin': - - # find the default executable from the windows registry - try: - import _winreg - except ImportError: - pass - else: - sam_flags = [0] - # KEY_WOW64_32KEY etc only appeared in 2.6+, but that's OK as - # only 2.6+ has functioning 64bit builds. - if hasattr(_winreg, "KEY_WOW64_32KEY"): - if "64 bit" in sys.version: - # a 64bit Python should also look in the 32bit registry - sam_flags.append(_winreg.KEY_WOW64_32KEY) - else: - # possibly a 32bit Python on 64bit Windows, so look in - # the 64bit registry incase there is a 64bit app. - sam_flags.append(_winreg.KEY_WOW64_64KEY) - for sam_flag in sam_flags: - try: - # assumes self.app_name is defined, as it should be for - # implementors - keyname = r"Software\Mozilla\Mozilla %s" % self.app_name - sam = _winreg.KEY_READ | sam_flag - app_key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, keyname, 0, sam) - version, _type = _winreg.QueryValueEx(app_key, "CurrentVersion") - version_key = _winreg.OpenKey(app_key, version + r"\Main") - path, _ = _winreg.QueryValueEx(version_key, "PathToExe") - return path - except _winreg.error: - pass - - # search for the binary in the path - for name in reversed(self.names): - binary = findInPath(name) - if sys.platform == 'cygwin': - program_files = os.environ['PROGRAMFILES'] - else: - program_files = os.environ['ProgramFiles'] - - if binary is None: - for bin in [(program_files, 'Mozilla Firefox', 'firefox.exe'), - (os.environ.get("ProgramFiles(x86)"),'Mozilla Firefox', 'firefox.exe'), - (program_files, 'Nightly', 'firefox.exe'), - (os.environ.get("ProgramFiles(x86)"),'Nightly', 'firefox.exe'), - (program_files, 'Aurora', 'firefox.exe'), - (os.environ.get("ProgramFiles(x86)"),'Aurora', 'firefox.exe') - ]: - path = os.path.join(*bin) - if os.path.isfile(path): - binary = path - break - elif sys.platform == 'darwin': - for bundle_name in self.bundle_names: - # Look for the application bundle in the user's home directory - # or the system-wide /Applications directory. If we don't find - # it in one of those locations, we move on to the next possible - # bundle name. - appdir = os.path.join("~/Applications/%s.app" % bundle_name) - if not os.path.isdir(appdir): - appdir = "/Applications/%s.app" % bundle_name - if not os.path.isdir(appdir): - continue - - # Look for a binary with any of the possible binary names - # inside the application bundle. - for binname in self.names: - binpath = os.path.join(appdir, - "Contents/MacOS/%s-bin" % binname) - if (os.path.isfile(binpath)): - binary = binpath - break - - if binary: - break - - if binary is None: - raise Exception('Mozrunner could not locate your binary, you will need to set it.') - return binary - - @property - def command(self): - """Returns the command list to run.""" - cmd = [self.binary, '-profile', self.profile.profile] - # On i386 OS X machines, i386+x86_64 universal binaries need to be told - # to run as i386 binaries. If we're not running a i386+x86_64 universal - # binary, then this command modification is harmless. - if sys.platform == 'darwin': - if hasattr(platform, 'architecture') and platform.architecture()[0] == '32bit': - cmd = ['arch', '-i386'] + cmd - return cmd - - def get_repositoryInfo(self): - """Read repository information from application.ini and platform.ini.""" - import ConfigParser - - config = ConfigParser.RawConfigParser() - dirname = os.path.dirname(self.binary) - repository = { } - - for entry in [['application', 'App'], ['platform', 'Build']]: - (file, section) = entry - config.read(os.path.join(dirname, '%s.ini' % file)) - - for entry in [['SourceRepository', 'repository'], ['SourceStamp', 'changeset']]: - (key, id) = entry - - try: - repository['%s_%s' % (file, id)] = config.get(section, key); - except: - repository['%s_%s' % (file, id)] = None - - return repository - - def start(self): - """Run self.command in the proper environment.""" - if self.profile is None: - self.profile = self.profile_class() - self.process_handler = run_command(self.command+self.cmdargs, self.env, **self.kp_kwargs) - - def wait(self, timeout=None): - """Wait for the browser to exit.""" - self.process_handler.wait(timeout=timeout) - - if sys.platform != 'win32': - for name in self.names: - for pid in get_pids(name, self.process_handler.pid): - self.process_handler.pid = pid - self.process_handler.wait(timeout=timeout) - - def kill(self, kill_signal=signal.SIGTERM): - """Kill the browser""" - if sys.platform != 'win32': - self.process_handler.kill() - for name in self.names: - for pid in get_pids(name, self.process_handler.pid): - self.process_handler.pid = pid - self.process_handler.kill() - else: - try: - self.process_handler.kill(group=True) - # On windows, it sometimes behooves one to wait for dust to settle - # after killing processes. Let's try that. - # TODO: Bug 640047 is invesitgating the correct way to handle this case - self.process_handler.wait(timeout=10) - except Exception, e: - logger.error('Cannot kill process, '+type(e).__name__+' '+e.message) - - def stop(self): - self.kill() - -class FirefoxRunner(Runner): - """Specialized Runner subclass for running Firefox.""" - - app_name = 'Firefox' - profile_class = FirefoxProfile - - # The possible names of application bundles on Mac OS X, in order of - # preference from most to least preferred. - # Note: Nightly is obsolete, as it has been renamed to FirefoxNightly, - # but it will still be present if users update an older nightly build - # only via the app update service. - bundle_names = ['Firefox', 'FirefoxNightly', 'Nightly'] - - @property - def names(self): - if sys.platform == 'darwin': - return ['firefox', 'nightly', 'shiretoko'] - if sys.platform in ('linux2', 'sunos5', 'solaris') \ - or sys.platform.startswith('freebsd'): - return ['firefox', 'mozilla-firefox', 'iceweasel'] - if os.name == 'nt' or sys.platform == 'cygwin': - return ['firefox'] - -class ThunderbirdRunner(Runner): - """Specialized Runner subclass for running Thunderbird""" - - app_name = 'Thunderbird' - profile_class = ThunderbirdProfile - - # The possible names of application bundles on Mac OS X, in order of - # preference from most to least preferred. - bundle_names = ["Thunderbird", "Shredder"] - - # The possible names of binaries, in order of preference from most to least - # preferred. - names = ["thunderbird", "shredder"] - -class CLI(object): - """Command line interface.""" - - runner_class = FirefoxRunner - profile_class = FirefoxProfile - module = "mozrunner" - - parser_options = {("-b", "--binary",): dict(dest="binary", help="Binary path.", - metavar=None, default=None), - ('-p', "--profile",): dict(dest="profile", help="Profile path.", - metavar=None, default=None), - ('-a', "--addons",): dict(dest="addons", - help="Addons paths to install.", - metavar=None, default=None), - ("--info",): dict(dest="info", default=False, - action="store_true", - help="Print module information") - } - - def __init__(self): - """ Setup command line parser and parse arguments """ - self.metadata = self.get_metadata_from_egg() - self.parser = optparse.OptionParser(version="%prog " + self.metadata["Version"]) - for names, opts in self.parser_options.items(): - self.parser.add_option(*names, **opts) - (self.options, self.args) = self.parser.parse_args() - - if self.options.info: - self.print_metadata() - sys.exit(0) - - # XXX should use action='append' instead of rolling our own - try: - self.addons = self.options.addons.split(',') - except: - self.addons = [] - - def get_metadata_from_egg(self): - import pkg_resources - ret = {} - dist = pkg_resources.get_distribution(self.module) - if dist.has_metadata("PKG-INFO"): - for line in dist.get_metadata_lines("PKG-INFO"): - key, value = line.split(':', 1) - ret[key] = value - if dist.has_metadata("requires.txt"): - ret["Dependencies"] = "\n" + dist.get_metadata("requires.txt") - return ret - - def print_metadata(self, data=("Name", "Version", "Summary", "Home-page", - "Author", "Author-email", "License", "Platform", "Dependencies")): - for key in data: - if key in self.metadata: - print key + ": " + self.metadata[key] - - def create_runner(self): - """ Get the runner object """ - runner = self.get_runner(binary=self.options.binary) - profile = self.get_profile(binary=runner.binary, - profile=self.options.profile, - addons=self.addons) - runner.profile = profile - return runner - - def get_runner(self, binary=None, profile=None): - """Returns the runner instance for the given command line binary argument - the profile instance returned from self.get_profile().""" - return self.runner_class(binary, profile) - - def get_profile(self, binary=None, profile=None, addons=None, preferences=None): - """Returns the profile instance for the given command line arguments.""" - addons = addons or [] - preferences = preferences or {} - return self.profile_class(binary, profile, addons, preferences) - - def run(self): - runner = self.create_runner() - self.start(runner) - runner.profile.cleanup() - - def start(self, runner): - """Starts the runner and waits for Firefox to exitor Keyboard Interrupt. - Shoule be overwritten to provide custom running of the runner instance.""" - runner.start() - print 'Started:', ' '.join(runner.command) - try: - runner.wait() - except KeyboardInterrupt: - runner.stop() - - -def cli(): - CLI().run() diff --git a/addon-sdk/source/python-lib/mozrunner/killableprocess.py b/addon-sdk/source/python-lib/mozrunner/killableprocess.py deleted file mode 100644 index 21eac69652..0000000000 --- a/addon-sdk/source/python-lib/mozrunner/killableprocess.py +++ /dev/null @@ -1,329 +0,0 @@ -# killableprocess - subprocesses which can be reliably killed -# -# Parts of this module are copied from the subprocess.py file contained -# in the Python distribution. -# -# Copyright (c) 2003-2004 by Peter Astrand -# -# Additions and modifications written by Benjamin Smedberg -# are Copyright (c) 2006 by the Mozilla Foundation -# -# -# More Modifications -# Copyright (c) 2006-2007 by Mike Taylor -# Copyright (c) 2007-2008 by Mikeal Rogers -# -# By obtaining, using, and/or copying this software and/or its -# associated documentation, you agree that you have read, understood, -# and will comply with the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and -# its associated documentation for any purpose and without fee is -# hereby granted, provided that the above copyright notice appears in -# all copies, and that both that copyright notice and this permission -# notice appear in supporting documentation, and that the name of the -# author not be used in advertising or publicity pertaining to -# distribution of the software without specific, written prior -# permission. -# -# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR -# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, -# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION -# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -"""killableprocess - Subprocesses which can be reliably killed - -This module is a subclass of the builtin "subprocess" module. It allows -processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method. - -It also adds a timeout argument to Wait() for a limited period of time before -forcefully killing the process. - -Note: On Windows, this module requires Windows 2000 or higher (no support for -Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with -Python 2.5+ or available from http://python.net/crew/theller/ctypes/ -""" - -import subprocess -import sys -import os -import time -import datetime -import types -import exceptions - -try: - from subprocess import CalledProcessError -except ImportError: - # Python 2.4 doesn't implement CalledProcessError - class CalledProcessError(Exception): - """This exception is raised when a process run by check_call() returns - a non-zero exit status. The exit status will be stored in the - returncode attribute.""" - def __init__(self, returncode, cmd): - self.returncode = returncode - self.cmd = cmd - def __str__(self): - return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) - -mswindows = (sys.platform == "win32") - -if mswindows: - import winprocess -else: - import signal - -# This is normally defined in win32con, but we don't want -# to incur the huge tree of dependencies (pywin32 and friends) -# just to get one constant. So here's our hack -STILL_ACTIVE = 259 - -def call(*args, **kwargs): - waitargs = {} - if "timeout" in kwargs: - waitargs["timeout"] = kwargs.pop("timeout") - - return Popen(*args, **kwargs).wait(**waitargs) - -def check_call(*args, **kwargs): - """Call a program with an optional timeout. If the program has a non-zero - exit status, raises a CalledProcessError.""" - - retcode = call(*args, **kwargs) - if retcode: - cmd = kwargs.get("args") - if cmd is None: - cmd = args[0] - raise CalledProcessError(retcode, cmd) - -if not mswindows: - def DoNothing(*args): - pass - -class Popen(subprocess.Popen): - kill_called = False - if mswindows: - def _execute_child(self, *args_tuple): - # workaround for bug 958609 - if sys.hexversion < 0x02070600: # prior to 2.7.6 - (args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, startupinfo, - creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = args_tuple - to_close = set() - else: # 2.7.6 and later - (args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, startupinfo, - creationflags, shell, to_close, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = args_tuple - - if not isinstance(args, types.StringTypes): - args = subprocess.list2cmdline(args) - - # Always or in the create new process group - creationflags |= winprocess.CREATE_NEW_PROCESS_GROUP - - if startupinfo is None: - startupinfo = winprocess.STARTUPINFO() - - if None not in (p2cread, c2pwrite, errwrite): - startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES - - startupinfo.hStdInput = int(p2cread) - startupinfo.hStdOutput = int(c2pwrite) - startupinfo.hStdError = int(errwrite) - if shell: - startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = winprocess.SW_HIDE - comspec = os.environ.get("COMSPEC", "cmd.exe") - args = comspec + " /c " + args - - # determine if we can create create a job - canCreateJob = winprocess.CanCreateJobObject() - - # set process creation flags - creationflags |= winprocess.CREATE_SUSPENDED - creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT - if canCreateJob: - # Uncomment this line below to discover very useful things about your environment - #print "++++ killableprocess: releng twistd patch not applied, we can create job objects" - creationflags |= winprocess.CREATE_BREAKAWAY_FROM_JOB - - # create the process - hp, ht, pid, tid = winprocess.CreateProcess( - executable, args, - None, None, # No special security - 1, # Must inherit handles! - creationflags, - winprocess.EnvironmentBlock(env), - cwd, startupinfo) - self._child_created = True - self._handle = hp - self._thread = ht - self.pid = pid - self.tid = tid - - if canCreateJob: - # We create a new job for this process, so that we can kill - # the process and any sub-processes - self._job = winprocess.CreateJobObject() - winprocess.AssignProcessToJobObject(self._job, int(hp)) - else: - self._job = None - - winprocess.ResumeThread(int(ht)) - ht.Close() - - if p2cread is not None: - p2cread.Close() - if c2pwrite is not None: - c2pwrite.Close() - if errwrite is not None: - errwrite.Close() - time.sleep(.1) - - def kill(self, group=True): - """Kill the process. If group=True, all sub-processes will also be killed.""" - self.kill_called = True - - if mswindows: - if group and self._job: - winprocess.TerminateJobObject(self._job, 127) - else: - winprocess.TerminateProcess(self._handle, 127) - self.returncode = 127 - else: - if group: - try: - os.killpg(self.pid, signal.SIGKILL) - except: pass - else: - os.kill(self.pid, signal.SIGKILL) - self.returncode = -9 - - def wait(self, timeout=None, group=True): - """Wait for the process to terminate. Returns returncode attribute. - If timeout seconds are reached and the process has not terminated, - it will be forcefully killed. If timeout is -1, wait will not - time out.""" - if timeout is not None: - # timeout is now in milliseconds - timeout = timeout * 1000 - - starttime = datetime.datetime.utcnow() - - if mswindows: - if timeout is None: - timeout = -1 - rc = winprocess.WaitForSingleObject(self._handle, timeout) - - if (rc == winprocess.WAIT_OBJECT_0 or - rc == winprocess.WAIT_ABANDONED or - rc == winprocess.WAIT_FAILED): - # Object has either signaled, or the API call has failed. In - # both cases we want to give the OS the benefit of the doubt - # and supply a little time before we start shooting processes - # with an M-16. - - # Returns 1 if running, 0 if not, -1 if timed out - def check(): - now = datetime.datetime.utcnow() - diff = now - starttime - if (diff.seconds * 1000000 + diff.microseconds) < (timeout * 1000): # (1000*1000) - if self._job: - if (winprocess.QueryInformationJobObject(self._job, 8)['BasicInfo']['ActiveProcesses'] > 0): - # Job Object is still containing active processes - return 1 - else: - # No job, we use GetExitCodeProcess, which will tell us if the process is still active - self.returncode = winprocess.GetExitCodeProcess(self._handle) - if (self.returncode == STILL_ACTIVE): - # Process still active, continue waiting - return 1 - # Process not active, return 0 - return 0 - else: - # Timed out, return -1 - return -1 - - notdone = check() - while notdone == 1: - time.sleep(.5) - notdone = check() - - if notdone == -1: - # Then check timed out, we have a hung process, attempt - # last ditch kill with explosives - self.kill(group) - - else: - # In this case waitforsingleobject timed out. We have to - # take the process behind the woodshed and shoot it. - self.kill(group) - - else: - if sys.platform in ('linux2', 'sunos5', 'solaris') \ - or sys.platform.startswith('freebsd'): - def group_wait(timeout): - try: - os.waitpid(self.pid, 0) - except OSError, e: - pass # If wait has already been called on this pid, bad things happen - return self.returncode - elif sys.platform == 'darwin': - def group_wait(timeout): - try: - count = 0 - if timeout is None and self.kill_called: - timeout = 10 # Have to set some kind of timeout or else this could go on forever - if timeout is None: - while 1: - os.killpg(self.pid, signal.SIG_DFL) - while ((count * 2) <= timeout): - os.killpg(self.pid, signal.SIG_DFL) - # count is increased by 500ms for every 0.5s of sleep - time.sleep(.5); count += 500 - except exceptions.OSError: - return self.returncode - - if timeout is None: - if group is True: - return group_wait(timeout) - else: - subprocess.Popen.wait(self) - return self.returncode - - returncode = False - - now = datetime.datetime.utcnow() - diff = now - starttime - while (diff.seconds * 1000 * 1000 + diff.microseconds) < (timeout * 1000) and ( returncode is False ): - if group is True: - return group_wait(timeout) - else: - if subprocess.poll() is not None: - returncode = self.returncode - time.sleep(.5) - now = datetime.datetime.utcnow() - diff = now - starttime - return self.returncode - - return self.returncode - # We get random maxint errors from subprocesses __del__ - __del__ = lambda self: None - -def setpgid_preexec_fn(): - os.setpgid(0, 0) - -def runCommand(cmd, **kwargs): - if sys.platform != "win32": - return Popen(cmd, preexec_fn=setpgid_preexec_fn, **kwargs) - else: - return Popen(cmd, **kwargs) diff --git a/addon-sdk/source/python-lib/mozrunner/qijo.py b/addon-sdk/source/python-lib/mozrunner/qijo.py deleted file mode 100644 index 0580557316..0000000000 --- a/addon-sdk/source/python-lib/mozrunner/qijo.py +++ /dev/null @@ -1,166 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE, addressof, c_size_t, c_ulong -from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LARGE_INTEGER - -LPVOID = c_void_p -LPDWORD = POINTER(DWORD) -SIZE_T = c_size_t -ULONG_PTR = POINTER(c_ulong) - -# A ULONGLONG is a 64-bit unsigned integer. -# Thus there are 8 bytes in a ULONGLONG. -# XXX why not import c_ulonglong ? -ULONGLONG = BYTE * 8 - -class IO_COUNTERS(Structure): - # The IO_COUNTERS struct is 6 ULONGLONGs. - # TODO: Replace with non-dummy fields. - _fields_ = [('dummy', ULONGLONG * 6)] - -class JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(Structure): - _fields_ = [('TotalUserTime', LARGE_INTEGER), - ('TotalKernelTime', LARGE_INTEGER), - ('ThisPeriodTotalUserTime', LARGE_INTEGER), - ('ThisPeriodTotalKernelTime', LARGE_INTEGER), - ('TotalPageFaultCount', DWORD), - ('TotalProcesses', DWORD), - ('ActiveProcesses', DWORD), - ('TotalTerminatedProcesses', DWORD)] - -class JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION(Structure): - _fields_ = [('BasicInfo', JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), - ('IoInfo', IO_COUNTERS)] - -# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx -class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure): - _fields_ = [('PerProcessUserTimeLimit', LARGE_INTEGER), - ('PerJobUserTimeLimit', LARGE_INTEGER), - ('LimitFlags', DWORD), - ('MinimumWorkingSetSize', SIZE_T), - ('MaximumWorkingSetSize', SIZE_T), - ('ActiveProcessLimit', DWORD), - ('Affinity', ULONG_PTR), - ('PriorityClass', DWORD), - ('SchedulingClass', DWORD) - ] - -# see http://msdn.microsoft.com/en-us/library/ms684156%28VS.85%29.aspx -class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure): - _fields_ = [('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION), - ('IoInfo', IO_COUNTERS), - ('ProcessMemoryLimit', SIZE_T), - ('JobMemoryLimit', SIZE_T), - ('PeakProcessMemoryUsed', SIZE_T), - ('PeakJobMemoryUsed', SIZE_T)] - -# XXX Magical numbers like 8 should be documented -JobObjectBasicAndIoAccountingInformation = 8 - -# ...like magical number 9 comes from -# http://community.flexerasoftware.com/archive/index.php?t-181670.html -# I wish I had a more canonical source -JobObjectExtendedLimitInformation = 9 - -class JobObjectInfo(object): - mapping = { 'JobObjectBasicAndIoAccountingInformation': 8, - 'JobObjectExtendedLimitInformation': 9 - } - structures = { 8: JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, - 9: JOBOBJECT_EXTENDED_LIMIT_INFORMATION - } - def __init__(self, _class): - if isinstance(_class, basestring): - assert _class in self.mapping, 'Class should be one of %s; you gave %s' % (self.mapping, _class) - _class = self.mapping[_class] - assert _class in self.structures, 'Class should be one of %s; you gave %s' % (self.structures, _class) - self.code = _class - self.info = self.structures[_class]() - - -QueryInformationJobObjectProto = WINFUNCTYPE( - BOOL, # Return type - HANDLE, # hJob - DWORD, # JobObjectInfoClass - LPVOID, # lpJobObjectInfo - DWORD, # cbJobObjectInfoLength - LPDWORD # lpReturnLength - ) - -QueryInformationJobObjectFlags = ( - (1, 'hJob'), - (1, 'JobObjectInfoClass'), - (1, 'lpJobObjectInfo'), - (1, 'cbJobObjectInfoLength'), - (1, 'lpReturnLength', None) - ) - -_QueryInformationJobObject = QueryInformationJobObjectProto( - ('QueryInformationJobObject', windll.kernel32), - QueryInformationJobObjectFlags - ) - -class SubscriptableReadOnlyStruct(object): - def __init__(self, struct): - self._struct = struct - - def _delegate(self, name): - result = getattr(self._struct, name) - if isinstance(result, Structure): - return SubscriptableReadOnlyStruct(result) - return result - - def __getitem__(self, name): - match = [fname for fname, ftype in self._struct._fields_ - if fname == name] - if match: - return self._delegate(name) - raise KeyError(name) - - def __getattr__(self, name): - return self._delegate(name) - -def QueryInformationJobObject(hJob, JobObjectInfoClass): - jobinfo = JobObjectInfo(JobObjectInfoClass) - result = _QueryInformationJobObject( - hJob=hJob, - JobObjectInfoClass=jobinfo.code, - lpJobObjectInfo=addressof(jobinfo.info), - cbJobObjectInfoLength=sizeof(jobinfo.info) - ) - if not result: - raise WinError() - return SubscriptableReadOnlyStruct(jobinfo.info) - -def test_qijo(): - from killableprocess import Popen - - popen = Popen('c:\\windows\\notepad.exe') - - try: - result = QueryInformationJobObject(0, 8) - raise AssertionError('throw should occur') - except WindowsError, e: - pass - - try: - result = QueryInformationJobObject(0, 1) - raise AssertionError('throw should occur') - except NotImplementedError, e: - pass - - result = QueryInformationJobObject(popen._job, 8) - if result['BasicInfo']['ActiveProcesses'] != 1: - raise AssertionError('expected ActiveProcesses to be 1') - popen.kill() - - result = QueryInformationJobObject(popen._job, 8) - if result.BasicInfo.ActiveProcesses != 0: - raise AssertionError('expected ActiveProcesses to be 0') - -if __name__ == '__main__': - print "testing." - test_qijo() - print "success!" diff --git a/addon-sdk/source/python-lib/mozrunner/winprocess.py b/addon-sdk/source/python-lib/mozrunner/winprocess.py deleted file mode 100644 index 16666b0ebb..0000000000 --- a/addon-sdk/source/python-lib/mozrunner/winprocess.py +++ /dev/null @@ -1,379 +0,0 @@ -# A module to expose various thread/process/job related structures and -# methods from kernel32 -# -# The MIT License -# -# Copyright (c) 2003-2004 by Peter Astrand -# -# Additions and modifications written by Benjamin Smedberg -# are Copyright (c) 2006 by the Mozilla Foundation -# -# -# More Modifications -# Copyright (c) 2006-2007 by Mike Taylor -# Copyright (c) 2007-2008 by Mikeal Rogers -# -# By obtaining, using, and/or copying this software and/or its -# associated documentation, you agree that you have read, understood, -# and will comply with the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and -# its associated documentation for any purpose and without fee is -# hereby granted, provided that the above copyright notice appears in -# all copies, and that both that copyright notice and this permission -# notice appear in supporting documentation, and that the name of the -# author not be used in advertising or publicity pertaining to -# distribution of the software without specific, written prior -# permission. -# -# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR -# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, -# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION -# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE -from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD, \ - c_buffer, c_ulong, byref -from qijo import QueryInformationJobObject - -LPVOID = c_void_p -LPBYTE = POINTER(BYTE) -LPDWORD = POINTER(DWORD) -LPBOOL = POINTER(BOOL) - -def ErrCheckBool(result, func, args): - """errcheck function for Windows functions that return a BOOL True - on success""" - if not result: - raise WinError() - return args - - -# AutoHANDLE - -class AutoHANDLE(HANDLE): - """Subclass of HANDLE which will call CloseHandle() on deletion.""" - - CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE) - CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32)) - CloseHandle.errcheck = ErrCheckBool - - def Close(self): - if self.value and self.value != HANDLE(-1).value: - self.CloseHandle(self) - self.value = 0 - - def __del__(self): - self.Close() - - def __int__(self): - return self.value - -def ErrCheckHandle(result, func, args): - """errcheck function for Windows functions that return a HANDLE.""" - if not result: - raise WinError() - return AutoHANDLE(result) - -# PROCESS_INFORMATION structure - -class PROCESS_INFORMATION(Structure): - _fields_ = [("hProcess", HANDLE), - ("hThread", HANDLE), - ("dwProcessID", DWORD), - ("dwThreadID", DWORD)] - - def __init__(self): - Structure.__init__(self) - - self.cb = sizeof(self) - -LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) - -# STARTUPINFO structure - -class STARTUPINFO(Structure): - _fields_ = [("cb", DWORD), - ("lpReserved", LPWSTR), - ("lpDesktop", LPWSTR), - ("lpTitle", LPWSTR), - ("dwX", DWORD), - ("dwY", DWORD), - ("dwXSize", DWORD), - ("dwYSize", DWORD), - ("dwXCountChars", DWORD), - ("dwYCountChars", DWORD), - ("dwFillAttribute", DWORD), - ("dwFlags", DWORD), - ("wShowWindow", WORD), - ("cbReserved2", WORD), - ("lpReserved2", LPBYTE), - ("hStdInput", HANDLE), - ("hStdOutput", HANDLE), - ("hStdError", HANDLE) - ] -LPSTARTUPINFO = POINTER(STARTUPINFO) - -SW_HIDE = 0 - -STARTF_USESHOWWINDOW = 0x01 -STARTF_USESIZE = 0x02 -STARTF_USEPOSITION = 0x04 -STARTF_USECOUNTCHARS = 0x08 -STARTF_USEFILLATTRIBUTE = 0x10 -STARTF_RUNFULLSCREEN = 0x20 -STARTF_FORCEONFEEDBACK = 0x40 -STARTF_FORCEOFFFEEDBACK = 0x80 -STARTF_USESTDHANDLES = 0x100 - -# EnvironmentBlock - -class EnvironmentBlock: - """An object which can be passed as the lpEnv parameter of CreateProcess. - It is initialized with a dictionary.""" - - def __init__(self, dict): - if not dict: - self._as_parameter_ = None - else: - values = ["%s=%s" % (key, value) - for (key, value) in dict.iteritems()] - values.append("") - self._as_parameter_ = LPCWSTR("\0".join(values)) - -# CreateProcess() - -CreateProcessProto = WINFUNCTYPE(BOOL, # Return type - LPCWSTR, # lpApplicationName - LPWSTR, # lpCommandLine - LPVOID, # lpProcessAttributes - LPVOID, # lpThreadAttributes - BOOL, # bInheritHandles - DWORD, # dwCreationFlags - LPVOID, # lpEnvironment - LPCWSTR, # lpCurrentDirectory - LPSTARTUPINFO, # lpStartupInfo - LPPROCESS_INFORMATION # lpProcessInformation - ) - -CreateProcessFlags = ((1, "lpApplicationName", None), - (1, "lpCommandLine"), - (1, "lpProcessAttributes", None), - (1, "lpThreadAttributes", None), - (1, "bInheritHandles", True), - (1, "dwCreationFlags", 0), - (1, "lpEnvironment", None), - (1, "lpCurrentDirectory", None), - (1, "lpStartupInfo"), - (2, "lpProcessInformation")) - -def ErrCheckCreateProcess(result, func, args): - ErrCheckBool(result, func, args) - # return a tuple (hProcess, hThread, dwProcessID, dwThreadID) - pi = args[9] - return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID - -CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32), - CreateProcessFlags) -CreateProcess.errcheck = ErrCheckCreateProcess - -# flags for CreateProcess -CREATE_BREAKAWAY_FROM_JOB = 0x01000000 -CREATE_DEFAULT_ERROR_MODE = 0x04000000 -CREATE_NEW_CONSOLE = 0x00000010 -CREATE_NEW_PROCESS_GROUP = 0x00000200 -CREATE_NO_WINDOW = 0x08000000 -CREATE_SUSPENDED = 0x00000004 -CREATE_UNICODE_ENVIRONMENT = 0x00000400 - -# flags for job limit information -# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx -JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 -JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 - -# XXX these flags should be documented -DEBUG_ONLY_THIS_PROCESS = 0x00000002 -DEBUG_PROCESS = 0x00000001 -DETACHED_PROCESS = 0x00000008 - -# CreateJobObject() - -CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type - LPVOID, # lpJobAttributes - LPCWSTR # lpName - ) - -CreateJobObjectFlags = ((1, "lpJobAttributes", None), - (1, "lpName", None)) - -CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32), - CreateJobObjectFlags) -CreateJobObject.errcheck = ErrCheckHandle - -# AssignProcessToJobObject() - -AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type - HANDLE, # hJob - HANDLE # hProcess - ) -AssignProcessToJobObjectFlags = ((1, "hJob"), - (1, "hProcess")) -AssignProcessToJobObject = AssignProcessToJobObjectProto( - ("AssignProcessToJobObject", windll.kernel32), - AssignProcessToJobObjectFlags) -AssignProcessToJobObject.errcheck = ErrCheckBool - -# GetCurrentProcess() -# because os.getPid() is way too easy -GetCurrentProcessProto = WINFUNCTYPE(HANDLE # Return type - ) -GetCurrentProcessFlags = () -GetCurrentProcess = GetCurrentProcessProto( - ("GetCurrentProcess", windll.kernel32), - GetCurrentProcessFlags) -GetCurrentProcess.errcheck = ErrCheckHandle - -# IsProcessInJob() -try: - IsProcessInJobProto = WINFUNCTYPE(BOOL, # Return type - HANDLE, # Process Handle - HANDLE, # Job Handle - LPBOOL # Result - ) - IsProcessInJobFlags = ((1, "ProcessHandle"), - (1, "JobHandle", HANDLE(0)), - (2, "Result")) - IsProcessInJob = IsProcessInJobProto( - ("IsProcessInJob", windll.kernel32), - IsProcessInJobFlags) - IsProcessInJob.errcheck = ErrCheckBool -except AttributeError: - # windows 2k doesn't have this API - def IsProcessInJob(process): - return False - - -# ResumeThread() - -def ErrCheckResumeThread(result, func, args): - if result == -1: - raise WinError() - - return args - -ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type - HANDLE # hThread - ) -ResumeThreadFlags = ((1, "hThread"),) -ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32), - ResumeThreadFlags) -ResumeThread.errcheck = ErrCheckResumeThread - -# TerminateProcess() - -TerminateProcessProto = WINFUNCTYPE(BOOL, # Return type - HANDLE, # hProcess - UINT # uExitCode - ) -TerminateProcessFlags = ((1, "hProcess"), - (1, "uExitCode", 127)) -TerminateProcess = TerminateProcessProto( - ("TerminateProcess", windll.kernel32), - TerminateProcessFlags) -TerminateProcess.errcheck = ErrCheckBool - -# TerminateJobObject() - -TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type - HANDLE, # hJob - UINT # uExitCode - ) -TerminateJobObjectFlags = ((1, "hJob"), - (1, "uExitCode", 127)) -TerminateJobObject = TerminateJobObjectProto( - ("TerminateJobObject", windll.kernel32), - TerminateJobObjectFlags) -TerminateJobObject.errcheck = ErrCheckBool - -# WaitForSingleObject() - -WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type - HANDLE, # hHandle - DWORD, # dwMilliseconds - ) -WaitForSingleObjectFlags = ((1, "hHandle"), - (1, "dwMilliseconds", -1)) -WaitForSingleObject = WaitForSingleObjectProto( - ("WaitForSingleObject", windll.kernel32), - WaitForSingleObjectFlags) - -INFINITE = -1 -WAIT_TIMEOUT = 0x0102 -WAIT_OBJECT_0 = 0x0 -WAIT_ABANDONED = 0x0080 -WAIT_FAILED = 0xFFFFFFFF - -# GetExitCodeProcess() - -GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type - HANDLE, # hProcess - LPDWORD, # lpExitCode - ) -GetExitCodeProcessFlags = ((1, "hProcess"), - (2, "lpExitCode")) -GetExitCodeProcess = GetExitCodeProcessProto( - ("GetExitCodeProcess", windll.kernel32), - GetExitCodeProcessFlags) -GetExitCodeProcess.errcheck = ErrCheckBool - -def CanCreateJobObject(): - # Running firefox in a job (from cfx) hangs on sites using flash plugin - # so job creation is turned off for now. (see Bug 768651). - return False - -### testing functions - -def parent(): - print 'Starting parent' - currentProc = GetCurrentProcess() - if IsProcessInJob(currentProc): - print >> sys.stderr, "You should not be in a job object to test" - sys.exit(1) - assert CanCreateJobObject() - print 'File: %s' % __file__ - command = [sys.executable, __file__, '-child'] - print 'Running command: %s' % command - process = Popen(command) - process.kill() - code = process.returncode - print 'Child code: %s' % code - assert code == 127 - -def child(): - print 'Starting child' - currentProc = GetCurrentProcess() - injob = IsProcessInJob(currentProc) - print "Is in a job?: %s" % injob - can_create = CanCreateJobObject() - print 'Can create job?: %s' % can_create - process = Popen('c:\\windows\\notepad.exe') - assert process._job - jobinfo = QueryInformationJobObject(process._job, 'JobObjectExtendedLimitInformation') - print 'Job info: %s' % jobinfo - limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] - print 'LimitFlags: %s' % limitflags - process.kill() - -if __name__ == '__main__': - import sys - from killableprocess import Popen - nargs = len(sys.argv[1:]) - if nargs: - if nargs != 1 or sys.argv[1] != '-child': - raise AssertionError('Wrong flags; run like `python /path/to/winprocess.py`') - child() - else: - parent() diff --git a/addon-sdk/source/python-lib/mozrunner/wpk.py b/addon-sdk/source/python-lib/mozrunner/wpk.py deleted file mode 100644 index 6c92f5d4e8..0000000000 --- a/addon-sdk/source/python-lib/mozrunner/wpk.py +++ /dev/null @@ -1,80 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -from ctypes import sizeof, windll, addressof, c_wchar, create_unicode_buffer -from ctypes.wintypes import DWORD, HANDLE - -PROCESS_TERMINATE = 0x0001 -PROCESS_QUERY_INFORMATION = 0x0400 -PROCESS_VM_READ = 0x0010 - -def get_pids(process_name): - BIG_ARRAY = DWORD * 4096 - processes = BIG_ARRAY() - needed = DWORD() - - pids = [] - result = windll.psapi.EnumProcesses(processes, - sizeof(processes), - addressof(needed)) - if not result: - return pids - - num_results = needed.value / sizeof(DWORD) - - for i in range(num_results): - pid = processes[i] - process = windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | - PROCESS_VM_READ, - 0, pid) - if process: - module = HANDLE() - result = windll.psapi.EnumProcessModules(process, - addressof(module), - sizeof(module), - addressof(needed)) - if result: - name = create_unicode_buffer(1024) - result = windll.psapi.GetModuleBaseNameW(process, module, - name, len(name)) - # TODO: This might not be the best way to - # match a process name; maybe use a regexp instead. - if name.value.startswith(process_name): - pids.append(pid) - windll.kernel32.CloseHandle(module) - windll.kernel32.CloseHandle(process) - - return pids - -def kill_pid(pid): - process = windll.kernel32.OpenProcess(PROCESS_TERMINATE, 0, pid) - if process: - windll.kernel32.TerminateProcess(process, 0) - windll.kernel32.CloseHandle(process) - -if __name__ == '__main__': - import subprocess - import time - - # This test just opens a new notepad instance and kills it. - - name = 'notepad' - - old_pids = set(get_pids(name)) - subprocess.Popen([name]) - time.sleep(0.25) - new_pids = set(get_pids(name)).difference(old_pids) - - if len(new_pids) != 1: - raise Exception('%s was not opened or get_pids() is ' - 'malfunctioning' % name) - - kill_pid(tuple(new_pids)[0]) - - newest_pids = set(get_pids(name)).difference(old_pids) - - if len(newest_pids) != 0: - raise Exception('kill_pid() is malfunctioning') - - print "Test passed." diff --git a/addon-sdk/source/python-lib/plural-rules-generator.py b/addon-sdk/source/python-lib/plural-rules-generator.py deleted file mode 100644 index 02cdee1351..0000000000 --- a/addon-sdk/source/python-lib/plural-rules-generator.py +++ /dev/null @@ -1,185 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Program used to generate /packages/api-utils/lib/l10n/plural-rules.js -# Fetch unicode.org data in order to build functions specific to each language -# that will return for a given integer, its plural form name. -# Plural form names are: zero, one, two, few, many, other. -# -# More information here: -# http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html -# http://cldr.unicode.org/index/cldr-spec/plural-rules - -# Usage: -# $ python plural-rules-generator.py > ../packages/api-utils/lib/l10n/plural-rules.js - -import urllib2 -import xml.dom.minidom -import json -import re - -PRINT_CONDITIONS_IN_COMMENTS = False - -UNICODE_ORG_XML_URL = "http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml" - -CONDITION_RE = r'n( mod \d+)? (is|in|within|(not in))( not)? ([^\s]+)' - - -def parseCondition(g): - """ - For a given regexp.MatchObject `g` for `CONDITION_RE`, - returns the equivalent JS piece of code - i.e. maps pseudo conditional language from unicode.org XML to JS code - """ - lvalue = "n" - if g.group(1): - lvalue = "(n %% %d)" % int(g.group(1).replace("mod ", "")) - - operator = g.group(2) - if g.group(4): - operator += " not" - - rvalue = g.group(5) - - if operator == "is": - return "%s == %s" % (lvalue, rvalue) - if operator == "is not": - return "%s != %s" % (lvalue, rvalue) - - # "in", "within" or "not in" case: - notPrefix = "" - if operator == "not in": - notPrefix = "!" - - # `rvalue` is a comma seperated list of either: - # - numbers: 42 - # - ranges: 42..72 - sections = rvalue.split(',') - - if ".." not in rvalue: - # If we don't have range, but only a list of integer, - # we can simplify the generated code by using `isIn` - # n in 1,3,6,42 - return "%sisIn(%s, [%s])" % (notPrefix, lvalue, ", ".join(sections)) - - # n in 1..42 - # n in 1..3,42 - subCondition = [] - integers = [] - for sub in sections: - if ".." in sub: - left, right = sub.split("..") - subCondition.append("isBetween(%s, %d, %d)" % ( - lvalue, - int(left), - int(right) - )) - else: - integers.append(int(sub)) - if len(integers) > 1: - subCondition.append("isIn(%s, [%s])" % (lvalue, ", ".join(integers))) - elif len(integers) == 1: - subCondition.append("(%s == %s)" % (lvalue, integers[0])) - return "%s(%s)" % (notPrefix, " || ".join(subCondition)) - -def computeRules(): - """ - Fetch plural rules data directly from unicode.org website: - """ - url = UNICODE_ORG_XML_URL - f = urllib2.urlopen(url) - doc = xml.dom.minidom.parse(f) - - # Read XML document and extract locale to rules mapping - localesMapping = {} - algorithms = {} - for index,pluralRules in enumerate(doc.getElementsByTagName("pluralRules")): - if not index in algorithms: - algorithms[index] = {} - for locale in pluralRules.getAttribute("locales").split(): - localesMapping[locale] = index - for rule in pluralRules.childNodes: - if rule.nodeType != rule.ELEMENT_NODE or rule.tagName != "pluralRule": - continue - pluralForm = rule.getAttribute("count") - algorithm = rule.firstChild.nodeValue - algorithms[index][pluralForm] = algorithm - - # Go through all rules and compute a Javascript code for each of them - rules = {} - for index,rule in algorithms.iteritems(): - lines = [] - for pluralForm in rule: - condition = rule[pluralForm] - originalCondition = str(condition) - - # Convert pseudo language to JS code - condition = rule[pluralForm].lower() - condition = re.sub(CONDITION_RE, parseCondition, condition) - condition = re.sub(r'or', "||", condition) - condition = re.sub(r'and', "&&", condition) - - # Prints original condition in unicode.org pseudo language - if PRINT_CONDITIONS_IN_COMMENTS: - lines.append( '// %s' % originalCondition ) - - lines.append( 'if (%s)' % condition ) - lines.append( ' return "%s";' % pluralForm ) - - rules[index] = "\n ".join(lines) - return localesMapping, rules - - -localesMapping, rules = computeRules() - -rulesLines = [] -for index in rules: - lines = rules[index] - rulesLines.append('"%d": function (n) {' % index) - rulesLines.append(' %s' % lines) - rulesLines.append(' return "other"') - rulesLines.append('},') - -print """/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// This file is automatically generated with /python-lib/plural-rules-generator.py -// Fetching data from: %s - -// Mapping of short locale name == to == > rule index in following list -const LOCALES_TO_RULES = %s; - -// Utility functions for plural rules methods -function isIn(n, list) { - return list.indexOf(n) !== -1; -} -function isBetween(n, start, end) { - return start <= n && n <= end; -} - -// List of all plural rules methods, that maps an integer to the plural form name to use -const RULES = { - %s -}; - -/** - * Return a function that gives the plural form name for a given integer - * for the specified `locale` - * let fun = getRulesForLocale('en'); - * fun(1) -> 'one' - * fun(0) -> 'other' - * fun(1000) -> 'other' - */ -exports.getRulesForLocale = function getRulesForLocale(locale) { - let index = LOCALES_TO_RULES[locale]; - if (!(index in RULES)) { - console.warn('Plural form unknown for locale "' + locale + '"'); - return function () { return "other"; }; - } - return RULES[index]; -} -""" % (UNICODE_ORG_XML_URL, - json.dumps(localesMapping, sort_keys=True, indent=2), - "\n ".join(rulesLines)) diff --git a/addon-sdk/source/python-lib/simplejson/LICENSE.txt b/addon-sdk/source/python-lib/simplejson/LICENSE.txt deleted file mode 100644 index ad95f29c17..0000000000 --- a/addon-sdk/source/python-lib/simplejson/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2006 Bob Ippolito - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/addon-sdk/source/python-lib/simplejson/__init__.py b/addon-sdk/source/python-lib/simplejson/__init__.py deleted file mode 100644 index adcce7efb5..0000000000 --- a/addon-sdk/source/python-lib/simplejson/__init__.py +++ /dev/null @@ -1,376 +0,0 @@ -r""" -A simple, fast, extensible JSON encoder and decoder - -JSON (JavaScript Object Notation) is a subset of -JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data -interchange format. - -simplejson exposes an API familiar to uses of the standard library -marshal and pickle modules. - -Encoding basic Python object hierarchies:: - - >>> import simplejson - >>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) - '["foo", {"bar": ["baz", null, 1.0, 2]}]' - >>> print simplejson.dumps("\"foo\bar") - "\"foo\bar" - >>> print simplejson.dumps(u'\u1234') - "\u1234" - >>> print simplejson.dumps('\\') - "\\" - >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) - {"a": 0, "b": 0, "c": 0} - >>> from StringIO import StringIO - >>> io = StringIO() - >>> simplejson.dump(['streaming API'], io) - >>> io.getvalue() - '["streaming API"]' - -Compact encoding:: - - >>> import simplejson - >>> simplejson.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) - '[1,2,3,{"4":5,"6":7}]' - -Pretty printing:: - - >>> import simplejson - >>> print simplejson.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) - { - "4": 5, - "6": 7 - } - -Decoding JSON:: - - >>> import simplejson - >>> simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') - [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] - >>> simplejson.loads('"\\"foo\\bar"') - u'"foo\x08ar' - >>> from StringIO import StringIO - >>> io = StringIO('["streaming API"]') - >>> simplejson.load(io) - [u'streaming API'] - -Specializing JSON object decoding:: - - >>> import simplejson - >>> def as_complex(dct): - ... if '__complex__' in dct: - ... return complex(dct['real'], dct['imag']) - ... return dct - ... - >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}', - ... object_hook=as_complex) - (1+2j) - >>> import decimal - >>> simplejson.loads('1.1', parse_float=decimal.Decimal) - Decimal("1.1") - -Extending JSONEncoder:: - - >>> import simplejson - >>> class ComplexEncoder(simplejson.JSONEncoder): - ... def default(self, obj): - ... if isinstance(obj, complex): - ... return [obj.real, obj.imag] - ... return simplejson.JSONEncoder.default(self, obj) - ... - >>> dumps(2 + 1j, cls=ComplexEncoder) - '[2.0, 1.0]' - >>> ComplexEncoder().encode(2 + 1j) - '[2.0, 1.0]' - >>> list(ComplexEncoder().iterencode(2 + 1j)) - ['[', '2.0', ', ', '1.0', ']'] - - -Using simplejson from the shell to validate and -pretty-print:: - - $ echo '{"json":"obj"}' | python -msimplejson.tool - { - "json": "obj" - } - $ echo '{ 1.2:3.4}' | python -msimplejson.tool - Expecting property name: line 1 column 2 (char 2) - -Note that the JSON produced by this module's default settings -is a subset of YAML, so it may be used as a serializer for that as well. -""" -__version__ = '1.9.2' -__all__ = [ - 'dump', 'dumps', 'load', 'loads', - 'JSONDecoder', 'JSONEncoder', -] - -if __name__ == '__main__': - import warnings - warnings.warn('python -msimplejson is deprecated, use python -msiplejson.tool', DeprecationWarning) - from simplejson.decoder import JSONDecoder - from simplejson.encoder import JSONEncoder -else: - from decoder import JSONDecoder - from encoder import JSONEncoder - -_default_encoder = JSONEncoder( - skipkeys=False, - ensure_ascii=True, - check_circular=True, - allow_nan=True, - indent=None, - separators=None, - encoding='utf-8', - default=None, -) - -def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, **kw): - """ - Serialize ``obj`` as a JSON formatted stream to ``fp`` (a - ``.write()``-supporting file-like object). - - If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) - will be skipped instead of raising a ``TypeError``. - - If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp`` - may be ``unicode`` instances, subject to normal Python ``str`` to - ``unicode`` coercion rules. Unless ``fp.write()`` explicitly - understands ``unicode`` (as in ``codecs.getwriter()``) this is likely - to cause an error. - - If ``check_circular`` is ``False``, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). - - If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) - in strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). - - If ``indent`` is a non-negative integer, then JSON array elements and object - members will be pretty-printed with that indent level. An indent level - of 0 will only insert newlines. ``None`` is the most compact representation. - - If ``separators`` is an ``(item_separator, dict_separator)`` tuple - then it will be used instead of the default ``(', ', ': ')`` separators. - ``(',', ':')`` is the most compact JSON representation. - - ``encoding`` is the character encoding for str instances, default is UTF-8. - - ``default(obj)`` is a function that should return a serializable version - of obj or raise TypeError. The default simply raises TypeError. - - To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the - ``.default()`` method to serialize additional types), specify it with - the ``cls`` kwarg. - """ - # cached encoder - if (skipkeys is False and ensure_ascii is True and - check_circular is True and allow_nan is True and - cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not kw): - iterable = _default_encoder.iterencode(obj) - else: - if cls is None: - cls = JSONEncoder - iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, - check_circular=check_circular, allow_nan=allow_nan, indent=indent, - separators=separators, encoding=encoding, - default=default, **kw).iterencode(obj) - # could accelerate with writelines in some versions of Python, at - # a debuggability cost - for chunk in iterable: - fp.write(chunk) - - -def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, **kw): - """ - Serialize ``obj`` to a JSON formatted ``str``. - - If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) - will be skipped instead of raising a ``TypeError``. - - If ``ensure_ascii`` is ``False``, then the return value will be a - ``unicode`` instance subject to normal Python ``str`` to ``unicode`` - coercion rules instead of being escaped to an ASCII ``str``. - - If ``check_circular`` is ``False``, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). - - If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in - strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). - - If ``indent`` is a non-negative integer, then JSON array elements and - object members will be pretty-printed with that indent level. An indent - level of 0 will only insert newlines. ``None`` is the most compact - representation. - - If ``separators`` is an ``(item_separator, dict_separator)`` tuple - then it will be used instead of the default ``(', ', ': ')`` separators. - ``(',', ':')`` is the most compact JSON representation. - - ``encoding`` is the character encoding for str instances, default is UTF-8. - - ``default(obj)`` is a function that should return a serializable version - of obj or raise TypeError. The default simply raises TypeError. - - To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the - ``.default()`` method to serialize additional types), specify it with - the ``cls`` kwarg. - """ - # cached encoder - if (skipkeys is False and ensure_ascii is True and - check_circular is True and allow_nan is True and - cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not kw): - return _default_encoder.encode(obj) - if cls is None: - cls = JSONEncoder - return cls( - skipkeys=skipkeys, ensure_ascii=ensure_ascii, - check_circular=check_circular, allow_nan=allow_nan, indent=indent, - separators=separators, encoding=encoding, default=default, - **kw).encode(obj) - - -_default_decoder = JSONDecoder(encoding=None, object_hook=None) - - -def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, **kw): - """ - Deserialize ``fp`` (a ``.read()``-supporting file-like object containing - a JSON document) to a Python object. - - If the contents of ``fp`` is encoded with an ASCII based encoding other - than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must - be specified. Encodings that are not ASCII based (such as UCS-2) are - not allowed, and should be wrapped with - ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` - object and passed to ``loads()`` - - ``object_hook`` is an optional function that will be called with the - result of any object literal decode (a ``dict``). The return value of - ``object_hook`` will be used instead of the ``dict``. This feature - can be used to implement custom decoders (e.g. JSON-RPC class hinting). - - To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` - kwarg. - """ - return loads(fp.read(), - encoding=encoding, cls=cls, object_hook=object_hook, - parse_float=parse_float, parse_int=parse_int, - parse_constant=parse_constant, **kw) - - -def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, **kw): - """ - Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON - document) to a Python object. - - If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding - other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name - must be specified. Encodings that are not ASCII based (such as UCS-2) - are not allowed and should be decoded to ``unicode`` first. - - ``object_hook`` is an optional function that will be called with the - result of any object literal decode (a ``dict``). The return value of - ``object_hook`` will be used instead of the ``dict``. This feature - can be used to implement custom decoders (e.g. JSON-RPC class hinting). - - ``parse_float``, if specified, will be called with the string - of every JSON float to be decoded. By default this is equivalent to - float(num_str). This can be used to use another datatype or parser - for JSON floats (e.g. decimal.Decimal). - - ``parse_int``, if specified, will be called with the string - of every JSON int to be decoded. By default this is equivalent to - int(num_str). This can be used to use another datatype or parser - for JSON integers (e.g. float). - - ``parse_constant``, if specified, will be called with one of the - following strings: -Infinity, Infinity, NaN, null, true, false. - This can be used to raise an exception if invalid JSON numbers - are encountered. - - To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` - kwarg. - """ - if (cls is None and encoding is None and object_hook is None and - parse_int is None and parse_float is None and - parse_constant is None and not kw): - return _default_decoder.decode(s) - if cls is None: - cls = JSONDecoder - if object_hook is not None: - kw['object_hook'] = object_hook - if parse_float is not None: - kw['parse_float'] = parse_float - if parse_int is not None: - kw['parse_int'] = parse_int - if parse_constant is not None: - kw['parse_constant'] = parse_constant - return cls(encoding=encoding, **kw).decode(s) - - -# -# Compatibility cruft from other libraries -# - - -def decode(s): - """ - demjson, python-cjson API compatibility hook. Use loads(s) instead. - """ - import warnings - warnings.warn("simplejson.loads(s) should be used instead of decode(s)", - DeprecationWarning) - return loads(s) - - -def encode(obj): - """ - demjson, python-cjson compatibility hook. Use dumps(s) instead. - """ - import warnings - warnings.warn("simplejson.dumps(s) should be used instead of encode(s)", - DeprecationWarning) - return dumps(obj) - - -def read(s): - """ - jsonlib, JsonUtils, python-json, json-py API compatibility hook. - Use loads(s) instead. - """ - import warnings - warnings.warn("simplejson.loads(s) should be used instead of read(s)", - DeprecationWarning) - return loads(s) - - -def write(obj): - """ - jsonlib, JsonUtils, python-json, json-py API compatibility hook. - Use dumps(s) instead. - """ - import warnings - warnings.warn("simplejson.dumps(s) should be used instead of write(s)", - DeprecationWarning) - return dumps(obj) - - -if __name__ == '__main__': - import simplejson.tool - simplejson.tool.main() diff --git a/addon-sdk/source/python-lib/simplejson/decoder.py b/addon-sdk/source/python-lib/simplejson/decoder.py deleted file mode 100644 index baf10e9904..0000000000 --- a/addon-sdk/source/python-lib/simplejson/decoder.py +++ /dev/null @@ -1,343 +0,0 @@ -""" -Implementation of JSONDecoder -""" -import re -import sys - -from simplejson.scanner import Scanner, pattern -try: - from simplejson._speedups import scanstring as c_scanstring -except ImportError: - pass - -FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL - -def _floatconstants(): - import struct - import sys - _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') - if sys.byteorder != 'big': - _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] - nan, inf = struct.unpack('dd', _BYTES) - return nan, inf, -inf - -NaN, PosInf, NegInf = _floatconstants() - - -def linecol(doc, pos): - lineno = doc.count('\n', 0, pos) + 1 - if lineno == 1: - colno = pos - else: - colno = pos - doc.rindex('\n', 0, pos) - return lineno, colno - - -def errmsg(msg, doc, pos, end=None): - lineno, colno = linecol(doc, pos) - if end is None: - return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) - endlineno, endcolno = linecol(doc, end) - return '%s: line %d column %d - line %d column %d (char %d - %d)' % ( - msg, lineno, colno, endlineno, endcolno, pos, end) - - -_CONSTANTS = { - '-Infinity': NegInf, - 'Infinity': PosInf, - 'NaN': NaN, - 'true': True, - 'false': False, - 'null': None, -} - -def JSONConstant(match, context, c=_CONSTANTS): - s = match.group(0) - fn = getattr(context, 'parse_constant', None) - if fn is None: - rval = c[s] - else: - rval = fn(s) - return rval, None -pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant) - - -def JSONNumber(match, context): - match = JSONNumber.regex.match(match.string, *match.span()) - integer, frac, exp = match.groups() - if frac or exp: - fn = getattr(context, 'parse_float', None) or float - res = fn(integer + (frac or '') + (exp or '')) - else: - fn = getattr(context, 'parse_int', None) or int - res = fn(integer) - return res, None -pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber) - - -STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) -BACKSLASH = { - '"': u'"', '\\': u'\\', '/': u'/', - 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', -} - -DEFAULT_ENCODING = "utf-8" - -def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): - if encoding is None: - encoding = DEFAULT_ENCODING - chunks = [] - _append = chunks.append - begin = end - 1 - while 1: - chunk = _m(s, end) - if chunk is None: - raise ValueError( - errmsg("Unterminated string starting at", s, begin)) - end = chunk.end() - content, terminator = chunk.groups() - if content: - if not isinstance(content, unicode): - content = unicode(content, encoding) - _append(content) - if terminator == '"': - break - elif terminator != '\\': - if strict: - raise ValueError(errmsg("Invalid control character %r at", s, end)) - else: - _append(terminator) - continue - try: - esc = s[end] - except IndexError: - raise ValueError( - errmsg("Unterminated string starting at", s, begin)) - if esc != 'u': - try: - m = _b[esc] - except KeyError: - raise ValueError( - errmsg("Invalid \\escape: %r" % (esc,), s, end)) - end += 1 - else: - esc = s[end + 1:end + 5] - next_end = end + 5 - msg = "Invalid \\uXXXX escape" - try: - if len(esc) != 4: - raise ValueError - uni = int(esc, 16) - if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: - msg = "Invalid \\uXXXX\\uXXXX surrogate pair" - if not s[end + 5:end + 7] == '\\u': - raise ValueError - esc2 = s[end + 7:end + 11] - if len(esc2) != 4: - raise ValueError - uni2 = int(esc2, 16) - uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) - next_end += 6 - m = unichr(uni) - except ValueError: - raise ValueError(errmsg(msg, s, end)) - end = next_end - _append(m) - return u''.join(chunks), end - - -# Use speedup -try: - scanstring = c_scanstring -except NameError: - scanstring = py_scanstring - -def JSONString(match, context): - encoding = getattr(context, 'encoding', None) - strict = getattr(context, 'strict', True) - return scanstring(match.string, match.end(), encoding, strict) -pattern(r'"')(JSONString) - - -WHITESPACE = re.compile(r'\s*', FLAGS) - -def JSONObject(match, context, _w=WHITESPACE.match): - pairs = {} - s = match.string - end = _w(s, match.end()).end() - nextchar = s[end:end + 1] - # Trivial empty object - if nextchar == '}': - return pairs, end + 1 - if nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end)) - end += 1 - encoding = getattr(context, 'encoding', None) - strict = getattr(context, 'strict', True) - iterscan = JSONScanner.iterscan - while True: - key, end = scanstring(s, end, encoding, strict) - end = _w(s, end).end() - if s[end:end + 1] != ':': - raise ValueError(errmsg("Expecting : delimiter", s, end)) - end = _w(s, end + 1).end() - try: - value, end = iterscan(s, idx=end, context=context).next() - except StopIteration: - raise ValueError(errmsg("Expecting object", s, end)) - pairs[key] = value - end = _w(s, end).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar == '}': - break - if nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) - end = _w(s, end).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end - 1)) - object_hook = getattr(context, 'object_hook', None) - if object_hook is not None: - pairs = object_hook(pairs) - return pairs, end -pattern(r'{')(JSONObject) - - -def JSONArray(match, context, _w=WHITESPACE.match): - values = [] - s = match.string - end = _w(s, match.end()).end() - # Look-ahead for trivial empty array - nextchar = s[end:end + 1] - if nextchar == ']': - return values, end + 1 - iterscan = JSONScanner.iterscan - while True: - try: - value, end = iterscan(s, idx=end, context=context).next() - except StopIteration: - raise ValueError(errmsg("Expecting object", s, end)) - values.append(value) - end = _w(s, end).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar == ']': - break - if nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end)) - end = _w(s, end).end() - return values, end -pattern(r'\[')(JSONArray) - - -ANYTHING = [ - JSONObject, - JSONArray, - JSONString, - JSONConstant, - JSONNumber, -] - -JSONScanner = Scanner(ANYTHING) - - -class JSONDecoder(object): - """ - Simple JSON decoder - - Performs the following translations in decoding by default: - - +---------------+-------------------+ - | JSON | Python | - +===============+===================+ - | object | dict | - +---------------+-------------------+ - | array | list | - +---------------+-------------------+ - | string | unicode | - +---------------+-------------------+ - | number (int) | int, long | - +---------------+-------------------+ - | number (real) | float | - +---------------+-------------------+ - | true | True | - +---------------+-------------------+ - | false | False | - +---------------+-------------------+ - | null | None | - +---------------+-------------------+ - - It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as - their corresponding ``float`` values, which is outside the JSON spec. - """ - - _scanner = Scanner(ANYTHING) - __all__ = ['__init__', 'decode', 'raw_decode'] - - def __init__(self, encoding=None, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, strict=True): - """ - ``encoding`` determines the encoding used to interpret any ``str`` - objects decoded by this instance (utf-8 by default). It has no - effect when decoding ``unicode`` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as ``unicode``. - - ``object_hook``, if specified, will be called with the result - of every JSON object decoded and its return value will be used in - place of the given ``dict``. This can be used to provide custom - deserializations (e.g. to support JSON-RPC class hinting). - - ``parse_float``, if specified, will be called with the string - of every JSON float to be decoded. By default this is equivalent to - float(num_str). This can be used to use another datatype or parser - for JSON floats (e.g. decimal.Decimal). - - ``parse_int``, if specified, will be called with the string - of every JSON int to be decoded. By default this is equivalent to - int(num_str). This can be used to use another datatype or parser - for JSON integers (e.g. float). - - ``parse_constant``, if specified, will be called with one of the - following strings: -Infinity, Infinity, NaN, null, true, false. - This can be used to raise an exception if invalid JSON numbers - are encountered. - """ - self.encoding = encoding - self.object_hook = object_hook - self.parse_float = parse_float - self.parse_int = parse_int - self.parse_constant = parse_constant - self.strict = strict - - def decode(self, s, _w=WHITESPACE.match): - """ - Return the Python representation of ``s`` (a ``str`` or ``unicode`` - instance containing a JSON document) - """ - obj, end = self.raw_decode(s, idx=_w(s, 0).end()) - end = _w(s, end).end() - if end != len(s): - raise ValueError(errmsg("Extra data", s, end, len(s))) - return obj - - def raw_decode(self, s, **kw): - """ - Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning - with a JSON document) and return a 2-tuple of the Python - representation and the index in ``s`` where the document ended. - - This can be used to decode a JSON document from a string that may - have extraneous data at the end. - """ - kw.setdefault('context', self) - try: - obj, end = self._scanner.iterscan(s, **kw).next() - except StopIteration: - raise ValueError("No JSON object could be decoded") - return obj, end - -__all__ = ['JSONDecoder'] diff --git a/addon-sdk/source/python-lib/simplejson/encoder.py b/addon-sdk/source/python-lib/simplejson/encoder.py deleted file mode 100644 index befc5c1f5d..0000000000 --- a/addon-sdk/source/python-lib/simplejson/encoder.py +++ /dev/null @@ -1,395 +0,0 @@ -""" -Implementation of JSONEncoder -""" -import re - -try: - from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii -except ImportError: - pass - -ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') -ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') -HAS_UTF8 = re.compile(r'[\x80-\xff]') -ESCAPE_DCT = { - '\\': '\\\\', - '"': '\\"', - '\b': '\\b', - '\f': '\\f', - '\n': '\\n', - '\r': '\\r', - '\t': '\\t', -} -for i in range(0x20): - ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) - -# Assume this produces an infinity on all machines (probably not guaranteed) -INFINITY = float('1e66666') -FLOAT_REPR = repr - -def floatstr(o, allow_nan=True): - """ - Check for specials. Note that this type of test is processor- and/or - platform-specific, so do tests which don't depend on the internals. - """ - if o != o: - text = 'NaN' - elif o == INFINITY: - text = 'Infinity' - elif o == -INFINITY: - text = '-Infinity' - else: - return FLOAT_REPR(o) - - if not allow_nan: - raise ValueError("Out of range float values are not JSON compliant: %r" - % (o,)) - - return text - - -def encode_basestring(s): - """ - Return a JSON representation of a Python string - """ - def replace(match): - return ESCAPE_DCT[match.group(0)] - return '"' + ESCAPE.sub(replace, s) + '"' - - -def py_encode_basestring_ascii(s): - if isinstance(s, str) and HAS_UTF8.search(s) is not None: - s = s.decode('utf-8') - def replace(match): - s = match.group(0) - try: - return ESCAPE_DCT[s] - except KeyError: - n = ord(s) - if n < 0x10000: - return '\\u%04x' % (n,) - else: - # surrogate pair - n -= 0x10000 - s1 = 0xd800 | ((n >> 10) & 0x3ff) - s2 = 0xdc00 | (n & 0x3ff) - return '\\u%04x\\u%04x' % (s1, s2) - return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' - - -try: - encode_basestring_ascii = c_encode_basestring_ascii -except NameError: - encode_basestring_ascii = py_encode_basestring_ascii - - -class JSONEncoder(object): - """ - Extensible JSON encoder for Python data structures. - - Supports the following objects and types by default: - - +-------------------+---------------+ - | Python | JSON | - +===================+===============+ - | dict | object | - +-------------------+---------------+ - | list, tuple | array | - +-------------------+---------------+ - | str, unicode | string | - +-------------------+---------------+ - | int, long, float | number | - +-------------------+---------------+ - | True | true | - +-------------------+---------------+ - | False | false | - +-------------------+---------------+ - | None | null | - +-------------------+---------------+ - - To extend this to recognize other objects, subclass and implement a - ``.default()`` method with another method that returns a serializable - object for ``o`` if possible, otherwise it should call the superclass - implementation (to raise ``TypeError``). - """ - __all__ = ['__init__', 'default', 'encode', 'iterencode'] - item_separator = ', ' - key_separator = ': ' - def __init__(self, skipkeys=False, ensure_ascii=True, - check_circular=True, allow_nan=True, sort_keys=False, - indent=None, separators=None, encoding='utf-8', default=None): - """ - Constructor for JSONEncoder, with sensible defaults. - - If skipkeys is False, then it is a TypeError to attempt - encoding of keys that are not str, int, long, float or None. If - skipkeys is True, such items are simply skipped. - - If ensure_ascii is True, the output is guaranteed to be str - objects with all incoming unicode characters escaped. If - ensure_ascii is false, the output will be unicode object. - - If check_circular is True, then lists, dicts, and custom encoded - objects will be checked for circular references during encoding to - prevent an infinite recursion (which would cause an OverflowError). - Otherwise, no such check takes place. - - If allow_nan is True, then NaN, Infinity, and -Infinity will be - encoded as such. This behavior is not JSON specification compliant, - but is consistent with most JavaScript based encoders and decoders. - Otherwise, it will be a ValueError to encode such floats. - - If sort_keys is True, then the output of dictionaries will be - sorted by key; this is useful for regression tests to ensure - that JSON serializations can be compared on a day-to-day basis. - - If indent is a non-negative integer, then JSON array - elements and object members will be pretty-printed with that - indent level. An indent level of 0 will only insert newlines. - None is the most compact representation. - - If specified, separators should be a (item_separator, key_separator) - tuple. The default is (', ', ': '). To get the most compact JSON - representation you should specify (',', ':') to eliminate whitespace. - - If specified, default is a function that gets called for objects - that can't otherwise be serialized. It should return a JSON encodable - version of the object or raise a ``TypeError``. - - If encoding is not None, then all input strings will be - transformed into unicode using that encoding prior to JSON-encoding. - The default is UTF-8. - """ - - self.skipkeys = skipkeys - self.ensure_ascii = ensure_ascii - self.check_circular = check_circular - self.allow_nan = allow_nan - self.sort_keys = sort_keys - self.indent = indent - self.current_indent_level = 0 - if separators is not None: - self.item_separator, self.key_separator = separators - if default is not None: - self.default = default - self.encoding = encoding - - def _newline_indent(self): - """ - Indent lines by level - """ - return '\n' + (' ' * (self.indent * self.current_indent_level)) - - def _iterencode_list(self, lst, markers=None): - """ - Encoding lists, yielding by level - """ - if not lst: - yield '[]' - return - if markers is not None: - markerid = id(lst) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = lst - yield '[' - if self.indent is not None: - self.current_indent_level += 1 - newline_indent = self._newline_indent() - separator = self.item_separator + newline_indent - yield newline_indent - else: - newline_indent = None - separator = self.item_separator - first = True - for value in lst: - if first: - first = False - else: - yield separator - for chunk in self._iterencode(value, markers): - yield chunk - if newline_indent is not None: - self.current_indent_level -= 1 - yield self._newline_indent() - yield ']' - if markers is not None: - del markers[markerid] - - def _iterencode_dict(self, dct, markers=None): - """ - Encoding dictionaries, yielding by level - """ - if not dct: - yield '{}' - return - if markers is not None: - markerid = id(dct) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = dct - yield '{' - key_separator = self.key_separator - if self.indent is not None: - self.current_indent_level += 1 - newline_indent = self._newline_indent() - item_separator = self.item_separator + newline_indent - yield newline_indent - else: - newline_indent = None - item_separator = self.item_separator - first = True - if self.ensure_ascii: - encoder = encode_basestring_ascii - else: - encoder = encode_basestring - allow_nan = self.allow_nan - if self.sort_keys: - keys = dct.keys() - keys.sort() - items = [(k, dct[k]) for k in keys] - else: - items = dct.iteritems() - _encoding = self.encoding - _do_decode = (_encoding is not None - and not (_encoding == 'utf-8')) - for key, value in items: - if isinstance(key, str): - if _do_decode: - key = key.decode(_encoding) - elif isinstance(key, basestring): - pass - # JavaScript is weakly typed for these, so it makes sense to - # also allow them. Many encoders seem to do something like this. - elif isinstance(key, float): - key = floatstr(key, allow_nan) - elif isinstance(key, (int, long)): - key = str(key) - elif key is True: - key = 'true' - elif key is False: - key = 'false' - elif key is None: - key = 'null' - elif self.skipkeys: - continue - else: - raise TypeError("key %r is not a string" % (key,)) - if first: - first = False - else: - yield item_separator - yield encoder(key) - yield key_separator - for chunk in self._iterencode(value, markers): - yield chunk - if newline_indent is not None: - self.current_indent_level -= 1 - yield self._newline_indent() - yield '}' - if markers is not None: - del markers[markerid] - - def _iterencode(self, o, markers=None): - if isinstance(o, basestring): - if self.ensure_ascii: - encoder = encode_basestring_ascii - else: - encoder = encode_basestring - _encoding = self.encoding - if (_encoding is not None and isinstance(o, str) - and not (_encoding == 'utf-8')): - o = o.decode(_encoding) - yield encoder(o) - elif o is None: - yield 'null' - elif o is True: - yield 'true' - elif o is False: - yield 'false' - elif isinstance(o, (int, long)): - yield str(o) - elif isinstance(o, float): - yield floatstr(o, self.allow_nan) - elif isinstance(o, (list, tuple)): - for chunk in self._iterencode_list(o, markers): - yield chunk - elif isinstance(o, dict): - for chunk in self._iterencode_dict(o, markers): - yield chunk - else: - if markers is not None: - markerid = id(o) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = o - for chunk in self._iterencode_default(o, markers): - yield chunk - if markers is not None: - del markers[markerid] - - def _iterencode_default(self, o, markers=None): - newobj = self.default(o) - return self._iterencode(newobj, markers) - - def default(self, o): - """ - Implement this method in a subclass such that it returns - a serializable object for ``o``, or calls the base implementation - (to raise a ``TypeError``). - - For example, to support arbitrary iterators, you could - implement default like this:: - - def default(self, o): - try: - iterable = iter(o) - except TypeError: - pass - else: - return list(iterable) - return JSONEncoder.default(self, o) - """ - raise TypeError("%r is not JSON serializable" % (o,)) - - def encode(self, o): - """ - Return a JSON string representation of a Python data structure. - - >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) - '{"foo": ["bar", "baz"]}' - """ - # This is for extremely simple cases and benchmarks. - if isinstance(o, basestring): - if isinstance(o, str): - _encoding = self.encoding - if (_encoding is not None - and not (_encoding == 'utf-8')): - o = o.decode(_encoding) - if self.ensure_ascii: - return encode_basestring_ascii(o) - else: - return encode_basestring(o) - # This doesn't pass the iterator directly to ''.join() because the - # exceptions aren't as detailed. The list call should be roughly - # equivalent to the PySequence_Fast that ''.join() would do. - chunks = list(self.iterencode(o)) - return ''.join(chunks) - - def iterencode(self, o): - """ - Encode the given object and yield each string - representation as available. - - For example:: - - for chunk in JSONEncoder().iterencode(bigobject): - mysocket.write(chunk) - """ - if self.check_circular: - markers = {} - else: - markers = None - return self._iterencode(o, markers) - -__all__ = ['JSONEncoder'] diff --git a/addon-sdk/source/python-lib/simplejson/scanner.py b/addon-sdk/source/python-lib/simplejson/scanner.py deleted file mode 100644 index 2a18390d0d..0000000000 --- a/addon-sdk/source/python-lib/simplejson/scanner.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Iterator based sre token scanner -""" -import re -from re import VERBOSE, MULTILINE, DOTALL -import sre_parse -import sre_compile -import sre_constants -from sre_constants import BRANCH, SUBPATTERN - -__all__ = ['Scanner', 'pattern'] - -FLAGS = (VERBOSE | MULTILINE | DOTALL) - -class Scanner(object): - def __init__(self, lexicon, flags=FLAGS): - self.actions = [None] - # Combine phrases into a compound pattern - s = sre_parse.Pattern() - s.flags = flags - p = [] - for idx, token in enumerate(lexicon): - phrase = token.pattern - try: - subpattern = sre_parse.SubPattern(s, - [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))]) - except sre_constants.error: - raise - p.append(subpattern) - self.actions.append(token) - - s.groups = len(p) + 1 # NOTE(guido): Added to make SRE validation work - p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) - self.scanner = sre_compile.compile(p) - - def iterscan(self, string, idx=0, context=None): - """ - Yield match, end_idx for each match - """ - match = self.scanner.scanner(string, idx).match - actions = self.actions - lastend = idx - end = len(string) - while True: - m = match() - if m is None: - break - matchbegin, matchend = m.span() - if lastend == matchend: - break - action = actions[m.lastindex] - if action is not None: - rval, next_pos = action(m, context) - if next_pos is not None and next_pos != matchend: - # "fast forward" the scanner - matchend = next_pos - match = self.scanner.scanner(string, matchend).match - yield rval, matchend - lastend = matchend - - -def pattern(pattern, flags=FLAGS): - def decorator(fn): - fn.pattern = pattern - fn.regex = re.compile(pattern, flags) - return fn - return decorator \ No newline at end of file diff --git a/addon-sdk/source/python-lib/simplejson/tool.py b/addon-sdk/source/python-lib/simplejson/tool.py deleted file mode 100644 index caa1818f8c..0000000000 --- a/addon-sdk/source/python-lib/simplejson/tool.py +++ /dev/null @@ -1,44 +0,0 @@ -r""" -Using simplejson from the shell to validate and -pretty-print:: - - $ echo '{"json":"obj"}' | python -msimplejson - { - "json": "obj" - } - $ echo '{ 1.2:3.4}' | python -msimplejson - Expecting property name: line 1 column 2 (char 2) - -Note that the JSON produced by this module's default settings -is a subset of YAML, so it may be used as a serializer for that as well. -""" -import simplejson - -# -# Pretty printer: -# curl http://mochikit.com/examples/ajax_tables/domains.json | python -msimplejson.tool -# - -def main(): - import sys - if len(sys.argv) == 1: - infile = sys.stdin - outfile = sys.stdout - elif len(sys.argv) == 2: - infile = open(sys.argv[1], 'rb') - outfile = sys.stdout - elif len(sys.argv) == 3: - infile = open(sys.argv[1], 'rb') - outfile = open(sys.argv[2], 'wb') - else: - raise SystemExit("%s [infile [outfile]]" % (sys.argv[0],)) - try: - obj = simplejson.load(infile) - except ValueError, e: - raise SystemExit(e) - simplejson.dump(obj, outfile, sort_keys=True, indent=4) - outfile.write('\n') - - -if __name__ == '__main__': - main() diff --git a/addon-sdk/source/test/addons/addon-manager/lib/main.js b/addon-sdk/source/test/addons/addon-manager/lib/main.js deleted file mode 100644 index 043424f593..0000000000 --- a/addon-sdk/source/test/addons/addon-manager/lib/main.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.exports = require("./test-main.js"); - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/addon-manager/lib/test-main.js b/addon-sdk/source/test/addons/addon-manager/lib/test-main.js deleted file mode 100644 index 7204c4a40f..0000000000 --- a/addon-sdk/source/test/addons/addon-manager/lib/test-main.js +++ /dev/null @@ -1,12 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { id } = require("sdk/self"); -const { getAddonByID } = require("sdk/addon/manager"); - -exports["test getAddonByID"] = function*(assert) { - let addon = yield getAddonByID(id); - assert.equal(addon.id, id, "getAddonByID works"); -} diff --git a/addon-sdk/source/test/addons/addon-manager/package.json b/addon-sdk/source/test/addons/addon-manager/package.json deleted file mode 100644 index 2ed7484988..0000000000 --- a/addon-sdk/source/test/addons/addon-manager/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "test-addon-manager@jetpack", - "main": "./lib/main.js", - "name": "test-addon-manager", - "version": "0.0.1", - "author": "Erik Vold" -} diff --git a/addon-sdk/source/test/addons/author-email/main.js b/addon-sdk/source/test/addons/author-email/main.js deleted file mode 100644 index 34786475a3..0000000000 --- a/addon-sdk/source/test/addons/author-email/main.js +++ /dev/null @@ -1,14 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { id } = require('sdk/self'); -const { getAddonByID } = require('sdk/addon/manager'); - -exports.testContributors = function*(assert) { - let addon = yield getAddonByID(id); - assert.equal(addon.creator.name, 'test ', '< and > characters work'); -} - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/author-email/package.json b/addon-sdk/source/test/addons/author-email/package.json deleted file mode 100644 index 2654ec431a..0000000000 --- a/addon-sdk/source/test/addons/author-email/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "test-addon-author-email@jetpack", - "author": "test ", - "version": "0.0.1", - "main": "./main.js" -} diff --git a/addon-sdk/source/test/addons/child_process/index.js b/addon-sdk/source/test/addons/child_process/index.js deleted file mode 100644 index bf69e0380d..0000000000 --- a/addon-sdk/source/test/addons/child_process/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -/** - * Ensures using child_process and underlying subprocess.jsm - * works within an addon - */ - -const { exec } = require("sdk/system/child_process"); -const { platform, pathFor } = require("sdk/system"); -const PROFILE_DIR = pathFor("ProfD"); -const isWindows = platform.toLowerCase().indexOf("win") === 0; -const app = require("sdk/system/xul-app"); - -// Once Bug 903018 is resolved, just move the application testing to -// module.metadata.engines -if (app.is("Firefox")) { - exports["test child_process in an addon"] = (assert, done) => { - exec(isWindows ? "DIR /A-D" : "ls -al", { - cwd: PROFILE_DIR - }, (err, stdout, stderr) => { - assert.equal(err, null, "no errors"); - assert.equal(stderr, "", "stderr is empty"); - assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files"); - - if (isWindows) - assert.ok(!//.test(stdout), "passing args works"); - else - assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works"); - done(); - }); - }; -} else { - exports["test unsupported"] = (assert) => assert.pass("This application is unsupported."); -} -require("sdk/test/runner").runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/child_process/package.json b/addon-sdk/source/test/addons/child_process/package.json deleted file mode 100644 index 3b882d0c45..0000000000 --- a/addon-sdk/source/test/addons/child_process/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "test-child-process@jetpack", - "main": "./index.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/chrome/chrome.manifest b/addon-sdk/source/test/addons/chrome/chrome.manifest deleted file mode 100644 index 35e59a1078..0000000000 --- a/addon-sdk/source/test/addons/chrome/chrome.manifest +++ /dev/null @@ -1,5 +0,0 @@ -content test chrome/content/ -skin test classic/1.0 chrome/skin/ - -locale test en-US chrome/locale/en-US/ -locale test ja-JP chrome/locale/ja-JP/ diff --git a/addon-sdk/source/test/addons/chrome/chrome/content/new-window.xul b/addon-sdk/source/test/addons/chrome/chrome/content/new-window.xul deleted file mode 100644 index 25e219b808..0000000000 --- a/addon-sdk/source/test/addons/chrome/chrome/content/new-window.xul +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/addon-sdk/source/test/addons/chrome/chrome/content/panel.html b/addon-sdk/source/test/addons/chrome/chrome/content/panel.html deleted file mode 100644 index 595cc74558..0000000000 --- a/addon-sdk/source/test/addons/chrome/chrome/content/panel.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/addon-sdk/source/test/addons/chrome/chrome/locale/en-US/description.properties b/addon-sdk/source/test/addons/chrome/chrome/locale/en-US/description.properties deleted file mode 100644 index 148e2a127f..0000000000 --- a/addon-sdk/source/test/addons/chrome/chrome/locale/en-US/description.properties +++ /dev/null @@ -1 +0,0 @@ -test=Test diff --git a/addon-sdk/source/test/addons/chrome/chrome/locale/ja-JP/description.properties b/addon-sdk/source/test/addons/chrome/chrome/locale/ja-JP/description.properties deleted file mode 100644 index cf01ac85bc..0000000000 --- a/addon-sdk/source/test/addons/chrome/chrome/locale/ja-JP/description.properties +++ /dev/null @@ -1 +0,0 @@ -test=テスト diff --git a/addon-sdk/source/test/addons/chrome/chrome/skin/style.css b/addon-sdk/source/test/addons/chrome/chrome/skin/style.css deleted file mode 100644 index 22abf35963..0000000000 --- a/addon-sdk/source/test/addons/chrome/chrome/skin/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -test{} diff --git a/addon-sdk/source/test/addons/chrome/data/panel.js b/addon-sdk/source/test/addons/chrome/data/panel.js deleted file mode 100644 index c38eca8522..0000000000 --- a/addon-sdk/source/test/addons/chrome/data/panel.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -self.port.on('echo', _ => { - self.port.emit('echo', ''); -}); - -self.port.emit('start', ''); diff --git a/addon-sdk/source/test/addons/chrome/main.js b/addon-sdk/source/test/addons/chrome/main.js deleted file mode 100644 index 84b8224584..0000000000 --- a/addon-sdk/source/test/addons/chrome/main.js +++ /dev/null @@ -1,97 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict' - -const { Cu, Cc, Ci } = require('chrome'); -const Request = require('sdk/request').Request; -const { WindowTracker } = require('sdk/deprecated/window-utils'); -const { close, open } = require('sdk/window/helpers'); -const { data } = require('sdk/self'); -const { Panel } = require('sdk/panel'); - -const XUL_URL = 'chrome://test/content/new-window.xul' - -const { Services } = Cu.import('resource://gre/modules/Services.jsm', {}); -const { NetUtil } = Cu.import('resource://gre/modules/NetUtil.jsm', {}); - -exports.testChromeSkin = function(assert, done) { - let skinURL = 'chrome://test/skin/style.css'; - - Request({ - url: skinURL, - overrideMimeType: 'text/plain', - onComplete: function (response) { - assert.ok(/test\{\}\s*$/.test(response.text), 'chrome.manifest skin folder was registered!'); - done(); - } - }).get(); - - assert.pass('requesting ' + skinURL); -} - -exports.testChromeContent = function(assert, done) { - let wt = WindowTracker({ - onTrack: function(window) { - if (window.document.documentElement.getAttribute('windowtype') === 'test:window') { - assert.pass('test xul window was opened'); - wt.unload(); - - close(window).then(done, assert.fail); - } - } - }); - - open(XUL_URL).then( - assert.pass.bind(assert, 'opened ' + XUL_URL), - assert.fail); - - assert.pass('opening ' + XUL_URL); -} - -exports.testChromeLocale = function(assert) { - let jpLocalePath = Cc['@mozilla.org/chrome/chrome-registry;1']. - getService(Ci.nsIChromeRegistry). - convertChromeURL(NetUtil.newURI('chrome://test/locale/description.properties')). - spec.replace(/(en\-US|ja\-JP)/, 'ja-JP'); - let enLocalePath = jpLocalePath.replace(/ja\-JP/, 'en-US'); - - let jpStringBundle = Services.strings.createBundle(jpLocalePath); - assert.equal(jpStringBundle.GetStringFromName('test'), - 'テスト', - 'locales ja-JP folder was copied correctly'); - - let enStringBundle = Services.strings.createBundle(enLocalePath); - assert.equal(enStringBundle.GetStringFromName('test'), - 'Test', - 'locales en-US folder was copied correctly'); -} - -exports.testChromeInPanel = function*(assert) { - let panel = Panel({ - contentURL: 'chrome://test/content/panel.html', - contentScriptWhen: 'end', - contentScriptFile: data.url('panel.js') - }); - - yield new Promise(resolve => panel.port.once('start', resolve)); - assert.pass('start was emitted'); - - yield new Promise(resolve => { - panel.once('show', resolve); - panel.show(); - }); - assert.pass('panel shown'); - - yield new Promise(resolve => { - panel.port.once('echo', resolve); - panel.port.emit('echo'); - }); - - assert.pass('got echo'); - - panel.destroy(); - assert.pass('panel is destroyed'); -} - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/chrome/package.json b/addon-sdk/source/test/addons/chrome/package.json deleted file mode 100644 index 82c6db8997..0000000000 --- a/addon-sdk/source/test/addons/chrome/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "test-chrome@jetpack", - "main": "./main.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/content-permissions/httpd.js b/addon-sdk/source/test/addons/content-permissions/httpd.js deleted file mode 100644 index 964dc9bbd3..0000000000 --- a/addon-sdk/source/test/addons/content-permissions/httpd.js +++ /dev/null @@ -1,5211 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* -* NOTE: do not edit this file, this is copied from: -* https://github.com/mozilla/addon-sdk/blob/master/test/lib/httpd.js -*/ - -module.metadata = { - "stability": "experimental" -}; - -const { components, CC, Cc, Ci, Cr, Cu } = require("chrome"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - - -const PR_UINT32_MAX = Math.pow(2, 32) - 1; - -/** True if debugging output is enabled, false otherwise. */ -var DEBUG = false; // non-const *only* so tweakable in server tests - -/** True if debugging output should be timestamped. */ -var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests - -var gGlobalObject = Cc["@mozilla.org/systemprincipal;1"].createInstance(); - -/** -* Asserts that the given condition holds. If it doesn't, the given message is -* dumped, a stack trace is printed, and an exception is thrown to attempt to -* stop execution (which unfortunately must rely upon the exception not being -* accidentally swallowed by the code that uses it). -*/ -function NS_ASSERT(cond, msg) -{ - if (DEBUG && !cond) - { - dumpn("###!!!"); - dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!")); - dumpn("###!!! Stack follows:"); - - var stack = new Error().stack.split(/\n/); - dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n")); - - throw Cr.NS_ERROR_ABORT; - } -} - -/** Constructs an HTTP error object. */ -function HttpError(code, description) -{ - this.code = code; - this.description = description; -} -HttpError.prototype = -{ - toString: function() - { - return this.code + " " + this.description; - } -}; - -/** -* Errors thrown to trigger specific HTTP server responses. -*/ -const HTTP_400 = new HttpError(400, "Bad Request"); -const HTTP_401 = new HttpError(401, "Unauthorized"); -const HTTP_402 = new HttpError(402, "Payment Required"); -const HTTP_403 = new HttpError(403, "Forbidden"); -const HTTP_404 = new HttpError(404, "Not Found"); -const HTTP_405 = new HttpError(405, "Method Not Allowed"); -const HTTP_406 = new HttpError(406, "Not Acceptable"); -const HTTP_407 = new HttpError(407, "Proxy Authentication Required"); -const HTTP_408 = new HttpError(408, "Request Timeout"); -const HTTP_409 = new HttpError(409, "Conflict"); -const HTTP_410 = new HttpError(410, "Gone"); -const HTTP_411 = new HttpError(411, "Length Required"); -const HTTP_412 = new HttpError(412, "Precondition Failed"); -const HTTP_413 = new HttpError(413, "Request Entity Too Large"); -const HTTP_414 = new HttpError(414, "Request-URI Too Long"); -const HTTP_415 = new HttpError(415, "Unsupported Media Type"); -const HTTP_417 = new HttpError(417, "Expectation Failed"); - -const HTTP_500 = new HttpError(500, "Internal Server Error"); -const HTTP_501 = new HttpError(501, "Not Implemented"); -const HTTP_502 = new HttpError(502, "Bad Gateway"); -const HTTP_503 = new HttpError(503, "Service Unavailable"); -const HTTP_504 = new HttpError(504, "Gateway Timeout"); -const HTTP_505 = new HttpError(505, "HTTP Version Not Supported"); - -/** Creates a hash with fields corresponding to the values in arr. */ -function array2obj(arr) -{ - var obj = {}; - for (var i = 0; i < arr.length; i++) - obj[arr[i]] = arr[i]; - return obj; -} - -/** Returns an array of the integers x through y, inclusive. */ -function range(x, y) -{ - var arr = []; - for (var i = x; i <= y; i++) - arr.push(i); - return arr; -} - -/** An object (hash) whose fields are the numbers of all HTTP error codes. */ -const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505))); - - -/** -* The character used to distinguish hidden files from non-hidden files, a la -* the leading dot in Apache. Since that mechanism also hides files from -* easy display in LXR, ls output, etc. however, we choose instead to use a -* suffix character. If a requested file ends with it, we append another -* when getting the file on the server. If it doesn't, we just look up that -* file. Therefore, any file whose name ends with exactly one of the character -* is "hidden" and available for use by the server. -*/ -const HIDDEN_CHAR = "^"; - -/** -* The file name suffix indicating the file containing overridden headers for -* a requested file. -*/ -const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR; - -/** Type used to denote SJS scripts for CGI-like functionality. */ -const SJS_TYPE = "sjs"; - -/** Base for relative timestamps produced by dumpn(). */ -var firstStamp = 0; - -/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */ -function dumpn(str) -{ - if (DEBUG) - { - var prefix = "HTTPD-INFO | "; - if (DEBUG_TIMESTAMP) - { - if (firstStamp === 0) - firstStamp = Date.now(); - - var elapsed = Date.now() - firstStamp; // milliseconds - var min = Math.floor(elapsed / 60000); - var sec = (elapsed % 60000) / 1000; - - if (sec < 10) - prefix += min + ":0" + sec.toFixed(3) + " | "; - else - prefix += min + ":" + sec.toFixed(3) + " | "; - } - - dump(prefix + str + "\n"); - } -} - -/** Dumps the current JS stack if DEBUG. */ -function dumpStack() -{ - // peel off the frames for dumpStack() and Error() - var stack = new Error().stack.split(/\n/).slice(2); - stack.forEach(dumpn); -} - - -/** The XPCOM thread manager. */ -var gThreadManager = null; - -/** The XPCOM prefs service. */ -var gRootPrefBranch = null; -function getRootPrefBranch() -{ - if (!gRootPrefBranch) - { - gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"] - .getService(Ci.nsIPrefBranch); - } - return gRootPrefBranch; -} - -/** -* JavaScript constructors for commonly-used classes; precreating these is a -* speedup over doing the same from base principles. See the docs at -* http://developer.mozilla.org/en/docs/components.Constructor for details. -*/ -const ServerSocket = CC("@mozilla.org/network/server-socket;1", - "nsIServerSocket", - "init"); -const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", - "nsIScriptableInputStream", - "init"); -const Pipe = CC("@mozilla.org/pipe;1", - "nsIPipe", - "init"); -const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", - "nsIFileInputStream", - "init"); -const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1", - "nsIConverterInputStream", - "init"); -const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1", - "nsIWritablePropertyBag2"); -const SupportsString = CC("@mozilla.org/supports-string;1", - "nsISupportsString"); - -/* These two are non-const only so a test can overwrite them. */ -var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", - "setInputStream"); -var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", - "nsIBinaryOutputStream", - "setOutputStream"); - -/** -* Returns the RFC 822/1123 representation of a date. -* -* @param date : Number -* the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT -* @returns string -* the representation of the given date -*/ -function toDateString(date) -{ - // - // rfc1123-date = wkday "," SP date1 SP time SP "GMT" - // date1 = 2DIGIT SP month SP 4DIGIT - // ; day month year (e.g., 02 Jun 1982) - // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - // ; 00:00:00 - 23:59:59 - // wkday = "Mon" | "Tue" | "Wed" - // | "Thu" | "Fri" | "Sat" | "Sun" - // month = "Jan" | "Feb" | "Mar" | "Apr" - // | "May" | "Jun" | "Jul" | "Aug" - // | "Sep" | "Oct" | "Nov" | "Dec" - // - - const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - /** -* Processes a date and returns the encoded UTC time as a string according to -* the format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toTime(date) - { - var hrs = date.getUTCHours(); - var rv = (hrs < 10) ? "0" + hrs : hrs; - - var mins = date.getUTCMinutes(); - rv += ":"; - rv += (mins < 10) ? "0" + mins : mins; - - var secs = date.getUTCSeconds(); - rv += ":"; - rv += (secs < 10) ? "0" + secs : secs; - - return rv; - } - - /** -* Processes a date and returns the encoded UTC date as a string according to -* the date1 format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toDate1(date) - { - var day = date.getUTCDate(); - var month = date.getUTCMonth(); - var year = date.getUTCFullYear(); - - var rv = (day < 10) ? "0" + day : day; - rv += " " + monthStrings[month]; - rv += " " + year; - - return rv; - } - - date = new Date(date); - - const fmtString = "%wkday%, %date1% %time% GMT"; - var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]); - rv = rv.replace("%time%", toTime(date)); - return rv.replace("%date1%", toDate1(date)); -} - -/** -* Prints out a human-readable representation of the object o and its fields, -* omitting those whose names begin with "_" if showMembers != true (to ignore -* "private" properties exposed via getters/setters). -*/ -function printObj(o, showMembers) -{ - var s = "******************************\n"; - s += "o = {\n"; - for (var i in o) - { - if (typeof(i) != "string" || - (showMembers || (i.length > 0 && i[0] != "_"))) - s+= " " + i + ": " + o[i] + ",\n"; - } - s += " };\n"; - s += "******************************"; - dumpn(s); -} - -/** -* Instantiates a new HTTP server. -*/ -function nsHttpServer() -{ - if (!gThreadManager) - gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService(); - - /** The port on which this server listens. */ - this._port = undefined; - - /** The socket associated with this. */ - this._socket = null; - - /** The handler used to process requests to this server. */ - this._handler = new ServerHandler(this); - - /** Naming information for this server. */ - this._identity = new ServerIdentity(); - - /** -* Indicates when the server is to be shut down at the end of the request. -*/ - this._doQuit = false; - - /** -* True if the socket in this is closed (and closure notifications have been -* sent and processed if the socket was ever opened), false otherwise. -*/ - this._socketClosed = true; - - /** -* Used for tracking existing connections and ensuring that all connections -* are properly cleaned up before server shutdown; increases by 1 for every -* new incoming connection. -*/ - this._connectionGen = 0; - - /** -* Hash of all open connections, indexed by connection number at time of -* creation. -*/ - this._connections = {}; -} -nsHttpServer.prototype = -{ - classID: components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"), - - // NSISERVERSOCKETLISTENER - - /** -* Processes an incoming request coming in on the given socket and contained -* in the given transport. -* -* @param socket : nsIServerSocket -* the socket through which the request was served -* @param trans : nsISocketTransport -* the transport for the request/response -* @see nsIServerSocketListener.onSocketAccepted -*/ - onSocketAccepted: function(socket, trans) - { - dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")"); - - dumpn(">>> new connection on " + trans.host + ":" + trans.port); - - const SEGMENT_SIZE = 8192; - const SEGMENT_COUNT = 1024; - try - { - var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT) - .QueryInterface(Ci.nsIAsyncInputStream); - var output = trans.openOutputStream(0, 0, 0); - } - catch (e) - { - dumpn("*** error opening transport streams: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - var connectionNumber = ++this._connectionGen; - - try - { - var conn = new Connection(input, output, this, socket.port, trans.port, - connectionNumber); - var reader = new RequestReader(conn); - - // XXX add request timeout functionality here! - - // Note: must use main thread here, or we might get a GC that will cause - // threadsafety assertions. We really need to fix XPConnect so that - // you can actually do things in multi-threaded JS. :-( - input.asyncWait(reader, 0, 0, gThreadManager.mainThread); - } - catch (e) - { - // Assume this connection can't be salvaged and bail on it completely; - // don't attempt to close it so that we can assert that any connection - // being closed is in this._connections. - dumpn("*** error in initial request-processing stages: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - this._connections[connectionNumber] = conn; - dumpn("*** starting connection " + connectionNumber); - }, - - /** -* Called when the socket associated with this is closed. -* -* @param socket : nsIServerSocket -* the socket being closed -* @param status : nsresult -* the reason the socket stopped listening (NS_BINDING_ABORTED if the server -* was stopped using nsIHttpServer.stop) -* @see nsIServerSocketListener.onStopListening -*/ - onStopListening: function(socket, status) - { - dumpn(">>> shutting down server on port " + socket.port); - this._socketClosed = true; - if (!this._hasOpenConnections()) - { - dumpn("*** no open connections, notifying async from onStopListening"); - - // Notify asynchronously so that any pending teardown in stop() has a - // chance to run first. - var self = this; - var stopEvent = - { - run: function() - { - dumpn("*** _notifyStopped async callback"); - self._notifyStopped(); - } - }; - gThreadManager.currentThread - .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL); - } - }, - - // NSIHTTPSERVER - - // - // see nsIHttpServer.start - // - start: function(port) - { - this._start(port, "localhost") - }, - - _start: function(port, host) - { - if (this._socket) - throw Cr.NS_ERROR_ALREADY_INITIALIZED; - - this._port = port; - this._doQuit = this._socketClosed = false; - - this._host = host; - - // The listen queue needs to be long enough to handle - // network.http.max-persistent-connections-per-server concurrent connections, - // plus a safety margin in case some other process is talking to - // the server as well. - var prefs = getRootPrefBranch(); - var maxConnections; - try { - // Bug 776860: The original pref was removed in favor of this new one: - maxConnections = prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5; - } - catch(e) { - maxConnections = prefs.getIntPref("network.http.max-connections-per-server") + 5; - } - - try - { - var loopback = true; - if (this._host != "127.0.0.1" && this._host != "localhost") { - var loopback = false; - } - - var socket = new ServerSocket(this._port, - loopback, // true = localhost, false = everybody - maxConnections); - dumpn(">>> listening on port " + socket.port + ", " + maxConnections + - " pending connections"); - socket.asyncListen(this); - this._identity._initialize(socket.port, host, true); - this._socket = socket; - } - catch (e) - { - dumpn("!!! could not start server on port " + port + ": " + e); - throw Cr.NS_ERROR_NOT_AVAILABLE; - } - }, - - // - // see nsIHttpServer.stop - // - stop: function(callback) - { - if (!callback) - throw Cr.NS_ERROR_NULL_POINTER; - if (!this._socket) - throw Cr.NS_ERROR_UNEXPECTED; - - this._stopCallback = typeof callback === "function" - ? callback - : function() { callback.onStopped(); }; - - dumpn(">>> stopping listening on port " + this._socket.port); - this._socket.close(); - this._socket = null; - - // We can't have this identity any more, and the port on which we're running - // this server now could be meaningless the next time around. - this._identity._teardown(); - - this._doQuit = false; - - // socket-close notification and pending request completion happen async - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (file && (!file.exists() || file.isDirectory())) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handler.registerFile(path, file); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // XXX true path validation! - if (path.charAt(0) != "/" || - path.charAt(path.length - 1) != "/" || - (directory && - (!directory.exists() || !directory.isDirectory()))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping - // exists! - - this._handler.registerDirectory(path, directory); - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - this._handler.registerPathHandler(path, handler); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - this._handler.registerPrefixHandler(prefix, handler); - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(code, handler) - { - this._handler.registerErrorHandler(code, handler); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - this._handler.setIndexHandler(handler); - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - this._handler.registerContentType(ext, type); - }, - - // - // see nsIHttpServer.serverIdentity - // - get identity() - { - return this._identity; - }, - - // - // see nsIHttpServer.getState - // - getState: function(path, k) - { - return this._handler._getState(path, k); - }, - - // - // see nsIHttpServer.setState - // - setState: function(path, k, v) - { - return this._handler._setState(path, k, v); - }, - - // - // see nsIHttpServer.getSharedState - // - getSharedState: function(k) - { - return this._handler._getSharedState(k); - }, - - // - // see nsIHttpServer.setSharedState - // - setSharedState: function(k, v) - { - return this._handler._setSharedState(k, v); - }, - - // - // see nsIHttpServer.getObjectState - // - getObjectState: function(k) - { - return this._handler._getObjectState(k); - }, - - // - // see nsIHttpServer.setObjectState - // - setObjectState: function(k, v) - { - return this._handler._setObjectState(k, v); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIServerSocketListener) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NON-XPCOM PUBLIC API - - /** -* Returns true iff this server is not running (and is not in the process of -* serving any requests still to be processed when the server was last -* stopped after being run). -*/ - isStopped: function() - { - return this._socketClosed && !this._hasOpenConnections(); - }, - - // PRIVATE IMPLEMENTATION - - /** True if this server has any open connections to it, false otherwise. */ - _hasOpenConnections: function() - { - // - // If we have any open connections, they're tracked as numeric properties on - // |this._connections|. The non-standard __count__ property could be used - // to check whether there are any properties, but standard-wise, even - // looking forward to ES5, there's no less ugly yet still O(1) way to do - // this. - // - for (var n in this._connections) - return true; - return false; - }, - - /** Calls the server-stopped callback provided when stop() was called. */ - _notifyStopped: function() - { - NS_ASSERT(this._stopCallback !== null, "double-notifying?"); - NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now"); - - // - // NB: We have to grab this now, null out the member, *then* call the - // callback here, or otherwise the callback could (indirectly) futz with - // this._stopCallback by starting and immediately stopping this, at - // which point we'd be nulling out a field we no longer have a right to - // modify. - // - var callback = this._stopCallback; - this._stopCallback = null; - try - { - callback(); - } - catch (e) - { - // not throwing because this is specified as being usually (but not - // always) asynchronous - dump("!!! error running onStopped callback: " + e + "\n"); - } - }, - - /** -* Notifies this server that the given connection has been closed. -* -* @param connection : Connection -* the connection that was closed -*/ - _connectionClosed: function(connection) - { - NS_ASSERT(connection.number in this._connections, - "closing a connection " + this + " that we never added to the " + - "set of open connections?"); - NS_ASSERT(this._connections[connection.number] === connection, - "connection number mismatch? " + - this._connections[connection.number]); - delete this._connections[connection.number]; - - // Fire a pending server-stopped notification if it's our responsibility. - if (!this._hasOpenConnections() && this._socketClosed) - this._notifyStopped(); - }, - - /** -* Requests that the server be shut down when possible. -*/ - _requestQuit: function() - { - dumpn(">>> requesting a quit"); - dumpStack(); - this._doQuit = true; - } -}; - - -// -// RFC 2396 section 3.2.2: -// -// host = hostname | IPv4address -// hostname = *( domainlabel "." ) toplabel [ "." ] -// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum -// toplabel = alpha | alpha *( alphanum | "-" ) alphanum -// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit -// - -const HOST_REGEX = - new RegExp("^(?:" + - // *( domainlabel "." ) - "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" + - // toplabel - "[a-z](?:[a-z0-9-]*[a-z0-9])?" + - "|" + - // IPv4 address - "\\d+\\.\\d+\\.\\d+\\.\\d+" + - ")$", - "i"); - - -/** -* Represents the identity of a server. An identity consists of a set of -* (scheme, host, port) tuples denoted as locations (allowing a single server to -* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any -* host/port). Any incoming request must be to one of these locations, or it -* will be rejected with an HTTP 400 error. One location, denoted as the -* primary location, is the location assigned in contexts where a location -* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests. -* -* A single identity may contain at most one location per unique host/port pair; -* other than that, no restrictions are placed upon what locations may -* constitute an identity. -*/ -function ServerIdentity() -{ - /** The scheme of the primary location. */ - this._primaryScheme = "http"; - - /** The hostname of the primary location. */ - this._primaryHost = "127.0.0.1" - - /** The port number of the primary location. */ - this._primaryPort = -1; - - /** -* The current port number for the corresponding server, stored so that a new -* primary location can always be set if the current one is removed. -*/ - this._defaultPort = -1; - - /** -* Maps hosts to maps of ports to schemes, e.g. the following would represent -* https://example.com:789/ and http://example.org/: -* -* { -* "xexample.com": { 789: "https" }, -* "xexample.org": { 80: "http" } -* } -* -* Note the "x" prefix on hostnames, which prevents collisions with special -* JS names like "prototype". -*/ - this._locations = { "xlocalhost": {} }; -} -ServerIdentity.prototype = -{ - // NSIHTTPSERVERIDENTITY - - // - // see nsIHttpServerIdentity.primaryScheme - // - get primaryScheme() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryScheme; - }, - - // - // see nsIHttpServerIdentity.primaryHost - // - get primaryHost() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryHost; - }, - - // - // see nsIHttpServerIdentity.primaryPort - // - get primaryPort() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryPort; - }, - - // - // see nsIHttpServerIdentity.add - // - add: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - this._locations["x" + host] = entry = {}; - - entry[port] = scheme; - }, - - // - // see nsIHttpServerIdentity.remove - // - remove: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return false; - - var present = port in entry; - delete entry[port]; - - if (this._primaryScheme == scheme && - this._primaryHost == host && - this._primaryPort == port && - this._defaultPort !== -1) - { - // Always keep at least one identity in existence at any time, unless - // we're in the process of shutting down (the last condition above). - this._primaryPort = -1; - this._initialize(this._defaultPort, host, false); - } - - return present; - }, - - // - // see nsIHttpServerIdentity.has - // - has: function(scheme, host, port) - { - this._validate(scheme, host, port); - - return "x" + host in this._locations && - scheme === this._locations["x" + host][port]; - }, - - // - // see nsIHttpServerIdentity.has - // - getScheme: function(host, port) - { - this._validate("http", host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return ""; - - return entry[port] || ""; - }, - - // - // see nsIHttpServerIdentity.setPrimary - // - setPrimary: function(scheme, host, port) - { - this._validate(scheme, host, port); - - this.add(scheme, host, port); - - this._primaryScheme = scheme; - this._primaryHost = host; - this._primaryPort = port; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Initializes the primary name for the corresponding server, based on the -* provided port number. -*/ - _initialize: function(port, host, addSecondaryDefault) - { - this._host = host; - if (this._primaryPort !== -1) - this.add("http", host, port); - else - this.setPrimary("http", "localhost", port); - this._defaultPort = port; - - // Only add this if we're being called at server startup - if (addSecondaryDefault && host != "127.0.0.1") - this.add("http", "127.0.0.1", port); - }, - - /** -* Called at server shutdown time, unsets the primary location only if it was -* the default-assigned location and removes the default location from the -* set of locations used. -*/ - _teardown: function() - { - if (this._host != "127.0.0.1") { - // Not the default primary location, nothing special to do here - this.remove("http", "127.0.0.1", this._defaultPort); - } - - // This is a *very* tricky bit of reasoning here; make absolutely sure the - // tests for this code pass before you commit changes to it. - if (this._primaryScheme == "http" && - this._primaryHost == this._host && - this._primaryPort == this._defaultPort) - { - // Make sure we don't trigger the readding logic in .remove(), then remove - // the default location. - var port = this._defaultPort; - this._defaultPort = -1; - this.remove("http", this._host, port); - - // Ensure a server start triggers the setPrimary() path in ._initialize() - this._primaryPort = -1; - } - else - { - // No reason not to remove directly as it's not our primary location - this.remove("http", this._host, this._defaultPort); - } - }, - - /** -* Ensures scheme, host, and port are all valid with respect to RFC 2396. -* -* @throws NS_ERROR_ILLEGAL_VALUE -* if any argument doesn't match the corresponding production -*/ - _validate: function(scheme, host, port) - { - if (scheme !== "http" && scheme !== "https") - { - dumpn("*** server only supports http/https schemes: '" + scheme + "'"); - dumpStack(); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (!HOST_REGEX.test(host)) - { - dumpn("*** unexpected host: '" + host + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (port < 0 || port > 65535) - { - dumpn("*** unexpected port: '" + port + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - } -}; - - -/** -* Represents a connection to the server (and possibly in the future the thread -* on which the connection is processed). -* -* @param input : nsIInputStream -* stream from which incoming data on the connection is read -* @param output : nsIOutputStream -* stream to write data out the connection -* @param server : nsHttpServer -* the server handling the connection -* @param port : int -* the port on which the server is running -* @param outgoingPort : int -* the outgoing port used by this connection -* @param number : uint -* a serial number used to uniquely identify this connection -*/ -function Connection(input, output, server, port, outgoingPort, number) -{ - dumpn("*** opening new connection " + number + " on port " + outgoingPort); - - /** Stream of incoming data. */ - this.input = input; - - /** Stream for outgoing data. */ - this.output = output; - - /** The server associated with this request. */ - this.server = server; - - /** The port on which the server is running. */ - this.port = port; - - /** The outgoing poort used by this connection. */ - this._outgoingPort = outgoingPort; - - /** The serial number of this connection. */ - this.number = number; - - /** -* The request for which a response is being generated, null if the -* incoming request has not been fully received or if it had errors. -*/ - this.request = null; - - /** State variables for debugging. */ - this._closed = this._processed = false; -} -Connection.prototype = -{ - /** Closes this connection's input/output streams. */ - close: function() - { - dumpn("*** closing connection " + this.number + - " on port " + this._outgoingPort); - - this.input.close(); - this.output.close(); - this._closed = true; - - var server = this.server; - server._connectionClosed(this); - - // If an error triggered a server shutdown, act on it now - if (server._doQuit) - server.stop(function() { /* not like we can do anything better */ }); - }, - - /** -* Initiates processing of this connection, using the data in the given -* request. -* -* @param request : Request -* the request which should be processed -*/ - process: function(request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - - this.request = request; - this.server._handler.handleResponse(this); - }, - - /** -* Initiates processing of this connection, generating a response with the -* given HTTP error code. -* -* @param code : uint -* an HTTP code, so in the range [0, 1000) -* @param request : Request -* incomplete data about the incoming request (since there were errors -* during its processing -*/ - processError: function(code, request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - this.request = request; - this.server._handler.handleError(code, this); - }, - - /** Converts this to a string for debugging purposes. */ - toString: function() - { - return ""; - } -}; - - - -/** Returns an array of count bytes from the given input stream. */ -function readBytes(inputStream, count) -{ - return new BinaryInputStream(inputStream).readByteArray(count); -} - - - -/** Request reader processing states; see RequestReader for details. */ -const READER_IN_REQUEST_LINE = 0; -const READER_IN_HEADERS = 1; -const READER_IN_BODY = 2; -const READER_FINISHED = 3; - - -/** -* Reads incoming request data asynchronously, does any necessary preprocessing, -* and forwards it to the request handler. Processing occurs in three states: -* -* READER_IN_REQUEST_LINE Reading the request's status line -* READER_IN_HEADERS Reading headers in the request -* READER_IN_BODY Reading the body of the request -* READER_FINISHED Entire request has been read and processed -* -* During the first two stages, initial metadata about the request is gathered -* into a Request object. Once the status line and headers have been processed, -* we start processing the body of the request into the Request. Finally, when -* the entire body has been read, we create a Response and hand it off to the -* ServerHandler to be given to the appropriate request handler. -* -* @param connection : Connection -* the connection for the request being read -*/ -function RequestReader(connection) -{ - /** Connection metadata for this request. */ - this._connection = connection; - - /** -* A container providing line-by-line access to the raw bytes that make up the -* data which has been read from the connection but has not yet been acted -* upon (by passing it to the request handler or by extracting request -* metadata from it). -*/ - this._data = new LineData(); - - /** -* The amount of data remaining to be read from the body of this request. -* After all headers in the request have been read this is the value in the -* Content-Length header, but as the body is read its value decreases to zero. -*/ - this._contentLength = 0; - - /** The current state of parsing the incoming request. */ - this._state = READER_IN_REQUEST_LINE; - - /** Metadata constructed from the incoming request for the request handler. */ - this._metadata = new Request(connection.port); - - /** -* Used to preserve state if we run out of line data midway through a -* multi-line header. _lastHeaderName stores the name of the header, while -* _lastHeaderValue stores the value we've seen so far for the header. -* -* These fields are always either both undefined or both strings. -*/ - this._lastHeaderName = this._lastHeaderValue = undefined; -} -RequestReader.prototype = -{ - // NSIINPUTSTREAMCALLBACK - - /** -* Called when more data from the incoming request is available. This method -* then reads the available data from input and deals with that data as -* necessary, depending upon the syntax of already-downloaded data. -* -* @param input : nsIAsyncInputStream -* the stream of incoming data from the connection -*/ - onInputStreamReady: function(input) - { - dumpn("*** onInputStreamReady(input=" + input + ") on thread " + - gThreadManager.currentThread + " (main is " + - gThreadManager.mainThread + ")"); - dumpn("*** this._state == " + this._state); - - // Handle cases where we get more data after a request error has been - // discovered but *before* we can close the connection. - var data = this._data; - if (!data) - return; - - try - { - data.appendBytes(readBytes(input, input.available())); - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** WARNING: unexpected error when reading from socket; will " + - "be treated as if the input stream had been closed"); - dumpn("*** WARNING: actual error was: " + e); - } - - // We've lost a race -- input has been closed, but we're still expecting - // to read more data. available() will throw in this case, and since - // we're dead in the water now, destroy the connection. - dumpn("*** onInputStreamReady called on a closed input, destroying " + - "connection"); - this._connection.close(); - return; - } - - switch (this._state) - { - default: - NS_ASSERT(false, "invalid state: " + this._state); - break; - - case READER_IN_REQUEST_LINE: - if (!this._processRequestLine()) - break; - /* fall through */ - - case READER_IN_HEADERS: - if (!this._processHeaders()) - break; - /* fall through */ - - case READER_IN_BODY: - this._processBody(); - } - - if (this._state != READER_FINISHED) - input.asyncWait(this, 0, 0, gThreadManager.currentThread); - }, - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIInputStreamCallback) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE API - - /** -* Processes unprocessed, downloaded data as a request line. -* -* @returns boolean -* true iff the request line has been fully processed -*/ - _processRequestLine: function() - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - // Servers SHOULD ignore any empty line(s) received where a Request-Line - // is expected (section 4.1). - var data = this._data; - var line = {}; - var readSuccess; - while ((readSuccess = data.readLine(line)) && line.value == "") - dumpn("*** ignoring beginning blank line..."); - - // if we don't have a full line, wait until we do - if (!readSuccess) - return false; - - // we have the first non-blank line - try - { - this._parseRequestLine(line.value); - this._state = READER_IN_HEADERS; - return true; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing request headers. -* -* @returns boolean -* true iff header data in the request has been fully processed -*/ - _processHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - // XXX things to fix here: - // - // - need to support RFC 2047-encoded non-US-ASCII characters - - try - { - var done = this._parseHeaders(); - if (done) - { - var request = this._metadata; - - // XXX this is wrong for requests with transfer-encodings applied to - // them, particularly chunked (which by its nature can have no - // meaningful Content-Length header)! - this._contentLength = request.hasHeader("Content-Length") - ? parseInt(request.getHeader("Content-Length"), 10) - : 0; - dumpn("_processHeaders, Content-length=" + this._contentLength); - - this._state = READER_IN_BODY; - } - return done; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing the request body. -* -* @returns boolean -* true iff the request body has been fully processed -*/ - _processBody: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - // XXX handle chunked transfer-coding request bodies! - - try - { - if (this._contentLength > 0) - { - var data = this._data.purge(); - var count = Math.min(data.length, this._contentLength); - dumpn("*** loading data=" + data + " len=" + data.length + - " excess=" + (data.length - count)); - - var bos = new BinaryOutputStream(this._metadata._bodyOutputStream); - bos.writeByteArray(data, count); - this._contentLength -= count; - } - - dumpn("*** remaining body data len=" + this._contentLength); - if (this._contentLength == 0) - { - this._validateRequest(); - this._state = READER_FINISHED; - this._handleResponse(); - return true; - } - - return false; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Does various post-header checks on the data in this request. -* -* @throws : HttpError -* if the request was malformed in some way -*/ - _validateRequest: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - dumpn("*** _validateRequest"); - - var metadata = this._metadata; - var headers = metadata._headers; - - // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header - var identity = this._connection.server.identity; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - { - if (!headers.hasHeader("Host")) - { - dumpn("*** malformed HTTP/1.1 or greater request with no Host header!"); - throw HTTP_400; - } - - // If the Request-URI wasn't absolute, then we need to determine our host. - // We have to determine what scheme was used to access us based on the - // server identity data at this point, because the request just doesn't - // contain enough data on its own to do this, sadly. - if (!metadata._host) - { - var host, port; - var hostPort = headers.getHeader("Host"); - var colon = hostPort.indexOf(":"); - if (colon < 0) - { - host = hostPort; - port = ""; - } - else - { - host = hostPort.substring(0, colon); - port = hostPort.substring(colon + 1); - } - - // NB: We allow an empty port here because, oddly, a colon may be - // present even without a port number, e.g. "example.com:"; in this - // case the default port applies. - if (!HOST_REGEX.test(host) || !/^\d*$/.test(port)) - { - dumpn("*** malformed hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - // If we're not given a port, we're stuck, because we don't know what - // scheme to use to look up the correct port here, in general. Since - // the HTTPS case requires a tunnel/proxy and thus requires that the - // requested URI be absolute (and thus contain the necessary - // information), let's assume HTTP will prevail and use that. - port = +port || 80; - - var scheme = identity.getScheme(host, port); - if (!scheme) - { - dumpn("*** unrecognized hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - } - } - else - { - NS_ASSERT(metadata._host === undefined, - "HTTP/1.0 doesn't allow absolute paths in the request line!"); - - metadata._scheme = identity.primaryScheme; - metadata._host = identity.primaryHost; - metadata._port = identity.primaryPort; - } - - NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port), - "must have a location we recognize by now!"); - }, - - /** -* Handles responses in case of error, either in the server or in the request. -* -* @param e -* the specific error encountered, which is an HttpError in the case where -* the request is in some way invalid or cannot be fulfilled; if this isn't -* an HttpError we're going to be paranoid and shut down, because that -* shouldn't happen, ever -*/ - _handleError: function(e) - { - // Don't fall back into normal processing! - this._state = READER_FINISHED; - - var server = this._connection.server; - if (e instanceof HttpError) - { - var code = e.code; - } - else - { - dumpn("!!! UNEXPECTED ERROR: " + e + - (e.lineNumber ? ", line " + e.lineNumber : "")); - - // no idea what happened -- be paranoid and shut down - code = 500; - server._requestQuit(); - } - - // make attempted reuse of data an error - this._data = null; - - this._connection.processError(code, this._metadata); - }, - - /** -* Now that we've read the request line and headers, we can actually hand off -* the request to be handled. -* -* This method is called once per request, after the request line and all -* headers and the body, if any, have been received. -*/ - _handleResponse: function() - { - NS_ASSERT(this._state == READER_FINISHED); - - // We don't need the line-based data any more, so make attempted reuse an - // error. - this._data = null; - - this._connection.process(this._metadata); - }, - - - // PARSING - - /** -* Parses the request line for the HTTP request associated with this. -* -* @param line : string -* the request line -*/ - _parseRequestLine: function(line) - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - dumpn("*** _parseRequestLine('" + line + "')"); - - var metadata = this._metadata; - - // clients and servers SHOULD accept any amount of SP or HT characters - // between fields, even though only a single SP is required (section 19.3) - var request = line.split(/[ \t]+/); - if (!request || request.length != 3) - throw HTTP_400; - - metadata._method = request[0]; - - // get the HTTP version - var ver = request[2]; - var match = ver.match(/^HTTP\/(\d+\.\d+)$/); - if (!match) - throw HTTP_400; - - // determine HTTP version - try - { - metadata._httpVersion = new nsHttpVersion(match[1]); - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0)) - throw "unsupported HTTP version"; - } - catch (e) - { - // we support HTTP/1.0 and HTTP/1.1 only - throw HTTP_501; - } - - - var fullPath = request[1]; - var serverIdentity = this._connection.server.identity; - - var scheme, host, port; - - if (fullPath.charAt(0) != "/") - { - // No absolute paths in the request line in HTTP prior to 1.1 - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - throw HTTP_400; - - try - { - var uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(fullPath, null, null); - fullPath = uri.path; - scheme = uri.scheme; - host = metadata._host = uri.asciiHost; - port = uri.port; - if (port === -1) - { - if (scheme === "http") - port = 80; - else if (scheme === "https") - port = 443; - else - throw HTTP_400; - } - } - catch (e) - { - // If the host is not a valid host on the server, the response MUST be a - // 400 (Bad Request) error message (section 5.2). Alternately, the URI - // is malformed. - throw HTTP_400; - } - - if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/") - throw HTTP_400; - } - - var splitter = fullPath.indexOf("?"); - if (splitter < 0) - { - // _queryString already set in ctor - metadata._path = fullPath; - } - else - { - metadata._path = fullPath.substring(0, splitter); - metadata._queryString = fullPath.substring(splitter + 1); - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - }, - - /** -* Parses all available HTTP headers in this until the header-ending CRLFCRLF, -* adding them to the store of headers in the request. -* -* @throws -* HTTP_400 if the headers are malformed -* @returns boolean -* true if all headers have now been processed, false otherwise -*/ - _parseHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - dumpn("*** _parseHeaders"); - - var data = this._data; - - var headers = this._metadata._headers; - var lastName = this._lastHeaderName; - var lastVal = this._lastHeaderValue; - - var line = {}; - while (true) - { - NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)), - lastName === undefined ? - "lastVal without lastName? lastVal: '" + lastVal + "'" : - "lastName without lastVal? lastName: '" + lastName + "'"); - - if (!data.readLine(line)) - { - // save any data we have from the header we might still be processing - this._lastHeaderName = lastName; - this._lastHeaderValue = lastVal; - return false; - } - - var lineText = line.value; - var firstChar = lineText.charAt(0); - - // blank line means end of headers - if (lineText == "") - { - // we're finished with the previous header - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - else - { - // no headers in request -- valid for HTTP/1.0 requests - } - - // either way, we're done processing headers - this._state = READER_IN_BODY; - return true; - } - else if (firstChar == " " || firstChar == "\t") - { - // multi-line header if we've already seen a header line - if (!lastName) - { - // we don't have a header to continue! - throw HTTP_400; - } - - // append this line's text to the value; starts with SP/HT, so no need - // for separating whitespace - lastVal += lineText; - } - else - { - // we have a new header, so set the old one (if one existed) - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - - var colon = lineText.indexOf(":"); // first colon must be splitter - if (colon < 1) - { - // no colon or missing header field-name - throw HTTP_400; - } - - // set header name, value (to be set in the next loop, usually) - lastName = lineText.substring(0, colon); - lastVal = lineText.substring(colon + 1); - } // empty, continuation, start of header - } // while (true) - } -}; - - -/** The character codes for CR and LF. */ -const CR = 0x0D, LF = 0x0A; - -/** -* Calculates the number of characters before the first CRLF pair in array, or -* -1 if the array contains no CRLF pair. -* -* @param array : Array -* an array of numbers in the range [0, 256), each representing a single -* character; the first CRLF is the lowest index i where -* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|, -* if such an |i| exists, and -1 otherwise -* @returns int -* the index of the first CRLF if any were present, -1 otherwise -*/ -function findCRLF(array) -{ - for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1)) - { - if (array[i + 1] == LF) - return i; - } - return -1; -} - - -/** -* A container which provides line-by-line access to the arrays of bytes with -* which it is seeded. -*/ -function LineData() -{ - /** An array of queued bytes from which to get line-based characters. */ - this._data = []; -} -LineData.prototype = -{ - /** -* Appends the bytes in the given array to the internal data cache maintained -* by this. -*/ - appendBytes: function(bytes) - { - Array.prototype.push.apply(this._data, bytes); - }, - - /** -* Removes and returns a line of data, delimited by CRLF, from this. -* -* @param out -* an object whose "value" property will be set to the first line of text -* present in this, sans CRLF, if this contains a full CRLF-delimited line -* of text; if this doesn't contain enough data, the value of the property -* is undefined -* @returns boolean -* true if a full line of data could be read from the data in this, false -* otherwise -*/ - readLine: function(out) - { - var data = this._data; - var length = findCRLF(data); - if (length < 0) - return false; - - // - // We have the index of the CR, so remove all the characters, including - // CRLF, from the array with splice, and convert the removed array into the - // corresponding string, from which we then strip the trailing CRLF. - // - // Getting the line in this matter acknowledges that substring is an O(1) - // operation in SpiderMonkey because strings are immutable, whereas two - // splices, both from the beginning of the data, are less likely to be as - // cheap as a single splice plus two extra character conversions. - // - var line = String.fromCharCode.apply(null, data.splice(0, length + 2)); - out.value = line.substring(0, length); - - return true; - }, - - /** -* Removes the bytes currently within this and returns them in an array. -* -* @returns Array -* the bytes within this when this method is called -*/ - purge: function() - { - var data = this._data; - this._data = []; - return data; - } -}; - - - -/** -* Creates a request-handling function for an nsIHttpRequestHandler object. -*/ -function createHandlerFunc(handler) -{ - return function(metadata, response) { handler.handle(metadata, response); }; -} - - -/** -* The default handler for directories; writes an HTML response containing a -* slightly-formatted directory listing. -*/ -function defaultIndexHandler(metadata, response) -{ - response.setHeader("Content-Type", "text/html", false); - - var path = htmlEscape(decodeURI(metadata.path)); - - // - // Just do a very basic bit of directory listings -- no need for too much - // fanciness, especially since we don't have a style sheet in which we can - // stick rules (don't want to pollute the default path-space). - // - - var body = '\ -\ -' + path + '\ -\ -\ -

' + path + '

\ -
    '; - - var directory = metadata.getProperty("directory").QueryInterface(Ci.nsILocalFile); - NS_ASSERT(directory && directory.isDirectory()); - - var fileList = []; - var files = directory.directoryEntries; - while (files.hasMoreElements()) - { - var f = files.getNext().QueryInterface(Ci.nsIFile); - var name = f.leafName; - if (!f.isHidden() && - (name.charAt(name.length - 1) != HIDDEN_CHAR || - name.charAt(name.length - 2) == HIDDEN_CHAR)) - fileList.push(f); - } - - fileList.sort(fileSort); - - for (var i = 0; i < fileList.length; i++) - { - var file = fileList[i]; - try - { - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - var sep = file.isDirectory() ? "/" : ""; - - // Note: using " to delimit the attribute here because encodeURIComponent - // passes through '. - var item = '
  1. ' + - htmlEscape(name) + sep + - '
  2. '; - - body += item; - } - catch (e) { /* some file system error, ignore the file */ } - } - - body += '
\ -\ -'; - - response.bodyOutputStream.write(body, body.length); -} - -/** -* Sorts a and b (nsIFile objects) into an aesthetically pleasing order. -*/ -function fileSort(a, b) -{ - var dira = a.isDirectory(), dirb = b.isDirectory(); - - if (dira && !dirb) - return -1; - if (dirb && !dira) - return 1; - - var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase(); - return nameb > namea ? -1 : 1; -} - - -/** -* Converts an externally-provided path into an internal path for use in -* determining file mappings. -* -* @param path -* the path to convert -* @param encoded -* true if the given path should be passed through decodeURI prior to -* conversion -* @throws URIError -* if path is incorrectly encoded -*/ -function toInternalPath(path, encoded) -{ - if (encoded) - path = decodeURI(path); - - var comps = path.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - if (comp.charAt(comp.length - 1) == HIDDEN_CHAR) - comps[i] = comp + HIDDEN_CHAR; - } - return comps.join("/"); -} - - -/** -* Adds custom-specified headers for the given file to the given response, if -* any such headers are specified. -* -* @param file -* the file on the disk which is to be written -* @param metadata -* metadata about the incoming request -* @param response -* the Response to which any specified headers/data should be written -* @throws HTTP_500 -* if an error occurred while processing custom-specified headers -*/ -function maybeAddHeaders(file, metadata, response) -{ - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - - var headerFile = file.parent; - headerFile.append(name + HEADERS_SUFFIX); - - if (!headerFile.exists()) - return; - - const PR_RDONLY = 0x01; - var fis = new FileInputStream(headerFile, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0); - lis.QueryInterface(Ci.nsIUnicharLineInputStream); - - var line = {value: ""}; - var more = lis.readLine(line); - - if (!more && line.value == "") - return; - - - // request line - - var status = line.value; - if (status.indexOf("HTTP ") == 0) - { - status = status.substring(5); - var space = status.indexOf(" "); - var code, description; - if (space < 0) - { - code = status; - description = ""; - } - else - { - code = status.substring(0, space); - description = status.substring(space + 1, status.length); - } - - response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description); - - line.value = ""; - more = lis.readLine(line); - } - - // headers - while (more || line.value != "") - { - var header = line.value; - var colon = header.indexOf(":"); - - response.setHeader(header.substring(0, colon), - header.substring(colon + 1, header.length), - false); // allow overriding server-set headers - - line.value = ""; - more = lis.readLine(line); - } - } - catch (e) - { - dumpn("WARNING: error in headers for " + metadata.path + ": " + e); - throw HTTP_500; - } - finally - { - fis.close(); - } -} - - -/** -* An object which handles requests for a server, executing default and -* overridden behaviors as instructed by the code which uses and manipulates it. -* Default behavior includes the paths / and /trace (diagnostics), with some -* support for HTTP error pages for various codes and fallback to HTTP 500 if -* those codes fail for any reason. -* -* @param server : nsHttpServer -* the server in which this handler is being used -*/ -function ServerHandler(server) -{ - // FIELDS - - /** -* The nsHttpServer instance associated with this handler. -*/ - this._server = server; - - /** -* A FileMap object containing the set of path->nsILocalFile mappings for -* all directory mappings set in the server (e.g., "/" for /var/www/html/, -* "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2). -* -* Note carefully: the leading and trailing "/" in each path (not file) are -* removed before insertion to simplify the code which uses this. You have -* been warned! -*/ - this._pathDirectoryMap = new FileMap(); - - /** -* Custom request handlers for the server in which this resides. Path-handler -* pairs are stored as property-value pairs in this property. -* -* @see ServerHandler.prototype._defaultPaths -*/ - this._overridePaths = {}; - - /** -* Custom request handlers for the server in which this resides. Prefix-handler -* pairs are stored as property-value pairs in this property. -*/ - this._overridePrefixes = {}; - - /** -* Custom request handlers for the error handlers in the server in which this -* resides. Path-handler pairs are stored as property-value pairs in this -* property. -* -* @see ServerHandler.prototype._defaultErrors -*/ - this._overrideErrors = {}; - - /** -* Maps file extensions to their MIME types in the server, overriding any -* mapping that might or might not exist in the MIME service. -*/ - this._mimeMappings = {}; - - /** -* The default handler for requests for directories, used to serve directories -* when no index file is present. -*/ - this._indexHandler = defaultIndexHandler; - - /** Per-path state storage for the server. */ - this._state = {}; - - /** Entire-server state storage. */ - this._sharedState = {}; - - /** Entire-server state storage for nsISupports values. */ - this._objectState = {}; -} -ServerHandler.prototype = -{ - // PUBLIC API - - /** -* Handles a request to this server, responding to the request appropriately -* and initiating server shutdown if necessary. -* -* This method never throws an exception. -* -* @param connection : Connection -* the connection for this request -*/ - handleResponse: function(connection) - { - var request = connection.request; - var response = new Response(connection); - - var path = request.path; - dumpn("*** path == " + path); - - try - { - try - { - if (path in this._overridePaths) - { - // explicit paths first, then files based on existing directory mappings, - // then (if the file doesn't exist) built-in server default paths - dumpn("calling override for " + path); - this._overridePaths[path](request, response); - } - else - { - let longestPrefix = ""; - for (let prefix in this._overridePrefixes) - { - if (prefix.length > longestPrefix.length && path.startsWith(prefix)) - { - longestPrefix = prefix; - } - } - if (longestPrefix.length > 0) - { - dumpn("calling prefix override for " + longestPrefix); - this._overridePrefixes[longestPrefix](request, response); - } - else - { - this._handleDefault(request, response); - } - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - if (!(e instanceof HttpError)) - { - dumpn("*** unexpected error: e == " + e); - throw HTTP_500; - } - if (e.code !== 404) - throw e; - - dumpn("*** default: " + (path in this._defaultPaths)); - - response = new Response(connection); - if (path in this._defaultPaths) - this._defaultPaths[path](request, response); - else - throw HTTP_404; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - var errorCode = "internal"; - - try - { - if (!(e instanceof HttpError)) - throw e; - - errorCode = e.code; - dumpn("*** errorCode == " + errorCode); - - response = new Response(connection); - if (e.customErrorHandling) - e.customErrorHandling(response); - this._handleError(errorCode, request, response); - return; - } - catch (e2) - { - dumpn("*** error handling " + errorCode + " error: " + - "e2 == " + e2 + ", shutting down server"); - - connection.server._requestQuit(); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (!file) - { - dumpn("*** unregistering '" + path + "' mapping"); - delete this._overridePaths[path]; - return; - } - - dumpn("*** registering '" + path + "' as mapping to " + file.path); - file = file.clone(); - - var self = this; - this._overridePaths[path] = - function(request, response) - { - if (!file.exists()) - throw HTTP_404; - - response.setStatusLine(request.httpVersion, 200, "OK"); - self._writeFileResponse(request, file, response, 0, file.fileSize); - }; - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - // XXX true path validation! - if (path.charAt(0) != "/") - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePaths, path); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - // XXX true prefix validation! - if (!(prefix.startsWith("/") && prefix.endsWith("/"))) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePrefixes, prefix); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // strip off leading and trailing '/' so that we can use lastIndexOf when - // determining exactly how a path maps onto a mapped directory -- - // conditional is required here to deal with "/".substring(1, 0) being - // converted to "/".substring(0, 1) per the JS specification - var key = path.length == 1 ? "" : path.substring(1, path.length - 1); - - // the path-to-directory mapping code requires that the first character not - // be "/", or it will go into an infinite loop - if (key.charAt(0) == "/") - throw Cr.NS_ERROR_INVALID_ARG; - - key = toInternalPath(key, false); - - if (directory) - { - dumpn("*** mapping '" + path + "' to the location " + directory.path); - this._pathDirectoryMap.put(key, directory); - } - else - { - dumpn("*** removing mapping for '" + path + "'"); - this._pathDirectoryMap.put(key, null); - } - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(err, handler) - { - if (!(err in HTTP_ERROR_CODES)) - dumpn("*** WARNING: registering non-HTTP/1.1 error code " + - "(" + err + ") handler -- was this intentional?"); - - this._handlerToField(handler, this._overrideErrors, err); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - if (!handler) - handler = defaultIndexHandler; - else if (typeof(handler) != "function") - handler = createHandlerFunc(handler); - - this._indexHandler = handler; - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - if (!type) - delete this._mimeMappings[ext]; - else - this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type); - }, - - // PRIVATE API - - /** -* Sets or remove (if handler is null) a handler in an object with a key. -* -* @param handler -* a handler, either function or an nsIHttpRequestHandler -* @param dict -* The object to attach the handler to. -* @param key -* The field name of the handler. -*/ - _handlerToField: function(handler, dict, key) - { - // for convenience, handler can be a function if this is run from xpcshell - if (typeof(handler) == "function") - dict[key] = handler; - else if (handler) - dict[key] = createHandlerFunc(handler); - else - delete dict[key]; - }, - - /** -* Handles a request which maps to a file in the local filesystem (if a base -* path has already been set; otherwise the 404 error is thrown). -* -* @param metadata : Request -* metadata for the incoming request -* @param response : Response -* an uninitialized Response to the given request, to be initialized by a -* request handler -* @throws HTTP_### -* if an HTTP error occurred (usually HTTP_404); note that in this case the -* calling code must handle post-processing of the response -*/ - _handleDefault: function(metadata, response) - { - dumpn("*** _handleDefault()"); - - response.setStatusLine(metadata.httpVersion, 200, "OK"); - - var path = metadata.path; - NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">"); - - // determine the actual on-disk file; this requires finding the deepest - // path-to-directory mapping in the requested URL - var file = this._getFileForPath(path); - - // the "file" might be a directory, in which case we either serve the - // contained index.html or make the index handler write the response - if (file.exists() && file.isDirectory()) - { - file.append("index.html"); // make configurable? - if (!file.exists() || file.isDirectory()) - { - metadata._ensurePropertyBag(); - metadata._bag.setPropertyAsInterface("directory", file.parent); - this._indexHandler(metadata, response); - return; - } - } - - // alternately, the file might not exist - if (!file.exists()) - throw HTTP_404; - - var start, end; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) && - metadata.hasHeader("Range") && - this._getTypeFromFile(file) !== SJS_TYPE) - { - var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/); - if (!rangeMatch) - throw HTTP_400; - - if (rangeMatch[1] !== undefined) - start = parseInt(rangeMatch[1], 10); - - if (rangeMatch[2] !== undefined) - end = parseInt(rangeMatch[2], 10); - - if (start === undefined && end === undefined) - throw HTTP_400; - - // No start given, so the end is really the count of bytes from the - // end of the file. - if (start === undefined) - { - start = Math.max(0, file.fileSize - end); - end = file.fileSize - 1; - } - - // start and end are inclusive - if (end === undefined || end >= file.fileSize) - end = file.fileSize - 1; - - if (start !== undefined && start >= file.fileSize) { - var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable"); - HTTP_416.customErrorHandling = function(errorResponse) - { - maybeAddHeaders(file, metadata, errorResponse); - }; - throw HTTP_416; - } - - if (end < start) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - start = 0; - end = file.fileSize - 1; - } - else - { - response.setStatusLine(metadata.httpVersion, 206, "Partial Content"); - var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize; - response.setHeader("Content-Range", contentRange); - } - } - else - { - start = 0; - end = file.fileSize - 1; - } - - // finally... - dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " + - start + " to " + end + " inclusive"); - this._writeFileResponse(metadata, file, response, start, end - start + 1); - }, - - /** -* Writes an HTTP response for the given file, including setting headers for -* file metadata. -* -* @param metadata : Request -* the Request for which a response is being generated -* @param file : nsILocalFile -* the file which is to be sent in the response -* @param response : Response -* the response to which the file should be written -* @param offset: uint -* the byte offset to skip to when writing -* @param count: uint -* the number of bytes to write -*/ - _writeFileResponse: function(metadata, file, response, offset, count) - { - const PR_RDONLY = 0x01; - - var type = this._getTypeFromFile(file); - if (type === SJS_TYPE) - { - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var sis = new ScriptableInputStream(fis); - var s = Cu.Sandbox(gGlobalObject); - s.importFunction(dump, "dump"); - - // Define a basic key-value state-preservation API across requests, with - // keys initially corresponding to the empty string. - var self = this; - var path = metadata.path; - s.importFunction(function getState(k) - { - return self._getState(path, k); - }); - s.importFunction(function setState(k, v) - { - self._setState(path, k, v); - }); - s.importFunction(function getSharedState(k) - { - return self._getSharedState(k); - }); - s.importFunction(function setSharedState(k, v) - { - self._setSharedState(k, v); - }); - s.importFunction(function getObjectState(k, callback) - { - callback(self._getObjectState(k)); - }); - s.importFunction(function setObjectState(k, v) - { - self._setObjectState(k, v); - }); - s.importFunction(function registerPathHandler(p, h) - { - self.registerPathHandler(p, h); - }); - - // Make it possible for sjs files to access their location - this._setState(path, "__LOCATION__", file.path); - - try - { - // Alas, the line number in errors dumped to console when calling the - // request handler is simply an offset from where we load the SJS file. - // Work around this in a reasonably non-fragile way by dynamically - // getting the line number where we evaluate the SJS file. Don't - // separate these two lines! - var line = new Error().lineNumber; - Cu.evalInSandbox(sis.read(file.fileSize), s); - } - catch (e) - { - dumpn("*** syntax error in SJS at " + file.path + ": " + e); - throw HTTP_500; - } - - try - { - s.handleRequest(metadata, response); - } - catch (e) - { - dump("*** error running SJS at " + file.path + ": " + - e + " on line " + - (e instanceof Error - ? e.lineNumber + " in httpd.js" - : (e.lineNumber - line)) + "\n"); - throw HTTP_500; - } - } - finally - { - fis.close(); - } - } - else - { - try - { - response.setHeader("Last-Modified", - toDateString(file.lastModifiedTime), - false); - } - catch (e) { /* lastModifiedTime threw, ignore */ } - - response.setHeader("Content-Type", type, false); - maybeAddHeaders(file, metadata, response); - response.setHeader("Content-Length", "" + count, false); - - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - offset = offset || 0; - count = count || file.fileSize; - NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset"); - NS_ASSERT(count >= 0, "bad count"); - NS_ASSERT(offset + count <= file.fileSize, "bad total data size"); - - try - { - if (offset !== 0) - { - // Seek (or read, if seeking isn't supported) to the correct offset so - // the data sent to the client matches the requested range. - if (fis instanceof Ci.nsISeekableStream) - fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset); - else - new ScriptableInputStream(fis).read(offset); - } - } - catch (e) - { - fis.close(); - throw e; - } - - let writeMore = function writeMore() - { - gThreadManager.currentThread - .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL); - } - - var input = new BinaryInputStream(fis); - var output = new BinaryOutputStream(response.bodyOutputStream); - var writeData = - { - run: function() - { - var chunkSize = Math.min(65536, count); - count -= chunkSize; - NS_ASSERT(count >= 0, "underflow"); - - try - { - var data = input.readByteArray(chunkSize); - NS_ASSERT(data.length === chunkSize, - "incorrect data returned? got " + data.length + - ", expected " + chunkSize); - output.writeByteArray(data, data.length); - if (count === 0) - { - fis.close(); - response.finish(); - } - else - { - writeMore(); - } - } - catch (e) - { - try - { - fis.close(); - } - finally - { - response.finish(); - } - throw e; - } - } - }; - - writeMore(); - - // Now that we know copying will start, flag the response as async. - response.processAsync(); - } - }, - - /** -* Get the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getState: function(path, k) - { - var state = this._state; - if (path in state && k in state[path]) - return state[path][k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setState: function(path, k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - var state = this._state; - if (!(path in state)) - state[path] = {}; - state[path][k] = v; - }, - - /** -* Get the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getSharedState: function(k) - { - var state = this._sharedState; - if (k in state) - return state[k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setSharedState: function(k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - this._sharedState[k] = v; - }, - - /** -* Returns the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be returned -* @returns nsISupports -* the corresponding object, or null if none was present -*/ - _getObjectState: function(k) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - return this._objectState[k] || null; - }, - - /** -* Sets the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be set -* @param v : nsISupports -* the object to be associated with the given key; may be null -*/ - _setObjectState: function(k, v) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - if (typeof v !== "object") - throw new Error("non-object value passed"); - if (v && !("QueryInterface" in v)) - { - throw new Error("must pass an nsISupports; use wrappedJSObject to ease " + - "pain when using the server from JS"); - } - - this._objectState[k] = v; - }, - - /** -* Gets a content-type for the given file, first by checking for any custom -* MIME-types registered with this handler for the file's extension, second by -* asking the global MIME service for a content-type, and finally by failing -* over to application/octet-stream. -* -* @param file : nsIFile -* the nsIFile for which to get a file type -* @returns string -* the best content-type which can be determined for the file -*/ - _getTypeFromFile: function(file) - { - try - { - var name = file.leafName; - var dot = name.lastIndexOf("."); - if (dot > 0) - { - var ext = name.slice(dot + 1); - if (ext in this._mimeMappings) - return this._mimeMappings[ext]; - } - return Cc["@mozilla.org/uriloader/external-helper-app-service;1"] - .getService(Ci.nsIMIMEService) - .getTypeFromFile(file); - } - catch (e) - { - return "application/octet-stream"; - } - }, - - /** -* Returns the nsILocalFile which corresponds to the path, as determined using -* all registered path->directory mappings and any paths which are explicitly -* overridden. -* -* @param path : string -* the server path for which a file should be retrieved, e.g. "/foo/bar" -* @throws HttpError -* when the correct action is the corresponding HTTP error (i.e., because no -* mapping was found for a directory in path, the referenced file doesn't -* exist, etc.) -* @returns nsILocalFile -* the file to be sent as the response to a request for the path -*/ - _getFileForPath: function(path) - { - // decode and add underscores as necessary - try - { - path = toInternalPath(path, true); - } - catch (e) - { - throw HTTP_400; // malformed path - } - - // next, get the directory which contains this path - var pathMap = this._pathDirectoryMap; - - // An example progression of tmp for a path "/foo/bar/baz/" might be: - // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", "" - var tmp = path.substring(1); - while (true) - { - // do we have a match for current head of the path? - var file = pathMap.get(tmp); - if (file) - { - // XXX hack; basically disable showing mapping for /foo/bar/ when the - // requested path was /foo/bar, because relative links on the page - // will all be incorrect -- we really need the ability to easily - // redirect here instead - if (tmp == path.substring(1) && - tmp.length != 0 && - tmp.charAt(tmp.length - 1) != "/") - file = null; - else - break; - } - - // if we've finished trying all prefixes, exit - if (tmp == "") - break; - - tmp = tmp.substring(0, tmp.lastIndexOf("/")); - } - - // no mapping applies, so 404 - if (!file) - throw HTTP_404; - - - // last, get the file for the path within the determined directory - var parentFolder = file.parent; - var dirIsRoot = (parentFolder == null); - - // Strategy here is to append components individually, making sure we - // never move above the given directory; this allows paths such as - // "/foo/../bar" but prevents paths such as "/../base-sibling"; - // this component-wise approach also means the code works even on platforms - // which don't use "/" as the directory separator, such as Windows - var leafPath = path.substring(tmp.length + 1); - var comps = leafPath.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - - if (comp == "..") - file = file.parent; - else if (comp == "." || comp == "") - continue; - else - file.append(comp); - - if (!dirIsRoot && file.equals(parentFolder)) - throw HTTP_403; - } - - return file; - }, - - /** -* Writes the error page for the given HTTP error code over the given -* connection. -* -* @param errorCode : uint -* the HTTP error code to be used -* @param connection : Connection -* the connection on which the error occurred -*/ - handleError: function(errorCode, connection) - { - var response = new Response(connection); - - dumpn("*** error in request: " + errorCode); - - this._handleError(errorCode, new Request(connection.port), response); - }, - - /** -* Handles a request which generates the given error code, using the -* user-defined error handler if one has been set, gracefully falling back to -* the x00 status code if the code has no handler, and failing to status code -* 500 if all else fails. -* -* @param errorCode : uint -* the HTTP error which is to be returned -* @param metadata : Request -* metadata for the request, which will often be incomplete since this is an -* error -* @param response : Response -* an uninitialized Response should be initialized when this method -* completes with information which represents the desired error code in the -* ideal case or a fallback code in abnormal circumstances (i.e., 500 is a -* fallback for 505, per HTTP specs) -*/ - _handleError: function(errorCode, metadata, response) - { - if (!metadata) - throw Cr.NS_ERROR_NULL_POINTER; - - var errorX00 = errorCode - (errorCode % 100); - - try - { - if (!(errorCode in HTTP_ERROR_CODES)) - dumpn("*** WARNING: requested invalid error: " + errorCode); - - // RFC 2616 says that we should try to handle an error by its class if we - // can't otherwise handle it -- if that fails, we revert to handling it as - // a 500 internal server error, and if that fails we throw and shut down - // the server - - // actually handle the error - try - { - if (errorCode in this._overrideErrors) - this._overrideErrors[errorCode](metadata, response); - else - this._defaultErrors[errorCode](metadata, response); - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - // don't retry the handler that threw - if (errorX00 == errorCode) - throw HTTP_500; - - dumpn("*** error in handling for error code " + errorCode + ", " + - "falling back to " + errorX00 + "..."); - response = new Response(response._connection); - if (errorX00 in this._overrideErrors) - this._overrideErrors[errorX00](metadata, response); - else if (errorX00 in this._defaultErrors) - this._defaultErrors[errorX00](metadata, response); - else - throw HTTP_500; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(); - return; - } - - // we've tried everything possible for a meaningful error -- now try 500 - dumpn("*** error in handling for error code " + errorX00 + ", falling " + - "back to 500..."); - - try - { - response = new Response(response._connection); - if (500 in this._overrideErrors) - this._overrideErrors[500](metadata, response); - else - this._defaultErrors[500](metadata, response); - } - catch (e2) - { - dumpn("*** multiple errors in default error handlers!"); - dumpn("*** e == " + e + ", e2 == " + e2); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // FIELDS - - /** -* This object contains the default handlers for the various HTTP error codes. -*/ - _defaultErrors: - { - 400: function(metadata, response) - { - // none of the data in metadata is reliable, so hard-code everything here - response.setStatusLine("1.1", 400, "Bad Request"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Bad request\n"; - response.bodyOutputStream.write(body, body.length); - }, - 403: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 403, "Forbidden"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -403 Forbidden\ -\ -

403 Forbidden

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 404: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 404, "Not Found"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -404 Not Found\ -\ -

404 Not Found

\ -

\ -" + - htmlEscape(metadata.path) + - " was not found.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 416: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 416, - "Requested Range Not Satisfiable"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -\ -416 Requested Range Not Satisfiable\ -\ -

416 Requested Range Not Satisfiable

\ -

The byte range was not valid for the\ -requested resource.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 500: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 500, - "Internal Server Error"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -500 Internal Server Error\ -\ -

500 Internal Server Error

\ -

Something's broken in this server and\ -needs to be fixed.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 501: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 501, "Not Implemented"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -501 Not Implemented\ -\ -

501 Not Implemented

\ -

This server is not (yet) Apache.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 505: function(metadata, response) - { - response.setStatusLine("1.1", 505, "HTTP Version Not Supported"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -505 HTTP Version Not Supported\ -\ -

505 HTTP Version Not Supported

\ -

This server only supports HTTP/1.0 and HTTP/1.1\ -connections.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - } - }, - - /** -* Contains handlers for the default set of URIs contained in this server. -*/ - _defaultPaths: - { - "/": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -httpd.js\ -\ -

httpd.js

\ -

If you're seeing this page, httpd.js is up and\ -serving requests! Now set a base path and serve some\ -files!

\ -\ -"; - - response.bodyOutputStream.write(body, body.length); - }, - - "/trace": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Request-URI: " + - metadata.scheme + "://" + metadata.host + ":" + metadata.port + - metadata.path + "\n\n"; - body += "Request (semantically equivalent, slightly reformatted):\n\n"; - body += metadata.method + " " + metadata.path; - - if (metadata.queryString) - body += "?" + metadata.queryString; - - body += " HTTP/" + metadata.httpVersion + "\r\n"; - - var headEnum = metadata.headers; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n"; - } - - response.bodyOutputStream.write(body, body.length); - } - } -}; - - -/** -* Maps absolute paths to files on the local file system (as nsILocalFiles). -*/ -function FileMap() -{ - /** Hash which will map paths to nsILocalFiles. */ - this._map = {}; -} -FileMap.prototype = -{ - // PUBLIC API - - /** -* Maps key to a clone of the nsILocalFile value if value is non-null; -* otherwise, removes any extant mapping for key. -* -* @param key : string -* string to which a clone of value is mapped -* @param value : nsILocalFile -* the file to map to key, or null to remove a mapping -*/ - put: function(key, value) - { - if (value) - this._map[key] = value.clone(); - else - delete this._map[key]; - }, - - /** -* Returns a clone of the nsILocalFile mapped to key, or null if no such -* mapping exists. -* -* @param key : string -* key to which the returned file maps -* @returns nsILocalFile -* a clone of the mapped file, or null if no mapping exists -*/ - get: function(key) - { - var val = this._map[key]; - return val ? val.clone() : null; - } -}; - - -// Response CONSTANTS - -// token = * -// CHAR = -// CTL = -// separators = "(" | ")" | "<" | ">" | "@" -// | "," | ";" | ":" | "\" | <"> -// | "/" | "[" | "]" | "?" | "=" -// | "{" | "}" | SP | HT -const IS_TOKEN_ARRAY = - [0, 0, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 0, 0, 0, 0, 0, 0, 0, // 24 - - 0, 1, 0, 1, 1, 1, 1, 1, // 32 - 0, 0, 1, 1, 0, 1, 1, 0, // 40 - 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 1, 1, 0, 0, 0, 0, 0, 0, // 56 - - 0, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, // 72 - 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 1, 1, 1, 0, 0, 0, 1, 1, // 88 - - 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, // 104 - 1, 1, 1, 1, 1, 1, 1, 1, // 112 - 1, 1, 1, 0, 1, 0, 1]; // 120 - - -/** -* Determines whether the given character code is a CTL. -* -* @param code : uint -* the character code -* @returns boolean -* true if code is a CTL, false otherwise -*/ -function isCTL(code) -{ - return (code >= 0 && code <= 31) || (code == 127); -} - -/** -* Represents a response to an HTTP request, encapsulating all details of that -* response. This includes all headers, the HTTP version, status code and -* explanation, and the entity itself. -* -* @param connection : Connection -* the connection over which this response is to be written -*/ -function Response(connection) -{ - /** The connection over which this response will be written. */ - this._connection = connection; - - /** -* The HTTP version of this response; defaults to 1.1 if not set by the -* handler. -*/ - this._httpVersion = nsHttpVersion.HTTP_1_1; - - /** -* The HTTP code of this response; defaults to 200. -*/ - this._httpCode = 200; - - /** -* The description of the HTTP code in this response; defaults to "OK". -*/ - this._httpDescription = "OK"; - - /** -* An nsIHttpHeaders object in which the headers in this response should be -* stored. This property is null after the status line and headers have been -* written to the network, and it may be modified up until it is cleared, -* except if this._finished is set first (in which case headers are written -* asynchronously in response to a finish() call not preceded by -* flushHeaders()). -*/ - this._headers = new nsHttpHeaders(); - - /** -* Set to true when this response is ended (completely constructed if possible -* and the connection closed); further actions on this will then fail. -*/ - this._ended = false; - - /** -* A stream used to hold data written to the body of this response. -*/ - this._bodyOutputStream = null; - - /** -* A stream containing all data that has been written to the body of this -* response so far. (Async handlers make the data contained in this -* unreliable as a way of determining content length in general, but auxiliary -* saved information can sometimes be used to guarantee reliability.) -*/ - this._bodyInputStream = null; - - /** -* A stream copier which copies data to the network. It is initially null -* until replaced with a copier for response headers; when headers have been -* fully sent it is replaced with a copier for the response body, remaining -* so for the duration of response processing. -*/ - this._asyncCopier = null; - - /** -* True if this response has been designated as being processed -* asynchronously rather than for the duration of a single call to -* nsIHttpRequestHandler.handle. -*/ - this._processAsync = false; - - /** -* True iff finish() has been called on this, signaling that no more changes -* to this may be made. -*/ - this._finished = false; - - /** -* True iff powerSeized() has been called on this, signaling that this -* response is to be handled manually by the response handler (which may then -* send arbitrary data in response, even non-HTTP responses). -*/ - this._powerSeized = false; -} -Response.prototype = -{ - // PUBLIC CONSTRUCTION API - - // - // see nsIHttpResponse.bodyOutputStream - // - get bodyOutputStream() - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - if (!this._bodyOutputStream) - { - var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX, - null); - this._bodyOutputStream = pipe.outputStream; - this._bodyInputStream = pipe.inputStream; - if (this._processAsync || this._powerSeized) - this._startAsyncProcessor(); - } - - return this._bodyOutputStream; - }, - - // - // see nsIHttpResponse.write - // - write: function(data) - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - var dataAsString = String(data); - this.bodyOutputStream.write(dataAsString, dataAsString.length); - }, - - // - // see nsIHttpResponse.setStatusLine - // - setStatusLine: function(httpVersion, code, description) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - if (!(code >= 0 && code < 1000)) - throw Cr.NS_ERROR_INVALID_ARG; - - try - { - var httpVer; - // avoid version construction for the most common cases - if (!httpVersion || httpVersion == "1.1") - httpVer = nsHttpVersion.HTTP_1_1; - else if (httpVersion == "1.0") - httpVer = nsHttpVersion.HTTP_1_0; - else - httpVer = new nsHttpVersion(httpVersion); - } - catch (e) - { - throw Cr.NS_ERROR_INVALID_ARG; - } - - // Reason-Phrase = * - // TEXT = - // - // XXX this ends up disallowing octets which aren't Unicode, I think -- not - // much to do if description is IDL'd as string - if (!description) - description = ""; - for (var i = 0; i < description.length; i++) - if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t") - throw Cr.NS_ERROR_INVALID_ARG; - - // set the values only after validation to preserve atomicity - this._httpDescription = description; - this._httpCode = code; - this._httpVersion = httpVer; - }, - - // - // see nsIHttpResponse.setHeader - // - setHeader: function(name, value, merge) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - this._headers.setHeader(name, value, merge); - }, - - // - // see nsIHttpResponse.processAsync - // - processAsync: function() - { - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._processAsync) - return; - this._ensureAlive(); - - dumpn("*** processing connection " + this._connection.number + " async"); - this._processAsync = true; - - /* -* Either the bodyOutputStream getter or this method is responsible for -* starting the asynchronous processor and catching writes of data to the -* response body of async responses as they happen, for the purpose of -* forwarding those writes to the actual connection's output stream. -* If bodyOutputStream is accessed first, calling this method will create -* the processor (when it first is clear that body data is to be written -* immediately, not buffered). If this method is called first, accessing -* bodyOutputStream will create the processor. If only this method is -* called, we'll write nothing, neither headers nor the nonexistent body, -* until finish() is called. Since that delay is easily avoided by simply -* getting bodyOutputStream or calling write(""), we don't worry about it. -*/ - if (this._bodyOutputStream && !this._asyncCopier) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.seizePower - // - seizePower: function() - { - if (this._processAsync) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - return; - this._ensureAlive(); - - dumpn("*** forcefully seizing power over connection " + - this._connection.number + "..."); - - // Purge any already-written data without sending it. We could as easily - // swap out the streams entirely, but that makes it possible to acquire and - // unknowingly use a stale reference, so we require there only be one of - // each stream ever for any response to avoid this complication. - if (this._asyncCopier) - this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED); - this._asyncCopier = null; - if (this._bodyOutputStream) - { - var input = new BinaryInputStream(this._bodyInputStream); - var avail; - while ((avail = input.available()) > 0) - input.readByteArray(avail); - } - - this._powerSeized = true; - if (this._bodyOutputStream) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.finish - // - finish: function() - { - if (!this._processAsync && !this._powerSeized) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._finished) - return; - - dumpn("*** finishing connection " + this._connection.number); - this._startAsyncProcessor(); // in case bodyOutputStream was never accessed - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - this._finished = true; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // POST-CONSTRUCTION API (not exposed externally) - - /** -* The HTTP version number of this, as a string (e.g. "1.1"). -*/ - get httpVersion() - { - this._ensureAlive(); - return this._httpVersion.toString(); - }, - - /** -* The HTTP status code of this response, as a string of three characters per -* RFC 2616. -*/ - get httpCode() - { - this._ensureAlive(); - - var codeString = (this._httpCode < 10 ? "0" : "") + - (this._httpCode < 100 ? "0" : "") + - this._httpCode; - return codeString; - }, - - /** -* The description of the HTTP status code of this response, or "" if none is -* set. -*/ - get httpDescription() - { - this._ensureAlive(); - - return this._httpDescription; - }, - - /** -* The headers in this response, as an nsHttpHeaders object. -*/ - get headers() - { - this._ensureAlive(); - - return this._headers; - }, - - // - // see nsHttpHeaders.getHeader - // - getHeader: function(name) - { - this._ensureAlive(); - - return this._headers.getHeader(name); - }, - - /** -* Determines whether this response may be abandoned in favor of a newly -* constructed response. A response may be abandoned only if it is not being -* sent asynchronously and if raw control over it has not been taken from the -* server. -* -* @returns boolean -* true iff no data has been written to the network -*/ - partiallySent: function() - { - dumpn("*** partiallySent()"); - return this._processAsync || this._powerSeized; - }, - - /** -* If necessary, kicks off the remaining request processing needed to be done -* after a request handler performs its initial work upon this response. -*/ - complete: function() - { - dumpn("*** complete()"); - if (this._processAsync || this._powerSeized) - { - NS_ASSERT(this._processAsync ^ this._powerSeized, - "can't both send async and relinquish power"); - return; - } - - NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?"); - - this._startAsyncProcessor(); - - // Now make sure we finish processing this request! - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - }, - - /** -* Abruptly ends processing of this response, usually due to an error in an -* incoming request but potentially due to a bad error handler. Since we -* cannot handle the error in the usual way (giving an HTTP error page in -* response) because data may already have been sent (or because the response -* might be expected to have been generated asynchronously or completely from -* scratch by the handler), we stop processing this response and abruptly -* close the connection. -* -* @param e : Error -* the exception which precipitated this abort, or null if no such exception -* was generated -*/ - abort: function(e) - { - dumpn("*** abort(<" + e + ">)"); - - // This response will be ended by the processor if one was created. - var copier = this._asyncCopier; - if (copier) - { - // We dispatch asynchronously here so that any pending writes of data to - // the connection will be deterministically written. This makes it easier - // to specify exact behavior, and it makes observable behavior more - // predictable for clients. Note that the correctness of this depends on - // callbacks in response to _waitToReadData in WriteThroughCopier - // happening asynchronously with respect to the actual writing of data to - // bodyOutputStream, as they currently do; if they happened synchronously, - // an event which ran before this one could write more data to the - // response body before we get around to canceling the copier. We have - // tests for this in test_seizepower.js, however, and I can't think of a - // way to handle both cases without removing bodyOutputStream access and - // moving its effective write(data, length) method onto Response, which - // would be slower and require more code than this anyway. - gThreadManager.currentThread.dispatch({ - run: function() - { - dumpn("*** canceling copy asynchronously..."); - copier.cancel(Cr.NS_ERROR_UNEXPECTED); - } - }, Ci.nsIThread.DISPATCH_NORMAL); - } - else - { - this.end(); - } - }, - - /** -* Closes this response's network connection, marks the response as finished, -* and notifies the server handler that the request is done being processed. -*/ - end: function() - { - NS_ASSERT(!this._ended, "ending this response twice?!?!"); - - this._connection.close(); - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - - this._finished = true; - this._ended = true; - }, - - // PRIVATE IMPLEMENTATION - - /** -* Sends the status line and headers of this response if they haven't been -* sent and initiates the process of copying data written to this response's -* body to the network. -*/ - _startAsyncProcessor: function() - { - dumpn("*** _startAsyncProcessor()"); - - // Handle cases where we're being called a second time. The former case - // happens when this is triggered both by complete() and by processAsync(), - // while the latter happens when processAsync() in conjunction with sent - // data causes abort() to be called. - if (this._asyncCopier || this._ended) - { - dumpn("*** ignoring second call to _startAsyncProcessor"); - return; - } - - // Send headers if they haven't been sent already and should be sent, then - // asynchronously continue to send the body. - if (this._headers && !this._powerSeized) - { - this._sendHeaders(); - return; - } - - this._headers = null; - this._sendBody(); - }, - - /** -* Signals that all modifications to the response status line and headers are -* complete and then sends that data over the network to the client. Once -* this method completes, a different response to the request that resulted -* in this response cannot be sent -- the only possible action in case of -* error is to abort the response and close the connection. -*/ - _sendHeaders: function() - { - dumpn("*** _sendHeaders()"); - - NS_ASSERT(this._headers); - NS_ASSERT(!this._powerSeized); - - // request-line - var statusLine = "HTTP/" + this.httpVersion + " " + - this.httpCode + " " + - this.httpDescription + "\r\n"; - - // header post-processing - - var headers = this._headers; - headers.setHeader("Connection", "close", false); - headers.setHeader("Server", "httpd.js", false); - if (!headers.hasHeader("Date")) - headers.setHeader("Date", toDateString(Date.now()), false); - - // Any response not being processed asynchronously must have an associated - // Content-Length header for reasons of backwards compatibility with the - // initial server, which fully buffered every response before sending it. - // Beyond that, however, it's good to do this anyway because otherwise it's - // impossible to test behaviors that depend on the presence or absence of a - // Content-Length header. - if (!this._processAsync) - { - dumpn("*** non-async response, set Content-Length"); - - var bodyStream = this._bodyInputStream; - var avail = bodyStream ? bodyStream.available() : 0; - - // XXX assumes stream will always report the full amount of data available - headers.setHeader("Content-Length", "" + avail, false); - } - - - // construct and send response - dumpn("*** header post-processing completed, sending response head..."); - - // request-line - var preambleData = [statusLine]; - - // headers - var headEnum = headers.enumerator; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - var values = headers.getHeaderValues(fieldName); - for (var i = 0, sz = values.length; i < sz; i++) - preambleData.push(fieldName + ": " + values[i] + "\r\n"); - } - - // end request-line/headers - preambleData.push("\r\n"); - - var preamble = preambleData.join(""); - - var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null); - responseHeadPipe.outputStream.write(preamble, preamble.length); - - var response = this; - var copyObserver = - { - onStartRequest: function(request, cx) - { - dumpn("*** preamble copying started"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** preamble copying complete " + - "[status=0x" + statusCode.toString(16) + "]"); - - if (!components.isSuccessCode(statusCode)) - { - dumpn("!!! header copying problems: non-success statusCode, " + - "ending response"); - - response.end(); - } - else - { - response._sendBody(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - var headerCopier = this._asyncCopier = - new WriteThroughCopier(responseHeadPipe.inputStream, - this._connection.output, - copyObserver, null); - - responseHeadPipe.outputStream.close(); - - // Forbid setting any more headers or modifying the request line. - this._headers = null; - }, - - /** -* Asynchronously writes the body of the response (or the entire response, if -* seizePower() has been called) to the network. -*/ - _sendBody: function() - { - dumpn("*** _sendBody"); - - NS_ASSERT(!this._headers, "still have headers around but sending body?"); - - // If no body data was written, we're done - if (!this._bodyInputStream) - { - dumpn("*** empty body, response finished"); - this.end(); - return; - } - - var response = this; - var copyObserver = - { - onStartRequest: function(request, context) - { - dumpn("*** onStartRequest"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]"); - - if (statusCode === Cr.NS_BINDING_ABORTED) - { - dumpn("*** terminating copy observer without ending the response"); - } - else - { - if (!components.isSuccessCode(statusCode)) - dumpn("*** WARNING: non-success statusCode in onStopRequest"); - - response.end(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - dumpn("*** starting async copier of body data..."); - this._asyncCopier = - new WriteThroughCopier(this._bodyInputStream, this._connection.output, - copyObserver, null); - }, - - /** Ensures that this hasn't been ended. */ - _ensureAlive: function() - { - NS_ASSERT(!this._ended, "not handling response lifetime correctly"); - } -}; - -/** -* Size of the segments in the buffer used in storing response data and writing -* it to the socket. -*/ -Response.SEGMENT_SIZE = 8192; - -/** Serves double duty in WriteThroughCopier implementation. */ -function notImplemented() -{ - throw Cr.NS_ERROR_NOT_IMPLEMENTED; -} - -/** Returns true iff the given exception represents stream closure. */ -function streamClosed(e) -{ - return e === Cr.NS_BASE_STREAM_CLOSED || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED); -} - -/** Returns true iff the given exception represents a blocked stream. */ -function wouldBlock(e) -{ - return e === Cr.NS_BASE_STREAM_WOULD_BLOCK || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK); -} - -/** -* Copies data from source to sink as it becomes available, when that data can -* be written to sink without blocking. -* -* @param source : nsIAsyncInputStream -* the stream from which data is to be read -* @param sink : nsIAsyncOutputStream -* the stream to which data is to be copied -* @param observer : nsIRequestObserver -* an observer which will be notified when the copy starts and finishes -* @param context : nsISupports -* context passed to observer when notified of start/stop -* @throws NS_ERROR_NULL_POINTER -* if source, sink, or observer are null -*/ -function WriteThroughCopier(source, sink, observer, context) -{ - if (!source || !sink || !observer) - throw Cr.NS_ERROR_NULL_POINTER; - - /** Stream from which data is being read. */ - this._source = source; - - /** Stream to which data is being written. */ - this._sink = sink; - - /** Observer watching this copy. */ - this._observer = observer; - - /** Context for the observer watching this. */ - this._context = context; - - /** -* True iff this is currently being canceled (cancel has been called, the -* callback may not yet have been made). -*/ - this._canceled = false; - - /** -* False until all data has been read from input and written to output, at -* which point this copy is completed and cancel() is asynchronously called. -*/ - this._completed = false; - - /** Required by nsIRequest, meaningless. */ - this.loadFlags = 0; - /** Required by nsIRequest, meaningless. */ - this.loadGroup = null; - /** Required by nsIRequest, meaningless. */ - this.name = "response-body-copy"; - - /** Status of this request. */ - this.status = Cr.NS_OK; - - /** Arrays of byte strings waiting to be written to output. */ - this._pendingData = []; - - // start copying - try - { - observer.onStartRequest(this, context); - this._waitToReadData(); - this._waitForSinkClosure(); - } - catch (e) - { - dumpn("!!! error starting copy: " + e + - ("lineNumber" in e ? ", line " + e.lineNumber : "")); - dumpn(e.stack); - this.cancel(Cr.NS_ERROR_UNEXPECTED); - } -} -WriteThroughCopier.prototype = -{ - /* nsISupports implementation */ - - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIInputStreamCallback) || - iid.equals(Ci.nsIOutputStreamCallback) || - iid.equals(Ci.nsIRequest) || - iid.equals(Ci.nsISupports)) - { - return this; - } - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NSIINPUTSTREAMCALLBACK - - /** -* Receives a more-data-in-input notification and writes the corresponding -* data to the output. -* -* @param input : nsIAsyncInputStream -* the input stream on whose data we have been waiting -*/ - onInputStreamReady: function(input) - { - if (this._source === null) - return; - - dumpn("*** onInputStreamReady"); - - // - // Ordinarily we'll read a non-zero amount of data from input, queue it up - // to be written and then wait for further callbacks. The complications in - // this method are the cases where we deviate from that behavior when errors - // occur or when copying is drawing to a finish. - // - // The edge cases when reading data are: - // - // Zero data is read - // If zero data was read, we're at the end of available data, so we can - // should stop reading and move on to writing out what we have (or, if - // we've already done that, onto notifying of completion). - // A stream-closed exception is thrown - // This is effectively a less kind version of zero data being read; the - // only difference is that we notify of completion with that result - // rather than with NS_OK. - // Some other exception is thrown - // This is the least kind result. We don't know what happened, so we - // act as though the stream closed except that we notify of completion - // with the result NS_ERROR_UNEXPECTED. - // - - var bytesWanted = 0, bytesConsumed = -1; - try - { - input = new BinaryInputStream(input); - - bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE); - dumpn("*** input wanted: " + bytesWanted); - - if (bytesWanted > 0) - { - var data = input.readByteArray(bytesWanted); - bytesConsumed = data.length; - this._pendingData.push(String.fromCharCode.apply(String, data)); - } - - dumpn("*** " + bytesConsumed + " bytes read"); - - // Handle the zero-data edge case in the same place as all other edge - // cases are handled. - if (bytesWanted === 0) - throw Cr.NS_BASE_STREAM_CLOSED; - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** input stream closed"); - e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED; - } - else - { - dumpn("!!! unexpected error reading from input, canceling: " + e); - e = Cr.NS_ERROR_UNEXPECTED; - } - - this._doneReadingSource(e); - return; - } - - var pendingData = this._pendingData; - - NS_ASSERT(bytesConsumed > 0); - NS_ASSERT(pendingData.length > 0, "no pending data somehow?"); - NS_ASSERT(pendingData[pendingData.length - 1].length > 0, - "buffered zero bytes of data?"); - - NS_ASSERT(this._source !== null); - - // Reading has gone great, and we've gotten data to write now. What if we - // don't have a place to write that data, because output went away just - // before this read? Drop everything on the floor, including new data, and - // cancel at this point. - if (this._sink === null) - { - pendingData.length = 0; - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we've read the data, and we know we have a place to write it. We - // need to queue up the data to be written, but *only* if none is queued - // already -- if data's already queued, the code that actually writes the - // data will make sure to wait on unconsumed pending data. - try - { - if (pendingData.length === 1) - this._waitToWriteData(); - } - catch (e) - { - dumpn("!!! error waiting to write data just read, swallowing and " + - "writing only what we already have: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Whee! We successfully read some data, and it's successfully queued up to - // be written. All that remains now is to wait for more data to read. - try - { - this._waitToReadData(); - } - catch (e) - { - dumpn("!!! error waiting to read more data: " + e); - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - } - }, - - - // NSIOUTPUTSTREAMCALLBACK - - /** -* Callback when data may be written to the output stream without blocking, or -* when the output stream has been closed. -* -* @param output : nsIAsyncOutputStream -* the output stream on whose writability we've been waiting, also known as -* this._sink -*/ - onOutputStreamReady: function(output) - { - if (this._sink === null) - return; - - dumpn("*** onOutputStreamReady"); - - var pendingData = this._pendingData; - if (pendingData.length === 0) - { - // There's no pending data to write. The only way this can happen is if - // we're waiting on the output stream's closure, so we can respond to a - // copying failure as quickly as possible (rather than waiting for data to - // be available to read and then fail to be copied). Therefore, we must - // be done now -- don't bother to attempt to write anything and wrap - // things up. - dumpn("!!! output stream closed prematurely, ending copy"); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - - NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?"); - - // - // Write out the first pending quantum of data. The possible errors here - // are: - // - // The write might fail because we can't write that much data - // Okay, we've written what we can now, so re-queue what's left and - // finish writing it out later. - // The write failed because the stream was closed - // Discard pending data that we can no longer write, stop reading, and - // signal that copying finished. - // Some other error occurred. - // Same as if the stream were closed, but notify with the status - // NS_ERROR_UNEXPECTED so the observer knows something was wonky. - // - - try - { - var quantum = pendingData[0]; - - // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on - // undefined behavior! We're only using this because writeByteArray - // is unusably broken for asynchronous output streams; see bug 532834 - // for details. - var bytesWritten = output.write(quantum, quantum.length); - if (bytesWritten === quantum.length) - pendingData.shift(); - else - pendingData[0] = quantum.substring(bytesWritten); - - dumpn("*** wrote " + bytesWritten + " bytes of data"); - } - catch (e) - { - if (wouldBlock(e)) - { - NS_ASSERT(pendingData.length > 0, - "stream-blocking exception with no data to write?"); - NS_ASSERT(pendingData[0].length > 0, - "stream-blocking exception with empty quantum?"); - this._waitToWriteData(); - return; - } - - if (streamClosed(e)) - dumpn("!!! output stream prematurely closed, signaling error..."); - else - dumpn("!!! unknown error: " + e + ", quantum=" + quantum); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // The day is ours! Quantum written, now let's see if we have more data - // still to write. - try - { - if (pendingData.length > 0) - { - this._waitToWriteData(); - return; - } - } - catch (e) - { - dumpn("!!! unexpected error waiting to write pending data: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we have no more pending data to write -- but might we get more in - // the future? - if (this._source !== null) - { - /* -* If we might, then wait for the output stream to be closed. (We wait -* only for closure because we have no data to write -- and if we waited -* for a specific amount of data, we would get repeatedly notified for no -* reason if over time the output stream permitted more and more data to -* be written to it without blocking.) -*/ - this._waitForSinkClosure(); - } - else - { - /* -* On the other hand, if we can't have more data because the input -* stream's gone away, then it's time to notify of copy completion. -* Victory! -*/ - this._sink = null; - this._cancelOrDispatchCancelCallback(Cr.NS_OK); - } - }, - - - // NSIREQUEST - - /** Returns true if the cancel observer hasn't been notified yet. */ - isPending: function() - { - return !this._completed; - }, - - /** Not implemented, don't use! */ - suspend: notImplemented, - /** Not implemented, don't use! */ - resume: notImplemented, - - /** -* Cancels data reading from input, asynchronously writes out any pending -* data, and causes the observer to be notified with the given error code when -* all writing has finished. -* -* @param status : nsresult -* the status to pass to the observer when data copying has been canceled -*/ - cancel: function(status) - { - dumpn("*** cancel(" + status.toString(16) + ")"); - - if (this._canceled) - { - dumpn("*** suppressing a late cancel"); - return; - } - - this._canceled = true; - this.status = status; - - // We could be in the middle of absolutely anything at this point. Both - // input and output might still be around, we might have pending data to - // write, and in general we know nothing about the state of the world. We - // therefore must assume everything's in progress and take everything to its - // final steady state (or so far as it can go before we need to finish - // writing out remaining data). - - this._doneReadingSource(status); - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Stop reading input if we haven't already done so, passing e as the status -* when closing the stream, and kick off a copy-completion notice if no more -* data remains to be written. -* -* @param e : nsresult -* the status to be used when closing the input stream -*/ - _doneReadingSource: function(e) - { - dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")"); - - this._finishSource(e); - if (this._pendingData.length === 0) - this._sink = null; - else - NS_ASSERT(this._sink !== null, "null output?"); - - // If we've written out all data read up to this point, then it's time to - // signal completion. - if (this._sink === null) - { - NS_ASSERT(this._pendingData.length === 0, "pending data still?"); - this._cancelOrDispatchCancelCallback(e); - } - }, - - /** -* Stop writing output if we haven't already done so, discard any data that -* remained to be sent, close off input if it wasn't already closed, and kick -* off a copy-completion notice. -* -* @param e : nsresult -* the status to be used when closing input if it wasn't already closed -*/ - _doneWritingToSink: function(e) - { - dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")"); - - this._pendingData.length = 0; - this._sink = null; - this._doneReadingSource(e); - }, - - /** -* Completes processing of this copy: either by canceling the copy if it -* hasn't already been canceled using the provided status, or by dispatching -* the cancel callback event (with the originally provided status, of course) -* if it already has been canceled. -* -* @param status : nsresult -* the status code to use to cancel this, if this hasn't already been -* canceled -*/ - _cancelOrDispatchCancelCallback: function(status) - { - dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")"); - - NS_ASSERT(this._source === null, "should have finished input"); - NS_ASSERT(this._sink === null, "should have finished output"); - NS_ASSERT(this._pendingData.length === 0, "should have no pending data"); - - if (!this._canceled) - { - this.cancel(status); - return; - } - - var self = this; - var event = - { - run: function() - { - dumpn("*** onStopRequest async callback"); - - self._completed = true; - try - { - self._observer.onStopRequest(self, self._context, self.status); - } - catch (e) - { - NS_ASSERT(false, - "how are we throwing an exception here? we control " + - "all the callers! " + e); - } - } - }; - - gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL); - }, - - /** -* Kicks off another wait for more data to be available from the input stream. -*/ - _waitToReadData: function() - { - dumpn("*** _waitToReadData"); - this._source.asyncWait(this, 0, Response.SEGMENT_SIZE, - gThreadManager.mainThread); - }, - - /** -* Kicks off another wait until data can be written to the output stream. -*/ - _waitToWriteData: function() - { - dumpn("*** _waitToWriteData"); - - var pendingData = this._pendingData; - NS_ASSERT(pendingData.length > 0, "no pending data to write?"); - NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?"); - - this._sink.asyncWait(this, 0, pendingData[0].length, - gThreadManager.mainThread); - }, - - /** -* Kicks off a wait for the sink to which data is being copied to be closed. -* We wait for stream closure when we don't have any data to be copied, rather -* than waiting to write a specific amount of data. We can't wait to write -* data because the sink might be infinitely writable, and if no data appears -* in the source for a long time we might have to spin quite a bit waiting to -* write, waiting to write again, &c. Waiting on stream closure instead means -* we'll get just one notification if the sink dies. Note that when data -* starts arriving from the sink we'll resume waiting for data to be written, -* dropping this closure-only callback entirely. -*/ - _waitForSinkClosure: function() - { - dumpn("*** _waitForSinkClosure"); - - this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0, - gThreadManager.mainThread); - }, - - /** -* Closes input with the given status, if it hasn't already been closed; -* otherwise a no-op. -* -* @param status : nsresult -* status code use to close the source stream if necessary -*/ - _finishSource: function(status) - { - dumpn("*** _finishSource(" + status.toString(16) + ")"); - - if (this._source !== null) - { - this._source.closeWithStatus(status); - this._source = null; - } - } -}; - - -/** -* A container for utility functions used with HTTP headers. -*/ -const headerUtils = -{ - /** -* Normalizes fieldName (by converting it to lowercase) and ensures it is a -* valid header field name (although not necessarily one specified in RFC -* 2616). -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not match the field-name production in RFC 2616 -* @returns string -* fieldName converted to lowercase if it is a valid header, for characters -* where case conversion is possible -*/ - normalizeFieldName: function(fieldName) - { - if (fieldName == "") - throw Cr.NS_ERROR_INVALID_ARG; - - for (var i = 0, sz = fieldName.length; i < sz; i++) - { - if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)]) - { - dumpn(fieldName + " is not a valid header field name!"); - throw Cr.NS_ERROR_INVALID_ARG; - } - } - - return fieldName.toLowerCase(); - }, - - /** -* Ensures that fieldValue is a valid header field value (although not -* necessarily as specified in RFC 2616 if the corresponding field name is -* part of the HTTP protocol), normalizes the value if it is, and -* returns the normalized value. -* -* @param fieldValue : string -* a value to be normalized as an HTTP header field value -* @throws NS_ERROR_INVALID_ARG -* if fieldValue does not match the field-value production in RFC 2616 -* @returns string -* fieldValue as a normalized HTTP header field value -*/ - normalizeFieldValue: function(fieldValue) - { - // field-value = *( field-content | LWS ) - // field-content = - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // - // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) - // qdtext = > - // quoted-pair = "\" CHAR - // CHAR = - - // Any LWS that occurs between field-content MAY be replaced with a single - // SP before interpreting the field value or forwarding the message - // downstream (section 4.2); we replace 1*LWS with a single SP - var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " "); - - // remove leading/trailing LWS (which has been converted to SP) - val = val.replace(/^ +/, "").replace(/ +$/, ""); - - // that should have taken care of all CTLs, so val should contain no CTLs - for (var i = 0, len = val.length; i < len; i++) - if (isCTL(val.charCodeAt(i))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly - // normalize, however, so this can be construed as a tightening of the - // spec and not entirely as a bug - return val; - } -}; - - - -/** -* Converts the given string into a string which is safe for use in an HTML -* context. -* -* @param str : string -* the string to make HTML-safe -* @returns string -* an HTML-safe version of str -*/ -function htmlEscape(str) -{ - // this is naive, but it'll work - var s = ""; - for (var i = 0; i < str.length; i++) - s += "&#" + str.charCodeAt(i) + ";"; - return s; -} - - -/** -* Constructs an object representing an HTTP version (see section 3.1). -* -* @param versionString -* a string of the form "#.#", where # is an non-negative decimal integer with -* or without leading zeros -* @throws -* if versionString does not specify a valid HTTP version number -*/ -function nsHttpVersion(versionString) -{ - var matches = /^(\d+)\.(\d+)$/.exec(versionString); - if (!matches) - throw "Not a valid HTTP version!"; - - /** The major version number of this, as a number. */ - this.major = parseInt(matches[1], 10); - - /** The minor version number of this, as a number. */ - this.minor = parseInt(matches[2], 10); - - if (isNaN(this.major) || isNaN(this.minor) || - this.major < 0 || this.minor < 0) - throw "Not a valid HTTP version!"; -} -nsHttpVersion.prototype = -{ - /** -* Returns the standard string representation of the HTTP version represented -* by this (e.g., "1.1"). -*/ - toString: function () - { - return this.major + "." + this.minor; - }, - - /** -* Returns true if this represents the same HTTP version as otherVersion, -* false otherwise. -* -* @param otherVersion : nsHttpVersion -* the version to compare against this -*/ - equals: function (otherVersion) - { - return this.major == otherVersion.major && - this.minor == otherVersion.minor; - }, - - /** True if this >= otherVersion, false otherwise. */ - atLeast: function(otherVersion) - { - return this.major > otherVersion.major || - (this.major == otherVersion.major && - this.minor >= otherVersion.minor); - } -}; - -nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0"); -nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1"); - - -/** -* An object which stores HTTP headers for a request or response. -* -* Note that since headers are case-insensitive, this object converts headers to -* lowercase before storing them. This allows the getHeader and hasHeader -* methods to work correctly for any case of a header, but it means that the -* values returned by .enumerator may not be equal case-sensitively to the -* values passed to setHeader when adding headers to this. -*/ -function nsHttpHeaders() -{ - /** -* A hash of headers, with header field names as the keys and header field -* values as the values. Header field names are case-insensitive, but upon -* insertion here they are converted to lowercase. Header field values are -* normalized upon insertion to contain no leading or trailing whitespace. -* -* Note also that per RFC 2616, section 4.2, two headers with the same name in -* a message may be treated as one header with the same field name and a field -* value consisting of the separate field values joined together with a "," in -* their original order. This hash stores multiple headers with the same name -* in this manner. -*/ - this._headers = {}; -} -nsHttpHeaders.prototype = -{ - /** -* Sets the header represented by name and value in this. -* -* @param name : string -* the header name -* @param value : string -* the header value -* @throws NS_ERROR_INVALID_ARG -* if name or value is not a valid header component -*/ - setHeader: function(fieldName, fieldValue, merge) - { - var name = headerUtils.normalizeFieldName(fieldName); - var value = headerUtils.normalizeFieldValue(fieldValue); - - // The following three headers are stored as arrays because their real-world - // syntax prevents joining individual headers into a single header using - // ",". See also - if (merge && name in this._headers) - { - if (name === "www-authenticate" || - name === "proxy-authenticate" || - name === "set-cookie") - { - this._headers[name].push(value); - } - else - { - this._headers[name][0] += "," + value; - NS_ASSERT(this._headers[name].length === 1, - "how'd a non-special header have multiple values?") - } - } - else - { - this._headers[name] = [value]; - } - }, - - /** -* Returns the value for the header specified by this. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns string -* the field value for the given header, possibly with non-semantic changes -* (i.e., leading/trailing whitespace stripped, whitespace runs replaced -* with spaces, etc.) at the option of the implementation; multiple -* instances of the header will be combined with a comma, except for -* the three headers noted in the description of getHeaderValues -*/ - getHeader: function(fieldName) - { - return this.getHeaderValues(fieldName).join("\n"); - }, - - /** -* Returns the value for the header specified by fieldName as an array. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns [string] -* an array of all the header values in this for the given -* header name. Header values will generally be collapsed -* into a single header by joining all header values together -* with commas, but certain headers (Proxy-Authenticate, -* WWW-Authenticate, and Set-Cookie) violate the HTTP spec -* and cannot be collapsed in this manner. For these headers -* only, the returned array may contain multiple elements if -* that header has been added more than once. -*/ - getHeaderValues: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - - if (name in this._headers) - return this._headers[name]; - else - throw Cr.NS_ERROR_NOT_AVAILABLE; - }, - - /** -* Returns true if a header with the given field name exists in this, false -* otherwise. -* -* @param fieldName : string -* the field name whose existence is to be determined in this -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @returns boolean -* true if the header's present, false otherwise -*/ - hasHeader: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - return (name in this._headers); - }, - - /** -* Returns a new enumerator over the field names of the headers in this, as -* nsISupportsStrings. The names returned will be in lowercase, regardless of -* how they were input using setHeader (header names are case-insensitive per -* RFC 2616). -*/ - get enumerator() - { - var headers = []; - for (var i in this._headers) - { - var supports = new SupportsString(); - supports.data = i; - headers.push(supports); - } - - return new nsSimpleEnumerator(headers); - } -}; - - -/** -* Constructs an nsISimpleEnumerator for the given array of items. -* -* @param items : Array -* the items, which must all implement nsISupports -*/ -function nsSimpleEnumerator(items) -{ - this._items = items; - this._nextIndex = 0; -} -nsSimpleEnumerator.prototype = -{ - hasMoreElements: function() - { - return this._nextIndex < this._items.length; - }, - getNext: function() - { - if (!this.hasMoreElements()) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - return this._items[this._nextIndex++]; - }, - QueryInterface: function(aIID) - { - if (Ci.nsISimpleEnumerator.equals(aIID) || - Ci.nsISupports.equals(aIID)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } -}; - - -/** -* A representation of the data in an HTTP request. -* -* @param port : uint -* the port on which the server receiving this request runs -*/ -function Request(port) -{ - /** Method of this request, e.g. GET or POST. */ - this._method = ""; - - /** Path of the requested resource; empty paths are converted to '/'. */ - this._path = ""; - - /** Query string, if any, associated with this request (not including '?'). */ - this._queryString = ""; - - /** Scheme of requested resource, usually http, always lowercase. */ - this._scheme = "http"; - - /** Hostname on which the requested resource resides. */ - this._host = undefined; - - /** Port number over which the request was received. */ - this._port = port; - - var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null); - - /** Stream from which data in this request's body may be read. */ - this._bodyInputStream = bodyPipe.inputStream; - - /** Stream to which data in this request's body is written. */ - this._bodyOutputStream = bodyPipe.outputStream; - - /** -* The headers in this request. -*/ - this._headers = new nsHttpHeaders(); - - /** -* For the addition of ad-hoc properties and new functionality without having -* to change nsIHttpRequest every time; currently lazily created, as its only -* use is in directory listings. -*/ - this._bag = null; -} -Request.prototype = -{ - // SERVER METADATA - - // - // see nsIHttpRequest.scheme - // - get scheme() - { - return this._scheme; - }, - - // - // see nsIHttpRequest.host - // - get host() - { - return this._host; - }, - - // - // see nsIHttpRequest.port - // - get port() - { - return this._port; - }, - - // REQUEST LINE - - // - // see nsIHttpRequest.method - // - get method() - { - return this._method; - }, - - // - // see nsIHttpRequest.httpVersion - // - get httpVersion() - { - return this._httpVersion.toString(); - }, - - // - // see nsIHttpRequest.path - // - get path() - { - return this._path; - }, - - // - // see nsIHttpRequest.queryString - // - get queryString() - { - return this._queryString; - }, - - // HEADERS - - // - // see nsIHttpRequest.getHeader - // - getHeader: function(name) - { - return this._headers.getHeader(name); - }, - - // - // see nsIHttpRequest.hasHeader - // - hasHeader: function(name) - { - return this._headers.hasHeader(name); - }, - - // - // see nsIHttpRequest.headers - // - get headers() - { - return this._headers.enumerator; - }, - - // - // see nsIPropertyBag.enumerator - // - get enumerator() - { - this._ensurePropertyBag(); - return this._bag.enumerator; - }, - - // - // see nsIHttpRequest.headers - // - get bodyInputStream() - { - return this._bodyInputStream; - }, - - // - // see nsIPropertyBag.getProperty - // - getProperty: function(name) - { - this._ensurePropertyBag(); - return this._bag.getProperty(name); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** Ensures a property bag has been created for ad-hoc behaviors. */ - _ensurePropertyBag: function() - { - if (!this._bag) - this._bag = new WritablePropertyBag(); - } -}; - - -// XPCOM trappings -if ("XPCOMUtils" in this && // Firefox 3.6 doesn't load XPCOMUtils in this scope for some reason... - "generateNSGetFactory" in XPCOMUtils) { - var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]); -} - - - -/** -* Creates a new HTTP server listening for loopback traffic on the given port, -* starts it, and runs the server until the server processes a shutdown request, -* spinning an event loop so that events posted by the server's socket are -* processed. -* -* This method is primarily intended for use in running this script from within -* xpcshell and running a functional HTTP server without having to deal with -* non-essential details. -* -* Note that running multiple servers using variants of this method probably -* doesn't work, simply due to how the internal event loop is spun and stopped. -* -* @note -* This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code); -* you should use this server as a component in Mozilla 1.8. -* @param port -* the port on which the server will run, or -1 if there exists no preference -* for a specific port; note that attempting to use some values for this -* parameter (particularly those below 1024) may cause this method to throw or -* may result in the server being prematurely shut down -* @param basePath -* a local directory from which requests will be served (i.e., if this is -* "/home/jwalden/" then a request to /index.html will load -* /home/jwalden/index.html); if this is omitted, only the default URLs in -* this server implementation will be functional -*/ -function server(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - // if you're running this, you probably want to see debugging info - DEBUG = true; - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", SJS_TYPE); - srv.start(port); - - var thread = gThreadManager.currentThread; - while (!srv.isStopped()) - thread.processNextEvent(true); - - // get rid of any pending requests - while (thread.hasPendingEvents()) - thread.processNextEvent(true); - - DEBUG = false; -} - -function startServerAsync(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", "sjs"); - srv.start(port); - return srv; -} - -exports.nsHttpServer = nsHttpServer; -exports.ScriptableInputStream = ScriptableInputStream; -exports.server = server; -exports.startServerAsync = startServerAsync; diff --git a/addon-sdk/source/test/addons/content-permissions/main.js b/addon-sdk/source/test/addons/content-permissions/main.js deleted file mode 100644 index b476ccb74b..0000000000 --- a/addon-sdk/source/test/addons/content-permissions/main.js +++ /dev/null @@ -1,89 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { PageMod } = require("sdk/page-mod"); -const tabs = require("sdk/tabs"); -const { startServerAsync } = require("./httpd"); - -const serverPort = 8099; -const TEST_TAB_URL = "about:mozilla"; - -exports.testCrossDomainIframe = function(assert, done) { - let server = startServerAsync(serverPort); - server.registerPathHandler("/iframe", function handle(request, response) { - response.write("foo"); - }); - - let pageMod = PageMod({ - include: TEST_TAB_URL, - contentScript: "new " + function ContentScriptScope() { - self.on("message", function (url) { - let iframe = document.createElement("iframe"); - iframe.addEventListener("load", function onload() { - iframe.removeEventListener("load", onload, false); - self.postMessage(iframe.contentWindow.document.body.innerHTML); - }, false); - iframe.setAttribute("src", url); - document.documentElement.appendChild(iframe); - }); - }, - onAttach: function(w) { - w.on("message", function (body) { - assert.equal(body, "foo", "received iframe html content"); - pageMod.destroy(); - w.tab.close(function() { - server.stop(done); - }); - }); - - w.postMessage("http://localhost:" + serverPort + "/iframe"); - } - }); - - tabs.open({ - url: TEST_TAB_URL, - inBackground: true - }); -}; - -exports.testCrossDomainXHR = function(assert, done) { - let server = startServerAsync(serverPort); - server.registerPathHandler("/xhr", function handle(request, response) { - response.write("foo"); - }); - - let pageMod = PageMod({ - include: TEST_TAB_URL, - contentScript: "new " + function ContentScriptScope() { - self.on("message", function (url) { - let request = new XMLHttpRequest(); - request.overrideMimeType("text/plain"); - request.open("GET", url, true); - request.onload = function () { - self.postMessage(request.responseText); - }; - request.send(null); - }); - }, - onAttach: function(w) { - w.on("message", function (body) { - assert.equal(body, "foo", "received XHR content"); - pageMod.destroy(); - w.tab.close(function() { - server.stop(done); - }); - }); - - w.postMessage("http://localhost:" + serverPort + "/xhr"); - } - }); - - tabs.open({ - url: TEST_TAB_URL, - inBackground: true - }); -}; - -require("sdk/test/runner").runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/content-permissions/package.json b/addon-sdk/source/test/addons/content-permissions/package.json deleted file mode 100644 index 6e75d20445..0000000000 --- a/addon-sdk/source/test/addons/content-permissions/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "content-permissions@jetpack", - "permissions": { - "cross-domain-content": ["http://localhost:8099"] - }, - "main": "./main.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/content-script-messages-latency/httpd.js b/addon-sdk/source/test/addons/content-script-messages-latency/httpd.js deleted file mode 100644 index 964dc9bbd3..0000000000 --- a/addon-sdk/source/test/addons/content-script-messages-latency/httpd.js +++ /dev/null @@ -1,5211 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* -* NOTE: do not edit this file, this is copied from: -* https://github.com/mozilla/addon-sdk/blob/master/test/lib/httpd.js -*/ - -module.metadata = { - "stability": "experimental" -}; - -const { components, CC, Cc, Ci, Cr, Cu } = require("chrome"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - - -const PR_UINT32_MAX = Math.pow(2, 32) - 1; - -/** True if debugging output is enabled, false otherwise. */ -var DEBUG = false; // non-const *only* so tweakable in server tests - -/** True if debugging output should be timestamped. */ -var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests - -var gGlobalObject = Cc["@mozilla.org/systemprincipal;1"].createInstance(); - -/** -* Asserts that the given condition holds. If it doesn't, the given message is -* dumped, a stack trace is printed, and an exception is thrown to attempt to -* stop execution (which unfortunately must rely upon the exception not being -* accidentally swallowed by the code that uses it). -*/ -function NS_ASSERT(cond, msg) -{ - if (DEBUG && !cond) - { - dumpn("###!!!"); - dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!")); - dumpn("###!!! Stack follows:"); - - var stack = new Error().stack.split(/\n/); - dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n")); - - throw Cr.NS_ERROR_ABORT; - } -} - -/** Constructs an HTTP error object. */ -function HttpError(code, description) -{ - this.code = code; - this.description = description; -} -HttpError.prototype = -{ - toString: function() - { - return this.code + " " + this.description; - } -}; - -/** -* Errors thrown to trigger specific HTTP server responses. -*/ -const HTTP_400 = new HttpError(400, "Bad Request"); -const HTTP_401 = new HttpError(401, "Unauthorized"); -const HTTP_402 = new HttpError(402, "Payment Required"); -const HTTP_403 = new HttpError(403, "Forbidden"); -const HTTP_404 = new HttpError(404, "Not Found"); -const HTTP_405 = new HttpError(405, "Method Not Allowed"); -const HTTP_406 = new HttpError(406, "Not Acceptable"); -const HTTP_407 = new HttpError(407, "Proxy Authentication Required"); -const HTTP_408 = new HttpError(408, "Request Timeout"); -const HTTP_409 = new HttpError(409, "Conflict"); -const HTTP_410 = new HttpError(410, "Gone"); -const HTTP_411 = new HttpError(411, "Length Required"); -const HTTP_412 = new HttpError(412, "Precondition Failed"); -const HTTP_413 = new HttpError(413, "Request Entity Too Large"); -const HTTP_414 = new HttpError(414, "Request-URI Too Long"); -const HTTP_415 = new HttpError(415, "Unsupported Media Type"); -const HTTP_417 = new HttpError(417, "Expectation Failed"); - -const HTTP_500 = new HttpError(500, "Internal Server Error"); -const HTTP_501 = new HttpError(501, "Not Implemented"); -const HTTP_502 = new HttpError(502, "Bad Gateway"); -const HTTP_503 = new HttpError(503, "Service Unavailable"); -const HTTP_504 = new HttpError(504, "Gateway Timeout"); -const HTTP_505 = new HttpError(505, "HTTP Version Not Supported"); - -/** Creates a hash with fields corresponding to the values in arr. */ -function array2obj(arr) -{ - var obj = {}; - for (var i = 0; i < arr.length; i++) - obj[arr[i]] = arr[i]; - return obj; -} - -/** Returns an array of the integers x through y, inclusive. */ -function range(x, y) -{ - var arr = []; - for (var i = x; i <= y; i++) - arr.push(i); - return arr; -} - -/** An object (hash) whose fields are the numbers of all HTTP error codes. */ -const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505))); - - -/** -* The character used to distinguish hidden files from non-hidden files, a la -* the leading dot in Apache. Since that mechanism also hides files from -* easy display in LXR, ls output, etc. however, we choose instead to use a -* suffix character. If a requested file ends with it, we append another -* when getting the file on the server. If it doesn't, we just look up that -* file. Therefore, any file whose name ends with exactly one of the character -* is "hidden" and available for use by the server. -*/ -const HIDDEN_CHAR = "^"; - -/** -* The file name suffix indicating the file containing overridden headers for -* a requested file. -*/ -const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR; - -/** Type used to denote SJS scripts for CGI-like functionality. */ -const SJS_TYPE = "sjs"; - -/** Base for relative timestamps produced by dumpn(). */ -var firstStamp = 0; - -/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */ -function dumpn(str) -{ - if (DEBUG) - { - var prefix = "HTTPD-INFO | "; - if (DEBUG_TIMESTAMP) - { - if (firstStamp === 0) - firstStamp = Date.now(); - - var elapsed = Date.now() - firstStamp; // milliseconds - var min = Math.floor(elapsed / 60000); - var sec = (elapsed % 60000) / 1000; - - if (sec < 10) - prefix += min + ":0" + sec.toFixed(3) + " | "; - else - prefix += min + ":" + sec.toFixed(3) + " | "; - } - - dump(prefix + str + "\n"); - } -} - -/** Dumps the current JS stack if DEBUG. */ -function dumpStack() -{ - // peel off the frames for dumpStack() and Error() - var stack = new Error().stack.split(/\n/).slice(2); - stack.forEach(dumpn); -} - - -/** The XPCOM thread manager. */ -var gThreadManager = null; - -/** The XPCOM prefs service. */ -var gRootPrefBranch = null; -function getRootPrefBranch() -{ - if (!gRootPrefBranch) - { - gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"] - .getService(Ci.nsIPrefBranch); - } - return gRootPrefBranch; -} - -/** -* JavaScript constructors for commonly-used classes; precreating these is a -* speedup over doing the same from base principles. See the docs at -* http://developer.mozilla.org/en/docs/components.Constructor for details. -*/ -const ServerSocket = CC("@mozilla.org/network/server-socket;1", - "nsIServerSocket", - "init"); -const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", - "nsIScriptableInputStream", - "init"); -const Pipe = CC("@mozilla.org/pipe;1", - "nsIPipe", - "init"); -const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", - "nsIFileInputStream", - "init"); -const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1", - "nsIConverterInputStream", - "init"); -const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1", - "nsIWritablePropertyBag2"); -const SupportsString = CC("@mozilla.org/supports-string;1", - "nsISupportsString"); - -/* These two are non-const only so a test can overwrite them. */ -var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", - "setInputStream"); -var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", - "nsIBinaryOutputStream", - "setOutputStream"); - -/** -* Returns the RFC 822/1123 representation of a date. -* -* @param date : Number -* the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT -* @returns string -* the representation of the given date -*/ -function toDateString(date) -{ - // - // rfc1123-date = wkday "," SP date1 SP time SP "GMT" - // date1 = 2DIGIT SP month SP 4DIGIT - // ; day month year (e.g., 02 Jun 1982) - // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - // ; 00:00:00 - 23:59:59 - // wkday = "Mon" | "Tue" | "Wed" - // | "Thu" | "Fri" | "Sat" | "Sun" - // month = "Jan" | "Feb" | "Mar" | "Apr" - // | "May" | "Jun" | "Jul" | "Aug" - // | "Sep" | "Oct" | "Nov" | "Dec" - // - - const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - /** -* Processes a date and returns the encoded UTC time as a string according to -* the format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toTime(date) - { - var hrs = date.getUTCHours(); - var rv = (hrs < 10) ? "0" + hrs : hrs; - - var mins = date.getUTCMinutes(); - rv += ":"; - rv += (mins < 10) ? "0" + mins : mins; - - var secs = date.getUTCSeconds(); - rv += ":"; - rv += (secs < 10) ? "0" + secs : secs; - - return rv; - } - - /** -* Processes a date and returns the encoded UTC date as a string according to -* the date1 format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toDate1(date) - { - var day = date.getUTCDate(); - var month = date.getUTCMonth(); - var year = date.getUTCFullYear(); - - var rv = (day < 10) ? "0" + day : day; - rv += " " + monthStrings[month]; - rv += " " + year; - - return rv; - } - - date = new Date(date); - - const fmtString = "%wkday%, %date1% %time% GMT"; - var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]); - rv = rv.replace("%time%", toTime(date)); - return rv.replace("%date1%", toDate1(date)); -} - -/** -* Prints out a human-readable representation of the object o and its fields, -* omitting those whose names begin with "_" if showMembers != true (to ignore -* "private" properties exposed via getters/setters). -*/ -function printObj(o, showMembers) -{ - var s = "******************************\n"; - s += "o = {\n"; - for (var i in o) - { - if (typeof(i) != "string" || - (showMembers || (i.length > 0 && i[0] != "_"))) - s+= " " + i + ": " + o[i] + ",\n"; - } - s += " };\n"; - s += "******************************"; - dumpn(s); -} - -/** -* Instantiates a new HTTP server. -*/ -function nsHttpServer() -{ - if (!gThreadManager) - gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService(); - - /** The port on which this server listens. */ - this._port = undefined; - - /** The socket associated with this. */ - this._socket = null; - - /** The handler used to process requests to this server. */ - this._handler = new ServerHandler(this); - - /** Naming information for this server. */ - this._identity = new ServerIdentity(); - - /** -* Indicates when the server is to be shut down at the end of the request. -*/ - this._doQuit = false; - - /** -* True if the socket in this is closed (and closure notifications have been -* sent and processed if the socket was ever opened), false otherwise. -*/ - this._socketClosed = true; - - /** -* Used for tracking existing connections and ensuring that all connections -* are properly cleaned up before server shutdown; increases by 1 for every -* new incoming connection. -*/ - this._connectionGen = 0; - - /** -* Hash of all open connections, indexed by connection number at time of -* creation. -*/ - this._connections = {}; -} -nsHttpServer.prototype = -{ - classID: components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"), - - // NSISERVERSOCKETLISTENER - - /** -* Processes an incoming request coming in on the given socket and contained -* in the given transport. -* -* @param socket : nsIServerSocket -* the socket through which the request was served -* @param trans : nsISocketTransport -* the transport for the request/response -* @see nsIServerSocketListener.onSocketAccepted -*/ - onSocketAccepted: function(socket, trans) - { - dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")"); - - dumpn(">>> new connection on " + trans.host + ":" + trans.port); - - const SEGMENT_SIZE = 8192; - const SEGMENT_COUNT = 1024; - try - { - var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT) - .QueryInterface(Ci.nsIAsyncInputStream); - var output = trans.openOutputStream(0, 0, 0); - } - catch (e) - { - dumpn("*** error opening transport streams: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - var connectionNumber = ++this._connectionGen; - - try - { - var conn = new Connection(input, output, this, socket.port, trans.port, - connectionNumber); - var reader = new RequestReader(conn); - - // XXX add request timeout functionality here! - - // Note: must use main thread here, or we might get a GC that will cause - // threadsafety assertions. We really need to fix XPConnect so that - // you can actually do things in multi-threaded JS. :-( - input.asyncWait(reader, 0, 0, gThreadManager.mainThread); - } - catch (e) - { - // Assume this connection can't be salvaged and bail on it completely; - // don't attempt to close it so that we can assert that any connection - // being closed is in this._connections. - dumpn("*** error in initial request-processing stages: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - this._connections[connectionNumber] = conn; - dumpn("*** starting connection " + connectionNumber); - }, - - /** -* Called when the socket associated with this is closed. -* -* @param socket : nsIServerSocket -* the socket being closed -* @param status : nsresult -* the reason the socket stopped listening (NS_BINDING_ABORTED if the server -* was stopped using nsIHttpServer.stop) -* @see nsIServerSocketListener.onStopListening -*/ - onStopListening: function(socket, status) - { - dumpn(">>> shutting down server on port " + socket.port); - this._socketClosed = true; - if (!this._hasOpenConnections()) - { - dumpn("*** no open connections, notifying async from onStopListening"); - - // Notify asynchronously so that any pending teardown in stop() has a - // chance to run first. - var self = this; - var stopEvent = - { - run: function() - { - dumpn("*** _notifyStopped async callback"); - self._notifyStopped(); - } - }; - gThreadManager.currentThread - .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL); - } - }, - - // NSIHTTPSERVER - - // - // see nsIHttpServer.start - // - start: function(port) - { - this._start(port, "localhost") - }, - - _start: function(port, host) - { - if (this._socket) - throw Cr.NS_ERROR_ALREADY_INITIALIZED; - - this._port = port; - this._doQuit = this._socketClosed = false; - - this._host = host; - - // The listen queue needs to be long enough to handle - // network.http.max-persistent-connections-per-server concurrent connections, - // plus a safety margin in case some other process is talking to - // the server as well. - var prefs = getRootPrefBranch(); - var maxConnections; - try { - // Bug 776860: The original pref was removed in favor of this new one: - maxConnections = prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5; - } - catch(e) { - maxConnections = prefs.getIntPref("network.http.max-connections-per-server") + 5; - } - - try - { - var loopback = true; - if (this._host != "127.0.0.1" && this._host != "localhost") { - var loopback = false; - } - - var socket = new ServerSocket(this._port, - loopback, // true = localhost, false = everybody - maxConnections); - dumpn(">>> listening on port " + socket.port + ", " + maxConnections + - " pending connections"); - socket.asyncListen(this); - this._identity._initialize(socket.port, host, true); - this._socket = socket; - } - catch (e) - { - dumpn("!!! could not start server on port " + port + ": " + e); - throw Cr.NS_ERROR_NOT_AVAILABLE; - } - }, - - // - // see nsIHttpServer.stop - // - stop: function(callback) - { - if (!callback) - throw Cr.NS_ERROR_NULL_POINTER; - if (!this._socket) - throw Cr.NS_ERROR_UNEXPECTED; - - this._stopCallback = typeof callback === "function" - ? callback - : function() { callback.onStopped(); }; - - dumpn(">>> stopping listening on port " + this._socket.port); - this._socket.close(); - this._socket = null; - - // We can't have this identity any more, and the port on which we're running - // this server now could be meaningless the next time around. - this._identity._teardown(); - - this._doQuit = false; - - // socket-close notification and pending request completion happen async - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (file && (!file.exists() || file.isDirectory())) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handler.registerFile(path, file); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // XXX true path validation! - if (path.charAt(0) != "/" || - path.charAt(path.length - 1) != "/" || - (directory && - (!directory.exists() || !directory.isDirectory()))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping - // exists! - - this._handler.registerDirectory(path, directory); - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - this._handler.registerPathHandler(path, handler); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - this._handler.registerPrefixHandler(prefix, handler); - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(code, handler) - { - this._handler.registerErrorHandler(code, handler); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - this._handler.setIndexHandler(handler); - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - this._handler.registerContentType(ext, type); - }, - - // - // see nsIHttpServer.serverIdentity - // - get identity() - { - return this._identity; - }, - - // - // see nsIHttpServer.getState - // - getState: function(path, k) - { - return this._handler._getState(path, k); - }, - - // - // see nsIHttpServer.setState - // - setState: function(path, k, v) - { - return this._handler._setState(path, k, v); - }, - - // - // see nsIHttpServer.getSharedState - // - getSharedState: function(k) - { - return this._handler._getSharedState(k); - }, - - // - // see nsIHttpServer.setSharedState - // - setSharedState: function(k, v) - { - return this._handler._setSharedState(k, v); - }, - - // - // see nsIHttpServer.getObjectState - // - getObjectState: function(k) - { - return this._handler._getObjectState(k); - }, - - // - // see nsIHttpServer.setObjectState - // - setObjectState: function(k, v) - { - return this._handler._setObjectState(k, v); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIServerSocketListener) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NON-XPCOM PUBLIC API - - /** -* Returns true iff this server is not running (and is not in the process of -* serving any requests still to be processed when the server was last -* stopped after being run). -*/ - isStopped: function() - { - return this._socketClosed && !this._hasOpenConnections(); - }, - - // PRIVATE IMPLEMENTATION - - /** True if this server has any open connections to it, false otherwise. */ - _hasOpenConnections: function() - { - // - // If we have any open connections, they're tracked as numeric properties on - // |this._connections|. The non-standard __count__ property could be used - // to check whether there are any properties, but standard-wise, even - // looking forward to ES5, there's no less ugly yet still O(1) way to do - // this. - // - for (var n in this._connections) - return true; - return false; - }, - - /** Calls the server-stopped callback provided when stop() was called. */ - _notifyStopped: function() - { - NS_ASSERT(this._stopCallback !== null, "double-notifying?"); - NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now"); - - // - // NB: We have to grab this now, null out the member, *then* call the - // callback here, or otherwise the callback could (indirectly) futz with - // this._stopCallback by starting and immediately stopping this, at - // which point we'd be nulling out a field we no longer have a right to - // modify. - // - var callback = this._stopCallback; - this._stopCallback = null; - try - { - callback(); - } - catch (e) - { - // not throwing because this is specified as being usually (but not - // always) asynchronous - dump("!!! error running onStopped callback: " + e + "\n"); - } - }, - - /** -* Notifies this server that the given connection has been closed. -* -* @param connection : Connection -* the connection that was closed -*/ - _connectionClosed: function(connection) - { - NS_ASSERT(connection.number in this._connections, - "closing a connection " + this + " that we never added to the " + - "set of open connections?"); - NS_ASSERT(this._connections[connection.number] === connection, - "connection number mismatch? " + - this._connections[connection.number]); - delete this._connections[connection.number]; - - // Fire a pending server-stopped notification if it's our responsibility. - if (!this._hasOpenConnections() && this._socketClosed) - this._notifyStopped(); - }, - - /** -* Requests that the server be shut down when possible. -*/ - _requestQuit: function() - { - dumpn(">>> requesting a quit"); - dumpStack(); - this._doQuit = true; - } -}; - - -// -// RFC 2396 section 3.2.2: -// -// host = hostname | IPv4address -// hostname = *( domainlabel "." ) toplabel [ "." ] -// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum -// toplabel = alpha | alpha *( alphanum | "-" ) alphanum -// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit -// - -const HOST_REGEX = - new RegExp("^(?:" + - // *( domainlabel "." ) - "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" + - // toplabel - "[a-z](?:[a-z0-9-]*[a-z0-9])?" + - "|" + - // IPv4 address - "\\d+\\.\\d+\\.\\d+\\.\\d+" + - ")$", - "i"); - - -/** -* Represents the identity of a server. An identity consists of a set of -* (scheme, host, port) tuples denoted as locations (allowing a single server to -* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any -* host/port). Any incoming request must be to one of these locations, or it -* will be rejected with an HTTP 400 error. One location, denoted as the -* primary location, is the location assigned in contexts where a location -* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests. -* -* A single identity may contain at most one location per unique host/port pair; -* other than that, no restrictions are placed upon what locations may -* constitute an identity. -*/ -function ServerIdentity() -{ - /** The scheme of the primary location. */ - this._primaryScheme = "http"; - - /** The hostname of the primary location. */ - this._primaryHost = "127.0.0.1" - - /** The port number of the primary location. */ - this._primaryPort = -1; - - /** -* The current port number for the corresponding server, stored so that a new -* primary location can always be set if the current one is removed. -*/ - this._defaultPort = -1; - - /** -* Maps hosts to maps of ports to schemes, e.g. the following would represent -* https://example.com:789/ and http://example.org/: -* -* { -* "xexample.com": { 789: "https" }, -* "xexample.org": { 80: "http" } -* } -* -* Note the "x" prefix on hostnames, which prevents collisions with special -* JS names like "prototype". -*/ - this._locations = { "xlocalhost": {} }; -} -ServerIdentity.prototype = -{ - // NSIHTTPSERVERIDENTITY - - // - // see nsIHttpServerIdentity.primaryScheme - // - get primaryScheme() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryScheme; - }, - - // - // see nsIHttpServerIdentity.primaryHost - // - get primaryHost() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryHost; - }, - - // - // see nsIHttpServerIdentity.primaryPort - // - get primaryPort() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryPort; - }, - - // - // see nsIHttpServerIdentity.add - // - add: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - this._locations["x" + host] = entry = {}; - - entry[port] = scheme; - }, - - // - // see nsIHttpServerIdentity.remove - // - remove: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return false; - - var present = port in entry; - delete entry[port]; - - if (this._primaryScheme == scheme && - this._primaryHost == host && - this._primaryPort == port && - this._defaultPort !== -1) - { - // Always keep at least one identity in existence at any time, unless - // we're in the process of shutting down (the last condition above). - this._primaryPort = -1; - this._initialize(this._defaultPort, host, false); - } - - return present; - }, - - // - // see nsIHttpServerIdentity.has - // - has: function(scheme, host, port) - { - this._validate(scheme, host, port); - - return "x" + host in this._locations && - scheme === this._locations["x" + host][port]; - }, - - // - // see nsIHttpServerIdentity.has - // - getScheme: function(host, port) - { - this._validate("http", host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return ""; - - return entry[port] || ""; - }, - - // - // see nsIHttpServerIdentity.setPrimary - // - setPrimary: function(scheme, host, port) - { - this._validate(scheme, host, port); - - this.add(scheme, host, port); - - this._primaryScheme = scheme; - this._primaryHost = host; - this._primaryPort = port; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Initializes the primary name for the corresponding server, based on the -* provided port number. -*/ - _initialize: function(port, host, addSecondaryDefault) - { - this._host = host; - if (this._primaryPort !== -1) - this.add("http", host, port); - else - this.setPrimary("http", "localhost", port); - this._defaultPort = port; - - // Only add this if we're being called at server startup - if (addSecondaryDefault && host != "127.0.0.1") - this.add("http", "127.0.0.1", port); - }, - - /** -* Called at server shutdown time, unsets the primary location only if it was -* the default-assigned location and removes the default location from the -* set of locations used. -*/ - _teardown: function() - { - if (this._host != "127.0.0.1") { - // Not the default primary location, nothing special to do here - this.remove("http", "127.0.0.1", this._defaultPort); - } - - // This is a *very* tricky bit of reasoning here; make absolutely sure the - // tests for this code pass before you commit changes to it. - if (this._primaryScheme == "http" && - this._primaryHost == this._host && - this._primaryPort == this._defaultPort) - { - // Make sure we don't trigger the readding logic in .remove(), then remove - // the default location. - var port = this._defaultPort; - this._defaultPort = -1; - this.remove("http", this._host, port); - - // Ensure a server start triggers the setPrimary() path in ._initialize() - this._primaryPort = -1; - } - else - { - // No reason not to remove directly as it's not our primary location - this.remove("http", this._host, this._defaultPort); - } - }, - - /** -* Ensures scheme, host, and port are all valid with respect to RFC 2396. -* -* @throws NS_ERROR_ILLEGAL_VALUE -* if any argument doesn't match the corresponding production -*/ - _validate: function(scheme, host, port) - { - if (scheme !== "http" && scheme !== "https") - { - dumpn("*** server only supports http/https schemes: '" + scheme + "'"); - dumpStack(); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (!HOST_REGEX.test(host)) - { - dumpn("*** unexpected host: '" + host + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (port < 0 || port > 65535) - { - dumpn("*** unexpected port: '" + port + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - } -}; - - -/** -* Represents a connection to the server (and possibly in the future the thread -* on which the connection is processed). -* -* @param input : nsIInputStream -* stream from which incoming data on the connection is read -* @param output : nsIOutputStream -* stream to write data out the connection -* @param server : nsHttpServer -* the server handling the connection -* @param port : int -* the port on which the server is running -* @param outgoingPort : int -* the outgoing port used by this connection -* @param number : uint -* a serial number used to uniquely identify this connection -*/ -function Connection(input, output, server, port, outgoingPort, number) -{ - dumpn("*** opening new connection " + number + " on port " + outgoingPort); - - /** Stream of incoming data. */ - this.input = input; - - /** Stream for outgoing data. */ - this.output = output; - - /** The server associated with this request. */ - this.server = server; - - /** The port on which the server is running. */ - this.port = port; - - /** The outgoing poort used by this connection. */ - this._outgoingPort = outgoingPort; - - /** The serial number of this connection. */ - this.number = number; - - /** -* The request for which a response is being generated, null if the -* incoming request has not been fully received or if it had errors. -*/ - this.request = null; - - /** State variables for debugging. */ - this._closed = this._processed = false; -} -Connection.prototype = -{ - /** Closes this connection's input/output streams. */ - close: function() - { - dumpn("*** closing connection " + this.number + - " on port " + this._outgoingPort); - - this.input.close(); - this.output.close(); - this._closed = true; - - var server = this.server; - server._connectionClosed(this); - - // If an error triggered a server shutdown, act on it now - if (server._doQuit) - server.stop(function() { /* not like we can do anything better */ }); - }, - - /** -* Initiates processing of this connection, using the data in the given -* request. -* -* @param request : Request -* the request which should be processed -*/ - process: function(request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - - this.request = request; - this.server._handler.handleResponse(this); - }, - - /** -* Initiates processing of this connection, generating a response with the -* given HTTP error code. -* -* @param code : uint -* an HTTP code, so in the range [0, 1000) -* @param request : Request -* incomplete data about the incoming request (since there were errors -* during its processing -*/ - processError: function(code, request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - this.request = request; - this.server._handler.handleError(code, this); - }, - - /** Converts this to a string for debugging purposes. */ - toString: function() - { - return ""; - } -}; - - - -/** Returns an array of count bytes from the given input stream. */ -function readBytes(inputStream, count) -{ - return new BinaryInputStream(inputStream).readByteArray(count); -} - - - -/** Request reader processing states; see RequestReader for details. */ -const READER_IN_REQUEST_LINE = 0; -const READER_IN_HEADERS = 1; -const READER_IN_BODY = 2; -const READER_FINISHED = 3; - - -/** -* Reads incoming request data asynchronously, does any necessary preprocessing, -* and forwards it to the request handler. Processing occurs in three states: -* -* READER_IN_REQUEST_LINE Reading the request's status line -* READER_IN_HEADERS Reading headers in the request -* READER_IN_BODY Reading the body of the request -* READER_FINISHED Entire request has been read and processed -* -* During the first two stages, initial metadata about the request is gathered -* into a Request object. Once the status line and headers have been processed, -* we start processing the body of the request into the Request. Finally, when -* the entire body has been read, we create a Response and hand it off to the -* ServerHandler to be given to the appropriate request handler. -* -* @param connection : Connection -* the connection for the request being read -*/ -function RequestReader(connection) -{ - /** Connection metadata for this request. */ - this._connection = connection; - - /** -* A container providing line-by-line access to the raw bytes that make up the -* data which has been read from the connection but has not yet been acted -* upon (by passing it to the request handler or by extracting request -* metadata from it). -*/ - this._data = new LineData(); - - /** -* The amount of data remaining to be read from the body of this request. -* After all headers in the request have been read this is the value in the -* Content-Length header, but as the body is read its value decreases to zero. -*/ - this._contentLength = 0; - - /** The current state of parsing the incoming request. */ - this._state = READER_IN_REQUEST_LINE; - - /** Metadata constructed from the incoming request for the request handler. */ - this._metadata = new Request(connection.port); - - /** -* Used to preserve state if we run out of line data midway through a -* multi-line header. _lastHeaderName stores the name of the header, while -* _lastHeaderValue stores the value we've seen so far for the header. -* -* These fields are always either both undefined or both strings. -*/ - this._lastHeaderName = this._lastHeaderValue = undefined; -} -RequestReader.prototype = -{ - // NSIINPUTSTREAMCALLBACK - - /** -* Called when more data from the incoming request is available. This method -* then reads the available data from input and deals with that data as -* necessary, depending upon the syntax of already-downloaded data. -* -* @param input : nsIAsyncInputStream -* the stream of incoming data from the connection -*/ - onInputStreamReady: function(input) - { - dumpn("*** onInputStreamReady(input=" + input + ") on thread " + - gThreadManager.currentThread + " (main is " + - gThreadManager.mainThread + ")"); - dumpn("*** this._state == " + this._state); - - // Handle cases where we get more data after a request error has been - // discovered but *before* we can close the connection. - var data = this._data; - if (!data) - return; - - try - { - data.appendBytes(readBytes(input, input.available())); - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** WARNING: unexpected error when reading from socket; will " + - "be treated as if the input stream had been closed"); - dumpn("*** WARNING: actual error was: " + e); - } - - // We've lost a race -- input has been closed, but we're still expecting - // to read more data. available() will throw in this case, and since - // we're dead in the water now, destroy the connection. - dumpn("*** onInputStreamReady called on a closed input, destroying " + - "connection"); - this._connection.close(); - return; - } - - switch (this._state) - { - default: - NS_ASSERT(false, "invalid state: " + this._state); - break; - - case READER_IN_REQUEST_LINE: - if (!this._processRequestLine()) - break; - /* fall through */ - - case READER_IN_HEADERS: - if (!this._processHeaders()) - break; - /* fall through */ - - case READER_IN_BODY: - this._processBody(); - } - - if (this._state != READER_FINISHED) - input.asyncWait(this, 0, 0, gThreadManager.currentThread); - }, - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIInputStreamCallback) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE API - - /** -* Processes unprocessed, downloaded data as a request line. -* -* @returns boolean -* true iff the request line has been fully processed -*/ - _processRequestLine: function() - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - // Servers SHOULD ignore any empty line(s) received where a Request-Line - // is expected (section 4.1). - var data = this._data; - var line = {}; - var readSuccess; - while ((readSuccess = data.readLine(line)) && line.value == "") - dumpn("*** ignoring beginning blank line..."); - - // if we don't have a full line, wait until we do - if (!readSuccess) - return false; - - // we have the first non-blank line - try - { - this._parseRequestLine(line.value); - this._state = READER_IN_HEADERS; - return true; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing request headers. -* -* @returns boolean -* true iff header data in the request has been fully processed -*/ - _processHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - // XXX things to fix here: - // - // - need to support RFC 2047-encoded non-US-ASCII characters - - try - { - var done = this._parseHeaders(); - if (done) - { - var request = this._metadata; - - // XXX this is wrong for requests with transfer-encodings applied to - // them, particularly chunked (which by its nature can have no - // meaningful Content-Length header)! - this._contentLength = request.hasHeader("Content-Length") - ? parseInt(request.getHeader("Content-Length"), 10) - : 0; - dumpn("_processHeaders, Content-length=" + this._contentLength); - - this._state = READER_IN_BODY; - } - return done; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing the request body. -* -* @returns boolean -* true iff the request body has been fully processed -*/ - _processBody: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - // XXX handle chunked transfer-coding request bodies! - - try - { - if (this._contentLength > 0) - { - var data = this._data.purge(); - var count = Math.min(data.length, this._contentLength); - dumpn("*** loading data=" + data + " len=" + data.length + - " excess=" + (data.length - count)); - - var bos = new BinaryOutputStream(this._metadata._bodyOutputStream); - bos.writeByteArray(data, count); - this._contentLength -= count; - } - - dumpn("*** remaining body data len=" + this._contentLength); - if (this._contentLength == 0) - { - this._validateRequest(); - this._state = READER_FINISHED; - this._handleResponse(); - return true; - } - - return false; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Does various post-header checks on the data in this request. -* -* @throws : HttpError -* if the request was malformed in some way -*/ - _validateRequest: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - dumpn("*** _validateRequest"); - - var metadata = this._metadata; - var headers = metadata._headers; - - // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header - var identity = this._connection.server.identity; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - { - if (!headers.hasHeader("Host")) - { - dumpn("*** malformed HTTP/1.1 or greater request with no Host header!"); - throw HTTP_400; - } - - // If the Request-URI wasn't absolute, then we need to determine our host. - // We have to determine what scheme was used to access us based on the - // server identity data at this point, because the request just doesn't - // contain enough data on its own to do this, sadly. - if (!metadata._host) - { - var host, port; - var hostPort = headers.getHeader("Host"); - var colon = hostPort.indexOf(":"); - if (colon < 0) - { - host = hostPort; - port = ""; - } - else - { - host = hostPort.substring(0, colon); - port = hostPort.substring(colon + 1); - } - - // NB: We allow an empty port here because, oddly, a colon may be - // present even without a port number, e.g. "example.com:"; in this - // case the default port applies. - if (!HOST_REGEX.test(host) || !/^\d*$/.test(port)) - { - dumpn("*** malformed hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - // If we're not given a port, we're stuck, because we don't know what - // scheme to use to look up the correct port here, in general. Since - // the HTTPS case requires a tunnel/proxy and thus requires that the - // requested URI be absolute (and thus contain the necessary - // information), let's assume HTTP will prevail and use that. - port = +port || 80; - - var scheme = identity.getScheme(host, port); - if (!scheme) - { - dumpn("*** unrecognized hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - } - } - else - { - NS_ASSERT(metadata._host === undefined, - "HTTP/1.0 doesn't allow absolute paths in the request line!"); - - metadata._scheme = identity.primaryScheme; - metadata._host = identity.primaryHost; - metadata._port = identity.primaryPort; - } - - NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port), - "must have a location we recognize by now!"); - }, - - /** -* Handles responses in case of error, either in the server or in the request. -* -* @param e -* the specific error encountered, which is an HttpError in the case where -* the request is in some way invalid or cannot be fulfilled; if this isn't -* an HttpError we're going to be paranoid and shut down, because that -* shouldn't happen, ever -*/ - _handleError: function(e) - { - // Don't fall back into normal processing! - this._state = READER_FINISHED; - - var server = this._connection.server; - if (e instanceof HttpError) - { - var code = e.code; - } - else - { - dumpn("!!! UNEXPECTED ERROR: " + e + - (e.lineNumber ? ", line " + e.lineNumber : "")); - - // no idea what happened -- be paranoid and shut down - code = 500; - server._requestQuit(); - } - - // make attempted reuse of data an error - this._data = null; - - this._connection.processError(code, this._metadata); - }, - - /** -* Now that we've read the request line and headers, we can actually hand off -* the request to be handled. -* -* This method is called once per request, after the request line and all -* headers and the body, if any, have been received. -*/ - _handleResponse: function() - { - NS_ASSERT(this._state == READER_FINISHED); - - // We don't need the line-based data any more, so make attempted reuse an - // error. - this._data = null; - - this._connection.process(this._metadata); - }, - - - // PARSING - - /** -* Parses the request line for the HTTP request associated with this. -* -* @param line : string -* the request line -*/ - _parseRequestLine: function(line) - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - dumpn("*** _parseRequestLine('" + line + "')"); - - var metadata = this._metadata; - - // clients and servers SHOULD accept any amount of SP or HT characters - // between fields, even though only a single SP is required (section 19.3) - var request = line.split(/[ \t]+/); - if (!request || request.length != 3) - throw HTTP_400; - - metadata._method = request[0]; - - // get the HTTP version - var ver = request[2]; - var match = ver.match(/^HTTP\/(\d+\.\d+)$/); - if (!match) - throw HTTP_400; - - // determine HTTP version - try - { - metadata._httpVersion = new nsHttpVersion(match[1]); - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0)) - throw "unsupported HTTP version"; - } - catch (e) - { - // we support HTTP/1.0 and HTTP/1.1 only - throw HTTP_501; - } - - - var fullPath = request[1]; - var serverIdentity = this._connection.server.identity; - - var scheme, host, port; - - if (fullPath.charAt(0) != "/") - { - // No absolute paths in the request line in HTTP prior to 1.1 - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - throw HTTP_400; - - try - { - var uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(fullPath, null, null); - fullPath = uri.path; - scheme = uri.scheme; - host = metadata._host = uri.asciiHost; - port = uri.port; - if (port === -1) - { - if (scheme === "http") - port = 80; - else if (scheme === "https") - port = 443; - else - throw HTTP_400; - } - } - catch (e) - { - // If the host is not a valid host on the server, the response MUST be a - // 400 (Bad Request) error message (section 5.2). Alternately, the URI - // is malformed. - throw HTTP_400; - } - - if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/") - throw HTTP_400; - } - - var splitter = fullPath.indexOf("?"); - if (splitter < 0) - { - // _queryString already set in ctor - metadata._path = fullPath; - } - else - { - metadata._path = fullPath.substring(0, splitter); - metadata._queryString = fullPath.substring(splitter + 1); - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - }, - - /** -* Parses all available HTTP headers in this until the header-ending CRLFCRLF, -* adding them to the store of headers in the request. -* -* @throws -* HTTP_400 if the headers are malformed -* @returns boolean -* true if all headers have now been processed, false otherwise -*/ - _parseHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - dumpn("*** _parseHeaders"); - - var data = this._data; - - var headers = this._metadata._headers; - var lastName = this._lastHeaderName; - var lastVal = this._lastHeaderValue; - - var line = {}; - while (true) - { - NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)), - lastName === undefined ? - "lastVal without lastName? lastVal: '" + lastVal + "'" : - "lastName without lastVal? lastName: '" + lastName + "'"); - - if (!data.readLine(line)) - { - // save any data we have from the header we might still be processing - this._lastHeaderName = lastName; - this._lastHeaderValue = lastVal; - return false; - } - - var lineText = line.value; - var firstChar = lineText.charAt(0); - - // blank line means end of headers - if (lineText == "") - { - // we're finished with the previous header - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - else - { - // no headers in request -- valid for HTTP/1.0 requests - } - - // either way, we're done processing headers - this._state = READER_IN_BODY; - return true; - } - else if (firstChar == " " || firstChar == "\t") - { - // multi-line header if we've already seen a header line - if (!lastName) - { - // we don't have a header to continue! - throw HTTP_400; - } - - // append this line's text to the value; starts with SP/HT, so no need - // for separating whitespace - lastVal += lineText; - } - else - { - // we have a new header, so set the old one (if one existed) - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - - var colon = lineText.indexOf(":"); // first colon must be splitter - if (colon < 1) - { - // no colon or missing header field-name - throw HTTP_400; - } - - // set header name, value (to be set in the next loop, usually) - lastName = lineText.substring(0, colon); - lastVal = lineText.substring(colon + 1); - } // empty, continuation, start of header - } // while (true) - } -}; - - -/** The character codes for CR and LF. */ -const CR = 0x0D, LF = 0x0A; - -/** -* Calculates the number of characters before the first CRLF pair in array, or -* -1 if the array contains no CRLF pair. -* -* @param array : Array -* an array of numbers in the range [0, 256), each representing a single -* character; the first CRLF is the lowest index i where -* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|, -* if such an |i| exists, and -1 otherwise -* @returns int -* the index of the first CRLF if any were present, -1 otherwise -*/ -function findCRLF(array) -{ - for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1)) - { - if (array[i + 1] == LF) - return i; - } - return -1; -} - - -/** -* A container which provides line-by-line access to the arrays of bytes with -* which it is seeded. -*/ -function LineData() -{ - /** An array of queued bytes from which to get line-based characters. */ - this._data = []; -} -LineData.prototype = -{ - /** -* Appends the bytes in the given array to the internal data cache maintained -* by this. -*/ - appendBytes: function(bytes) - { - Array.prototype.push.apply(this._data, bytes); - }, - - /** -* Removes and returns a line of data, delimited by CRLF, from this. -* -* @param out -* an object whose "value" property will be set to the first line of text -* present in this, sans CRLF, if this contains a full CRLF-delimited line -* of text; if this doesn't contain enough data, the value of the property -* is undefined -* @returns boolean -* true if a full line of data could be read from the data in this, false -* otherwise -*/ - readLine: function(out) - { - var data = this._data; - var length = findCRLF(data); - if (length < 0) - return false; - - // - // We have the index of the CR, so remove all the characters, including - // CRLF, from the array with splice, and convert the removed array into the - // corresponding string, from which we then strip the trailing CRLF. - // - // Getting the line in this matter acknowledges that substring is an O(1) - // operation in SpiderMonkey because strings are immutable, whereas two - // splices, both from the beginning of the data, are less likely to be as - // cheap as a single splice plus two extra character conversions. - // - var line = String.fromCharCode.apply(null, data.splice(0, length + 2)); - out.value = line.substring(0, length); - - return true; - }, - - /** -* Removes the bytes currently within this and returns them in an array. -* -* @returns Array -* the bytes within this when this method is called -*/ - purge: function() - { - var data = this._data; - this._data = []; - return data; - } -}; - - - -/** -* Creates a request-handling function for an nsIHttpRequestHandler object. -*/ -function createHandlerFunc(handler) -{ - return function(metadata, response) { handler.handle(metadata, response); }; -} - - -/** -* The default handler for directories; writes an HTML response containing a -* slightly-formatted directory listing. -*/ -function defaultIndexHandler(metadata, response) -{ - response.setHeader("Content-Type", "text/html", false); - - var path = htmlEscape(decodeURI(metadata.path)); - - // - // Just do a very basic bit of directory listings -- no need for too much - // fanciness, especially since we don't have a style sheet in which we can - // stick rules (don't want to pollute the default path-space). - // - - var body = '\ -\ -' + path + '\ -\ -\ -

' + path + '

\ -
    '; - - var directory = metadata.getProperty("directory").QueryInterface(Ci.nsILocalFile); - NS_ASSERT(directory && directory.isDirectory()); - - var fileList = []; - var files = directory.directoryEntries; - while (files.hasMoreElements()) - { - var f = files.getNext().QueryInterface(Ci.nsIFile); - var name = f.leafName; - if (!f.isHidden() && - (name.charAt(name.length - 1) != HIDDEN_CHAR || - name.charAt(name.length - 2) == HIDDEN_CHAR)) - fileList.push(f); - } - - fileList.sort(fileSort); - - for (var i = 0; i < fileList.length; i++) - { - var file = fileList[i]; - try - { - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - var sep = file.isDirectory() ? "/" : ""; - - // Note: using " to delimit the attribute here because encodeURIComponent - // passes through '. - var item = '
  1. ' + - htmlEscape(name) + sep + - '
  2. '; - - body += item; - } - catch (e) { /* some file system error, ignore the file */ } - } - - body += '
\ -\ -'; - - response.bodyOutputStream.write(body, body.length); -} - -/** -* Sorts a and b (nsIFile objects) into an aesthetically pleasing order. -*/ -function fileSort(a, b) -{ - var dira = a.isDirectory(), dirb = b.isDirectory(); - - if (dira && !dirb) - return -1; - if (dirb && !dira) - return 1; - - var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase(); - return nameb > namea ? -1 : 1; -} - - -/** -* Converts an externally-provided path into an internal path for use in -* determining file mappings. -* -* @param path -* the path to convert -* @param encoded -* true if the given path should be passed through decodeURI prior to -* conversion -* @throws URIError -* if path is incorrectly encoded -*/ -function toInternalPath(path, encoded) -{ - if (encoded) - path = decodeURI(path); - - var comps = path.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - if (comp.charAt(comp.length - 1) == HIDDEN_CHAR) - comps[i] = comp + HIDDEN_CHAR; - } - return comps.join("/"); -} - - -/** -* Adds custom-specified headers for the given file to the given response, if -* any such headers are specified. -* -* @param file -* the file on the disk which is to be written -* @param metadata -* metadata about the incoming request -* @param response -* the Response to which any specified headers/data should be written -* @throws HTTP_500 -* if an error occurred while processing custom-specified headers -*/ -function maybeAddHeaders(file, metadata, response) -{ - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - - var headerFile = file.parent; - headerFile.append(name + HEADERS_SUFFIX); - - if (!headerFile.exists()) - return; - - const PR_RDONLY = 0x01; - var fis = new FileInputStream(headerFile, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0); - lis.QueryInterface(Ci.nsIUnicharLineInputStream); - - var line = {value: ""}; - var more = lis.readLine(line); - - if (!more && line.value == "") - return; - - - // request line - - var status = line.value; - if (status.indexOf("HTTP ") == 0) - { - status = status.substring(5); - var space = status.indexOf(" "); - var code, description; - if (space < 0) - { - code = status; - description = ""; - } - else - { - code = status.substring(0, space); - description = status.substring(space + 1, status.length); - } - - response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description); - - line.value = ""; - more = lis.readLine(line); - } - - // headers - while (more || line.value != "") - { - var header = line.value; - var colon = header.indexOf(":"); - - response.setHeader(header.substring(0, colon), - header.substring(colon + 1, header.length), - false); // allow overriding server-set headers - - line.value = ""; - more = lis.readLine(line); - } - } - catch (e) - { - dumpn("WARNING: error in headers for " + metadata.path + ": " + e); - throw HTTP_500; - } - finally - { - fis.close(); - } -} - - -/** -* An object which handles requests for a server, executing default and -* overridden behaviors as instructed by the code which uses and manipulates it. -* Default behavior includes the paths / and /trace (diagnostics), with some -* support for HTTP error pages for various codes and fallback to HTTP 500 if -* those codes fail for any reason. -* -* @param server : nsHttpServer -* the server in which this handler is being used -*/ -function ServerHandler(server) -{ - // FIELDS - - /** -* The nsHttpServer instance associated with this handler. -*/ - this._server = server; - - /** -* A FileMap object containing the set of path->nsILocalFile mappings for -* all directory mappings set in the server (e.g., "/" for /var/www/html/, -* "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2). -* -* Note carefully: the leading and trailing "/" in each path (not file) are -* removed before insertion to simplify the code which uses this. You have -* been warned! -*/ - this._pathDirectoryMap = new FileMap(); - - /** -* Custom request handlers for the server in which this resides. Path-handler -* pairs are stored as property-value pairs in this property. -* -* @see ServerHandler.prototype._defaultPaths -*/ - this._overridePaths = {}; - - /** -* Custom request handlers for the server in which this resides. Prefix-handler -* pairs are stored as property-value pairs in this property. -*/ - this._overridePrefixes = {}; - - /** -* Custom request handlers for the error handlers in the server in which this -* resides. Path-handler pairs are stored as property-value pairs in this -* property. -* -* @see ServerHandler.prototype._defaultErrors -*/ - this._overrideErrors = {}; - - /** -* Maps file extensions to their MIME types in the server, overriding any -* mapping that might or might not exist in the MIME service. -*/ - this._mimeMappings = {}; - - /** -* The default handler for requests for directories, used to serve directories -* when no index file is present. -*/ - this._indexHandler = defaultIndexHandler; - - /** Per-path state storage for the server. */ - this._state = {}; - - /** Entire-server state storage. */ - this._sharedState = {}; - - /** Entire-server state storage for nsISupports values. */ - this._objectState = {}; -} -ServerHandler.prototype = -{ - // PUBLIC API - - /** -* Handles a request to this server, responding to the request appropriately -* and initiating server shutdown if necessary. -* -* This method never throws an exception. -* -* @param connection : Connection -* the connection for this request -*/ - handleResponse: function(connection) - { - var request = connection.request; - var response = new Response(connection); - - var path = request.path; - dumpn("*** path == " + path); - - try - { - try - { - if (path in this._overridePaths) - { - // explicit paths first, then files based on existing directory mappings, - // then (if the file doesn't exist) built-in server default paths - dumpn("calling override for " + path); - this._overridePaths[path](request, response); - } - else - { - let longestPrefix = ""; - for (let prefix in this._overridePrefixes) - { - if (prefix.length > longestPrefix.length && path.startsWith(prefix)) - { - longestPrefix = prefix; - } - } - if (longestPrefix.length > 0) - { - dumpn("calling prefix override for " + longestPrefix); - this._overridePrefixes[longestPrefix](request, response); - } - else - { - this._handleDefault(request, response); - } - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - if (!(e instanceof HttpError)) - { - dumpn("*** unexpected error: e == " + e); - throw HTTP_500; - } - if (e.code !== 404) - throw e; - - dumpn("*** default: " + (path in this._defaultPaths)); - - response = new Response(connection); - if (path in this._defaultPaths) - this._defaultPaths[path](request, response); - else - throw HTTP_404; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - var errorCode = "internal"; - - try - { - if (!(e instanceof HttpError)) - throw e; - - errorCode = e.code; - dumpn("*** errorCode == " + errorCode); - - response = new Response(connection); - if (e.customErrorHandling) - e.customErrorHandling(response); - this._handleError(errorCode, request, response); - return; - } - catch (e2) - { - dumpn("*** error handling " + errorCode + " error: " + - "e2 == " + e2 + ", shutting down server"); - - connection.server._requestQuit(); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (!file) - { - dumpn("*** unregistering '" + path + "' mapping"); - delete this._overridePaths[path]; - return; - } - - dumpn("*** registering '" + path + "' as mapping to " + file.path); - file = file.clone(); - - var self = this; - this._overridePaths[path] = - function(request, response) - { - if (!file.exists()) - throw HTTP_404; - - response.setStatusLine(request.httpVersion, 200, "OK"); - self._writeFileResponse(request, file, response, 0, file.fileSize); - }; - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - // XXX true path validation! - if (path.charAt(0) != "/") - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePaths, path); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - // XXX true prefix validation! - if (!(prefix.startsWith("/") && prefix.endsWith("/"))) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePrefixes, prefix); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // strip off leading and trailing '/' so that we can use lastIndexOf when - // determining exactly how a path maps onto a mapped directory -- - // conditional is required here to deal with "/".substring(1, 0) being - // converted to "/".substring(0, 1) per the JS specification - var key = path.length == 1 ? "" : path.substring(1, path.length - 1); - - // the path-to-directory mapping code requires that the first character not - // be "/", or it will go into an infinite loop - if (key.charAt(0) == "/") - throw Cr.NS_ERROR_INVALID_ARG; - - key = toInternalPath(key, false); - - if (directory) - { - dumpn("*** mapping '" + path + "' to the location " + directory.path); - this._pathDirectoryMap.put(key, directory); - } - else - { - dumpn("*** removing mapping for '" + path + "'"); - this._pathDirectoryMap.put(key, null); - } - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(err, handler) - { - if (!(err in HTTP_ERROR_CODES)) - dumpn("*** WARNING: registering non-HTTP/1.1 error code " + - "(" + err + ") handler -- was this intentional?"); - - this._handlerToField(handler, this._overrideErrors, err); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - if (!handler) - handler = defaultIndexHandler; - else if (typeof(handler) != "function") - handler = createHandlerFunc(handler); - - this._indexHandler = handler; - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - if (!type) - delete this._mimeMappings[ext]; - else - this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type); - }, - - // PRIVATE API - - /** -* Sets or remove (if handler is null) a handler in an object with a key. -* -* @param handler -* a handler, either function or an nsIHttpRequestHandler -* @param dict -* The object to attach the handler to. -* @param key -* The field name of the handler. -*/ - _handlerToField: function(handler, dict, key) - { - // for convenience, handler can be a function if this is run from xpcshell - if (typeof(handler) == "function") - dict[key] = handler; - else if (handler) - dict[key] = createHandlerFunc(handler); - else - delete dict[key]; - }, - - /** -* Handles a request which maps to a file in the local filesystem (if a base -* path has already been set; otherwise the 404 error is thrown). -* -* @param metadata : Request -* metadata for the incoming request -* @param response : Response -* an uninitialized Response to the given request, to be initialized by a -* request handler -* @throws HTTP_### -* if an HTTP error occurred (usually HTTP_404); note that in this case the -* calling code must handle post-processing of the response -*/ - _handleDefault: function(metadata, response) - { - dumpn("*** _handleDefault()"); - - response.setStatusLine(metadata.httpVersion, 200, "OK"); - - var path = metadata.path; - NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">"); - - // determine the actual on-disk file; this requires finding the deepest - // path-to-directory mapping in the requested URL - var file = this._getFileForPath(path); - - // the "file" might be a directory, in which case we either serve the - // contained index.html or make the index handler write the response - if (file.exists() && file.isDirectory()) - { - file.append("index.html"); // make configurable? - if (!file.exists() || file.isDirectory()) - { - metadata._ensurePropertyBag(); - metadata._bag.setPropertyAsInterface("directory", file.parent); - this._indexHandler(metadata, response); - return; - } - } - - // alternately, the file might not exist - if (!file.exists()) - throw HTTP_404; - - var start, end; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) && - metadata.hasHeader("Range") && - this._getTypeFromFile(file) !== SJS_TYPE) - { - var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/); - if (!rangeMatch) - throw HTTP_400; - - if (rangeMatch[1] !== undefined) - start = parseInt(rangeMatch[1], 10); - - if (rangeMatch[2] !== undefined) - end = parseInt(rangeMatch[2], 10); - - if (start === undefined && end === undefined) - throw HTTP_400; - - // No start given, so the end is really the count of bytes from the - // end of the file. - if (start === undefined) - { - start = Math.max(0, file.fileSize - end); - end = file.fileSize - 1; - } - - // start and end are inclusive - if (end === undefined || end >= file.fileSize) - end = file.fileSize - 1; - - if (start !== undefined && start >= file.fileSize) { - var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable"); - HTTP_416.customErrorHandling = function(errorResponse) - { - maybeAddHeaders(file, metadata, errorResponse); - }; - throw HTTP_416; - } - - if (end < start) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - start = 0; - end = file.fileSize - 1; - } - else - { - response.setStatusLine(metadata.httpVersion, 206, "Partial Content"); - var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize; - response.setHeader("Content-Range", contentRange); - } - } - else - { - start = 0; - end = file.fileSize - 1; - } - - // finally... - dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " + - start + " to " + end + " inclusive"); - this._writeFileResponse(metadata, file, response, start, end - start + 1); - }, - - /** -* Writes an HTTP response for the given file, including setting headers for -* file metadata. -* -* @param metadata : Request -* the Request for which a response is being generated -* @param file : nsILocalFile -* the file which is to be sent in the response -* @param response : Response -* the response to which the file should be written -* @param offset: uint -* the byte offset to skip to when writing -* @param count: uint -* the number of bytes to write -*/ - _writeFileResponse: function(metadata, file, response, offset, count) - { - const PR_RDONLY = 0x01; - - var type = this._getTypeFromFile(file); - if (type === SJS_TYPE) - { - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var sis = new ScriptableInputStream(fis); - var s = Cu.Sandbox(gGlobalObject); - s.importFunction(dump, "dump"); - - // Define a basic key-value state-preservation API across requests, with - // keys initially corresponding to the empty string. - var self = this; - var path = metadata.path; - s.importFunction(function getState(k) - { - return self._getState(path, k); - }); - s.importFunction(function setState(k, v) - { - self._setState(path, k, v); - }); - s.importFunction(function getSharedState(k) - { - return self._getSharedState(k); - }); - s.importFunction(function setSharedState(k, v) - { - self._setSharedState(k, v); - }); - s.importFunction(function getObjectState(k, callback) - { - callback(self._getObjectState(k)); - }); - s.importFunction(function setObjectState(k, v) - { - self._setObjectState(k, v); - }); - s.importFunction(function registerPathHandler(p, h) - { - self.registerPathHandler(p, h); - }); - - // Make it possible for sjs files to access their location - this._setState(path, "__LOCATION__", file.path); - - try - { - // Alas, the line number in errors dumped to console when calling the - // request handler is simply an offset from where we load the SJS file. - // Work around this in a reasonably non-fragile way by dynamically - // getting the line number where we evaluate the SJS file. Don't - // separate these two lines! - var line = new Error().lineNumber; - Cu.evalInSandbox(sis.read(file.fileSize), s); - } - catch (e) - { - dumpn("*** syntax error in SJS at " + file.path + ": " + e); - throw HTTP_500; - } - - try - { - s.handleRequest(metadata, response); - } - catch (e) - { - dump("*** error running SJS at " + file.path + ": " + - e + " on line " + - (e instanceof Error - ? e.lineNumber + " in httpd.js" - : (e.lineNumber - line)) + "\n"); - throw HTTP_500; - } - } - finally - { - fis.close(); - } - } - else - { - try - { - response.setHeader("Last-Modified", - toDateString(file.lastModifiedTime), - false); - } - catch (e) { /* lastModifiedTime threw, ignore */ } - - response.setHeader("Content-Type", type, false); - maybeAddHeaders(file, metadata, response); - response.setHeader("Content-Length", "" + count, false); - - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - offset = offset || 0; - count = count || file.fileSize; - NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset"); - NS_ASSERT(count >= 0, "bad count"); - NS_ASSERT(offset + count <= file.fileSize, "bad total data size"); - - try - { - if (offset !== 0) - { - // Seek (or read, if seeking isn't supported) to the correct offset so - // the data sent to the client matches the requested range. - if (fis instanceof Ci.nsISeekableStream) - fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset); - else - new ScriptableInputStream(fis).read(offset); - } - } - catch (e) - { - fis.close(); - throw e; - } - - let writeMore = function writeMore() - { - gThreadManager.currentThread - .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL); - } - - var input = new BinaryInputStream(fis); - var output = new BinaryOutputStream(response.bodyOutputStream); - var writeData = - { - run: function() - { - var chunkSize = Math.min(65536, count); - count -= chunkSize; - NS_ASSERT(count >= 0, "underflow"); - - try - { - var data = input.readByteArray(chunkSize); - NS_ASSERT(data.length === chunkSize, - "incorrect data returned? got " + data.length + - ", expected " + chunkSize); - output.writeByteArray(data, data.length); - if (count === 0) - { - fis.close(); - response.finish(); - } - else - { - writeMore(); - } - } - catch (e) - { - try - { - fis.close(); - } - finally - { - response.finish(); - } - throw e; - } - } - }; - - writeMore(); - - // Now that we know copying will start, flag the response as async. - response.processAsync(); - } - }, - - /** -* Get the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getState: function(path, k) - { - var state = this._state; - if (path in state && k in state[path]) - return state[path][k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setState: function(path, k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - var state = this._state; - if (!(path in state)) - state[path] = {}; - state[path][k] = v; - }, - - /** -* Get the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getSharedState: function(k) - { - var state = this._sharedState; - if (k in state) - return state[k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setSharedState: function(k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - this._sharedState[k] = v; - }, - - /** -* Returns the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be returned -* @returns nsISupports -* the corresponding object, or null if none was present -*/ - _getObjectState: function(k) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - return this._objectState[k] || null; - }, - - /** -* Sets the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be set -* @param v : nsISupports -* the object to be associated with the given key; may be null -*/ - _setObjectState: function(k, v) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - if (typeof v !== "object") - throw new Error("non-object value passed"); - if (v && !("QueryInterface" in v)) - { - throw new Error("must pass an nsISupports; use wrappedJSObject to ease " + - "pain when using the server from JS"); - } - - this._objectState[k] = v; - }, - - /** -* Gets a content-type for the given file, first by checking for any custom -* MIME-types registered with this handler for the file's extension, second by -* asking the global MIME service for a content-type, and finally by failing -* over to application/octet-stream. -* -* @param file : nsIFile -* the nsIFile for which to get a file type -* @returns string -* the best content-type which can be determined for the file -*/ - _getTypeFromFile: function(file) - { - try - { - var name = file.leafName; - var dot = name.lastIndexOf("."); - if (dot > 0) - { - var ext = name.slice(dot + 1); - if (ext in this._mimeMappings) - return this._mimeMappings[ext]; - } - return Cc["@mozilla.org/uriloader/external-helper-app-service;1"] - .getService(Ci.nsIMIMEService) - .getTypeFromFile(file); - } - catch (e) - { - return "application/octet-stream"; - } - }, - - /** -* Returns the nsILocalFile which corresponds to the path, as determined using -* all registered path->directory mappings and any paths which are explicitly -* overridden. -* -* @param path : string -* the server path for which a file should be retrieved, e.g. "/foo/bar" -* @throws HttpError -* when the correct action is the corresponding HTTP error (i.e., because no -* mapping was found for a directory in path, the referenced file doesn't -* exist, etc.) -* @returns nsILocalFile -* the file to be sent as the response to a request for the path -*/ - _getFileForPath: function(path) - { - // decode and add underscores as necessary - try - { - path = toInternalPath(path, true); - } - catch (e) - { - throw HTTP_400; // malformed path - } - - // next, get the directory which contains this path - var pathMap = this._pathDirectoryMap; - - // An example progression of tmp for a path "/foo/bar/baz/" might be: - // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", "" - var tmp = path.substring(1); - while (true) - { - // do we have a match for current head of the path? - var file = pathMap.get(tmp); - if (file) - { - // XXX hack; basically disable showing mapping for /foo/bar/ when the - // requested path was /foo/bar, because relative links on the page - // will all be incorrect -- we really need the ability to easily - // redirect here instead - if (tmp == path.substring(1) && - tmp.length != 0 && - tmp.charAt(tmp.length - 1) != "/") - file = null; - else - break; - } - - // if we've finished trying all prefixes, exit - if (tmp == "") - break; - - tmp = tmp.substring(0, tmp.lastIndexOf("/")); - } - - // no mapping applies, so 404 - if (!file) - throw HTTP_404; - - - // last, get the file for the path within the determined directory - var parentFolder = file.parent; - var dirIsRoot = (parentFolder == null); - - // Strategy here is to append components individually, making sure we - // never move above the given directory; this allows paths such as - // "/foo/../bar" but prevents paths such as "/../base-sibling"; - // this component-wise approach also means the code works even on platforms - // which don't use "/" as the directory separator, such as Windows - var leafPath = path.substring(tmp.length + 1); - var comps = leafPath.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - - if (comp == "..") - file = file.parent; - else if (comp == "." || comp == "") - continue; - else - file.append(comp); - - if (!dirIsRoot && file.equals(parentFolder)) - throw HTTP_403; - } - - return file; - }, - - /** -* Writes the error page for the given HTTP error code over the given -* connection. -* -* @param errorCode : uint -* the HTTP error code to be used -* @param connection : Connection -* the connection on which the error occurred -*/ - handleError: function(errorCode, connection) - { - var response = new Response(connection); - - dumpn("*** error in request: " + errorCode); - - this._handleError(errorCode, new Request(connection.port), response); - }, - - /** -* Handles a request which generates the given error code, using the -* user-defined error handler if one has been set, gracefully falling back to -* the x00 status code if the code has no handler, and failing to status code -* 500 if all else fails. -* -* @param errorCode : uint -* the HTTP error which is to be returned -* @param metadata : Request -* metadata for the request, which will often be incomplete since this is an -* error -* @param response : Response -* an uninitialized Response should be initialized when this method -* completes with information which represents the desired error code in the -* ideal case or a fallback code in abnormal circumstances (i.e., 500 is a -* fallback for 505, per HTTP specs) -*/ - _handleError: function(errorCode, metadata, response) - { - if (!metadata) - throw Cr.NS_ERROR_NULL_POINTER; - - var errorX00 = errorCode - (errorCode % 100); - - try - { - if (!(errorCode in HTTP_ERROR_CODES)) - dumpn("*** WARNING: requested invalid error: " + errorCode); - - // RFC 2616 says that we should try to handle an error by its class if we - // can't otherwise handle it -- if that fails, we revert to handling it as - // a 500 internal server error, and if that fails we throw and shut down - // the server - - // actually handle the error - try - { - if (errorCode in this._overrideErrors) - this._overrideErrors[errorCode](metadata, response); - else - this._defaultErrors[errorCode](metadata, response); - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - // don't retry the handler that threw - if (errorX00 == errorCode) - throw HTTP_500; - - dumpn("*** error in handling for error code " + errorCode + ", " + - "falling back to " + errorX00 + "..."); - response = new Response(response._connection); - if (errorX00 in this._overrideErrors) - this._overrideErrors[errorX00](metadata, response); - else if (errorX00 in this._defaultErrors) - this._defaultErrors[errorX00](metadata, response); - else - throw HTTP_500; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(); - return; - } - - // we've tried everything possible for a meaningful error -- now try 500 - dumpn("*** error in handling for error code " + errorX00 + ", falling " + - "back to 500..."); - - try - { - response = new Response(response._connection); - if (500 in this._overrideErrors) - this._overrideErrors[500](metadata, response); - else - this._defaultErrors[500](metadata, response); - } - catch (e2) - { - dumpn("*** multiple errors in default error handlers!"); - dumpn("*** e == " + e + ", e2 == " + e2); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // FIELDS - - /** -* This object contains the default handlers for the various HTTP error codes. -*/ - _defaultErrors: - { - 400: function(metadata, response) - { - // none of the data in metadata is reliable, so hard-code everything here - response.setStatusLine("1.1", 400, "Bad Request"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Bad request\n"; - response.bodyOutputStream.write(body, body.length); - }, - 403: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 403, "Forbidden"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -403 Forbidden\ -\ -

403 Forbidden

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 404: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 404, "Not Found"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -404 Not Found\ -\ -

404 Not Found

\ -

\ -" + - htmlEscape(metadata.path) + - " was not found.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 416: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 416, - "Requested Range Not Satisfiable"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -\ -416 Requested Range Not Satisfiable\ -\ -

416 Requested Range Not Satisfiable

\ -

The byte range was not valid for the\ -requested resource.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 500: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 500, - "Internal Server Error"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -500 Internal Server Error\ -\ -

500 Internal Server Error

\ -

Something's broken in this server and\ -needs to be fixed.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 501: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 501, "Not Implemented"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -501 Not Implemented\ -\ -

501 Not Implemented

\ -

This server is not (yet) Apache.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 505: function(metadata, response) - { - response.setStatusLine("1.1", 505, "HTTP Version Not Supported"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -505 HTTP Version Not Supported\ -\ -

505 HTTP Version Not Supported

\ -

This server only supports HTTP/1.0 and HTTP/1.1\ -connections.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - } - }, - - /** -* Contains handlers for the default set of URIs contained in this server. -*/ - _defaultPaths: - { - "/": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -httpd.js\ -\ -

httpd.js

\ -

If you're seeing this page, httpd.js is up and\ -serving requests! Now set a base path and serve some\ -files!

\ -\ -"; - - response.bodyOutputStream.write(body, body.length); - }, - - "/trace": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Request-URI: " + - metadata.scheme + "://" + metadata.host + ":" + metadata.port + - metadata.path + "\n\n"; - body += "Request (semantically equivalent, slightly reformatted):\n\n"; - body += metadata.method + " " + metadata.path; - - if (metadata.queryString) - body += "?" + metadata.queryString; - - body += " HTTP/" + metadata.httpVersion + "\r\n"; - - var headEnum = metadata.headers; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n"; - } - - response.bodyOutputStream.write(body, body.length); - } - } -}; - - -/** -* Maps absolute paths to files on the local file system (as nsILocalFiles). -*/ -function FileMap() -{ - /** Hash which will map paths to nsILocalFiles. */ - this._map = {}; -} -FileMap.prototype = -{ - // PUBLIC API - - /** -* Maps key to a clone of the nsILocalFile value if value is non-null; -* otherwise, removes any extant mapping for key. -* -* @param key : string -* string to which a clone of value is mapped -* @param value : nsILocalFile -* the file to map to key, or null to remove a mapping -*/ - put: function(key, value) - { - if (value) - this._map[key] = value.clone(); - else - delete this._map[key]; - }, - - /** -* Returns a clone of the nsILocalFile mapped to key, or null if no such -* mapping exists. -* -* @param key : string -* key to which the returned file maps -* @returns nsILocalFile -* a clone of the mapped file, or null if no mapping exists -*/ - get: function(key) - { - var val = this._map[key]; - return val ? val.clone() : null; - } -}; - - -// Response CONSTANTS - -// token = * -// CHAR = -// CTL = -// separators = "(" | ")" | "<" | ">" | "@" -// | "," | ";" | ":" | "\" | <"> -// | "/" | "[" | "]" | "?" | "=" -// | "{" | "}" | SP | HT -const IS_TOKEN_ARRAY = - [0, 0, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 0, 0, 0, 0, 0, 0, 0, // 24 - - 0, 1, 0, 1, 1, 1, 1, 1, // 32 - 0, 0, 1, 1, 0, 1, 1, 0, // 40 - 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 1, 1, 0, 0, 0, 0, 0, 0, // 56 - - 0, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, // 72 - 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 1, 1, 1, 0, 0, 0, 1, 1, // 88 - - 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, // 104 - 1, 1, 1, 1, 1, 1, 1, 1, // 112 - 1, 1, 1, 0, 1, 0, 1]; // 120 - - -/** -* Determines whether the given character code is a CTL. -* -* @param code : uint -* the character code -* @returns boolean -* true if code is a CTL, false otherwise -*/ -function isCTL(code) -{ - return (code >= 0 && code <= 31) || (code == 127); -} - -/** -* Represents a response to an HTTP request, encapsulating all details of that -* response. This includes all headers, the HTTP version, status code and -* explanation, and the entity itself. -* -* @param connection : Connection -* the connection over which this response is to be written -*/ -function Response(connection) -{ - /** The connection over which this response will be written. */ - this._connection = connection; - - /** -* The HTTP version of this response; defaults to 1.1 if not set by the -* handler. -*/ - this._httpVersion = nsHttpVersion.HTTP_1_1; - - /** -* The HTTP code of this response; defaults to 200. -*/ - this._httpCode = 200; - - /** -* The description of the HTTP code in this response; defaults to "OK". -*/ - this._httpDescription = "OK"; - - /** -* An nsIHttpHeaders object in which the headers in this response should be -* stored. This property is null after the status line and headers have been -* written to the network, and it may be modified up until it is cleared, -* except if this._finished is set first (in which case headers are written -* asynchronously in response to a finish() call not preceded by -* flushHeaders()). -*/ - this._headers = new nsHttpHeaders(); - - /** -* Set to true when this response is ended (completely constructed if possible -* and the connection closed); further actions on this will then fail. -*/ - this._ended = false; - - /** -* A stream used to hold data written to the body of this response. -*/ - this._bodyOutputStream = null; - - /** -* A stream containing all data that has been written to the body of this -* response so far. (Async handlers make the data contained in this -* unreliable as a way of determining content length in general, but auxiliary -* saved information can sometimes be used to guarantee reliability.) -*/ - this._bodyInputStream = null; - - /** -* A stream copier which copies data to the network. It is initially null -* until replaced with a copier for response headers; when headers have been -* fully sent it is replaced with a copier for the response body, remaining -* so for the duration of response processing. -*/ - this._asyncCopier = null; - - /** -* True if this response has been designated as being processed -* asynchronously rather than for the duration of a single call to -* nsIHttpRequestHandler.handle. -*/ - this._processAsync = false; - - /** -* True iff finish() has been called on this, signaling that no more changes -* to this may be made. -*/ - this._finished = false; - - /** -* True iff powerSeized() has been called on this, signaling that this -* response is to be handled manually by the response handler (which may then -* send arbitrary data in response, even non-HTTP responses). -*/ - this._powerSeized = false; -} -Response.prototype = -{ - // PUBLIC CONSTRUCTION API - - // - // see nsIHttpResponse.bodyOutputStream - // - get bodyOutputStream() - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - if (!this._bodyOutputStream) - { - var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX, - null); - this._bodyOutputStream = pipe.outputStream; - this._bodyInputStream = pipe.inputStream; - if (this._processAsync || this._powerSeized) - this._startAsyncProcessor(); - } - - return this._bodyOutputStream; - }, - - // - // see nsIHttpResponse.write - // - write: function(data) - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - var dataAsString = String(data); - this.bodyOutputStream.write(dataAsString, dataAsString.length); - }, - - // - // see nsIHttpResponse.setStatusLine - // - setStatusLine: function(httpVersion, code, description) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - if (!(code >= 0 && code < 1000)) - throw Cr.NS_ERROR_INVALID_ARG; - - try - { - var httpVer; - // avoid version construction for the most common cases - if (!httpVersion || httpVersion == "1.1") - httpVer = nsHttpVersion.HTTP_1_1; - else if (httpVersion == "1.0") - httpVer = nsHttpVersion.HTTP_1_0; - else - httpVer = new nsHttpVersion(httpVersion); - } - catch (e) - { - throw Cr.NS_ERROR_INVALID_ARG; - } - - // Reason-Phrase = * - // TEXT = - // - // XXX this ends up disallowing octets which aren't Unicode, I think -- not - // much to do if description is IDL'd as string - if (!description) - description = ""; - for (var i = 0; i < description.length; i++) - if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t") - throw Cr.NS_ERROR_INVALID_ARG; - - // set the values only after validation to preserve atomicity - this._httpDescription = description; - this._httpCode = code; - this._httpVersion = httpVer; - }, - - // - // see nsIHttpResponse.setHeader - // - setHeader: function(name, value, merge) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - this._headers.setHeader(name, value, merge); - }, - - // - // see nsIHttpResponse.processAsync - // - processAsync: function() - { - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._processAsync) - return; - this._ensureAlive(); - - dumpn("*** processing connection " + this._connection.number + " async"); - this._processAsync = true; - - /* -* Either the bodyOutputStream getter or this method is responsible for -* starting the asynchronous processor and catching writes of data to the -* response body of async responses as they happen, for the purpose of -* forwarding those writes to the actual connection's output stream. -* If bodyOutputStream is accessed first, calling this method will create -* the processor (when it first is clear that body data is to be written -* immediately, not buffered). If this method is called first, accessing -* bodyOutputStream will create the processor. If only this method is -* called, we'll write nothing, neither headers nor the nonexistent body, -* until finish() is called. Since that delay is easily avoided by simply -* getting bodyOutputStream or calling write(""), we don't worry about it. -*/ - if (this._bodyOutputStream && !this._asyncCopier) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.seizePower - // - seizePower: function() - { - if (this._processAsync) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - return; - this._ensureAlive(); - - dumpn("*** forcefully seizing power over connection " + - this._connection.number + "..."); - - // Purge any already-written data without sending it. We could as easily - // swap out the streams entirely, but that makes it possible to acquire and - // unknowingly use a stale reference, so we require there only be one of - // each stream ever for any response to avoid this complication. - if (this._asyncCopier) - this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED); - this._asyncCopier = null; - if (this._bodyOutputStream) - { - var input = new BinaryInputStream(this._bodyInputStream); - var avail; - while ((avail = input.available()) > 0) - input.readByteArray(avail); - } - - this._powerSeized = true; - if (this._bodyOutputStream) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.finish - // - finish: function() - { - if (!this._processAsync && !this._powerSeized) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._finished) - return; - - dumpn("*** finishing connection " + this._connection.number); - this._startAsyncProcessor(); // in case bodyOutputStream was never accessed - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - this._finished = true; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // POST-CONSTRUCTION API (not exposed externally) - - /** -* The HTTP version number of this, as a string (e.g. "1.1"). -*/ - get httpVersion() - { - this._ensureAlive(); - return this._httpVersion.toString(); - }, - - /** -* The HTTP status code of this response, as a string of three characters per -* RFC 2616. -*/ - get httpCode() - { - this._ensureAlive(); - - var codeString = (this._httpCode < 10 ? "0" : "") + - (this._httpCode < 100 ? "0" : "") + - this._httpCode; - return codeString; - }, - - /** -* The description of the HTTP status code of this response, or "" if none is -* set. -*/ - get httpDescription() - { - this._ensureAlive(); - - return this._httpDescription; - }, - - /** -* The headers in this response, as an nsHttpHeaders object. -*/ - get headers() - { - this._ensureAlive(); - - return this._headers; - }, - - // - // see nsHttpHeaders.getHeader - // - getHeader: function(name) - { - this._ensureAlive(); - - return this._headers.getHeader(name); - }, - - /** -* Determines whether this response may be abandoned in favor of a newly -* constructed response. A response may be abandoned only if it is not being -* sent asynchronously and if raw control over it has not been taken from the -* server. -* -* @returns boolean -* true iff no data has been written to the network -*/ - partiallySent: function() - { - dumpn("*** partiallySent()"); - return this._processAsync || this._powerSeized; - }, - - /** -* If necessary, kicks off the remaining request processing needed to be done -* after a request handler performs its initial work upon this response. -*/ - complete: function() - { - dumpn("*** complete()"); - if (this._processAsync || this._powerSeized) - { - NS_ASSERT(this._processAsync ^ this._powerSeized, - "can't both send async and relinquish power"); - return; - } - - NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?"); - - this._startAsyncProcessor(); - - // Now make sure we finish processing this request! - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - }, - - /** -* Abruptly ends processing of this response, usually due to an error in an -* incoming request but potentially due to a bad error handler. Since we -* cannot handle the error in the usual way (giving an HTTP error page in -* response) because data may already have been sent (or because the response -* might be expected to have been generated asynchronously or completely from -* scratch by the handler), we stop processing this response and abruptly -* close the connection. -* -* @param e : Error -* the exception which precipitated this abort, or null if no such exception -* was generated -*/ - abort: function(e) - { - dumpn("*** abort(<" + e + ">)"); - - // This response will be ended by the processor if one was created. - var copier = this._asyncCopier; - if (copier) - { - // We dispatch asynchronously here so that any pending writes of data to - // the connection will be deterministically written. This makes it easier - // to specify exact behavior, and it makes observable behavior more - // predictable for clients. Note that the correctness of this depends on - // callbacks in response to _waitToReadData in WriteThroughCopier - // happening asynchronously with respect to the actual writing of data to - // bodyOutputStream, as they currently do; if they happened synchronously, - // an event which ran before this one could write more data to the - // response body before we get around to canceling the copier. We have - // tests for this in test_seizepower.js, however, and I can't think of a - // way to handle both cases without removing bodyOutputStream access and - // moving its effective write(data, length) method onto Response, which - // would be slower and require more code than this anyway. - gThreadManager.currentThread.dispatch({ - run: function() - { - dumpn("*** canceling copy asynchronously..."); - copier.cancel(Cr.NS_ERROR_UNEXPECTED); - } - }, Ci.nsIThread.DISPATCH_NORMAL); - } - else - { - this.end(); - } - }, - - /** -* Closes this response's network connection, marks the response as finished, -* and notifies the server handler that the request is done being processed. -*/ - end: function() - { - NS_ASSERT(!this._ended, "ending this response twice?!?!"); - - this._connection.close(); - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - - this._finished = true; - this._ended = true; - }, - - // PRIVATE IMPLEMENTATION - - /** -* Sends the status line and headers of this response if they haven't been -* sent and initiates the process of copying data written to this response's -* body to the network. -*/ - _startAsyncProcessor: function() - { - dumpn("*** _startAsyncProcessor()"); - - // Handle cases where we're being called a second time. The former case - // happens when this is triggered both by complete() and by processAsync(), - // while the latter happens when processAsync() in conjunction with sent - // data causes abort() to be called. - if (this._asyncCopier || this._ended) - { - dumpn("*** ignoring second call to _startAsyncProcessor"); - return; - } - - // Send headers if they haven't been sent already and should be sent, then - // asynchronously continue to send the body. - if (this._headers && !this._powerSeized) - { - this._sendHeaders(); - return; - } - - this._headers = null; - this._sendBody(); - }, - - /** -* Signals that all modifications to the response status line and headers are -* complete and then sends that data over the network to the client. Once -* this method completes, a different response to the request that resulted -* in this response cannot be sent -- the only possible action in case of -* error is to abort the response and close the connection. -*/ - _sendHeaders: function() - { - dumpn("*** _sendHeaders()"); - - NS_ASSERT(this._headers); - NS_ASSERT(!this._powerSeized); - - // request-line - var statusLine = "HTTP/" + this.httpVersion + " " + - this.httpCode + " " + - this.httpDescription + "\r\n"; - - // header post-processing - - var headers = this._headers; - headers.setHeader("Connection", "close", false); - headers.setHeader("Server", "httpd.js", false); - if (!headers.hasHeader("Date")) - headers.setHeader("Date", toDateString(Date.now()), false); - - // Any response not being processed asynchronously must have an associated - // Content-Length header for reasons of backwards compatibility with the - // initial server, which fully buffered every response before sending it. - // Beyond that, however, it's good to do this anyway because otherwise it's - // impossible to test behaviors that depend on the presence or absence of a - // Content-Length header. - if (!this._processAsync) - { - dumpn("*** non-async response, set Content-Length"); - - var bodyStream = this._bodyInputStream; - var avail = bodyStream ? bodyStream.available() : 0; - - // XXX assumes stream will always report the full amount of data available - headers.setHeader("Content-Length", "" + avail, false); - } - - - // construct and send response - dumpn("*** header post-processing completed, sending response head..."); - - // request-line - var preambleData = [statusLine]; - - // headers - var headEnum = headers.enumerator; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - var values = headers.getHeaderValues(fieldName); - for (var i = 0, sz = values.length; i < sz; i++) - preambleData.push(fieldName + ": " + values[i] + "\r\n"); - } - - // end request-line/headers - preambleData.push("\r\n"); - - var preamble = preambleData.join(""); - - var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null); - responseHeadPipe.outputStream.write(preamble, preamble.length); - - var response = this; - var copyObserver = - { - onStartRequest: function(request, cx) - { - dumpn("*** preamble copying started"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** preamble copying complete " + - "[status=0x" + statusCode.toString(16) + "]"); - - if (!components.isSuccessCode(statusCode)) - { - dumpn("!!! header copying problems: non-success statusCode, " + - "ending response"); - - response.end(); - } - else - { - response._sendBody(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - var headerCopier = this._asyncCopier = - new WriteThroughCopier(responseHeadPipe.inputStream, - this._connection.output, - copyObserver, null); - - responseHeadPipe.outputStream.close(); - - // Forbid setting any more headers or modifying the request line. - this._headers = null; - }, - - /** -* Asynchronously writes the body of the response (or the entire response, if -* seizePower() has been called) to the network. -*/ - _sendBody: function() - { - dumpn("*** _sendBody"); - - NS_ASSERT(!this._headers, "still have headers around but sending body?"); - - // If no body data was written, we're done - if (!this._bodyInputStream) - { - dumpn("*** empty body, response finished"); - this.end(); - return; - } - - var response = this; - var copyObserver = - { - onStartRequest: function(request, context) - { - dumpn("*** onStartRequest"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]"); - - if (statusCode === Cr.NS_BINDING_ABORTED) - { - dumpn("*** terminating copy observer without ending the response"); - } - else - { - if (!components.isSuccessCode(statusCode)) - dumpn("*** WARNING: non-success statusCode in onStopRequest"); - - response.end(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - dumpn("*** starting async copier of body data..."); - this._asyncCopier = - new WriteThroughCopier(this._bodyInputStream, this._connection.output, - copyObserver, null); - }, - - /** Ensures that this hasn't been ended. */ - _ensureAlive: function() - { - NS_ASSERT(!this._ended, "not handling response lifetime correctly"); - } -}; - -/** -* Size of the segments in the buffer used in storing response data and writing -* it to the socket. -*/ -Response.SEGMENT_SIZE = 8192; - -/** Serves double duty in WriteThroughCopier implementation. */ -function notImplemented() -{ - throw Cr.NS_ERROR_NOT_IMPLEMENTED; -} - -/** Returns true iff the given exception represents stream closure. */ -function streamClosed(e) -{ - return e === Cr.NS_BASE_STREAM_CLOSED || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED); -} - -/** Returns true iff the given exception represents a blocked stream. */ -function wouldBlock(e) -{ - return e === Cr.NS_BASE_STREAM_WOULD_BLOCK || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK); -} - -/** -* Copies data from source to sink as it becomes available, when that data can -* be written to sink without blocking. -* -* @param source : nsIAsyncInputStream -* the stream from which data is to be read -* @param sink : nsIAsyncOutputStream -* the stream to which data is to be copied -* @param observer : nsIRequestObserver -* an observer which will be notified when the copy starts and finishes -* @param context : nsISupports -* context passed to observer when notified of start/stop -* @throws NS_ERROR_NULL_POINTER -* if source, sink, or observer are null -*/ -function WriteThroughCopier(source, sink, observer, context) -{ - if (!source || !sink || !observer) - throw Cr.NS_ERROR_NULL_POINTER; - - /** Stream from which data is being read. */ - this._source = source; - - /** Stream to which data is being written. */ - this._sink = sink; - - /** Observer watching this copy. */ - this._observer = observer; - - /** Context for the observer watching this. */ - this._context = context; - - /** -* True iff this is currently being canceled (cancel has been called, the -* callback may not yet have been made). -*/ - this._canceled = false; - - /** -* False until all data has been read from input and written to output, at -* which point this copy is completed and cancel() is asynchronously called. -*/ - this._completed = false; - - /** Required by nsIRequest, meaningless. */ - this.loadFlags = 0; - /** Required by nsIRequest, meaningless. */ - this.loadGroup = null; - /** Required by nsIRequest, meaningless. */ - this.name = "response-body-copy"; - - /** Status of this request. */ - this.status = Cr.NS_OK; - - /** Arrays of byte strings waiting to be written to output. */ - this._pendingData = []; - - // start copying - try - { - observer.onStartRequest(this, context); - this._waitToReadData(); - this._waitForSinkClosure(); - } - catch (e) - { - dumpn("!!! error starting copy: " + e + - ("lineNumber" in e ? ", line " + e.lineNumber : "")); - dumpn(e.stack); - this.cancel(Cr.NS_ERROR_UNEXPECTED); - } -} -WriteThroughCopier.prototype = -{ - /* nsISupports implementation */ - - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIInputStreamCallback) || - iid.equals(Ci.nsIOutputStreamCallback) || - iid.equals(Ci.nsIRequest) || - iid.equals(Ci.nsISupports)) - { - return this; - } - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NSIINPUTSTREAMCALLBACK - - /** -* Receives a more-data-in-input notification and writes the corresponding -* data to the output. -* -* @param input : nsIAsyncInputStream -* the input stream on whose data we have been waiting -*/ - onInputStreamReady: function(input) - { - if (this._source === null) - return; - - dumpn("*** onInputStreamReady"); - - // - // Ordinarily we'll read a non-zero amount of data from input, queue it up - // to be written and then wait for further callbacks. The complications in - // this method are the cases where we deviate from that behavior when errors - // occur or when copying is drawing to a finish. - // - // The edge cases when reading data are: - // - // Zero data is read - // If zero data was read, we're at the end of available data, so we can - // should stop reading and move on to writing out what we have (or, if - // we've already done that, onto notifying of completion). - // A stream-closed exception is thrown - // This is effectively a less kind version of zero data being read; the - // only difference is that we notify of completion with that result - // rather than with NS_OK. - // Some other exception is thrown - // This is the least kind result. We don't know what happened, so we - // act as though the stream closed except that we notify of completion - // with the result NS_ERROR_UNEXPECTED. - // - - var bytesWanted = 0, bytesConsumed = -1; - try - { - input = new BinaryInputStream(input); - - bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE); - dumpn("*** input wanted: " + bytesWanted); - - if (bytesWanted > 0) - { - var data = input.readByteArray(bytesWanted); - bytesConsumed = data.length; - this._pendingData.push(String.fromCharCode.apply(String, data)); - } - - dumpn("*** " + bytesConsumed + " bytes read"); - - // Handle the zero-data edge case in the same place as all other edge - // cases are handled. - if (bytesWanted === 0) - throw Cr.NS_BASE_STREAM_CLOSED; - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** input stream closed"); - e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED; - } - else - { - dumpn("!!! unexpected error reading from input, canceling: " + e); - e = Cr.NS_ERROR_UNEXPECTED; - } - - this._doneReadingSource(e); - return; - } - - var pendingData = this._pendingData; - - NS_ASSERT(bytesConsumed > 0); - NS_ASSERT(pendingData.length > 0, "no pending data somehow?"); - NS_ASSERT(pendingData[pendingData.length - 1].length > 0, - "buffered zero bytes of data?"); - - NS_ASSERT(this._source !== null); - - // Reading has gone great, and we've gotten data to write now. What if we - // don't have a place to write that data, because output went away just - // before this read? Drop everything on the floor, including new data, and - // cancel at this point. - if (this._sink === null) - { - pendingData.length = 0; - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we've read the data, and we know we have a place to write it. We - // need to queue up the data to be written, but *only* if none is queued - // already -- if data's already queued, the code that actually writes the - // data will make sure to wait on unconsumed pending data. - try - { - if (pendingData.length === 1) - this._waitToWriteData(); - } - catch (e) - { - dumpn("!!! error waiting to write data just read, swallowing and " + - "writing only what we already have: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Whee! We successfully read some data, and it's successfully queued up to - // be written. All that remains now is to wait for more data to read. - try - { - this._waitToReadData(); - } - catch (e) - { - dumpn("!!! error waiting to read more data: " + e); - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - } - }, - - - // NSIOUTPUTSTREAMCALLBACK - - /** -* Callback when data may be written to the output stream without blocking, or -* when the output stream has been closed. -* -* @param output : nsIAsyncOutputStream -* the output stream on whose writability we've been waiting, also known as -* this._sink -*/ - onOutputStreamReady: function(output) - { - if (this._sink === null) - return; - - dumpn("*** onOutputStreamReady"); - - var pendingData = this._pendingData; - if (pendingData.length === 0) - { - // There's no pending data to write. The only way this can happen is if - // we're waiting on the output stream's closure, so we can respond to a - // copying failure as quickly as possible (rather than waiting for data to - // be available to read and then fail to be copied). Therefore, we must - // be done now -- don't bother to attempt to write anything and wrap - // things up. - dumpn("!!! output stream closed prematurely, ending copy"); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - - NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?"); - - // - // Write out the first pending quantum of data. The possible errors here - // are: - // - // The write might fail because we can't write that much data - // Okay, we've written what we can now, so re-queue what's left and - // finish writing it out later. - // The write failed because the stream was closed - // Discard pending data that we can no longer write, stop reading, and - // signal that copying finished. - // Some other error occurred. - // Same as if the stream were closed, but notify with the status - // NS_ERROR_UNEXPECTED so the observer knows something was wonky. - // - - try - { - var quantum = pendingData[0]; - - // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on - // undefined behavior! We're only using this because writeByteArray - // is unusably broken for asynchronous output streams; see bug 532834 - // for details. - var bytesWritten = output.write(quantum, quantum.length); - if (bytesWritten === quantum.length) - pendingData.shift(); - else - pendingData[0] = quantum.substring(bytesWritten); - - dumpn("*** wrote " + bytesWritten + " bytes of data"); - } - catch (e) - { - if (wouldBlock(e)) - { - NS_ASSERT(pendingData.length > 0, - "stream-blocking exception with no data to write?"); - NS_ASSERT(pendingData[0].length > 0, - "stream-blocking exception with empty quantum?"); - this._waitToWriteData(); - return; - } - - if (streamClosed(e)) - dumpn("!!! output stream prematurely closed, signaling error..."); - else - dumpn("!!! unknown error: " + e + ", quantum=" + quantum); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // The day is ours! Quantum written, now let's see if we have more data - // still to write. - try - { - if (pendingData.length > 0) - { - this._waitToWriteData(); - return; - } - } - catch (e) - { - dumpn("!!! unexpected error waiting to write pending data: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we have no more pending data to write -- but might we get more in - // the future? - if (this._source !== null) - { - /* -* If we might, then wait for the output stream to be closed. (We wait -* only for closure because we have no data to write -- and if we waited -* for a specific amount of data, we would get repeatedly notified for no -* reason if over time the output stream permitted more and more data to -* be written to it without blocking.) -*/ - this._waitForSinkClosure(); - } - else - { - /* -* On the other hand, if we can't have more data because the input -* stream's gone away, then it's time to notify of copy completion. -* Victory! -*/ - this._sink = null; - this._cancelOrDispatchCancelCallback(Cr.NS_OK); - } - }, - - - // NSIREQUEST - - /** Returns true if the cancel observer hasn't been notified yet. */ - isPending: function() - { - return !this._completed; - }, - - /** Not implemented, don't use! */ - suspend: notImplemented, - /** Not implemented, don't use! */ - resume: notImplemented, - - /** -* Cancels data reading from input, asynchronously writes out any pending -* data, and causes the observer to be notified with the given error code when -* all writing has finished. -* -* @param status : nsresult -* the status to pass to the observer when data copying has been canceled -*/ - cancel: function(status) - { - dumpn("*** cancel(" + status.toString(16) + ")"); - - if (this._canceled) - { - dumpn("*** suppressing a late cancel"); - return; - } - - this._canceled = true; - this.status = status; - - // We could be in the middle of absolutely anything at this point. Both - // input and output might still be around, we might have pending data to - // write, and in general we know nothing about the state of the world. We - // therefore must assume everything's in progress and take everything to its - // final steady state (or so far as it can go before we need to finish - // writing out remaining data). - - this._doneReadingSource(status); - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Stop reading input if we haven't already done so, passing e as the status -* when closing the stream, and kick off a copy-completion notice if no more -* data remains to be written. -* -* @param e : nsresult -* the status to be used when closing the input stream -*/ - _doneReadingSource: function(e) - { - dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")"); - - this._finishSource(e); - if (this._pendingData.length === 0) - this._sink = null; - else - NS_ASSERT(this._sink !== null, "null output?"); - - // If we've written out all data read up to this point, then it's time to - // signal completion. - if (this._sink === null) - { - NS_ASSERT(this._pendingData.length === 0, "pending data still?"); - this._cancelOrDispatchCancelCallback(e); - } - }, - - /** -* Stop writing output if we haven't already done so, discard any data that -* remained to be sent, close off input if it wasn't already closed, and kick -* off a copy-completion notice. -* -* @param e : nsresult -* the status to be used when closing input if it wasn't already closed -*/ - _doneWritingToSink: function(e) - { - dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")"); - - this._pendingData.length = 0; - this._sink = null; - this._doneReadingSource(e); - }, - - /** -* Completes processing of this copy: either by canceling the copy if it -* hasn't already been canceled using the provided status, or by dispatching -* the cancel callback event (with the originally provided status, of course) -* if it already has been canceled. -* -* @param status : nsresult -* the status code to use to cancel this, if this hasn't already been -* canceled -*/ - _cancelOrDispatchCancelCallback: function(status) - { - dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")"); - - NS_ASSERT(this._source === null, "should have finished input"); - NS_ASSERT(this._sink === null, "should have finished output"); - NS_ASSERT(this._pendingData.length === 0, "should have no pending data"); - - if (!this._canceled) - { - this.cancel(status); - return; - } - - var self = this; - var event = - { - run: function() - { - dumpn("*** onStopRequest async callback"); - - self._completed = true; - try - { - self._observer.onStopRequest(self, self._context, self.status); - } - catch (e) - { - NS_ASSERT(false, - "how are we throwing an exception here? we control " + - "all the callers! " + e); - } - } - }; - - gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL); - }, - - /** -* Kicks off another wait for more data to be available from the input stream. -*/ - _waitToReadData: function() - { - dumpn("*** _waitToReadData"); - this._source.asyncWait(this, 0, Response.SEGMENT_SIZE, - gThreadManager.mainThread); - }, - - /** -* Kicks off another wait until data can be written to the output stream. -*/ - _waitToWriteData: function() - { - dumpn("*** _waitToWriteData"); - - var pendingData = this._pendingData; - NS_ASSERT(pendingData.length > 0, "no pending data to write?"); - NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?"); - - this._sink.asyncWait(this, 0, pendingData[0].length, - gThreadManager.mainThread); - }, - - /** -* Kicks off a wait for the sink to which data is being copied to be closed. -* We wait for stream closure when we don't have any data to be copied, rather -* than waiting to write a specific amount of data. We can't wait to write -* data because the sink might be infinitely writable, and if no data appears -* in the source for a long time we might have to spin quite a bit waiting to -* write, waiting to write again, &c. Waiting on stream closure instead means -* we'll get just one notification if the sink dies. Note that when data -* starts arriving from the sink we'll resume waiting for data to be written, -* dropping this closure-only callback entirely. -*/ - _waitForSinkClosure: function() - { - dumpn("*** _waitForSinkClosure"); - - this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0, - gThreadManager.mainThread); - }, - - /** -* Closes input with the given status, if it hasn't already been closed; -* otherwise a no-op. -* -* @param status : nsresult -* status code use to close the source stream if necessary -*/ - _finishSource: function(status) - { - dumpn("*** _finishSource(" + status.toString(16) + ")"); - - if (this._source !== null) - { - this._source.closeWithStatus(status); - this._source = null; - } - } -}; - - -/** -* A container for utility functions used with HTTP headers. -*/ -const headerUtils = -{ - /** -* Normalizes fieldName (by converting it to lowercase) and ensures it is a -* valid header field name (although not necessarily one specified in RFC -* 2616). -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not match the field-name production in RFC 2616 -* @returns string -* fieldName converted to lowercase if it is a valid header, for characters -* where case conversion is possible -*/ - normalizeFieldName: function(fieldName) - { - if (fieldName == "") - throw Cr.NS_ERROR_INVALID_ARG; - - for (var i = 0, sz = fieldName.length; i < sz; i++) - { - if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)]) - { - dumpn(fieldName + " is not a valid header field name!"); - throw Cr.NS_ERROR_INVALID_ARG; - } - } - - return fieldName.toLowerCase(); - }, - - /** -* Ensures that fieldValue is a valid header field value (although not -* necessarily as specified in RFC 2616 if the corresponding field name is -* part of the HTTP protocol), normalizes the value if it is, and -* returns the normalized value. -* -* @param fieldValue : string -* a value to be normalized as an HTTP header field value -* @throws NS_ERROR_INVALID_ARG -* if fieldValue does not match the field-value production in RFC 2616 -* @returns string -* fieldValue as a normalized HTTP header field value -*/ - normalizeFieldValue: function(fieldValue) - { - // field-value = *( field-content | LWS ) - // field-content = - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // - // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) - // qdtext = > - // quoted-pair = "\" CHAR - // CHAR = - - // Any LWS that occurs between field-content MAY be replaced with a single - // SP before interpreting the field value or forwarding the message - // downstream (section 4.2); we replace 1*LWS with a single SP - var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " "); - - // remove leading/trailing LWS (which has been converted to SP) - val = val.replace(/^ +/, "").replace(/ +$/, ""); - - // that should have taken care of all CTLs, so val should contain no CTLs - for (var i = 0, len = val.length; i < len; i++) - if (isCTL(val.charCodeAt(i))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly - // normalize, however, so this can be construed as a tightening of the - // spec and not entirely as a bug - return val; - } -}; - - - -/** -* Converts the given string into a string which is safe for use in an HTML -* context. -* -* @param str : string -* the string to make HTML-safe -* @returns string -* an HTML-safe version of str -*/ -function htmlEscape(str) -{ - // this is naive, but it'll work - var s = ""; - for (var i = 0; i < str.length; i++) - s += "&#" + str.charCodeAt(i) + ";"; - return s; -} - - -/** -* Constructs an object representing an HTTP version (see section 3.1). -* -* @param versionString -* a string of the form "#.#", where # is an non-negative decimal integer with -* or without leading zeros -* @throws -* if versionString does not specify a valid HTTP version number -*/ -function nsHttpVersion(versionString) -{ - var matches = /^(\d+)\.(\d+)$/.exec(versionString); - if (!matches) - throw "Not a valid HTTP version!"; - - /** The major version number of this, as a number. */ - this.major = parseInt(matches[1], 10); - - /** The minor version number of this, as a number. */ - this.minor = parseInt(matches[2], 10); - - if (isNaN(this.major) || isNaN(this.minor) || - this.major < 0 || this.minor < 0) - throw "Not a valid HTTP version!"; -} -nsHttpVersion.prototype = -{ - /** -* Returns the standard string representation of the HTTP version represented -* by this (e.g., "1.1"). -*/ - toString: function () - { - return this.major + "." + this.minor; - }, - - /** -* Returns true if this represents the same HTTP version as otherVersion, -* false otherwise. -* -* @param otherVersion : nsHttpVersion -* the version to compare against this -*/ - equals: function (otherVersion) - { - return this.major == otherVersion.major && - this.minor == otherVersion.minor; - }, - - /** True if this >= otherVersion, false otherwise. */ - atLeast: function(otherVersion) - { - return this.major > otherVersion.major || - (this.major == otherVersion.major && - this.minor >= otherVersion.minor); - } -}; - -nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0"); -nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1"); - - -/** -* An object which stores HTTP headers for a request or response. -* -* Note that since headers are case-insensitive, this object converts headers to -* lowercase before storing them. This allows the getHeader and hasHeader -* methods to work correctly for any case of a header, but it means that the -* values returned by .enumerator may not be equal case-sensitively to the -* values passed to setHeader when adding headers to this. -*/ -function nsHttpHeaders() -{ - /** -* A hash of headers, with header field names as the keys and header field -* values as the values. Header field names are case-insensitive, but upon -* insertion here they are converted to lowercase. Header field values are -* normalized upon insertion to contain no leading or trailing whitespace. -* -* Note also that per RFC 2616, section 4.2, two headers with the same name in -* a message may be treated as one header with the same field name and a field -* value consisting of the separate field values joined together with a "," in -* their original order. This hash stores multiple headers with the same name -* in this manner. -*/ - this._headers = {}; -} -nsHttpHeaders.prototype = -{ - /** -* Sets the header represented by name and value in this. -* -* @param name : string -* the header name -* @param value : string -* the header value -* @throws NS_ERROR_INVALID_ARG -* if name or value is not a valid header component -*/ - setHeader: function(fieldName, fieldValue, merge) - { - var name = headerUtils.normalizeFieldName(fieldName); - var value = headerUtils.normalizeFieldValue(fieldValue); - - // The following three headers are stored as arrays because their real-world - // syntax prevents joining individual headers into a single header using - // ",". See also - if (merge && name in this._headers) - { - if (name === "www-authenticate" || - name === "proxy-authenticate" || - name === "set-cookie") - { - this._headers[name].push(value); - } - else - { - this._headers[name][0] += "," + value; - NS_ASSERT(this._headers[name].length === 1, - "how'd a non-special header have multiple values?") - } - } - else - { - this._headers[name] = [value]; - } - }, - - /** -* Returns the value for the header specified by this. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns string -* the field value for the given header, possibly with non-semantic changes -* (i.e., leading/trailing whitespace stripped, whitespace runs replaced -* with spaces, etc.) at the option of the implementation; multiple -* instances of the header will be combined with a comma, except for -* the three headers noted in the description of getHeaderValues -*/ - getHeader: function(fieldName) - { - return this.getHeaderValues(fieldName).join("\n"); - }, - - /** -* Returns the value for the header specified by fieldName as an array. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns [string] -* an array of all the header values in this for the given -* header name. Header values will generally be collapsed -* into a single header by joining all header values together -* with commas, but certain headers (Proxy-Authenticate, -* WWW-Authenticate, and Set-Cookie) violate the HTTP spec -* and cannot be collapsed in this manner. For these headers -* only, the returned array may contain multiple elements if -* that header has been added more than once. -*/ - getHeaderValues: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - - if (name in this._headers) - return this._headers[name]; - else - throw Cr.NS_ERROR_NOT_AVAILABLE; - }, - - /** -* Returns true if a header with the given field name exists in this, false -* otherwise. -* -* @param fieldName : string -* the field name whose existence is to be determined in this -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @returns boolean -* true if the header's present, false otherwise -*/ - hasHeader: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - return (name in this._headers); - }, - - /** -* Returns a new enumerator over the field names of the headers in this, as -* nsISupportsStrings. The names returned will be in lowercase, regardless of -* how they were input using setHeader (header names are case-insensitive per -* RFC 2616). -*/ - get enumerator() - { - var headers = []; - for (var i in this._headers) - { - var supports = new SupportsString(); - supports.data = i; - headers.push(supports); - } - - return new nsSimpleEnumerator(headers); - } -}; - - -/** -* Constructs an nsISimpleEnumerator for the given array of items. -* -* @param items : Array -* the items, which must all implement nsISupports -*/ -function nsSimpleEnumerator(items) -{ - this._items = items; - this._nextIndex = 0; -} -nsSimpleEnumerator.prototype = -{ - hasMoreElements: function() - { - return this._nextIndex < this._items.length; - }, - getNext: function() - { - if (!this.hasMoreElements()) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - return this._items[this._nextIndex++]; - }, - QueryInterface: function(aIID) - { - if (Ci.nsISimpleEnumerator.equals(aIID) || - Ci.nsISupports.equals(aIID)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } -}; - - -/** -* A representation of the data in an HTTP request. -* -* @param port : uint -* the port on which the server receiving this request runs -*/ -function Request(port) -{ - /** Method of this request, e.g. GET or POST. */ - this._method = ""; - - /** Path of the requested resource; empty paths are converted to '/'. */ - this._path = ""; - - /** Query string, if any, associated with this request (not including '?'). */ - this._queryString = ""; - - /** Scheme of requested resource, usually http, always lowercase. */ - this._scheme = "http"; - - /** Hostname on which the requested resource resides. */ - this._host = undefined; - - /** Port number over which the request was received. */ - this._port = port; - - var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null); - - /** Stream from which data in this request's body may be read. */ - this._bodyInputStream = bodyPipe.inputStream; - - /** Stream to which data in this request's body is written. */ - this._bodyOutputStream = bodyPipe.outputStream; - - /** -* The headers in this request. -*/ - this._headers = new nsHttpHeaders(); - - /** -* For the addition of ad-hoc properties and new functionality without having -* to change nsIHttpRequest every time; currently lazily created, as its only -* use is in directory listings. -*/ - this._bag = null; -} -Request.prototype = -{ - // SERVER METADATA - - // - // see nsIHttpRequest.scheme - // - get scheme() - { - return this._scheme; - }, - - // - // see nsIHttpRequest.host - // - get host() - { - return this._host; - }, - - // - // see nsIHttpRequest.port - // - get port() - { - return this._port; - }, - - // REQUEST LINE - - // - // see nsIHttpRequest.method - // - get method() - { - return this._method; - }, - - // - // see nsIHttpRequest.httpVersion - // - get httpVersion() - { - return this._httpVersion.toString(); - }, - - // - // see nsIHttpRequest.path - // - get path() - { - return this._path; - }, - - // - // see nsIHttpRequest.queryString - // - get queryString() - { - return this._queryString; - }, - - // HEADERS - - // - // see nsIHttpRequest.getHeader - // - getHeader: function(name) - { - return this._headers.getHeader(name); - }, - - // - // see nsIHttpRequest.hasHeader - // - hasHeader: function(name) - { - return this._headers.hasHeader(name); - }, - - // - // see nsIHttpRequest.headers - // - get headers() - { - return this._headers.enumerator; - }, - - // - // see nsIPropertyBag.enumerator - // - get enumerator() - { - this._ensurePropertyBag(); - return this._bag.enumerator; - }, - - // - // see nsIHttpRequest.headers - // - get bodyInputStream() - { - return this._bodyInputStream; - }, - - // - // see nsIPropertyBag.getProperty - // - getProperty: function(name) - { - this._ensurePropertyBag(); - return this._bag.getProperty(name); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** Ensures a property bag has been created for ad-hoc behaviors. */ - _ensurePropertyBag: function() - { - if (!this._bag) - this._bag = new WritablePropertyBag(); - } -}; - - -// XPCOM trappings -if ("XPCOMUtils" in this && // Firefox 3.6 doesn't load XPCOMUtils in this scope for some reason... - "generateNSGetFactory" in XPCOMUtils) { - var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]); -} - - - -/** -* Creates a new HTTP server listening for loopback traffic on the given port, -* starts it, and runs the server until the server processes a shutdown request, -* spinning an event loop so that events posted by the server's socket are -* processed. -* -* This method is primarily intended for use in running this script from within -* xpcshell and running a functional HTTP server without having to deal with -* non-essential details. -* -* Note that running multiple servers using variants of this method probably -* doesn't work, simply due to how the internal event loop is spun and stopped. -* -* @note -* This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code); -* you should use this server as a component in Mozilla 1.8. -* @param port -* the port on which the server will run, or -1 if there exists no preference -* for a specific port; note that attempting to use some values for this -* parameter (particularly those below 1024) may cause this method to throw or -* may result in the server being prematurely shut down -* @param basePath -* a local directory from which requests will be served (i.e., if this is -* "/home/jwalden/" then a request to /index.html will load -* /home/jwalden/index.html); if this is omitted, only the default URLs in -* this server implementation will be functional -*/ -function server(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - // if you're running this, you probably want to see debugging info - DEBUG = true; - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", SJS_TYPE); - srv.start(port); - - var thread = gThreadManager.currentThread; - while (!srv.isStopped()) - thread.processNextEvent(true); - - // get rid of any pending requests - while (thread.hasPendingEvents()) - thread.processNextEvent(true); - - DEBUG = false; -} - -function startServerAsync(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", "sjs"); - srv.start(port); - return srv; -} - -exports.nsHttpServer = nsHttpServer; -exports.ScriptableInputStream = ScriptableInputStream; -exports.server = server; -exports.startServerAsync = startServerAsync; diff --git a/addon-sdk/source/test/addons/content-script-messages-latency/main.js b/addon-sdk/source/test/addons/content-script-messages-latency/main.js deleted file mode 100644 index 39bd7b64b9..0000000000 --- a/addon-sdk/source/test/addons/content-script-messages-latency/main.js +++ /dev/null @@ -1,90 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { PageMod } = require("sdk/page-mod"); -const tabs = require("sdk/tabs"); -const { startServerAsync } = require("./httpd"); -const { setTimeout } = require("sdk/timers"); - -const serverPort = 8099; - -exports.testContentScriptLatencyRegression = function*(assert) { - let server = startServerAsync(serverPort); - server.registerPathHandler("/", function handle(request, response) { - response.write(` - - - - - slow loading page... - - `); - }); - - server.registerPathHandler("/slow.css", function handle(request, response) { - response.processAsync(); - response.setHeader('Content-Type', 'text/css', false); - setTimeout(_ => { - response.write("body { background: red; }"); - response.finish(); - }, 2000); - }); - - - let pageMod; - - let worker = yield new Promise((resolve) => { - pageMod = PageMod({ - include: "http://localhost:8099/", - attachTo: "top", - contentScriptWhen: "start", - contentScript: "new " + function ContentScriptScope() { - self.port.on("a-port-message", function () { - self.port.emit("document-ready-state", document.readyState); - }); - }, - onAttach: function(w) { - resolve(w); - } - }); - - tabs.open({ - url: "http://localhost:8099/", - inBackground: true - }); - }); - - worker.port.emit("a-port-message"); - - let waitForPortMessage = new Promise((resolve) => { - worker.port.once("document-ready-state", (msg) => { - resolve(msg); - }); - }); - - let documentReadyState = yield waitForPortMessage; - - assert.notEqual( - "complete", documentReadyState, - "content script received the port message when the page was still loading" - ); - - assert.notEqual( - "uninitialized", documentReadyState, - "content script should be frozen if document.readyState is still uninitialized" - ); - - assert.ok( - ["loading", "interactive"].includes(documentReadyState), - "content script message received with document.readyState was interactive or loading" - ); - - // Cleanup. - pageMod.destroy(); - yield new Promise((resolve) => worker.tab.close(resolve)); - yield new Promise((resolve) => server.stop(resolve)); -}; - -require("sdk/test/runner").runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/content-script-messages-latency/package.json b/addon-sdk/source/test/addons/content-script-messages-latency/package.json deleted file mode 100644 index 8280fe18bf..0000000000 --- a/addon-sdk/source/test/addons/content-script-messages-latency/package.json +++ /dev/null @@ -1,6 +0,0 @@ - -{ - "id": "content-script-messages-latency@jetpack", - "main": "./main.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/contributors/main.js b/addon-sdk/source/test/addons/contributors/main.js deleted file mode 100644 index 3827f277b9..0000000000 --- a/addon-sdk/source/test/addons/contributors/main.js +++ /dev/null @@ -1,19 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { id } = require('sdk/self'); -const { getAddonByID } = require('sdk/addon/manager'); - -exports.testContributors = function*(assert) { - let addon = yield getAddonByID(id); - let count = 0; - addon.contributors.forEach(({ name }) => { - assert.equal(name, ++count == 1 ? 'A' : 'B', 'The contributors keys are correct'); - }); - assert.equal(count, 2, 'The key count is correct'); - assert.equal(addon.contributors.length, 2, 'The key length is correct'); -} - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/contributors/package.json b/addon-sdk/source/test/addons/contributors/package.json deleted file mode 100644 index b6f1798d3a..0000000000 --- a/addon-sdk/source/test/addons/contributors/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "test-contributors@jetpack", - "contributors": [ "A", "B" ], - "main": "./main.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/curly-id/lib/main.js b/addon-sdk/source/test/addons/curly-id/lib/main.js deleted file mode 100644 index 8b3f256459..0000000000 --- a/addon-sdk/source/test/addons/curly-id/lib/main.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const simple = require('sdk/simple-prefs'); -const service = require('sdk/preferences/service'); -const { id, preferencesBranch } = require('sdk/self'); -const { getAddonByID } = require('sdk/addon/manager'); - -exports.testCurlyID = function(assert) { - assert.equal(id, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'curly ID is curly'); - assert.equal(simple.prefs.test13, 26, 'test13 is 26'); - - simple.prefs.test14 = '15'; - assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), '15', 'test14 is 15'); - assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), simple.prefs.test14, 'simple test14 also 15'); -} - -// from `/test/test-self.js`, adapted to `sdk/test/assert` API -exports.testSelfID = function*(assert) { - assert.equal(typeof(id), 'string', 'self.id is a string'); - assert.ok(id.length > 0, 'self.id not empty'); - - let addon = yield getAddonByID(id); - assert.ok(addon, 'found addon with self.id'); -} - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/curly-id/package.json b/addon-sdk/source/test/addons/curly-id/package.json deleted file mode 100644 index 213844662f..0000000000 --- a/addon-sdk/source/test/addons/curly-id/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "{34a1eae1-c20a-464f-9b0e-000000000000}", - "fullName": "curly ID test", - "author": "Tomislav Jovanovic", - "preferences": [{ - "name": "test13", - "type": "integer", - "title": "test13", - "value": 26 - }], - "main": "./lib/main.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/developers/main.js b/addon-sdk/source/test/addons/developers/main.js deleted file mode 100644 index d42faf643c..0000000000 --- a/addon-sdk/source/test/addons/developers/main.js +++ /dev/null @@ -1,19 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { id } = require('sdk/self'); -const { getAddonByID } = require('sdk/addon/manager'); - -exports.testDevelopers = function*(assert) { - let addon = yield getAddonByID(id); - let count = 0; - addon.developers.forEach(({ name }) => { - assert.equal(name, ++count == 1 ? 'A' : 'B', 'The developers keys are correct'); - }); - assert.equal(count, 2, 'The key count is correct'); - assert.equal(addon.developers.length, 2, 'The key length is correct'); -} - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/developers/package.json b/addon-sdk/source/test/addons/developers/package.json deleted file mode 100644 index 1d2a189ecb..0000000000 --- a/addon-sdk/source/test/addons/developers/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "test-developers@jetpack", - "title": "Test developers package key", - "author": "Erik Vold", - "developers": [ "A", "B" ], - "main": "./main.js", - "version": "0.0.1" -} diff --git a/addon-sdk/source/test/addons/e10s-content/data/test-contentScriptFile.js b/addon-sdk/source/test/addons/e10s-content/data/test-contentScriptFile.js deleted file mode 100644 index 7dc0e3f242..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/data/test-contentScriptFile.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -self.postMessage("msg from contentScriptFile"); diff --git a/addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html b/addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html deleted file mode 100644 index 85264034ac..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Page Worker test - - -

Lorem ipsum dolor sit amet.

- - diff --git a/addon-sdk/source/test/addons/e10s-content/data/test-page-worker.js b/addon-sdk/source/test/addons/e10s-content/data/test-page-worker.js deleted file mode 100644 index 5114fe4e00..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/data/test-page-worker.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - -// get title directly -self.postMessage(["equal", document.title, "Page Worker test", - "Correct page title accessed directly"]); - -// get

directly -var p = document.getElementById("paragraph"); -self.postMessage(["ok", !!p, "

can be accessed directly"]); -self.postMessage(["equal", p.firstChild.nodeValue, - "Lorem ipsum dolor sit amet.", - "Correct text node expected"]); - -// Modify page -var div = document.createElement("div"); -div.setAttribute("id", "block"); -div.appendChild(document.createTextNode("Test text created")); -document.body.appendChild(div); - -// Check back the modification -div = document.getElementById("block"); -self.postMessage(["ok", !!div, "

can be accessed directly"]); -self.postMessage(["equal", div.firstChild.nodeValue, - "Test text created", "Correct text node expected"]); -self.postMessage(["done"]); - diff --git a/addon-sdk/source/test/addons/e10s-content/data/test.html b/addon-sdk/source/test/addons/e10s-content/data/test.html deleted file mode 100644 index 181e85f9b1..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/data/test.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - foo - - -

bar

- - diff --git a/addon-sdk/source/test/addons/e10s-content/lib/fixtures.js b/addon-sdk/source/test/addons/e10s-content/lib/fixtures.js deleted file mode 100644 index d3bd49300d..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/lib/fixtures.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { data } = require('sdk/self'); - -exports.url = data.url; diff --git a/addon-sdk/source/test/addons/e10s-content/lib/httpd.js b/addon-sdk/source/test/addons/e10s-content/lib/httpd.js deleted file mode 100644 index e46ca96a0d..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/lib/httpd.js +++ /dev/null @@ -1,5212 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* -* An implementation of an HTTP server both as a loadable script and as an XPCOM -* component. See the accompanying README file for user documentation on -* httpd.js. -*/ - -module.metadata = { - "stability": "experimental" -}; - -const { components, CC, Cc, Ci, Cr, Cu } = require("chrome"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - - -const PR_UINT32_MAX = Math.pow(2, 32) - 1; - -/** True if debugging output is enabled, false otherwise. */ -var DEBUG = false; // non-const *only* so tweakable in server tests - -/** True if debugging output should be timestamped. */ -var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests - -var gGlobalObject = Cc["@mozilla.org/systemprincipal;1"].createInstance(); - -/** -* Asserts that the given condition holds. If it doesn't, the given message is -* dumped, a stack trace is printed, and an exception is thrown to attempt to -* stop execution (which unfortunately must rely upon the exception not being -* accidentally swallowed by the code that uses it). -*/ -function NS_ASSERT(cond, msg) -{ - if (DEBUG && !cond) - { - dumpn("###!!!"); - dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!")); - dumpn("###!!! Stack follows:"); - - var stack = new Error().stack.split(/\n/); - dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n")); - - throw Cr.NS_ERROR_ABORT; - } -} - -/** Constructs an HTTP error object. */ -function HttpError(code, description) -{ - this.code = code; - this.description = description; -} -HttpError.prototype = -{ - toString: function() - { - return this.code + " " + this.description; - } -}; - -/** -* Errors thrown to trigger specific HTTP server responses. -*/ -const HTTP_400 = new HttpError(400, "Bad Request"); -const HTTP_401 = new HttpError(401, "Unauthorized"); -const HTTP_402 = new HttpError(402, "Payment Required"); -const HTTP_403 = new HttpError(403, "Forbidden"); -const HTTP_404 = new HttpError(404, "Not Found"); -const HTTP_405 = new HttpError(405, "Method Not Allowed"); -const HTTP_406 = new HttpError(406, "Not Acceptable"); -const HTTP_407 = new HttpError(407, "Proxy Authentication Required"); -const HTTP_408 = new HttpError(408, "Request Timeout"); -const HTTP_409 = new HttpError(409, "Conflict"); -const HTTP_410 = new HttpError(410, "Gone"); -const HTTP_411 = new HttpError(411, "Length Required"); -const HTTP_412 = new HttpError(412, "Precondition Failed"); -const HTTP_413 = new HttpError(413, "Request Entity Too Large"); -const HTTP_414 = new HttpError(414, "Request-URI Too Long"); -const HTTP_415 = new HttpError(415, "Unsupported Media Type"); -const HTTP_417 = new HttpError(417, "Expectation Failed"); - -const HTTP_500 = new HttpError(500, "Internal Server Error"); -const HTTP_501 = new HttpError(501, "Not Implemented"); -const HTTP_502 = new HttpError(502, "Bad Gateway"); -const HTTP_503 = new HttpError(503, "Service Unavailable"); -const HTTP_504 = new HttpError(504, "Gateway Timeout"); -const HTTP_505 = new HttpError(505, "HTTP Version Not Supported"); - -/** Creates a hash with fields corresponding to the values in arr. */ -function array2obj(arr) -{ - var obj = {}; - for (var i = 0; i < arr.length; i++) - obj[arr[i]] = arr[i]; - return obj; -} - -/** Returns an array of the integers x through y, inclusive. */ -function range(x, y) -{ - var arr = []; - for (var i = x; i <= y; i++) - arr.push(i); - return arr; -} - -/** An object (hash) whose fields are the numbers of all HTTP error codes. */ -const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505))); - - -/** -* The character used to distinguish hidden files from non-hidden files, a la -* the leading dot in Apache. Since that mechanism also hides files from -* easy display in LXR, ls output, etc. however, we choose instead to use a -* suffix character. If a requested file ends with it, we append another -* when getting the file on the server. If it doesn't, we just look up that -* file. Therefore, any file whose name ends with exactly one of the character -* is "hidden" and available for use by the server. -*/ -const HIDDEN_CHAR = "^"; - -/** -* The file name suffix indicating the file containing overridden headers for -* a requested file. -*/ -const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR; - -/** Type used to denote SJS scripts for CGI-like functionality. */ -const SJS_TYPE = "sjs"; - -/** Base for relative timestamps produced by dumpn(). */ -var firstStamp = 0; - -/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */ -function dumpn(str) -{ - if (DEBUG) - { - var prefix = "HTTPD-INFO | "; - if (DEBUG_TIMESTAMP) - { - if (firstStamp === 0) - firstStamp = Date.now(); - - var elapsed = Date.now() - firstStamp; // milliseconds - var min = Math.floor(elapsed / 60000); - var sec = (elapsed % 60000) / 1000; - - if (sec < 10) - prefix += min + ":0" + sec.toFixed(3) + " | "; - else - prefix += min + ":" + sec.toFixed(3) + " | "; - } - - dump(prefix + str + "\n"); - } -} - -/** Dumps the current JS stack if DEBUG. */ -function dumpStack() -{ - // peel off the frames for dumpStack() and Error() - var stack = new Error().stack.split(/\n/).slice(2); - stack.forEach(dumpn); -} - - -/** The XPCOM thread manager. */ -var gThreadManager = null; - -/** The XPCOM prefs service. */ -var gRootPrefBranch = null; -function getRootPrefBranch() -{ - if (!gRootPrefBranch) - { - gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"] - .getService(Ci.nsIPrefBranch); - } - return gRootPrefBranch; -} - -/** -* JavaScript constructors for commonly-used classes; precreating these is a -* speedup over doing the same from base principles. See the docs at -* http://developer.mozilla.org/en/docs/components.Constructor for details. -*/ -const ServerSocket = CC("@mozilla.org/network/server-socket;1", - "nsIServerSocket", - "init"); -const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", - "nsIScriptableInputStream", - "init"); -const Pipe = CC("@mozilla.org/pipe;1", - "nsIPipe", - "init"); -const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", - "nsIFileInputStream", - "init"); -const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1", - "nsIConverterInputStream", - "init"); -const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1", - "nsIWritablePropertyBag2"); -const SupportsString = CC("@mozilla.org/supports-string;1", - "nsISupportsString"); - -/* These two are non-const only so a test can overwrite them. */ -var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", - "setInputStream"); -var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", - "nsIBinaryOutputStream", - "setOutputStream"); - -/** -* Returns the RFC 822/1123 representation of a date. -* -* @param date : Number -* the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT -* @returns string -* the representation of the given date -*/ -function toDateString(date) -{ - // - // rfc1123-date = wkday "," SP date1 SP time SP "GMT" - // date1 = 2DIGIT SP month SP 4DIGIT - // ; day month year (e.g., 02 Jun 1982) - // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - // ; 00:00:00 - 23:59:59 - // wkday = "Mon" | "Tue" | "Wed" - // | "Thu" | "Fri" | "Sat" | "Sun" - // month = "Jan" | "Feb" | "Mar" | "Apr" - // | "May" | "Jun" | "Jul" | "Aug" - // | "Sep" | "Oct" | "Nov" | "Dec" - // - - const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - /** -* Processes a date and returns the encoded UTC time as a string according to -* the format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toTime(date) - { - var hrs = date.getUTCHours(); - var rv = (hrs < 10) ? "0" + hrs : hrs; - - var mins = date.getUTCMinutes(); - rv += ":"; - rv += (mins < 10) ? "0" + mins : mins; - - var secs = date.getUTCSeconds(); - rv += ":"; - rv += (secs < 10) ? "0" + secs : secs; - - return rv; - } - - /** -* Processes a date and returns the encoded UTC date as a string according to -* the date1 format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toDate1(date) - { - var day = date.getUTCDate(); - var month = date.getUTCMonth(); - var year = date.getUTCFullYear(); - - var rv = (day < 10) ? "0" + day : day; - rv += " " + monthStrings[month]; - rv += " " + year; - - return rv; - } - - date = new Date(date); - - const fmtString = "%wkday%, %date1% %time% GMT"; - var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]); - rv = rv.replace("%time%", toTime(date)); - return rv.replace("%date1%", toDate1(date)); -} - -/** -* Prints out a human-readable representation of the object o and its fields, -* omitting those whose names begin with "_" if showMembers != true (to ignore -* "private" properties exposed via getters/setters). -*/ -function printObj(o, showMembers) -{ - var s = "******************************\n"; - s += "o = {\n"; - for (var i in o) - { - if (typeof(i) != "string" || - (showMembers || (i.length > 0 && i[0] != "_"))) - s+= " " + i + ": " + o[i] + ",\n"; - } - s += " };\n"; - s += "******************************"; - dumpn(s); -} - -/** -* Instantiates a new HTTP server. -*/ -function nsHttpServer() -{ - if (!gThreadManager) - gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService(); - - /** The port on which this server listens. */ - this._port = undefined; - - /** The socket associated with this. */ - this._socket = null; - - /** The handler used to process requests to this server. */ - this._handler = new ServerHandler(this); - - /** Naming information for this server. */ - this._identity = new ServerIdentity(); - - /** -* Indicates when the server is to be shut down at the end of the request. -*/ - this._doQuit = false; - - /** -* True if the socket in this is closed (and closure notifications have been -* sent and processed if the socket was ever opened), false otherwise. -*/ - this._socketClosed = true; - - /** -* Used for tracking existing connections and ensuring that all connections -* are properly cleaned up before server shutdown; increases by 1 for every -* new incoming connection. -*/ - this._connectionGen = 0; - - /** -* Hash of all open connections, indexed by connection number at time of -* creation. -*/ - this._connections = {}; -} -nsHttpServer.prototype = -{ - classID: components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"), - - // NSISERVERSOCKETLISTENER - - /** -* Processes an incoming request coming in on the given socket and contained -* in the given transport. -* -* @param socket : nsIServerSocket -* the socket through which the request was served -* @param trans : nsISocketTransport -* the transport for the request/response -* @see nsIServerSocketListener.onSocketAccepted -*/ - onSocketAccepted: function(socket, trans) - { - dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")"); - - dumpn(">>> new connection on " + trans.host + ":" + trans.port); - - const SEGMENT_SIZE = 8192; - const SEGMENT_COUNT = 1024; - try - { - var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT) - .QueryInterface(Ci.nsIAsyncInputStream); - var output = trans.openOutputStream(0, 0, 0); - } - catch (e) - { - dumpn("*** error opening transport streams: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - var connectionNumber = ++this._connectionGen; - - try - { - var conn = new Connection(input, output, this, socket.port, trans.port, - connectionNumber); - var reader = new RequestReader(conn); - - // XXX add request timeout functionality here! - - // Note: must use main thread here, or we might get a GC that will cause - // threadsafety assertions. We really need to fix XPConnect so that - // you can actually do things in multi-threaded JS. :-( - input.asyncWait(reader, 0, 0, gThreadManager.mainThread); - } - catch (e) - { - // Assume this connection can't be salvaged and bail on it completely; - // don't attempt to close it so that we can assert that any connection - // being closed is in this._connections. - dumpn("*** error in initial request-processing stages: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - this._connections[connectionNumber] = conn; - dumpn("*** starting connection " + connectionNumber); - }, - - /** -* Called when the socket associated with this is closed. -* -* @param socket : nsIServerSocket -* the socket being closed -* @param status : nsresult -* the reason the socket stopped listening (NS_BINDING_ABORTED if the server -* was stopped using nsIHttpServer.stop) -* @see nsIServerSocketListener.onStopListening -*/ - onStopListening: function(socket, status) - { - dumpn(">>> shutting down server on port " + socket.port); - this._socketClosed = true; - if (!this._hasOpenConnections()) - { - dumpn("*** no open connections, notifying async from onStopListening"); - - // Notify asynchronously so that any pending teardown in stop() has a - // chance to run first. - var self = this; - var stopEvent = - { - run: function() - { - dumpn("*** _notifyStopped async callback"); - self._notifyStopped(); - } - }; - gThreadManager.currentThread - .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL); - } - }, - - // NSIHTTPSERVER - - // - // see nsIHttpServer.start - // - start: function(port) - { - this._start(port, "localhost") - }, - - _start: function(port, host) - { - if (this._socket) - throw Cr.NS_ERROR_ALREADY_INITIALIZED; - - this._port = port; - this._doQuit = this._socketClosed = false; - - this._host = host; - - // The listen queue needs to be long enough to handle - // network.http.max-persistent-connections-per-server concurrent connections, - // plus a safety margin in case some other process is talking to - // the server as well. - var prefs = getRootPrefBranch(); - var maxConnections; - try { - // Bug 776860: The original pref was removed in favor of this new one: - maxConnections = prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5; - } - catch(e) { - maxConnections = prefs.getIntPref("network.http.max-connections-per-server") + 5; - } - - try - { - var loopback = true; - if (this._host != "127.0.0.1" && this._host != "localhost") { - var loopback = false; - } - - var socket = new ServerSocket(this._port, - loopback, // true = localhost, false = everybody - maxConnections); - dumpn(">>> listening on port " + socket.port + ", " + maxConnections + - " pending connections"); - socket.asyncListen(this); - this._identity._initialize(socket.port, host, true); - this._socket = socket; - } - catch (e) - { - dumpn("!!! could not start server on port " + port + ": " + e); - throw Cr.NS_ERROR_NOT_AVAILABLE; - } - }, - - // - // see nsIHttpServer.stop - // - stop: function(callback) - { - if (!callback) - throw Cr.NS_ERROR_NULL_POINTER; - if (!this._socket) - throw Cr.NS_ERROR_UNEXPECTED; - - this._stopCallback = typeof callback === "function" - ? callback - : function() { callback.onStopped(); }; - - dumpn(">>> stopping listening on port " + this._socket.port); - this._socket.close(); - this._socket = null; - - // We can't have this identity any more, and the port on which we're running - // this server now could be meaningless the next time around. - this._identity._teardown(); - - this._doQuit = false; - - // socket-close notification and pending request completion happen async - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (file && (!file.exists() || file.isDirectory())) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handler.registerFile(path, file); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // XXX true path validation! - if (path.charAt(0) != "/" || - path.charAt(path.length - 1) != "/" || - (directory && - (!directory.exists() || !directory.isDirectory()))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping - // exists! - - this._handler.registerDirectory(path, directory); - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - this._handler.registerPathHandler(path, handler); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - this._handler.registerPrefixHandler(prefix, handler); - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(code, handler) - { - this._handler.registerErrorHandler(code, handler); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - this._handler.setIndexHandler(handler); - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - this._handler.registerContentType(ext, type); - }, - - // - // see nsIHttpServer.serverIdentity - // - get identity() - { - return this._identity; - }, - - // - // see nsIHttpServer.getState - // - getState: function(path, k) - { - return this._handler._getState(path, k); - }, - - // - // see nsIHttpServer.setState - // - setState: function(path, k, v) - { - return this._handler._setState(path, k, v); - }, - - // - // see nsIHttpServer.getSharedState - // - getSharedState: function(k) - { - return this._handler._getSharedState(k); - }, - - // - // see nsIHttpServer.setSharedState - // - setSharedState: function(k, v) - { - return this._handler._setSharedState(k, v); - }, - - // - // see nsIHttpServer.getObjectState - // - getObjectState: function(k) - { - return this._handler._getObjectState(k); - }, - - // - // see nsIHttpServer.setObjectState - // - setObjectState: function(k, v) - { - return this._handler._setObjectState(k, v); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIServerSocketListener) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NON-XPCOM PUBLIC API - - /** -* Returns true iff this server is not running (and is not in the process of -* serving any requests still to be processed when the server was last -* stopped after being run). -*/ - isStopped: function() - { - return this._socketClosed && !this._hasOpenConnections(); - }, - - // PRIVATE IMPLEMENTATION - - /** True if this server has any open connections to it, false otherwise. */ - _hasOpenConnections: function() - { - // - // If we have any open connections, they're tracked as numeric properties on - // |this._connections|. The non-standard __count__ property could be used - // to check whether there are any properties, but standard-wise, even - // looking forward to ES5, there's no less ugly yet still O(1) way to do - // this. - // - for (var n in this._connections) - return true; - return false; - }, - - /** Calls the server-stopped callback provided when stop() was called. */ - _notifyStopped: function() - { - NS_ASSERT(this._stopCallback !== null, "double-notifying?"); - NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now"); - - // - // NB: We have to grab this now, null out the member, *then* call the - // callback here, or otherwise the callback could (indirectly) futz with - // this._stopCallback by starting and immediately stopping this, at - // which point we'd be nulling out a field we no longer have a right to - // modify. - // - var callback = this._stopCallback; - this._stopCallback = null; - try - { - callback(); - } - catch (e) - { - // not throwing because this is specified as being usually (but not - // always) asynchronous - dump("!!! error running onStopped callback: " + e + "\n"); - } - }, - - /** -* Notifies this server that the given connection has been closed. -* -* @param connection : Connection -* the connection that was closed -*/ - _connectionClosed: function(connection) - { - NS_ASSERT(connection.number in this._connections, - "closing a connection " + this + " that we never added to the " + - "set of open connections?"); - NS_ASSERT(this._connections[connection.number] === connection, - "connection number mismatch? " + - this._connections[connection.number]); - delete this._connections[connection.number]; - - // Fire a pending server-stopped notification if it's our responsibility. - if (!this._hasOpenConnections() && this._socketClosed) - this._notifyStopped(); - }, - - /** -* Requests that the server be shut down when possible. -*/ - _requestQuit: function() - { - dumpn(">>> requesting a quit"); - dumpStack(); - this._doQuit = true; - } -}; - - -// -// RFC 2396 section 3.2.2: -// -// host = hostname | IPv4address -// hostname = *( domainlabel "." ) toplabel [ "." ] -// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum -// toplabel = alpha | alpha *( alphanum | "-" ) alphanum -// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit -// - -const HOST_REGEX = - new RegExp("^(?:" + - // *( domainlabel "." ) - "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" + - // toplabel - "[a-z](?:[a-z0-9-]*[a-z0-9])?" + - "|" + - // IPv4 address - "\\d+\\.\\d+\\.\\d+\\.\\d+" + - ")$", - "i"); - - -/** -* Represents the identity of a server. An identity consists of a set of -* (scheme, host, port) tuples denoted as locations (allowing a single server to -* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any -* host/port). Any incoming request must be to one of these locations, or it -* will be rejected with an HTTP 400 error. One location, denoted as the -* primary location, is the location assigned in contexts where a location -* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests. -* -* A single identity may contain at most one location per unique host/port pair; -* other than that, no restrictions are placed upon what locations may -* constitute an identity. -*/ -function ServerIdentity() -{ - /** The scheme of the primary location. */ - this._primaryScheme = "http"; - - /** The hostname of the primary location. */ - this._primaryHost = "127.0.0.1" - - /** The port number of the primary location. */ - this._primaryPort = -1; - - /** -* The current port number for the corresponding server, stored so that a new -* primary location can always be set if the current one is removed. -*/ - this._defaultPort = -1; - - /** -* Maps hosts to maps of ports to schemes, e.g. the following would represent -* https://example.com:789/ and http://example.org/: -* -* { -* "xexample.com": { 789: "https" }, -* "xexample.org": { 80: "http" } -* } -* -* Note the "x" prefix on hostnames, which prevents collisions with special -* JS names like "prototype". -*/ - this._locations = { "xlocalhost": {} }; -} -ServerIdentity.prototype = -{ - // NSIHTTPSERVERIDENTITY - - // - // see nsIHttpServerIdentity.primaryScheme - // - get primaryScheme() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryScheme; - }, - - // - // see nsIHttpServerIdentity.primaryHost - // - get primaryHost() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryHost; - }, - - // - // see nsIHttpServerIdentity.primaryPort - // - get primaryPort() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryPort; - }, - - // - // see nsIHttpServerIdentity.add - // - add: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - this._locations["x" + host] = entry = {}; - - entry[port] = scheme; - }, - - // - // see nsIHttpServerIdentity.remove - // - remove: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return false; - - var present = port in entry; - delete entry[port]; - - if (this._primaryScheme == scheme && - this._primaryHost == host && - this._primaryPort == port && - this._defaultPort !== -1) - { - // Always keep at least one identity in existence at any time, unless - // we're in the process of shutting down (the last condition above). - this._primaryPort = -1; - this._initialize(this._defaultPort, host, false); - } - - return present; - }, - - // - // see nsIHttpServerIdentity.has - // - has: function(scheme, host, port) - { - this._validate(scheme, host, port); - - return "x" + host in this._locations && - scheme === this._locations["x" + host][port]; - }, - - // - // see nsIHttpServerIdentity.has - // - getScheme: function(host, port) - { - this._validate("http", host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return ""; - - return entry[port] || ""; - }, - - // - // see nsIHttpServerIdentity.setPrimary - // - setPrimary: function(scheme, host, port) - { - this._validate(scheme, host, port); - - this.add(scheme, host, port); - - this._primaryScheme = scheme; - this._primaryHost = host; - this._primaryPort = port; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Initializes the primary name for the corresponding server, based on the -* provided port number. -*/ - _initialize: function(port, host, addSecondaryDefault) - { - this._host = host; - if (this._primaryPort !== -1) - this.add("http", host, port); - else - this.setPrimary("http", "localhost", port); - this._defaultPort = port; - - // Only add this if we're being called at server startup - if (addSecondaryDefault && host != "127.0.0.1") - this.add("http", "127.0.0.1", port); - }, - - /** -* Called at server shutdown time, unsets the primary location only if it was -* the default-assigned location and removes the default location from the -* set of locations used. -*/ - _teardown: function() - { - if (this._host != "127.0.0.1") { - // Not the default primary location, nothing special to do here - this.remove("http", "127.0.0.1", this._defaultPort); - } - - // This is a *very* tricky bit of reasoning here; make absolutely sure the - // tests for this code pass before you commit changes to it. - if (this._primaryScheme == "http" && - this._primaryHost == this._host && - this._primaryPort == this._defaultPort) - { - // Make sure we don't trigger the readding logic in .remove(), then remove - // the default location. - var port = this._defaultPort; - this._defaultPort = -1; - this.remove("http", this._host, port); - - // Ensure a server start triggers the setPrimary() path in ._initialize() - this._primaryPort = -1; - } - else - { - // No reason not to remove directly as it's not our primary location - this.remove("http", this._host, this._defaultPort); - } - }, - - /** -* Ensures scheme, host, and port are all valid with respect to RFC 2396. -* -* @throws NS_ERROR_ILLEGAL_VALUE -* if any argument doesn't match the corresponding production -*/ - _validate: function(scheme, host, port) - { - if (scheme !== "http" && scheme !== "https") - { - dumpn("*** server only supports http/https schemes: '" + scheme + "'"); - dumpStack(); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (!HOST_REGEX.test(host)) - { - dumpn("*** unexpected host: '" + host + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (port < 0 || port > 65535) - { - dumpn("*** unexpected port: '" + port + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - } -}; - - -/** -* Represents a connection to the server (and possibly in the future the thread -* on which the connection is processed). -* -* @param input : nsIInputStream -* stream from which incoming data on the connection is read -* @param output : nsIOutputStream -* stream to write data out the connection -* @param server : nsHttpServer -* the server handling the connection -* @param port : int -* the port on which the server is running -* @param outgoingPort : int -* the outgoing port used by this connection -* @param number : uint -* a serial number used to uniquely identify this connection -*/ -function Connection(input, output, server, port, outgoingPort, number) -{ - dumpn("*** opening new connection " + number + " on port " + outgoingPort); - - /** Stream of incoming data. */ - this.input = input; - - /** Stream for outgoing data. */ - this.output = output; - - /** The server associated with this request. */ - this.server = server; - - /** The port on which the server is running. */ - this.port = port; - - /** The outgoing poort used by this connection. */ - this._outgoingPort = outgoingPort; - - /** The serial number of this connection. */ - this.number = number; - - /** -* The request for which a response is being generated, null if the -* incoming request has not been fully received or if it had errors. -*/ - this.request = null; - - /** State variables for debugging. */ - this._closed = this._processed = false; -} -Connection.prototype = -{ - /** Closes this connection's input/output streams. */ - close: function() - { - dumpn("*** closing connection " + this.number + - " on port " + this._outgoingPort); - - this.input.close(); - this.output.close(); - this._closed = true; - - var server = this.server; - server._connectionClosed(this); - - // If an error triggered a server shutdown, act on it now - if (server._doQuit) - server.stop(function() { /* not like we can do anything better */ }); - }, - - /** -* Initiates processing of this connection, using the data in the given -* request. -* -* @param request : Request -* the request which should be processed -*/ - process: function(request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - - this.request = request; - this.server._handler.handleResponse(this); - }, - - /** -* Initiates processing of this connection, generating a response with the -* given HTTP error code. -* -* @param code : uint -* an HTTP code, so in the range [0, 1000) -* @param request : Request -* incomplete data about the incoming request (since there were errors -* during its processing -*/ - processError: function(code, request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - this.request = request; - this.server._handler.handleError(code, this); - }, - - /** Converts this to a string for debugging purposes. */ - toString: function() - { - return ""; - } -}; - - - -/** Returns an array of count bytes from the given input stream. */ -function readBytes(inputStream, count) -{ - return new BinaryInputStream(inputStream).readByteArray(count); -} - - - -/** Request reader processing states; see RequestReader for details. */ -const READER_IN_REQUEST_LINE = 0; -const READER_IN_HEADERS = 1; -const READER_IN_BODY = 2; -const READER_FINISHED = 3; - - -/** -* Reads incoming request data asynchronously, does any necessary preprocessing, -* and forwards it to the request handler. Processing occurs in three states: -* -* READER_IN_REQUEST_LINE Reading the request's status line -* READER_IN_HEADERS Reading headers in the request -* READER_IN_BODY Reading the body of the request -* READER_FINISHED Entire request has been read and processed -* -* During the first two stages, initial metadata about the request is gathered -* into a Request object. Once the status line and headers have been processed, -* we start processing the body of the request into the Request. Finally, when -* the entire body has been read, we create a Response and hand it off to the -* ServerHandler to be given to the appropriate request handler. -* -* @param connection : Connection -* the connection for the request being read -*/ -function RequestReader(connection) -{ - /** Connection metadata for this request. */ - this._connection = connection; - - /** -* A container providing line-by-line access to the raw bytes that make up the -* data which has been read from the connection but has not yet been acted -* upon (by passing it to the request handler or by extracting request -* metadata from it). -*/ - this._data = new LineData(); - - /** -* The amount of data remaining to be read from the body of this request. -* After all headers in the request have been read this is the value in the -* Content-Length header, but as the body is read its value decreases to zero. -*/ - this._contentLength = 0; - - /** The current state of parsing the incoming request. */ - this._state = READER_IN_REQUEST_LINE; - - /** Metadata constructed from the incoming request for the request handler. */ - this._metadata = new Request(connection.port); - - /** -* Used to preserve state if we run out of line data midway through a -* multi-line header. _lastHeaderName stores the name of the header, while -* _lastHeaderValue stores the value we've seen so far for the header. -* -* These fields are always either both undefined or both strings. -*/ - this._lastHeaderName = this._lastHeaderValue = undefined; -} -RequestReader.prototype = -{ - // NSIINPUTSTREAMCALLBACK - - /** -* Called when more data from the incoming request is available. This method -* then reads the available data from input and deals with that data as -* necessary, depending upon the syntax of already-downloaded data. -* -* @param input : nsIAsyncInputStream -* the stream of incoming data from the connection -*/ - onInputStreamReady: function(input) - { - dumpn("*** onInputStreamReady(input=" + input + ") on thread " + - gThreadManager.currentThread + " (main is " + - gThreadManager.mainThread + ")"); - dumpn("*** this._state == " + this._state); - - // Handle cases where we get more data after a request error has been - // discovered but *before* we can close the connection. - var data = this._data; - if (!data) - return; - - try - { - data.appendBytes(readBytes(input, input.available())); - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** WARNING: unexpected error when reading from socket; will " + - "be treated as if the input stream had been closed"); - dumpn("*** WARNING: actual error was: " + e); - } - - // We've lost a race -- input has been closed, but we're still expecting - // to read more data. available() will throw in this case, and since - // we're dead in the water now, destroy the connection. - dumpn("*** onInputStreamReady called on a closed input, destroying " + - "connection"); - this._connection.close(); - return; - } - - switch (this._state) - { - default: - NS_ASSERT(false, "invalid state: " + this._state); - break; - - case READER_IN_REQUEST_LINE: - if (!this._processRequestLine()) - break; - /* fall through */ - - case READER_IN_HEADERS: - if (!this._processHeaders()) - break; - /* fall through */ - - case READER_IN_BODY: - this._processBody(); - } - - if (this._state != READER_FINISHED) - input.asyncWait(this, 0, 0, gThreadManager.currentThread); - }, - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIInputStreamCallback) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE API - - /** -* Processes unprocessed, downloaded data as a request line. -* -* @returns boolean -* true iff the request line has been fully processed -*/ - _processRequestLine: function() - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - // Servers SHOULD ignore any empty line(s) received where a Request-Line - // is expected (section 4.1). - var data = this._data; - var line = {}; - var readSuccess; - while ((readSuccess = data.readLine(line)) && line.value == "") - dumpn("*** ignoring beginning blank line..."); - - // if we don't have a full line, wait until we do - if (!readSuccess) - return false; - - // we have the first non-blank line - try - { - this._parseRequestLine(line.value); - this._state = READER_IN_HEADERS; - return true; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing request headers. -* -* @returns boolean -* true iff header data in the request has been fully processed -*/ - _processHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - // XXX things to fix here: - // - // - need to support RFC 2047-encoded non-US-ASCII characters - - try - { - var done = this._parseHeaders(); - if (done) - { - var request = this._metadata; - - // XXX this is wrong for requests with transfer-encodings applied to - // them, particularly chunked (which by its nature can have no - // meaningful Content-Length header)! - this._contentLength = request.hasHeader("Content-Length") - ? parseInt(request.getHeader("Content-Length"), 10) - : 0; - dumpn("_processHeaders, Content-length=" + this._contentLength); - - this._state = READER_IN_BODY; - } - return done; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing the request body. -* -* @returns boolean -* true iff the request body has been fully processed -*/ - _processBody: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - // XXX handle chunked transfer-coding request bodies! - - try - { - if (this._contentLength > 0) - { - var data = this._data.purge(); - var count = Math.min(data.length, this._contentLength); - dumpn("*** loading data=" + data + " len=" + data.length + - " excess=" + (data.length - count)); - - var bos = new BinaryOutputStream(this._metadata._bodyOutputStream); - bos.writeByteArray(data, count); - this._contentLength -= count; - } - - dumpn("*** remaining body data len=" + this._contentLength); - if (this._contentLength == 0) - { - this._validateRequest(); - this._state = READER_FINISHED; - this._handleResponse(); - return true; - } - - return false; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Does various post-header checks on the data in this request. -* -* @throws : HttpError -* if the request was malformed in some way -*/ - _validateRequest: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - dumpn("*** _validateRequest"); - - var metadata = this._metadata; - var headers = metadata._headers; - - // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header - var identity = this._connection.server.identity; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - { - if (!headers.hasHeader("Host")) - { - dumpn("*** malformed HTTP/1.1 or greater request with no Host header!"); - throw HTTP_400; - } - - // If the Request-URI wasn't absolute, then we need to determine our host. - // We have to determine what scheme was used to access us based on the - // server identity data at this point, because the request just doesn't - // contain enough data on its own to do this, sadly. - if (!metadata._host) - { - var host, port; - var hostPort = headers.getHeader("Host"); - var colon = hostPort.indexOf(":"); - if (colon < 0) - { - host = hostPort; - port = ""; - } - else - { - host = hostPort.substring(0, colon); - port = hostPort.substring(colon + 1); - } - - // NB: We allow an empty port here because, oddly, a colon may be - // present even without a port number, e.g. "example.com:"; in this - // case the default port applies. - if (!HOST_REGEX.test(host) || !/^\d*$/.test(port)) - { - dumpn("*** malformed hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - // If we're not given a port, we're stuck, because we don't know what - // scheme to use to look up the correct port here, in general. Since - // the HTTPS case requires a tunnel/proxy and thus requires that the - // requested URI be absolute (and thus contain the necessary - // information), let's assume HTTP will prevail and use that. - port = +port || 80; - - var scheme = identity.getScheme(host, port); - if (!scheme) - { - dumpn("*** unrecognized hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - } - } - else - { - NS_ASSERT(metadata._host === undefined, - "HTTP/1.0 doesn't allow absolute paths in the request line!"); - - metadata._scheme = identity.primaryScheme; - metadata._host = identity.primaryHost; - metadata._port = identity.primaryPort; - } - - NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port), - "must have a location we recognize by now!"); - }, - - /** -* Handles responses in case of error, either in the server or in the request. -* -* @param e -* the specific error encountered, which is an HttpError in the case where -* the request is in some way invalid or cannot be fulfilled; if this isn't -* an HttpError we're going to be paranoid and shut down, because that -* shouldn't happen, ever -*/ - _handleError: function(e) - { - // Don't fall back into normal processing! - this._state = READER_FINISHED; - - var server = this._connection.server; - if (e instanceof HttpError) - { - var code = e.code; - } - else - { - dumpn("!!! UNEXPECTED ERROR: " + e + - (e.lineNumber ? ", line " + e.lineNumber : "")); - - // no idea what happened -- be paranoid and shut down - code = 500; - server._requestQuit(); - } - - // make attempted reuse of data an error - this._data = null; - - this._connection.processError(code, this._metadata); - }, - - /** -* Now that we've read the request line and headers, we can actually hand off -* the request to be handled. -* -* This method is called once per request, after the request line and all -* headers and the body, if any, have been received. -*/ - _handleResponse: function() - { - NS_ASSERT(this._state == READER_FINISHED); - - // We don't need the line-based data any more, so make attempted reuse an - // error. - this._data = null; - - this._connection.process(this._metadata); - }, - - - // PARSING - - /** -* Parses the request line for the HTTP request associated with this. -* -* @param line : string -* the request line -*/ - _parseRequestLine: function(line) - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - dumpn("*** _parseRequestLine('" + line + "')"); - - var metadata = this._metadata; - - // clients and servers SHOULD accept any amount of SP or HT characters - // between fields, even though only a single SP is required (section 19.3) - var request = line.split(/[ \t]+/); - if (!request || request.length != 3) - throw HTTP_400; - - metadata._method = request[0]; - - // get the HTTP version - var ver = request[2]; - var match = ver.match(/^HTTP\/(\d+\.\d+)$/); - if (!match) - throw HTTP_400; - - // determine HTTP version - try - { - metadata._httpVersion = new nsHttpVersion(match[1]); - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0)) - throw "unsupported HTTP version"; - } - catch (e) - { - // we support HTTP/1.0 and HTTP/1.1 only - throw HTTP_501; - } - - - var fullPath = request[1]; - var serverIdentity = this._connection.server.identity; - - var scheme, host, port; - - if (fullPath.charAt(0) != "/") - { - // No absolute paths in the request line in HTTP prior to 1.1 - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - throw HTTP_400; - - try - { - var uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(fullPath, null, null); - fullPath = uri.path; - scheme = uri.scheme; - host = metadata._host = uri.asciiHost; - port = uri.port; - if (port === -1) - { - if (scheme === "http") - port = 80; - else if (scheme === "https") - port = 443; - else - throw HTTP_400; - } - } - catch (e) - { - // If the host is not a valid host on the server, the response MUST be a - // 400 (Bad Request) error message (section 5.2). Alternately, the URI - // is malformed. - throw HTTP_400; - } - - if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/") - throw HTTP_400; - } - - var splitter = fullPath.indexOf("?"); - if (splitter < 0) - { - // _queryString already set in ctor - metadata._path = fullPath; - } - else - { - metadata._path = fullPath.substring(0, splitter); - metadata._queryString = fullPath.substring(splitter + 1); - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - }, - - /** -* Parses all available HTTP headers in this until the header-ending CRLFCRLF, -* adding them to the store of headers in the request. -* -* @throws -* HTTP_400 if the headers are malformed -* @returns boolean -* true if all headers have now been processed, false otherwise -*/ - _parseHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - dumpn("*** _parseHeaders"); - - var data = this._data; - - var headers = this._metadata._headers; - var lastName = this._lastHeaderName; - var lastVal = this._lastHeaderValue; - - var line = {}; - while (true) - { - NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)), - lastName === undefined ? - "lastVal without lastName? lastVal: '" + lastVal + "'" : - "lastName without lastVal? lastName: '" + lastName + "'"); - - if (!data.readLine(line)) - { - // save any data we have from the header we might still be processing - this._lastHeaderName = lastName; - this._lastHeaderValue = lastVal; - return false; - } - - var lineText = line.value; - var firstChar = lineText.charAt(0); - - // blank line means end of headers - if (lineText == "") - { - // we're finished with the previous header - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - else - { - // no headers in request -- valid for HTTP/1.0 requests - } - - // either way, we're done processing headers - this._state = READER_IN_BODY; - return true; - } - else if (firstChar == " " || firstChar == "\t") - { - // multi-line header if we've already seen a header line - if (!lastName) - { - // we don't have a header to continue! - throw HTTP_400; - } - - // append this line's text to the value; starts with SP/HT, so no need - // for separating whitespace - lastVal += lineText; - } - else - { - // we have a new header, so set the old one (if one existed) - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - - var colon = lineText.indexOf(":"); // first colon must be splitter - if (colon < 1) - { - // no colon or missing header field-name - throw HTTP_400; - } - - // set header name, value (to be set in the next loop, usually) - lastName = lineText.substring(0, colon); - lastVal = lineText.substring(colon + 1); - } // empty, continuation, start of header - } // while (true) - } -}; - - -/** The character codes for CR and LF. */ -const CR = 0x0D, LF = 0x0A; - -/** -* Calculates the number of characters before the first CRLF pair in array, or -* -1 if the array contains no CRLF pair. -* -* @param array : Array -* an array of numbers in the range [0, 256), each representing a single -* character; the first CRLF is the lowest index i where -* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|, -* if such an |i| exists, and -1 otherwise -* @returns int -* the index of the first CRLF if any were present, -1 otherwise -*/ -function findCRLF(array) -{ - for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1)) - { - if (array[i + 1] == LF) - return i; - } - return -1; -} - - -/** -* A container which provides line-by-line access to the arrays of bytes with -* which it is seeded. -*/ -function LineData() -{ - /** An array of queued bytes from which to get line-based characters. */ - this._data = []; -} -LineData.prototype = -{ - /** -* Appends the bytes in the given array to the internal data cache maintained -* by this. -*/ - appendBytes: function(bytes) - { - Array.prototype.push.apply(this._data, bytes); - }, - - /** -* Removes and returns a line of data, delimited by CRLF, from this. -* -* @param out -* an object whose "value" property will be set to the first line of text -* present in this, sans CRLF, if this contains a full CRLF-delimited line -* of text; if this doesn't contain enough data, the value of the property -* is undefined -* @returns boolean -* true if a full line of data could be read from the data in this, false -* otherwise -*/ - readLine: function(out) - { - var data = this._data; - var length = findCRLF(data); - if (length < 0) - return false; - - // - // We have the index of the CR, so remove all the characters, including - // CRLF, from the array with splice, and convert the removed array into the - // corresponding string, from which we then strip the trailing CRLF. - // - // Getting the line in this matter acknowledges that substring is an O(1) - // operation in SpiderMonkey because strings are immutable, whereas two - // splices, both from the beginning of the data, are less likely to be as - // cheap as a single splice plus two extra character conversions. - // - var line = String.fromCharCode.apply(null, data.splice(0, length + 2)); - out.value = line.substring(0, length); - - return true; - }, - - /** -* Removes the bytes currently within this and returns them in an array. -* -* @returns Array -* the bytes within this when this method is called -*/ - purge: function() - { - var data = this._data; - this._data = []; - return data; - } -}; - - - -/** -* Creates a request-handling function for an nsIHttpRequestHandler object. -*/ -function createHandlerFunc(handler) -{ - return function(metadata, response) { handler.handle(metadata, response); }; -} - - -/** -* The default handler for directories; writes an HTML response containing a -* slightly-formatted directory listing. -*/ -function defaultIndexHandler(metadata, response) -{ - response.setHeader("Content-Type", "text/html", false); - - var path = htmlEscape(decodeURI(metadata.path)); - - // - // Just do a very basic bit of directory listings -- no need for too much - // fanciness, especially since we don't have a style sheet in which we can - // stick rules (don't want to pollute the default path-space). - // - - var body = '\ -\ -' + path + '\ -\ -\ -

' + path + '

\ -
    '; - - var directory = metadata.getProperty("directory").QueryInterface(Ci.nsILocalFile); - NS_ASSERT(directory && directory.isDirectory()); - - var fileList = []; - var files = directory.directoryEntries; - while (files.hasMoreElements()) - { - var f = files.getNext().QueryInterface(Ci.nsIFile); - var name = f.leafName; - if (!f.isHidden() && - (name.charAt(name.length - 1) != HIDDEN_CHAR || - name.charAt(name.length - 2) == HIDDEN_CHAR)) - fileList.push(f); - } - - fileList.sort(fileSort); - - for (var i = 0; i < fileList.length; i++) - { - var file = fileList[i]; - try - { - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - var sep = file.isDirectory() ? "/" : ""; - - // Note: using " to delimit the attribute here because encodeURIComponent - // passes through '. - var item = '
  1. ' + - htmlEscape(name) + sep + - '
  2. '; - - body += item; - } - catch (e) { /* some file system error, ignore the file */ } - } - - body += '
\ -\ -'; - - response.bodyOutputStream.write(body, body.length); -} - -/** -* Sorts a and b (nsIFile objects) into an aesthetically pleasing order. -*/ -function fileSort(a, b) -{ - var dira = a.isDirectory(), dirb = b.isDirectory(); - - if (dira && !dirb) - return -1; - if (dirb && !dira) - return 1; - - var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase(); - return nameb > namea ? -1 : 1; -} - - -/** -* Converts an externally-provided path into an internal path for use in -* determining file mappings. -* -* @param path -* the path to convert -* @param encoded -* true if the given path should be passed through decodeURI prior to -* conversion -* @throws URIError -* if path is incorrectly encoded -*/ -function toInternalPath(path, encoded) -{ - if (encoded) - path = decodeURI(path); - - var comps = path.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - if (comp.charAt(comp.length - 1) == HIDDEN_CHAR) - comps[i] = comp + HIDDEN_CHAR; - } - return comps.join("/"); -} - - -/** -* Adds custom-specified headers for the given file to the given response, if -* any such headers are specified. -* -* @param file -* the file on the disk which is to be written -* @param metadata -* metadata about the incoming request -* @param response -* the Response to which any specified headers/data should be written -* @throws HTTP_500 -* if an error occurred while processing custom-specified headers -*/ -function maybeAddHeaders(file, metadata, response) -{ - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - - var headerFile = file.parent; - headerFile.append(name + HEADERS_SUFFIX); - - if (!headerFile.exists()) - return; - - const PR_RDONLY = 0x01; - var fis = new FileInputStream(headerFile, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0); - lis.QueryInterface(Ci.nsIUnicharLineInputStream); - - var line = {value: ""}; - var more = lis.readLine(line); - - if (!more && line.value == "") - return; - - - // request line - - var status = line.value; - if (status.indexOf("HTTP ") == 0) - { - status = status.substring(5); - var space = status.indexOf(" "); - var code, description; - if (space < 0) - { - code = status; - description = ""; - } - else - { - code = status.substring(0, space); - description = status.substring(space + 1, status.length); - } - - response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description); - - line.value = ""; - more = lis.readLine(line); - } - - // headers - while (more || line.value != "") - { - var header = line.value; - var colon = header.indexOf(":"); - - response.setHeader(header.substring(0, colon), - header.substring(colon + 1, header.length), - false); // allow overriding server-set headers - - line.value = ""; - more = lis.readLine(line); - } - } - catch (e) - { - dumpn("WARNING: error in headers for " + metadata.path + ": " + e); - throw HTTP_500; - } - finally - { - fis.close(); - } -} - - -/** -* An object which handles requests for a server, executing default and -* overridden behaviors as instructed by the code which uses and manipulates it. -* Default behavior includes the paths / and /trace (diagnostics), with some -* support for HTTP error pages for various codes and fallback to HTTP 500 if -* those codes fail for any reason. -* -* @param server : nsHttpServer -* the server in which this handler is being used -*/ -function ServerHandler(server) -{ - // FIELDS - - /** -* The nsHttpServer instance associated with this handler. -*/ - this._server = server; - - /** -* A FileMap object containing the set of path->nsILocalFile mappings for -* all directory mappings set in the server (e.g., "/" for /var/www/html/, -* "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2). -* -* Note carefully: the leading and trailing "/" in each path (not file) are -* removed before insertion to simplify the code which uses this. You have -* been warned! -*/ - this._pathDirectoryMap = new FileMap(); - - /** -* Custom request handlers for the server in which this resides. Path-handler -* pairs are stored as property-value pairs in this property. -* -* @see ServerHandler.prototype._defaultPaths -*/ - this._overridePaths = {}; - - /** -* Custom request handlers for the server in which this resides. Prefix-handler -* pairs are stored as property-value pairs in this property. -*/ - this._overridePrefixes = {}; - - /** -* Custom request handlers for the error handlers in the server in which this -* resides. Path-handler pairs are stored as property-value pairs in this -* property. -* -* @see ServerHandler.prototype._defaultErrors -*/ - this._overrideErrors = {}; - - /** -* Maps file extensions to their MIME types in the server, overriding any -* mapping that might or might not exist in the MIME service. -*/ - this._mimeMappings = {}; - - /** -* The default handler for requests for directories, used to serve directories -* when no index file is present. -*/ - this._indexHandler = defaultIndexHandler; - - /** Per-path state storage for the server. */ - this._state = {}; - - /** Entire-server state storage. */ - this._sharedState = {}; - - /** Entire-server state storage for nsISupports values. */ - this._objectState = {}; -} -ServerHandler.prototype = -{ - // PUBLIC API - - /** -* Handles a request to this server, responding to the request appropriately -* and initiating server shutdown if necessary. -* -* This method never throws an exception. -* -* @param connection : Connection -* the connection for this request -*/ - handleResponse: function(connection) - { - var request = connection.request; - var response = new Response(connection); - - var path = request.path; - dumpn("*** path == " + path); - - try - { - try - { - if (path in this._overridePaths) - { - // explicit paths first, then files based on existing directory mappings, - // then (if the file doesn't exist) built-in server default paths - dumpn("calling override for " + path); - this._overridePaths[path](request, response); - } - else - { - let longestPrefix = ""; - for (let prefix in this._overridePrefixes) - { - if (prefix.length > longestPrefix.length && path.startsWith(prefix)) - { - longestPrefix = prefix; - } - } - if (longestPrefix.length > 0) - { - dumpn("calling prefix override for " + longestPrefix); - this._overridePrefixes[longestPrefix](request, response); - } - else - { - this._handleDefault(request, response); - } - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - if (!(e instanceof HttpError)) - { - dumpn("*** unexpected error: e == " + e); - throw HTTP_500; - } - if (e.code !== 404) - throw e; - - dumpn("*** default: " + (path in this._defaultPaths)); - - response = new Response(connection); - if (path in this._defaultPaths) - this._defaultPaths[path](request, response); - else - throw HTTP_404; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - var errorCode = "internal"; - - try - { - if (!(e instanceof HttpError)) - throw e; - - errorCode = e.code; - dumpn("*** errorCode == " + errorCode); - - response = new Response(connection); - if (e.customErrorHandling) - e.customErrorHandling(response); - this._handleError(errorCode, request, response); - return; - } - catch (e2) - { - dumpn("*** error handling " + errorCode + " error: " + - "e2 == " + e2 + ", shutting down server"); - - connection.server._requestQuit(); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (!file) - { - dumpn("*** unregistering '" + path + "' mapping"); - delete this._overridePaths[path]; - return; - } - - dumpn("*** registering '" + path + "' as mapping to " + file.path); - file = file.clone(); - - var self = this; - this._overridePaths[path] = - function(request, response) - { - if (!file.exists()) - throw HTTP_404; - - response.setStatusLine(request.httpVersion, 200, "OK"); - self._writeFileResponse(request, file, response, 0, file.fileSize); - }; - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - // XXX true path validation! - if (path.charAt(0) != "/") - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePaths, path); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - // XXX true prefix validation! - if (!(prefix.startsWith("/") && prefix.endsWith("/"))) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePrefixes, prefix); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // strip off leading and trailing '/' so that we can use lastIndexOf when - // determining exactly how a path maps onto a mapped directory -- - // conditional is required here to deal with "/".substring(1, 0) being - // converted to "/".substring(0, 1) per the JS specification - var key = path.length == 1 ? "" : path.substring(1, path.length - 1); - - // the path-to-directory mapping code requires that the first character not - // be "/", or it will go into an infinite loop - if (key.charAt(0) == "/") - throw Cr.NS_ERROR_INVALID_ARG; - - key = toInternalPath(key, false); - - if (directory) - { - dumpn("*** mapping '" + path + "' to the location " + directory.path); - this._pathDirectoryMap.put(key, directory); - } - else - { - dumpn("*** removing mapping for '" + path + "'"); - this._pathDirectoryMap.put(key, null); - } - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(err, handler) - { - if (!(err in HTTP_ERROR_CODES)) - dumpn("*** WARNING: registering non-HTTP/1.1 error code " + - "(" + err + ") handler -- was this intentional?"); - - this._handlerToField(handler, this._overrideErrors, err); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - if (!handler) - handler = defaultIndexHandler; - else if (typeof(handler) != "function") - handler = createHandlerFunc(handler); - - this._indexHandler = handler; - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - if (!type) - delete this._mimeMappings[ext]; - else - this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type); - }, - - // PRIVATE API - - /** -* Sets or remove (if handler is null) a handler in an object with a key. -* -* @param handler -* a handler, either function or an nsIHttpRequestHandler -* @param dict -* The object to attach the handler to. -* @param key -* The field name of the handler. -*/ - _handlerToField: function(handler, dict, key) - { - // for convenience, handler can be a function if this is run from xpcshell - if (typeof(handler) == "function") - dict[key] = handler; - else if (handler) - dict[key] = createHandlerFunc(handler); - else - delete dict[key]; - }, - - /** -* Handles a request which maps to a file in the local filesystem (if a base -* path has already been set; otherwise the 404 error is thrown). -* -* @param metadata : Request -* metadata for the incoming request -* @param response : Response -* an uninitialized Response to the given request, to be initialized by a -* request handler -* @throws HTTP_### -* if an HTTP error occurred (usually HTTP_404); note that in this case the -* calling code must handle post-processing of the response -*/ - _handleDefault: function(metadata, response) - { - dumpn("*** _handleDefault()"); - - response.setStatusLine(metadata.httpVersion, 200, "OK"); - - var path = metadata.path; - NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">"); - - // determine the actual on-disk file; this requires finding the deepest - // path-to-directory mapping in the requested URL - var file = this._getFileForPath(path); - - // the "file" might be a directory, in which case we either serve the - // contained index.html or make the index handler write the response - if (file.exists() && file.isDirectory()) - { - file.append("index.html"); // make configurable? - if (!file.exists() || file.isDirectory()) - { - metadata._ensurePropertyBag(); - metadata._bag.setPropertyAsInterface("directory", file.parent); - this._indexHandler(metadata, response); - return; - } - } - - // alternately, the file might not exist - if (!file.exists()) - throw HTTP_404; - - var start, end; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) && - metadata.hasHeader("Range") && - this._getTypeFromFile(file) !== SJS_TYPE) - { - var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/); - if (!rangeMatch) - throw HTTP_400; - - if (rangeMatch[1] !== undefined) - start = parseInt(rangeMatch[1], 10); - - if (rangeMatch[2] !== undefined) - end = parseInt(rangeMatch[2], 10); - - if (start === undefined && end === undefined) - throw HTTP_400; - - // No start given, so the end is really the count of bytes from the - // end of the file. - if (start === undefined) - { - start = Math.max(0, file.fileSize - end); - end = file.fileSize - 1; - } - - // start and end are inclusive - if (end === undefined || end >= file.fileSize) - end = file.fileSize - 1; - - if (start !== undefined && start >= file.fileSize) { - var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable"); - HTTP_416.customErrorHandling = function(errorResponse) - { - maybeAddHeaders(file, metadata, errorResponse); - }; - throw HTTP_416; - } - - if (end < start) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - start = 0; - end = file.fileSize - 1; - } - else - { - response.setStatusLine(metadata.httpVersion, 206, "Partial Content"); - var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize; - response.setHeader("Content-Range", contentRange); - } - } - else - { - start = 0; - end = file.fileSize - 1; - } - - // finally... - dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " + - start + " to " + end + " inclusive"); - this._writeFileResponse(metadata, file, response, start, end - start + 1); - }, - - /** -* Writes an HTTP response for the given file, including setting headers for -* file metadata. -* -* @param metadata : Request -* the Request for which a response is being generated -* @param file : nsILocalFile -* the file which is to be sent in the response -* @param response : Response -* the response to which the file should be written -* @param offset: uint -* the byte offset to skip to when writing -* @param count: uint -* the number of bytes to write -*/ - _writeFileResponse: function(metadata, file, response, offset, count) - { - const PR_RDONLY = 0x01; - - var type = this._getTypeFromFile(file); - if (type === SJS_TYPE) - { - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var sis = new ScriptableInputStream(fis); - var s = Cu.Sandbox(gGlobalObject); - s.importFunction(dump, "dump"); - - // Define a basic key-value state-preservation API across requests, with - // keys initially corresponding to the empty string. - var self = this; - var path = metadata.path; - s.importFunction(function getState(k) - { - return self._getState(path, k); - }); - s.importFunction(function setState(k, v) - { - self._setState(path, k, v); - }); - s.importFunction(function getSharedState(k) - { - return self._getSharedState(k); - }); - s.importFunction(function setSharedState(k, v) - { - self._setSharedState(k, v); - }); - s.importFunction(function getObjectState(k, callback) - { - callback(self._getObjectState(k)); - }); - s.importFunction(function setObjectState(k, v) - { - self._setObjectState(k, v); - }); - s.importFunction(function registerPathHandler(p, h) - { - self.registerPathHandler(p, h); - }); - - // Make it possible for sjs files to access their location - this._setState(path, "__LOCATION__", file.path); - - try - { - // Alas, the line number in errors dumped to console when calling the - // request handler is simply an offset from where we load the SJS file. - // Work around this in a reasonably non-fragile way by dynamically - // getting the line number where we evaluate the SJS file. Don't - // separate these two lines! - var line = new Error().lineNumber; - Cu.evalInSandbox(sis.read(file.fileSize), s); - } - catch (e) - { - dumpn("*** syntax error in SJS at " + file.path + ": " + e); - throw HTTP_500; - } - - try - { - s.handleRequest(metadata, response); - } - catch (e) - { - dump("*** error running SJS at " + file.path + ": " + - e + " on line " + - (e instanceof Error - ? e.lineNumber + " in httpd.js" - : (e.lineNumber - line)) + "\n"); - throw HTTP_500; - } - } - finally - { - fis.close(); - } - } - else - { - try - { - response.setHeader("Last-Modified", - toDateString(file.lastModifiedTime), - false); - } - catch (e) { /* lastModifiedTime threw, ignore */ } - - response.setHeader("Content-Type", type, false); - maybeAddHeaders(file, metadata, response); - response.setHeader("Content-Length", "" + count, false); - - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - offset = offset || 0; - count = count || file.fileSize; - NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset"); - NS_ASSERT(count >= 0, "bad count"); - NS_ASSERT(offset + count <= file.fileSize, "bad total data size"); - - try - { - if (offset !== 0) - { - // Seek (or read, if seeking isn't supported) to the correct offset so - // the data sent to the client matches the requested range. - if (fis instanceof Ci.nsISeekableStream) - fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset); - else - new ScriptableInputStream(fis).read(offset); - } - } - catch (e) - { - fis.close(); - throw e; - } - - let writeMore = function writeMore() - { - gThreadManager.currentThread - .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL); - } - - var input = new BinaryInputStream(fis); - var output = new BinaryOutputStream(response.bodyOutputStream); - var writeData = - { - run: function() - { - var chunkSize = Math.min(65536, count); - count -= chunkSize; - NS_ASSERT(count >= 0, "underflow"); - - try - { - var data = input.readByteArray(chunkSize); - NS_ASSERT(data.length === chunkSize, - "incorrect data returned? got " + data.length + - ", expected " + chunkSize); - output.writeByteArray(data, data.length); - if (count === 0) - { - fis.close(); - response.finish(); - } - else - { - writeMore(); - } - } - catch (e) - { - try - { - fis.close(); - } - finally - { - response.finish(); - } - throw e; - } - } - }; - - writeMore(); - - // Now that we know copying will start, flag the response as async. - response.processAsync(); - } - }, - - /** -* Get the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getState: function(path, k) - { - var state = this._state; - if (path in state && k in state[path]) - return state[path][k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setState: function(path, k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - var state = this._state; - if (!(path in state)) - state[path] = {}; - state[path][k] = v; - }, - - /** -* Get the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getSharedState: function(k) - { - var state = this._sharedState; - if (k in state) - return state[k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setSharedState: function(k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - this._sharedState[k] = v; - }, - - /** -* Returns the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be returned -* @returns nsISupports -* the corresponding object, or null if none was present -*/ - _getObjectState: function(k) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - return this._objectState[k] || null; - }, - - /** -* Sets the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be set -* @param v : nsISupports -* the object to be associated with the given key; may be null -*/ - _setObjectState: function(k, v) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - if (typeof v !== "object") - throw new Error("non-object value passed"); - if (v && !("QueryInterface" in v)) - { - throw new Error("must pass an nsISupports; use wrappedJSObject to ease " + - "pain when using the server from JS"); - } - - this._objectState[k] = v; - }, - - /** -* Gets a content-type for the given file, first by checking for any custom -* MIME-types registered with this handler for the file's extension, second by -* asking the global MIME service for a content-type, and finally by failing -* over to application/octet-stream. -* -* @param file : nsIFile -* the nsIFile for which to get a file type -* @returns string -* the best content-type which can be determined for the file -*/ - _getTypeFromFile: function(file) - { - try - { - var name = file.leafName; - var dot = name.lastIndexOf("."); - if (dot > 0) - { - var ext = name.slice(dot + 1); - if (ext in this._mimeMappings) - return this._mimeMappings[ext]; - } - return Cc["@mozilla.org/uriloader/external-helper-app-service;1"] - .getService(Ci.nsIMIMEService) - .getTypeFromFile(file); - } - catch (e) - { - return "application/octet-stream"; - } - }, - - /** -* Returns the nsILocalFile which corresponds to the path, as determined using -* all registered path->directory mappings and any paths which are explicitly -* overridden. -* -* @param path : string -* the server path for which a file should be retrieved, e.g. "/foo/bar" -* @throws HttpError -* when the correct action is the corresponding HTTP error (i.e., because no -* mapping was found for a directory in path, the referenced file doesn't -* exist, etc.) -* @returns nsILocalFile -* the file to be sent as the response to a request for the path -*/ - _getFileForPath: function(path) - { - // decode and add underscores as necessary - try - { - path = toInternalPath(path, true); - } - catch (e) - { - throw HTTP_400; // malformed path - } - - // next, get the directory which contains this path - var pathMap = this._pathDirectoryMap; - - // An example progression of tmp for a path "/foo/bar/baz/" might be: - // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", "" - var tmp = path.substring(1); - while (true) - { - // do we have a match for current head of the path? - var file = pathMap.get(tmp); - if (file) - { - // XXX hack; basically disable showing mapping for /foo/bar/ when the - // requested path was /foo/bar, because relative links on the page - // will all be incorrect -- we really need the ability to easily - // redirect here instead - if (tmp == path.substring(1) && - tmp.length != 0 && - tmp.charAt(tmp.length - 1) != "/") - file = null; - else - break; - } - - // if we've finished trying all prefixes, exit - if (tmp == "") - break; - - tmp = tmp.substring(0, tmp.lastIndexOf("/")); - } - - // no mapping applies, so 404 - if (!file) - throw HTTP_404; - - - // last, get the file for the path within the determined directory - var parentFolder = file.parent; - var dirIsRoot = (parentFolder == null); - - // Strategy here is to append components individually, making sure we - // never move above the given directory; this allows paths such as - // "/foo/../bar" but prevents paths such as "/../base-sibling"; - // this component-wise approach also means the code works even on platforms - // which don't use "/" as the directory separator, such as Windows - var leafPath = path.substring(tmp.length + 1); - var comps = leafPath.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - - if (comp == "..") - file = file.parent; - else if (comp == "." || comp == "") - continue; - else - file.append(comp); - - if (!dirIsRoot && file.equals(parentFolder)) - throw HTTP_403; - } - - return file; - }, - - /** -* Writes the error page for the given HTTP error code over the given -* connection. -* -* @param errorCode : uint -* the HTTP error code to be used -* @param connection : Connection -* the connection on which the error occurred -*/ - handleError: function(errorCode, connection) - { - var response = new Response(connection); - - dumpn("*** error in request: " + errorCode); - - this._handleError(errorCode, new Request(connection.port), response); - }, - - /** -* Handles a request which generates the given error code, using the -* user-defined error handler if one has been set, gracefully falling back to -* the x00 status code if the code has no handler, and failing to status code -* 500 if all else fails. -* -* @param errorCode : uint -* the HTTP error which is to be returned -* @param metadata : Request -* metadata for the request, which will often be incomplete since this is an -* error -* @param response : Response -* an uninitialized Response should be initialized when this method -* completes with information which represents the desired error code in the -* ideal case or a fallback code in abnormal circumstances (i.e., 500 is a -* fallback for 505, per HTTP specs) -*/ - _handleError: function(errorCode, metadata, response) - { - if (!metadata) - throw Cr.NS_ERROR_NULL_POINTER; - - var errorX00 = errorCode - (errorCode % 100); - - try - { - if (!(errorCode in HTTP_ERROR_CODES)) - dumpn("*** WARNING: requested invalid error: " + errorCode); - - // RFC 2616 says that we should try to handle an error by its class if we - // can't otherwise handle it -- if that fails, we revert to handling it as - // a 500 internal server error, and if that fails we throw and shut down - // the server - - // actually handle the error - try - { - if (errorCode in this._overrideErrors) - this._overrideErrors[errorCode](metadata, response); - else - this._defaultErrors[errorCode](metadata, response); - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - // don't retry the handler that threw - if (errorX00 == errorCode) - throw HTTP_500; - - dumpn("*** error in handling for error code " + errorCode + ", " + - "falling back to " + errorX00 + "..."); - response = new Response(response._connection); - if (errorX00 in this._overrideErrors) - this._overrideErrors[errorX00](metadata, response); - else if (errorX00 in this._defaultErrors) - this._defaultErrors[errorX00](metadata, response); - else - throw HTTP_500; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(); - return; - } - - // we've tried everything possible for a meaningful error -- now try 500 - dumpn("*** error in handling for error code " + errorX00 + ", falling " + - "back to 500..."); - - try - { - response = new Response(response._connection); - if (500 in this._overrideErrors) - this._overrideErrors[500](metadata, response); - else - this._defaultErrors[500](metadata, response); - } - catch (e2) - { - dumpn("*** multiple errors in default error handlers!"); - dumpn("*** e == " + e + ", e2 == " + e2); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // FIELDS - - /** -* This object contains the default handlers for the various HTTP error codes. -*/ - _defaultErrors: - { - 400: function(metadata, response) - { - // none of the data in metadata is reliable, so hard-code everything here - response.setStatusLine("1.1", 400, "Bad Request"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Bad request\n"; - response.bodyOutputStream.write(body, body.length); - }, - 403: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 403, "Forbidden"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -403 Forbidden\ -\ -

403 Forbidden

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 404: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 404, "Not Found"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -404 Not Found\ -\ -

404 Not Found

\ -

\ -" + - htmlEscape(metadata.path) + - " was not found.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 416: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 416, - "Requested Range Not Satisfiable"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -\ -416 Requested Range Not Satisfiable\ -\ -

416 Requested Range Not Satisfiable

\ -

The byte range was not valid for the\ -requested resource.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 500: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 500, - "Internal Server Error"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -500 Internal Server Error\ -\ -

500 Internal Server Error

\ -

Something's broken in this server and\ -needs to be fixed.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 501: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 501, "Not Implemented"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -501 Not Implemented\ -\ -

501 Not Implemented

\ -

This server is not (yet) Apache.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 505: function(metadata, response) - { - response.setStatusLine("1.1", 505, "HTTP Version Not Supported"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -505 HTTP Version Not Supported\ -\ -

505 HTTP Version Not Supported

\ -

This server only supports HTTP/1.0 and HTTP/1.1\ -connections.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - } - }, - - /** -* Contains handlers for the default set of URIs contained in this server. -*/ - _defaultPaths: - { - "/": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -httpd.js\ -\ -

httpd.js

\ -

If you're seeing this page, httpd.js is up and\ -serving requests! Now set a base path and serve some\ -files!

\ -\ -"; - - response.bodyOutputStream.write(body, body.length); - }, - - "/trace": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Request-URI: " + - metadata.scheme + "://" + metadata.host + ":" + metadata.port + - metadata.path + "\n\n"; - body += "Request (semantically equivalent, slightly reformatted):\n\n"; - body += metadata.method + " " + metadata.path; - - if (metadata.queryString) - body += "?" + metadata.queryString; - - body += " HTTP/" + metadata.httpVersion + "\r\n"; - - var headEnum = metadata.headers; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n"; - } - - response.bodyOutputStream.write(body, body.length); - } - } -}; - - -/** -* Maps absolute paths to files on the local file system (as nsILocalFiles). -*/ -function FileMap() -{ - /** Hash which will map paths to nsILocalFiles. */ - this._map = {}; -} -FileMap.prototype = -{ - // PUBLIC API - - /** -* Maps key to a clone of the nsILocalFile value if value is non-null; -* otherwise, removes any extant mapping for key. -* -* @param key : string -* string to which a clone of value is mapped -* @param value : nsILocalFile -* the file to map to key, or null to remove a mapping -*/ - put: function(key, value) - { - if (value) - this._map[key] = value.clone(); - else - delete this._map[key]; - }, - - /** -* Returns a clone of the nsILocalFile mapped to key, or null if no such -* mapping exists. -* -* @param key : string -* key to which the returned file maps -* @returns nsILocalFile -* a clone of the mapped file, or null if no mapping exists -*/ - get: function(key) - { - var val = this._map[key]; - return val ? val.clone() : null; - } -}; - - -// Response CONSTANTS - -// token = * -// CHAR = -// CTL = -// separators = "(" | ")" | "<" | ">" | "@" -// | "," | ";" | ":" | "\" | <"> -// | "/" | "[" | "]" | "?" | "=" -// | "{" | "}" | SP | HT -const IS_TOKEN_ARRAY = - [0, 0, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 0, 0, 0, 0, 0, 0, 0, // 24 - - 0, 1, 0, 1, 1, 1, 1, 1, // 32 - 0, 0, 1, 1, 0, 1, 1, 0, // 40 - 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 1, 1, 0, 0, 0, 0, 0, 0, // 56 - - 0, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, // 72 - 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 1, 1, 1, 0, 0, 0, 1, 1, // 88 - - 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, // 104 - 1, 1, 1, 1, 1, 1, 1, 1, // 112 - 1, 1, 1, 0, 1, 0, 1]; // 120 - - -/** -* Determines whether the given character code is a CTL. -* -* @param code : uint -* the character code -* @returns boolean -* true if code is a CTL, false otherwise -*/ -function isCTL(code) -{ - return (code >= 0 && code <= 31) || (code == 127); -} - -/** -* Represents a response to an HTTP request, encapsulating all details of that -* response. This includes all headers, the HTTP version, status code and -* explanation, and the entity itself. -* -* @param connection : Connection -* the connection over which this response is to be written -*/ -function Response(connection) -{ - /** The connection over which this response will be written. */ - this._connection = connection; - - /** -* The HTTP version of this response; defaults to 1.1 if not set by the -* handler. -*/ - this._httpVersion = nsHttpVersion.HTTP_1_1; - - /** -* The HTTP code of this response; defaults to 200. -*/ - this._httpCode = 200; - - /** -* The description of the HTTP code in this response; defaults to "OK". -*/ - this._httpDescription = "OK"; - - /** -* An nsIHttpHeaders object in which the headers in this response should be -* stored. This property is null after the status line and headers have been -* written to the network, and it may be modified up until it is cleared, -* except if this._finished is set first (in which case headers are written -* asynchronously in response to a finish() call not preceded by -* flushHeaders()). -*/ - this._headers = new nsHttpHeaders(); - - /** -* Set to true when this response is ended (completely constructed if possible -* and the connection closed); further actions on this will then fail. -*/ - this._ended = false; - - /** -* A stream used to hold data written to the body of this response. -*/ - this._bodyOutputStream = null; - - /** -* A stream containing all data that has been written to the body of this -* response so far. (Async handlers make the data contained in this -* unreliable as a way of determining content length in general, but auxiliary -* saved information can sometimes be used to guarantee reliability.) -*/ - this._bodyInputStream = null; - - /** -* A stream copier which copies data to the network. It is initially null -* until replaced with a copier for response headers; when headers have been -* fully sent it is replaced with a copier for the response body, remaining -* so for the duration of response processing. -*/ - this._asyncCopier = null; - - /** -* True if this response has been designated as being processed -* asynchronously rather than for the duration of a single call to -* nsIHttpRequestHandler.handle. -*/ - this._processAsync = false; - - /** -* True iff finish() has been called on this, signaling that no more changes -* to this may be made. -*/ - this._finished = false; - - /** -* True iff powerSeized() has been called on this, signaling that this -* response is to be handled manually by the response handler (which may then -* send arbitrary data in response, even non-HTTP responses). -*/ - this._powerSeized = false; -} -Response.prototype = -{ - // PUBLIC CONSTRUCTION API - - // - // see nsIHttpResponse.bodyOutputStream - // - get bodyOutputStream() - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - if (!this._bodyOutputStream) - { - var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX, - null); - this._bodyOutputStream = pipe.outputStream; - this._bodyInputStream = pipe.inputStream; - if (this._processAsync || this._powerSeized) - this._startAsyncProcessor(); - } - - return this._bodyOutputStream; - }, - - // - // see nsIHttpResponse.write - // - write: function(data) - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - var dataAsString = String(data); - this.bodyOutputStream.write(dataAsString, dataAsString.length); - }, - - // - // see nsIHttpResponse.setStatusLine - // - setStatusLine: function(httpVersion, code, description) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - if (!(code >= 0 && code < 1000)) - throw Cr.NS_ERROR_INVALID_ARG; - - try - { - var httpVer; - // avoid version construction for the most common cases - if (!httpVersion || httpVersion == "1.1") - httpVer = nsHttpVersion.HTTP_1_1; - else if (httpVersion == "1.0") - httpVer = nsHttpVersion.HTTP_1_0; - else - httpVer = new nsHttpVersion(httpVersion); - } - catch (e) - { - throw Cr.NS_ERROR_INVALID_ARG; - } - - // Reason-Phrase = * - // TEXT = - // - // XXX this ends up disallowing octets which aren't Unicode, I think -- not - // much to do if description is IDL'd as string - if (!description) - description = ""; - for (var i = 0; i < description.length; i++) - if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t") - throw Cr.NS_ERROR_INVALID_ARG; - - // set the values only after validation to preserve atomicity - this._httpDescription = description; - this._httpCode = code; - this._httpVersion = httpVer; - }, - - // - // see nsIHttpResponse.setHeader - // - setHeader: function(name, value, merge) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - this._headers.setHeader(name, value, merge); - }, - - // - // see nsIHttpResponse.processAsync - // - processAsync: function() - { - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._processAsync) - return; - this._ensureAlive(); - - dumpn("*** processing connection " + this._connection.number + " async"); - this._processAsync = true; - - /* -* Either the bodyOutputStream getter or this method is responsible for -* starting the asynchronous processor and catching writes of data to the -* response body of async responses as they happen, for the purpose of -* forwarding those writes to the actual connection's output stream. -* If bodyOutputStream is accessed first, calling this method will create -* the processor (when it first is clear that body data is to be written -* immediately, not buffered). If this method is called first, accessing -* bodyOutputStream will create the processor. If only this method is -* called, we'll write nothing, neither headers nor the nonexistent body, -* until finish() is called. Since that delay is easily avoided by simply -* getting bodyOutputStream or calling write(""), we don't worry about it. -*/ - if (this._bodyOutputStream && !this._asyncCopier) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.seizePower - // - seizePower: function() - { - if (this._processAsync) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - return; - this._ensureAlive(); - - dumpn("*** forcefully seizing power over connection " + - this._connection.number + "..."); - - // Purge any already-written data without sending it. We could as easily - // swap out the streams entirely, but that makes it possible to acquire and - // unknowingly use a stale reference, so we require there only be one of - // each stream ever for any response to avoid this complication. - if (this._asyncCopier) - this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED); - this._asyncCopier = null; - if (this._bodyOutputStream) - { - var input = new BinaryInputStream(this._bodyInputStream); - var avail; - while ((avail = input.available()) > 0) - input.readByteArray(avail); - } - - this._powerSeized = true; - if (this._bodyOutputStream) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.finish - // - finish: function() - { - if (!this._processAsync && !this._powerSeized) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._finished) - return; - - dumpn("*** finishing connection " + this._connection.number); - this._startAsyncProcessor(); // in case bodyOutputStream was never accessed - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - this._finished = true; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // POST-CONSTRUCTION API (not exposed externally) - - /** -* The HTTP version number of this, as a string (e.g. "1.1"). -*/ - get httpVersion() - { - this._ensureAlive(); - return this._httpVersion.toString(); - }, - - /** -* The HTTP status code of this response, as a string of three characters per -* RFC 2616. -*/ - get httpCode() - { - this._ensureAlive(); - - var codeString = (this._httpCode < 10 ? "0" : "") + - (this._httpCode < 100 ? "0" : "") + - this._httpCode; - return codeString; - }, - - /** -* The description of the HTTP status code of this response, or "" if none is -* set. -*/ - get httpDescription() - { - this._ensureAlive(); - - return this._httpDescription; - }, - - /** -* The headers in this response, as an nsHttpHeaders object. -*/ - get headers() - { - this._ensureAlive(); - - return this._headers; - }, - - // - // see nsHttpHeaders.getHeader - // - getHeader: function(name) - { - this._ensureAlive(); - - return this._headers.getHeader(name); - }, - - /** -* Determines whether this response may be abandoned in favor of a newly -* constructed response. A response may be abandoned only if it is not being -* sent asynchronously and if raw control over it has not been taken from the -* server. -* -* @returns boolean -* true iff no data has been written to the network -*/ - partiallySent: function() - { - dumpn("*** partiallySent()"); - return this._processAsync || this._powerSeized; - }, - - /** -* If necessary, kicks off the remaining request processing needed to be done -* after a request handler performs its initial work upon this response. -*/ - complete: function() - { - dumpn("*** complete()"); - if (this._processAsync || this._powerSeized) - { - NS_ASSERT(this._processAsync ^ this._powerSeized, - "can't both send async and relinquish power"); - return; - } - - NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?"); - - this._startAsyncProcessor(); - - // Now make sure we finish processing this request! - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - }, - - /** -* Abruptly ends processing of this response, usually due to an error in an -* incoming request but potentially due to a bad error handler. Since we -* cannot handle the error in the usual way (giving an HTTP error page in -* response) because data may already have been sent (or because the response -* might be expected to have been generated asynchronously or completely from -* scratch by the handler), we stop processing this response and abruptly -* close the connection. -* -* @param e : Error -* the exception which precipitated this abort, or null if no such exception -* was generated -*/ - abort: function(e) - { - dumpn("*** abort(<" + e + ">)"); - - // This response will be ended by the processor if one was created. - var copier = this._asyncCopier; - if (copier) - { - // We dispatch asynchronously here so that any pending writes of data to - // the connection will be deterministically written. This makes it easier - // to specify exact behavior, and it makes observable behavior more - // predictable for clients. Note that the correctness of this depends on - // callbacks in response to _waitToReadData in WriteThroughCopier - // happening asynchronously with respect to the actual writing of data to - // bodyOutputStream, as they currently do; if they happened synchronously, - // an event which ran before this one could write more data to the - // response body before we get around to canceling the copier. We have - // tests for this in test_seizepower.js, however, and I can't think of a - // way to handle both cases without removing bodyOutputStream access and - // moving its effective write(data, length) method onto Response, which - // would be slower and require more code than this anyway. - gThreadManager.currentThread.dispatch({ - run: function() - { - dumpn("*** canceling copy asynchronously..."); - copier.cancel(Cr.NS_ERROR_UNEXPECTED); - } - }, Ci.nsIThread.DISPATCH_NORMAL); - } - else - { - this.end(); - } - }, - - /** -* Closes this response's network connection, marks the response as finished, -* and notifies the server handler that the request is done being processed. -*/ - end: function() - { - NS_ASSERT(!this._ended, "ending this response twice?!?!"); - - this._connection.close(); - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - - this._finished = true; - this._ended = true; - }, - - // PRIVATE IMPLEMENTATION - - /** -* Sends the status line and headers of this response if they haven't been -* sent and initiates the process of copying data written to this response's -* body to the network. -*/ - _startAsyncProcessor: function() - { - dumpn("*** _startAsyncProcessor()"); - - // Handle cases where we're being called a second time. The former case - // happens when this is triggered both by complete() and by processAsync(), - // while the latter happens when processAsync() in conjunction with sent - // data causes abort() to be called. - if (this._asyncCopier || this._ended) - { - dumpn("*** ignoring second call to _startAsyncProcessor"); - return; - } - - // Send headers if they haven't been sent already and should be sent, then - // asynchronously continue to send the body. - if (this._headers && !this._powerSeized) - { - this._sendHeaders(); - return; - } - - this._headers = null; - this._sendBody(); - }, - - /** -* Signals that all modifications to the response status line and headers are -* complete and then sends that data over the network to the client. Once -* this method completes, a different response to the request that resulted -* in this response cannot be sent -- the only possible action in case of -* error is to abort the response and close the connection. -*/ - _sendHeaders: function() - { - dumpn("*** _sendHeaders()"); - - NS_ASSERT(this._headers); - NS_ASSERT(!this._powerSeized); - - // request-line - var statusLine = "HTTP/" + this.httpVersion + " " + - this.httpCode + " " + - this.httpDescription + "\r\n"; - - // header post-processing - - var headers = this._headers; - headers.setHeader("Connection", "close", false); - headers.setHeader("Server", "httpd.js", false); - if (!headers.hasHeader("Date")) - headers.setHeader("Date", toDateString(Date.now()), false); - - // Any response not being processed asynchronously must have an associated - // Content-Length header for reasons of backwards compatibility with the - // initial server, which fully buffered every response before sending it. - // Beyond that, however, it's good to do this anyway because otherwise it's - // impossible to test behaviors that depend on the presence or absence of a - // Content-Length header. - if (!this._processAsync) - { - dumpn("*** non-async response, set Content-Length"); - - var bodyStream = this._bodyInputStream; - var avail = bodyStream ? bodyStream.available() : 0; - - // XXX assumes stream will always report the full amount of data available - headers.setHeader("Content-Length", "" + avail, false); - } - - - // construct and send response - dumpn("*** header post-processing completed, sending response head..."); - - // request-line - var preambleData = [statusLine]; - - // headers - var headEnum = headers.enumerator; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - var values = headers.getHeaderValues(fieldName); - for (var i = 0, sz = values.length; i < sz; i++) - preambleData.push(fieldName + ": " + values[i] + "\r\n"); - } - - // end request-line/headers - preambleData.push("\r\n"); - - var preamble = preambleData.join(""); - - var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null); - responseHeadPipe.outputStream.write(preamble, preamble.length); - - var response = this; - var copyObserver = - { - onStartRequest: function(request, cx) - { - dumpn("*** preamble copying started"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** preamble copying complete " + - "[status=0x" + statusCode.toString(16) + "]"); - - if (!components.isSuccessCode(statusCode)) - { - dumpn("!!! header copying problems: non-success statusCode, " + - "ending response"); - - response.end(); - } - else - { - response._sendBody(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - var headerCopier = this._asyncCopier = - new WriteThroughCopier(responseHeadPipe.inputStream, - this._connection.output, - copyObserver, null); - - responseHeadPipe.outputStream.close(); - - // Forbid setting any more headers or modifying the request line. - this._headers = null; - }, - - /** -* Asynchronously writes the body of the response (or the entire response, if -* seizePower() has been called) to the network. -*/ - _sendBody: function() - { - dumpn("*** _sendBody"); - - NS_ASSERT(!this._headers, "still have headers around but sending body?"); - - // If no body data was written, we're done - if (!this._bodyInputStream) - { - dumpn("*** empty body, response finished"); - this.end(); - return; - } - - var response = this; - var copyObserver = - { - onStartRequest: function(request, context) - { - dumpn("*** onStartRequest"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]"); - - if (statusCode === Cr.NS_BINDING_ABORTED) - { - dumpn("*** terminating copy observer without ending the response"); - } - else - { - if (!components.isSuccessCode(statusCode)) - dumpn("*** WARNING: non-success statusCode in onStopRequest"); - - response.end(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - dumpn("*** starting async copier of body data..."); - this._asyncCopier = - new WriteThroughCopier(this._bodyInputStream, this._connection.output, - copyObserver, null); - }, - - /** Ensures that this hasn't been ended. */ - _ensureAlive: function() - { - NS_ASSERT(!this._ended, "not handling response lifetime correctly"); - } -}; - -/** -* Size of the segments in the buffer used in storing response data and writing -* it to the socket. -*/ -Response.SEGMENT_SIZE = 8192; - -/** Serves double duty in WriteThroughCopier implementation. */ -function notImplemented() -{ - throw Cr.NS_ERROR_NOT_IMPLEMENTED; -} - -/** Returns true iff the given exception represents stream closure. */ -function streamClosed(e) -{ - return e === Cr.NS_BASE_STREAM_CLOSED || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED); -} - -/** Returns true iff the given exception represents a blocked stream. */ -function wouldBlock(e) -{ - return e === Cr.NS_BASE_STREAM_WOULD_BLOCK || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK); -} - -/** -* Copies data from source to sink as it becomes available, when that data can -* be written to sink without blocking. -* -* @param source : nsIAsyncInputStream -* the stream from which data is to be read -* @param sink : nsIAsyncOutputStream -* the stream to which data is to be copied -* @param observer : nsIRequestObserver -* an observer which will be notified when the copy starts and finishes -* @param context : nsISupports -* context passed to observer when notified of start/stop -* @throws NS_ERROR_NULL_POINTER -* if source, sink, or observer are null -*/ -function WriteThroughCopier(source, sink, observer, context) -{ - if (!source || !sink || !observer) - throw Cr.NS_ERROR_NULL_POINTER; - - /** Stream from which data is being read. */ - this._source = source; - - /** Stream to which data is being written. */ - this._sink = sink; - - /** Observer watching this copy. */ - this._observer = observer; - - /** Context for the observer watching this. */ - this._context = context; - - /** -* True iff this is currently being canceled (cancel has been called, the -* callback may not yet have been made). -*/ - this._canceled = false; - - /** -* False until all data has been read from input and written to output, at -* which point this copy is completed and cancel() is asynchronously called. -*/ - this._completed = false; - - /** Required by nsIRequest, meaningless. */ - this.loadFlags = 0; - /** Required by nsIRequest, meaningless. */ - this.loadGroup = null; - /** Required by nsIRequest, meaningless. */ - this.name = "response-body-copy"; - - /** Status of this request. */ - this.status = Cr.NS_OK; - - /** Arrays of byte strings waiting to be written to output. */ - this._pendingData = []; - - // start copying - try - { - observer.onStartRequest(this, context); - this._waitToReadData(); - this._waitForSinkClosure(); - } - catch (e) - { - dumpn("!!! error starting copy: " + e + - ("lineNumber" in e ? ", line " + e.lineNumber : "")); - dumpn(e.stack); - this.cancel(Cr.NS_ERROR_UNEXPECTED); - } -} -WriteThroughCopier.prototype = -{ - /* nsISupports implementation */ - - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIInputStreamCallback) || - iid.equals(Ci.nsIOutputStreamCallback) || - iid.equals(Ci.nsIRequest) || - iid.equals(Ci.nsISupports)) - { - return this; - } - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NSIINPUTSTREAMCALLBACK - - /** -* Receives a more-data-in-input notification and writes the corresponding -* data to the output. -* -* @param input : nsIAsyncInputStream -* the input stream on whose data we have been waiting -*/ - onInputStreamReady: function(input) - { - if (this._source === null) - return; - - dumpn("*** onInputStreamReady"); - - // - // Ordinarily we'll read a non-zero amount of data from input, queue it up - // to be written and then wait for further callbacks. The complications in - // this method are the cases where we deviate from that behavior when errors - // occur or when copying is drawing to a finish. - // - // The edge cases when reading data are: - // - // Zero data is read - // If zero data was read, we're at the end of available data, so we can - // should stop reading and move on to writing out what we have (or, if - // we've already done that, onto notifying of completion). - // A stream-closed exception is thrown - // This is effectively a less kind version of zero data being read; the - // only difference is that we notify of completion with that result - // rather than with NS_OK. - // Some other exception is thrown - // This is the least kind result. We don't know what happened, so we - // act as though the stream closed except that we notify of completion - // with the result NS_ERROR_UNEXPECTED. - // - - var bytesWanted = 0, bytesConsumed = -1; - try - { - input = new BinaryInputStream(input); - - bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE); - dumpn("*** input wanted: " + bytesWanted); - - if (bytesWanted > 0) - { - var data = input.readByteArray(bytesWanted); - bytesConsumed = data.length; - this._pendingData.push(String.fromCharCode.apply(String, data)); - } - - dumpn("*** " + bytesConsumed + " bytes read"); - - // Handle the zero-data edge case in the same place as all other edge - // cases are handled. - if (bytesWanted === 0) - throw Cr.NS_BASE_STREAM_CLOSED; - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** input stream closed"); - e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED; - } - else - { - dumpn("!!! unexpected error reading from input, canceling: " + e); - e = Cr.NS_ERROR_UNEXPECTED; - } - - this._doneReadingSource(e); - return; - } - - var pendingData = this._pendingData; - - NS_ASSERT(bytesConsumed > 0); - NS_ASSERT(pendingData.length > 0, "no pending data somehow?"); - NS_ASSERT(pendingData[pendingData.length - 1].length > 0, - "buffered zero bytes of data?"); - - NS_ASSERT(this._source !== null); - - // Reading has gone great, and we've gotten data to write now. What if we - // don't have a place to write that data, because output went away just - // before this read? Drop everything on the floor, including new data, and - // cancel at this point. - if (this._sink === null) - { - pendingData.length = 0; - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we've read the data, and we know we have a place to write it. We - // need to queue up the data to be written, but *only* if none is queued - // already -- if data's already queued, the code that actually writes the - // data will make sure to wait on unconsumed pending data. - try - { - if (pendingData.length === 1) - this._waitToWriteData(); - } - catch (e) - { - dumpn("!!! error waiting to write data just read, swallowing and " + - "writing only what we already have: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Whee! We successfully read some data, and it's successfully queued up to - // be written. All that remains now is to wait for more data to read. - try - { - this._waitToReadData(); - } - catch (e) - { - dumpn("!!! error waiting to read more data: " + e); - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - } - }, - - - // NSIOUTPUTSTREAMCALLBACK - - /** -* Callback when data may be written to the output stream without blocking, or -* when the output stream has been closed. -* -* @param output : nsIAsyncOutputStream -* the output stream on whose writability we've been waiting, also known as -* this._sink -*/ - onOutputStreamReady: function(output) - { - if (this._sink === null) - return; - - dumpn("*** onOutputStreamReady"); - - var pendingData = this._pendingData; - if (pendingData.length === 0) - { - // There's no pending data to write. The only way this can happen is if - // we're waiting on the output stream's closure, so we can respond to a - // copying failure as quickly as possible (rather than waiting for data to - // be available to read and then fail to be copied). Therefore, we must - // be done now -- don't bother to attempt to write anything and wrap - // things up. - dumpn("!!! output stream closed prematurely, ending copy"); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - - NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?"); - - // - // Write out the first pending quantum of data. The possible errors here - // are: - // - // The write might fail because we can't write that much data - // Okay, we've written what we can now, so re-queue what's left and - // finish writing it out later. - // The write failed because the stream was closed - // Discard pending data that we can no longer write, stop reading, and - // signal that copying finished. - // Some other error occurred. - // Same as if the stream were closed, but notify with the status - // NS_ERROR_UNEXPECTED so the observer knows something was wonky. - // - - try - { - var quantum = pendingData[0]; - - // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on - // undefined behavior! We're only using this because writeByteArray - // is unusably broken for asynchronous output streams; see bug 532834 - // for details. - var bytesWritten = output.write(quantum, quantum.length); - if (bytesWritten === quantum.length) - pendingData.shift(); - else - pendingData[0] = quantum.substring(bytesWritten); - - dumpn("*** wrote " + bytesWritten + " bytes of data"); - } - catch (e) - { - if (wouldBlock(e)) - { - NS_ASSERT(pendingData.length > 0, - "stream-blocking exception with no data to write?"); - NS_ASSERT(pendingData[0].length > 0, - "stream-blocking exception with empty quantum?"); - this._waitToWriteData(); - return; - } - - if (streamClosed(e)) - dumpn("!!! output stream prematurely closed, signaling error..."); - else - dumpn("!!! unknown error: " + e + ", quantum=" + quantum); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // The day is ours! Quantum written, now let's see if we have more data - // still to write. - try - { - if (pendingData.length > 0) - { - this._waitToWriteData(); - return; - } - } - catch (e) - { - dumpn("!!! unexpected error waiting to write pending data: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we have no more pending data to write -- but might we get more in - // the future? - if (this._source !== null) - { - /* -* If we might, then wait for the output stream to be closed. (We wait -* only for closure because we have no data to write -- and if we waited -* for a specific amount of data, we would get repeatedly notified for no -* reason if over time the output stream permitted more and more data to -* be written to it without blocking.) -*/ - this._waitForSinkClosure(); - } - else - { - /* -* On the other hand, if we can't have more data because the input -* stream's gone away, then it's time to notify of copy completion. -* Victory! -*/ - this._sink = null; - this._cancelOrDispatchCancelCallback(Cr.NS_OK); - } - }, - - - // NSIREQUEST - - /** Returns true if the cancel observer hasn't been notified yet. */ - isPending: function() - { - return !this._completed; - }, - - /** Not implemented, don't use! */ - suspend: notImplemented, - /** Not implemented, don't use! */ - resume: notImplemented, - - /** -* Cancels data reading from input, asynchronously writes out any pending -* data, and causes the observer to be notified with the given error code when -* all writing has finished. -* -* @param status : nsresult -* the status to pass to the observer when data copying has been canceled -*/ - cancel: function(status) - { - dumpn("*** cancel(" + status.toString(16) + ")"); - - if (this._canceled) - { - dumpn("*** suppressing a late cancel"); - return; - } - - this._canceled = true; - this.status = status; - - // We could be in the middle of absolutely anything at this point. Both - // input and output might still be around, we might have pending data to - // write, and in general we know nothing about the state of the world. We - // therefore must assume everything's in progress and take everything to its - // final steady state (or so far as it can go before we need to finish - // writing out remaining data). - - this._doneReadingSource(status); - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Stop reading input if we haven't already done so, passing e as the status -* when closing the stream, and kick off a copy-completion notice if no more -* data remains to be written. -* -* @param e : nsresult -* the status to be used when closing the input stream -*/ - _doneReadingSource: function(e) - { - dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")"); - - this._finishSource(e); - if (this._pendingData.length === 0) - this._sink = null; - else - NS_ASSERT(this._sink !== null, "null output?"); - - // If we've written out all data read up to this point, then it's time to - // signal completion. - if (this._sink === null) - { - NS_ASSERT(this._pendingData.length === 0, "pending data still?"); - this._cancelOrDispatchCancelCallback(e); - } - }, - - /** -* Stop writing output if we haven't already done so, discard any data that -* remained to be sent, close off input if it wasn't already closed, and kick -* off a copy-completion notice. -* -* @param e : nsresult -* the status to be used when closing input if it wasn't already closed -*/ - _doneWritingToSink: function(e) - { - dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")"); - - this._pendingData.length = 0; - this._sink = null; - this._doneReadingSource(e); - }, - - /** -* Completes processing of this copy: either by canceling the copy if it -* hasn't already been canceled using the provided status, or by dispatching -* the cancel callback event (with the originally provided status, of course) -* if it already has been canceled. -* -* @param status : nsresult -* the status code to use to cancel this, if this hasn't already been -* canceled -*/ - _cancelOrDispatchCancelCallback: function(status) - { - dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")"); - - NS_ASSERT(this._source === null, "should have finished input"); - NS_ASSERT(this._sink === null, "should have finished output"); - NS_ASSERT(this._pendingData.length === 0, "should have no pending data"); - - if (!this._canceled) - { - this.cancel(status); - return; - } - - var self = this; - var event = - { - run: function() - { - dumpn("*** onStopRequest async callback"); - - self._completed = true; - try - { - self._observer.onStopRequest(self, self._context, self.status); - } - catch (e) - { - NS_ASSERT(false, - "how are we throwing an exception here? we control " + - "all the callers! " + e); - } - } - }; - - gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL); - }, - - /** -* Kicks off another wait for more data to be available from the input stream. -*/ - _waitToReadData: function() - { - dumpn("*** _waitToReadData"); - this._source.asyncWait(this, 0, Response.SEGMENT_SIZE, - gThreadManager.mainThread); - }, - - /** -* Kicks off another wait until data can be written to the output stream. -*/ - _waitToWriteData: function() - { - dumpn("*** _waitToWriteData"); - - var pendingData = this._pendingData; - NS_ASSERT(pendingData.length > 0, "no pending data to write?"); - NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?"); - - this._sink.asyncWait(this, 0, pendingData[0].length, - gThreadManager.mainThread); - }, - - /** -* Kicks off a wait for the sink to which data is being copied to be closed. -* We wait for stream closure when we don't have any data to be copied, rather -* than waiting to write a specific amount of data. We can't wait to write -* data because the sink might be infinitely writable, and if no data appears -* in the source for a long time we might have to spin quite a bit waiting to -* write, waiting to write again, &c. Waiting on stream closure instead means -* we'll get just one notification if the sink dies. Note that when data -* starts arriving from the sink we'll resume waiting for data to be written, -* dropping this closure-only callback entirely. -*/ - _waitForSinkClosure: function() - { - dumpn("*** _waitForSinkClosure"); - - this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0, - gThreadManager.mainThread); - }, - - /** -* Closes input with the given status, if it hasn't already been closed; -* otherwise a no-op. -* -* @param status : nsresult -* status code use to close the source stream if necessary -*/ - _finishSource: function(status) - { - dumpn("*** _finishSource(" + status.toString(16) + ")"); - - if (this._source !== null) - { - this._source.closeWithStatus(status); - this._source = null; - } - } -}; - - -/** -* A container for utility functions used with HTTP headers. -*/ -const headerUtils = -{ - /** -* Normalizes fieldName (by converting it to lowercase) and ensures it is a -* valid header field name (although not necessarily one specified in RFC -* 2616). -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not match the field-name production in RFC 2616 -* @returns string -* fieldName converted to lowercase if it is a valid header, for characters -* where case conversion is possible -*/ - normalizeFieldName: function(fieldName) - { - if (fieldName == "") - throw Cr.NS_ERROR_INVALID_ARG; - - for (var i = 0, sz = fieldName.length; i < sz; i++) - { - if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)]) - { - dumpn(fieldName + " is not a valid header field name!"); - throw Cr.NS_ERROR_INVALID_ARG; - } - } - - return fieldName.toLowerCase(); - }, - - /** -* Ensures that fieldValue is a valid header field value (although not -* necessarily as specified in RFC 2616 if the corresponding field name is -* part of the HTTP protocol), normalizes the value if it is, and -* returns the normalized value. -* -* @param fieldValue : string -* a value to be normalized as an HTTP header field value -* @throws NS_ERROR_INVALID_ARG -* if fieldValue does not match the field-value production in RFC 2616 -* @returns string -* fieldValue as a normalized HTTP header field value -*/ - normalizeFieldValue: function(fieldValue) - { - // field-value = *( field-content | LWS ) - // field-content = - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // - // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) - // qdtext = > - // quoted-pair = "\" CHAR - // CHAR = - - // Any LWS that occurs between field-content MAY be replaced with a single - // SP before interpreting the field value or forwarding the message - // downstream (section 4.2); we replace 1*LWS with a single SP - var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " "); - - // remove leading/trailing LWS (which has been converted to SP) - val = val.replace(/^ +/, "").replace(/ +$/, ""); - - // that should have taken care of all CTLs, so val should contain no CTLs - for (var i = 0, len = val.length; i < len; i++) - if (isCTL(val.charCodeAt(i))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly - // normalize, however, so this can be construed as a tightening of the - // spec and not entirely as a bug - return val; - } -}; - - - -/** -* Converts the given string into a string which is safe for use in an HTML -* context. -* -* @param str : string -* the string to make HTML-safe -* @returns string -* an HTML-safe version of str -*/ -function htmlEscape(str) -{ - // this is naive, but it'll work - var s = ""; - for (var i = 0; i < str.length; i++) - s += "&#" + str.charCodeAt(i) + ";"; - return s; -} - - -/** -* Constructs an object representing an HTTP version (see section 3.1). -* -* @param versionString -* a string of the form "#.#", where # is an non-negative decimal integer with -* or without leading zeros -* @throws -* if versionString does not specify a valid HTTP version number -*/ -function nsHttpVersion(versionString) -{ - var matches = /^(\d+)\.(\d+)$/.exec(versionString); - if (!matches) - throw "Not a valid HTTP version!"; - - /** The major version number of this, as a number. */ - this.major = parseInt(matches[1], 10); - - /** The minor version number of this, as a number. */ - this.minor = parseInt(matches[2], 10); - - if (isNaN(this.major) || isNaN(this.minor) || - this.major < 0 || this.minor < 0) - throw "Not a valid HTTP version!"; -} -nsHttpVersion.prototype = -{ - /** -* Returns the standard string representation of the HTTP version represented -* by this (e.g., "1.1"). -*/ - toString: function () - { - return this.major + "." + this.minor; - }, - - /** -* Returns true if this represents the same HTTP version as otherVersion, -* false otherwise. -* -* @param otherVersion : nsHttpVersion -* the version to compare against this -*/ - equals: function (otherVersion) - { - return this.major == otherVersion.major && - this.minor == otherVersion.minor; - }, - - /** True if this >= otherVersion, false otherwise. */ - atLeast: function(otherVersion) - { - return this.major > otherVersion.major || - (this.major == otherVersion.major && - this.minor >= otherVersion.minor); - } -}; - -nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0"); -nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1"); - - -/** -* An object which stores HTTP headers for a request or response. -* -* Note that since headers are case-insensitive, this object converts headers to -* lowercase before storing them. This allows the getHeader and hasHeader -* methods to work correctly for any case of a header, but it means that the -* values returned by .enumerator may not be equal case-sensitively to the -* values passed to setHeader when adding headers to this. -*/ -function nsHttpHeaders() -{ - /** -* A hash of headers, with header field names as the keys and header field -* values as the values. Header field names are case-insensitive, but upon -* insertion here they are converted to lowercase. Header field values are -* normalized upon insertion to contain no leading or trailing whitespace. -* -* Note also that per RFC 2616, section 4.2, two headers with the same name in -* a message may be treated as one header with the same field name and a field -* value consisting of the separate field values joined together with a "," in -* their original order. This hash stores multiple headers with the same name -* in this manner. -*/ - this._headers = {}; -} -nsHttpHeaders.prototype = -{ - /** -* Sets the header represented by name and value in this. -* -* @param name : string -* the header name -* @param value : string -* the header value -* @throws NS_ERROR_INVALID_ARG -* if name or value is not a valid header component -*/ - setHeader: function(fieldName, fieldValue, merge) - { - var name = headerUtils.normalizeFieldName(fieldName); - var value = headerUtils.normalizeFieldValue(fieldValue); - - // The following three headers are stored as arrays because their real-world - // syntax prevents joining individual headers into a single header using - // ",". See also - if (merge && name in this._headers) - { - if (name === "www-authenticate" || - name === "proxy-authenticate" || - name === "set-cookie") - { - this._headers[name].push(value); - } - else - { - this._headers[name][0] += "," + value; - NS_ASSERT(this._headers[name].length === 1, - "how'd a non-special header have multiple values?") - } - } - else - { - this._headers[name] = [value]; - } - }, - - /** -* Returns the value for the header specified by this. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns string -* the field value for the given header, possibly with non-semantic changes -* (i.e., leading/trailing whitespace stripped, whitespace runs replaced -* with spaces, etc.) at the option of the implementation; multiple -* instances of the header will be combined with a comma, except for -* the three headers noted in the description of getHeaderValues -*/ - getHeader: function(fieldName) - { - return this.getHeaderValues(fieldName).join("\n"); - }, - - /** -* Returns the value for the header specified by fieldName as an array. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns [string] -* an array of all the header values in this for the given -* header name. Header values will generally be collapsed -* into a single header by joining all header values together -* with commas, but certain headers (Proxy-Authenticate, -* WWW-Authenticate, and Set-Cookie) violate the HTTP spec -* and cannot be collapsed in this manner. For these headers -* only, the returned array may contain multiple elements if -* that header has been added more than once. -*/ - getHeaderValues: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - - if (name in this._headers) - return this._headers[name]; - else - throw Cr.NS_ERROR_NOT_AVAILABLE; - }, - - /** -* Returns true if a header with the given field name exists in this, false -* otherwise. -* -* @param fieldName : string -* the field name whose existence is to be determined in this -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @returns boolean -* true if the header's present, false otherwise -*/ - hasHeader: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - return (name in this._headers); - }, - - /** -* Returns a new enumerator over the field names of the headers in this, as -* nsISupportsStrings. The names returned will be in lowercase, regardless of -* how they were input using setHeader (header names are case-insensitive per -* RFC 2616). -*/ - get enumerator() - { - var headers = []; - for (var i in this._headers) - { - var supports = new SupportsString(); - supports.data = i; - headers.push(supports); - } - - return new nsSimpleEnumerator(headers); - } -}; - - -/** -* Constructs an nsISimpleEnumerator for the given array of items. -* -* @param items : Array -* the items, which must all implement nsISupports -*/ -function nsSimpleEnumerator(items) -{ - this._items = items; - this._nextIndex = 0; -} -nsSimpleEnumerator.prototype = -{ - hasMoreElements: function() - { - return this._nextIndex < this._items.length; - }, - getNext: function() - { - if (!this.hasMoreElements()) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - return this._items[this._nextIndex++]; - }, - QueryInterface: function(aIID) - { - if (Ci.nsISimpleEnumerator.equals(aIID) || - Ci.nsISupports.equals(aIID)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } -}; - - -/** -* A representation of the data in an HTTP request. -* -* @param port : uint -* the port on which the server receiving this request runs -*/ -function Request(port) -{ - /** Method of this request, e.g. GET or POST. */ - this._method = ""; - - /** Path of the requested resource; empty paths are converted to '/'. */ - this._path = ""; - - /** Query string, if any, associated with this request (not including '?'). */ - this._queryString = ""; - - /** Scheme of requested resource, usually http, always lowercase. */ - this._scheme = "http"; - - /** Hostname on which the requested resource resides. */ - this._host = undefined; - - /** Port number over which the request was received. */ - this._port = port; - - var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null); - - /** Stream from which data in this request's body may be read. */ - this._bodyInputStream = bodyPipe.inputStream; - - /** Stream to which data in this request's body is written. */ - this._bodyOutputStream = bodyPipe.outputStream; - - /** -* The headers in this request. -*/ - this._headers = new nsHttpHeaders(); - - /** -* For the addition of ad-hoc properties and new functionality without having -* to change nsIHttpRequest every time; currently lazily created, as its only -* use is in directory listings. -*/ - this._bag = null; -} -Request.prototype = -{ - // SERVER METADATA - - // - // see nsIHttpRequest.scheme - // - get scheme() - { - return this._scheme; - }, - - // - // see nsIHttpRequest.host - // - get host() - { - return this._host; - }, - - // - // see nsIHttpRequest.port - // - get port() - { - return this._port; - }, - - // REQUEST LINE - - // - // see nsIHttpRequest.method - // - get method() - { - return this._method; - }, - - // - // see nsIHttpRequest.httpVersion - // - get httpVersion() - { - return this._httpVersion.toString(); - }, - - // - // see nsIHttpRequest.path - // - get path() - { - return this._path; - }, - - // - // see nsIHttpRequest.queryString - // - get queryString() - { - return this._queryString; - }, - - // HEADERS - - // - // see nsIHttpRequest.getHeader - // - getHeader: function(name) - { - return this._headers.getHeader(name); - }, - - // - // see nsIHttpRequest.hasHeader - // - hasHeader: function(name) - { - return this._headers.hasHeader(name); - }, - - // - // see nsIHttpRequest.headers - // - get headers() - { - return this._headers.enumerator; - }, - - // - // see nsIPropertyBag.enumerator - // - get enumerator() - { - this._ensurePropertyBag(); - return this._bag.enumerator; - }, - - // - // see nsIHttpRequest.headers - // - get bodyInputStream() - { - return this._bodyInputStream; - }, - - // - // see nsIPropertyBag.getProperty - // - getProperty: function(name) - { - this._ensurePropertyBag(); - return this._bag.getProperty(name); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** Ensures a property bag has been created for ad-hoc behaviors. */ - _ensurePropertyBag: function() - { - if (!this._bag) - this._bag = new WritablePropertyBag(); - } -}; - - -// XPCOM trappings -if ("XPCOMUtils" in this && // Firefox 3.6 doesn't load XPCOMUtils in this scope for some reason... - "generateNSGetFactory" in XPCOMUtils) { - var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]); -} - - - -/** -* Creates a new HTTP server listening for loopback traffic on the given port, -* starts it, and runs the server until the server processes a shutdown request, -* spinning an event loop so that events posted by the server's socket are -* processed. -* -* This method is primarily intended for use in running this script from within -* xpcshell and running a functional HTTP server without having to deal with -* non-essential details. -* -* Note that running multiple servers using variants of this method probably -* doesn't work, simply due to how the internal event loop is spun and stopped. -* -* @note -* This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code); -* you should use this server as a component in Mozilla 1.8. -* @param port -* the port on which the server will run, or -1 if there exists no preference -* for a specific port; note that attempting to use some values for this -* parameter (particularly those below 1024) may cause this method to throw or -* may result in the server being prematurely shut down -* @param basePath -* a local directory from which requests will be served (i.e., if this is -* "/home/jwalden/" then a request to /index.html will load -* /home/jwalden/index.html); if this is omitted, only the default URLs in -* this server implementation will be functional -*/ -function server(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - // if you're running this, you probably want to see debugging info - DEBUG = true; - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", SJS_TYPE); - srv.start(port); - - var thread = gThreadManager.currentThread; - while (!srv.isStopped()) - thread.processNextEvent(true); - - // get rid of any pending requests - while (thread.hasPendingEvents()) - thread.processNextEvent(true); - - DEBUG = false; -} - -function startServerAsync(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", "sjs"); - srv.start(port); - return srv; -} - -exports.nsHttpServer = nsHttpServer; -exports.ScriptableInputStream = ScriptableInputStream; -exports.server = server; -exports.startServerAsync = startServerAsync; diff --git a/addon-sdk/source/test/addons/e10s-content/lib/main.js b/addon-sdk/source/test/addons/e10s-content/lib/main.js deleted file mode 100644 index 6ca7dc48c4..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/lib/main.js +++ /dev/null @@ -1,22 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { merge } = require('sdk/util/object'); -const { version } = require('sdk/system'); - -const SKIPPING_TESTS = { - "test skip": (assert) => assert.pass("nothing to test here") -}; - -merge(module.exports, require('./test-content-script')); -merge(module.exports, require('./test-content-worker')); -merge(module.exports, require('./test-page-worker')); - -// run e10s tests only on builds from trunk, fx-team, Nightly.. -if (!version.endsWith('a1')) { - module.exports = SKIPPING_TESTS; -} - -require('sdk/test/runner').runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js b/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js deleted file mode 100644 index c24f841b2f..0000000000 --- a/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js +++ /dev/null @@ -1,845 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const hiddenFrames = require("sdk/frame/hidden-frame"); -const { create: makeFrame } = require("sdk/frame/utils"); -const { window } = require("sdk/addon/window"); -const { Loader } = require('sdk/test/loader'); -const { URL } = require("sdk/url"); -const testURI = require("./fixtures").url("test.html"); -const testHost = URL(testURI).scheme + '://' + URL(testURI).host; - -/* - * Utility function that allow to easily run a proxy test with a clean - * new HTML document. See first unit test for usage. - */ -function createProxyTest(html, callback) { - return function (assert, done) { - let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html); - let principalLoaded = false; - - let element = makeFrame(window.document, { - nodeName: "iframe", - type: "content", - allowJavascript: true, - allowPlugins: true, - allowAuth: true, - uri: testURI - }); - - element.addEventListener("DOMContentLoaded", onDOMReady, false); - - function onDOMReady() { - // Reload frame after getting principal from `testURI` - if (!principalLoaded) { - element.setAttribute("src", url); - principalLoaded = true; - return; - } - - assert.equal(element.getAttribute("src"), url, "correct URL loaded"); - element.removeEventListener("DOMContentLoaded", onDOMReady, - false); - let xrayWindow = element.contentWindow; - let rawWindow = xrayWindow.wrappedJSObject; - - let isDone = false; - let helper = { - xrayWindow: xrayWindow, - rawWindow: rawWindow, - createWorker: function (contentScript) { - return createWorker(assert, xrayWindow, contentScript, helper.done); - }, - done: function () { - if (isDone) - return; - isDone = true; - element.parentNode.removeChild(element); - done(); - } - }; - callback(helper, assert); - } - }; -} - -function createWorker(assert, xrayWindow, contentScript, done) { - let loader = Loader(module); - let Worker = loader.require("sdk/content/worker").Worker; - let worker = Worker({ - window: xrayWindow, - contentScript: [ - 'let assert, done; new ' + function () { - assert = function assert(v, msg) { - self.port.emit("assert", {assertion:v, msg:msg}); - } - done = function done() { - self.port.emit("done"); - } - }, - contentScript - ] - }); - - worker.port.on("done", done); - worker.port.on("assert", function (data) { - assert.ok(data.assertion, data.msg); - }); - - return worker; -} - -/* Examples for the `createProxyTest` uses */ - -var html = ""; - -exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) { - // You can get access to regular `test` object in second argument of - // `createProxyTest` method: - assert.ok(helper.rawWindow.documentGlobal, - "You have access to a raw window reference via `helper.rawWindow`"); - assert.ok(!("documentGlobal" in helper.xrayWindow), - "You have access to an XrayWrapper reference via `helper.xrayWindow`"); - - // If you do not create a Worker, you have to call helper.done(), - // in order to say when your test is finished - helper.done(); -}); - -exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) { - - helper.createWorker( - "new " + function WorkerScope() { - assert(true, "You can do assertions in your content script"); - // And if you create a worker, you either have to call `done` - // from content script or helper.done() - done(); - } - ); - -}); - -exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) { - - let worker = helper.createWorker( - "new " + function WorkerScope() { - self.port.emit("foo"); - } - ); - - worker.port.on("foo", function () { - assert.pass("You can use events"); - // And terminate your test with helper.done: - helper.done(); - }); - -}); - -/* Disabled due to bug 1038432 -// Bug 714778: There was some issue around `toString` functions -// that ended up being shared between content scripts -exports["test Shared To String Proxies"] = createProxyTest("", function(helper) { - - let worker = helper.createWorker( - 'new ' + function ContentScriptScope() { - // We ensure that `toString` can't be modified so that nothing could - // leak to/from the document and between content scripts - // It only applies to JS proxies, there isn't any such issue with xrays. - //document.location.toString = function foo() {}; - document.location.toString.foo = "bar"; - assert("foo" in document.location.toString, "document.location.toString can be modified"); - assert(document.location.toString() == "data:text/html;charset=utf-8,", - "First document.location.toString()"); - self.postMessage("next"); - } - ); - worker.on("message", function () { - helper.createWorker( - 'new ' + function ContentScriptScope2() { - assert(!("foo" in document.location.toString), - "document.location.toString is different for each content script"); - assert(document.location.toString() == "data:text/html;charset=utf-8,", - "Second document.location.toString()"); - done(); - } - ); - }); -}); -*/ - -// Ensure that postMessage is working correctly across documents with an iframe -var html = ' - - diff --git a/addon-sdk/source/test/fixtures/test-iframe.js b/addon-sdk/source/test/fixtures/test-iframe.js deleted file mode 100644 index bbfadc4ff8..0000000000 --- a/addon-sdk/source/test/fixtures/test-iframe.js +++ /dev/null @@ -1,16 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var count = 0; - -setTimeout(function() { - window.addEventListener("message", function(msg) { - if (++count > 1) { - self.postMessage(msg.data); - } - else msg.source.postMessage(msg.data, '*'); - }); - - document.getElementById('inner').src = iframePath; -}, 0); diff --git a/addon-sdk/source/test/fixtures/test-message-manager.js b/addon-sdk/source/test/fixtures/test-message-manager.js deleted file mode 100644 index d647bd8fd5..0000000000 --- a/addon-sdk/source/test/fixtures/test-message-manager.js +++ /dev/null @@ -1,6 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const TEST_VALUE = 11; - diff --git a/addon-sdk/source/test/fixtures/test-net-url.txt b/addon-sdk/source/test/fixtures/test-net-url.txt deleted file mode 100644 index 9f8166e614..0000000000 --- a/addon-sdk/source/test/fixtures/test-net-url.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, ゼロ! \ No newline at end of file diff --git a/addon-sdk/source/test/fixtures/test-page-mod.html b/addon-sdk/source/test/fixtures/test-page-mod.html deleted file mode 100644 index 901abefc49..0000000000 --- a/addon-sdk/source/test/fixtures/test-page-mod.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Page Mod test - - -

Lorem ipsum dolor sit amet.

- - diff --git a/addon-sdk/source/test/fixtures/test-sidebar-addon-global.html b/addon-sdk/source/test/fixtures/test-sidebar-addon-global.html deleted file mode 100644 index 4552e3d781..0000000000 --- a/addon-sdk/source/test/fixtures/test-sidebar-addon-global.html +++ /dev/null @@ -1,15 +0,0 @@ - - -SIDEBAR TEST diff --git a/addon-sdk/source/test/fixtures/test-trusted-document.html b/addon-sdk/source/test/fixtures/test-trusted-document.html deleted file mode 100644 index c31e055cbc..0000000000 --- a/addon-sdk/source/test/fixtures/test-trusted-document.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - Worker test - - -

Lorem ipsum dolor sit amet.

- - - diff --git a/addon-sdk/source/test/fixtures/test.html b/addon-sdk/source/test/fixtures/test.html deleted file mode 100644 index 70b5e31e56..0000000000 --- a/addon-sdk/source/test/fixtures/test.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - foo - - -

bar

- - - diff --git a/addon-sdk/source/test/fixtures/testLocalXhr.json b/addon-sdk/source/test/fixtures/testLocalXhr.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/addon-sdk/source/test/fixtures/testLocalXhr.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/addon-sdk/source/test/framescript-manager/frame-script.js b/addon-sdk/source/test/framescript-manager/frame-script.js deleted file mode 100644 index de9bb83850..0000000000 --- a/addon-sdk/source/test/framescript-manager/frame-script.js +++ /dev/null @@ -1,13 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const {onPing} = require("./pong"); - -exports.onPing = onPing; - -exports.onInit = (context) => { - context.sendAsyncMessage("framescript-manager/ready", {state: "ready"}); - context.addMessageListener("framescript-manager/ping", exports.onPing); -}; diff --git a/addon-sdk/source/test/framescript-manager/pong.js b/addon-sdk/source/test/framescript-manager/pong.js deleted file mode 100644 index b7fb7e0777..0000000000 --- a/addon-sdk/source/test/framescript-manager/pong.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -exports.onPing = message => - message.target.sendAsyncMessage("framescript-manager/pong", message.data); diff --git a/addon-sdk/source/test/framescript-util/frame-script.js b/addon-sdk/source/test/framescript-util/frame-script.js deleted file mode 100644 index 2a4a2284c1..0000000000 --- a/addon-sdk/source/test/framescript-util/frame-script.js +++ /dev/null @@ -1,21 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { windowToMessageManager, nodeToMessageManager } = require("framescript/util"); - - -const onInit = (context) => { - context.addMessageListener("framescript-util/window/request", _ => { - windowToMessageManager(context.content.window).sendAsyncMessage( - "framescript-util/window/response", {window: true}); - }); - - context.addMessageListener("framescript-util/node/request", message => { - const node = context.content.document.querySelector(message.data); - nodeToMessageManager(node).sendAsyncMessage( - "framescript-util/node/response", {node: true}); - }); -}; -exports.onInit = onInit; diff --git a/addon-sdk/source/test/jetpack-package.ini b/addon-sdk/source/test/jetpack-package.ini deleted file mode 100644 index fa93b9c2fd..0000000000 --- a/addon-sdk/source/test/jetpack-package.ini +++ /dev/null @@ -1,179 +0,0 @@ -[DEFAULT] -support-files = - buffers/** - commonjs-test-adapter/** - context-menu/** - event/** - fixtures.js - fixtures/** - fixtures/native-addon-test.xpi - fixtures/native-overrides-test.xpi - framescript-manager/** - framescript-util/** - lib/** - loader/** - modules/** - page-mod/** - path/** - private-browsing/** - querystring/** - sidebar/** - tabs/** - test-context-menu.html - traits/** - util.js - windows/** - zip/** -generated-files = - fixtures/native-addon-test.xpi - fixtures/native-overrides-test.xpi - -[test-addon-bootstrap.js] -[test-addon-extras.js] -[test-addon-installer.js] -[test-addon-window.js] -[test-api-utils.js] -[test-array.js] -[test-base64.js] -[test-bootstrap.js] -[test-browser-events.js] -[test-buffer.js] -[test-byte-streams.js] -[test-child_process.js] -[test-chrome.js] -[test-clipboard.js] -subsuite = clipboard -[test-collection.js] -[test-commonjs-test-adapter.js] -[test-content-events.js] -[test-content-script.js] -[test-content-sync-worker.js] -[test-content-worker.js] -[test-context-menu.js] -[test-context-menu@2.js] -[test-cuddlefish.js] -# Cuddlefish loader is unsupported -skip-if = true -[test-deprecate.js] -[test-dev-panel.js] -[test-diffpatcher.js] -[test-dispatcher.js] -[test-disposable.js] -[test-dom.js] -[test-environment.js] -[test-event-core.js] -[test-event-dom.js] -[test-event-target.js] -[test-event-utils.js] -[test-file.js] -[test-frame-utils.js] -[test-framescript-manager.js] -[test-framescript-util.js] -[test-fs.js] -[test-functional.js] -[test-globals.js] -[test-heritage.js] -[test-hidden-frame.js] -[test-host-events.js] -[test-hotkeys.js] -[test-httpd.js] -[test-indexed-db.js] -[test-jetpack-id.js] -[test-keyboard-observer.js] -[test-keyboard-utils.js] -[test-l10n-locale.js] -[test-l10n-plural-rules.js] -[test-lang-type.js] -[test-libxul.js] -[test-list.js] -[test-loader.js] -[test-match-pattern.js] -[test-method.js] -[test-module.js] -[test-modules.js] -[test-mozilla-toolkit-versioning.js] -[test-mpl2-license-header.js] -skip-if = true -[test-namespace.js] -[test-native-loader.js] -[test-native-options.js] -[test-net-url.js] -[test-node-os.js] -[test-notifications.js] -[test-object.js] -[test-observers.js] -[test-page-mod-debug.js] -[test-page-mod.js] -[test-page-worker.js] -[test-panel.js] -[test-passwords-utils.js] -[test-passwords.js] -[test-path.js] -[test-plain-text-console.js] -[test-preferences-service.js] -[test-preferences-target.js] -[test-private-browsing.js] -[test-promise.js] -[test-querystring.js] -[test-reference.js] -[test-request.js] -[test-require.js] -[test-rules.js] -[test-sandbox.js] -[test-selection.js] -[test-self.js] -[test-sequence.js] -[test-set-exports.js] -[test-shared-require.js] -[test-simple-prefs.js] -[test-simple-storage.js] -[test-system-events.js] -[test-system-input-output.js] -[test-system-runtime.js] -[test-system-startup.js] -[test-system.js] -[test-tab-events.js] -[test-tab-observer.js] -[test-tab-utils.js] -[test-tab.js] -[test-tabs-common.js] -[test-tabs.js] -[test-test-addon-file.js] -[test-test-assert.js] -[test-test-loader.js] -[test-test-memory.js] -[test-test-utils-async.js] -[test-test-utils-generator.js] -[test-test-utils-sync.js] -[test-test-utils.js] -[test-text-streams.js] -[test-timer.js] -[test-traceback.js] -[test-ui-action-button.js] -skip-if = debug || asan # Bug 1208727 -[test-ui-frame.js] -[test-ui-id.js] -[test-ui-sidebar-private-browsing.js] -[test-ui-sidebar.js] -[test-ui-toggle-button.js] -[test-ui-toolbar.js] -[test-unit-test-finder.js] -[test-unit-test.js] -[test-unload.js] -[test-unsupported-skip.js] -# Bug 1037235 -skip-if = true -[test-uri-resource.js] -[test-url.js] -[test-uuid.js] -[test-weak-set.js] -[test-window-events.js] -[test-window-observer.js] -[test-window-utils-private-browsing.js] -[test-window-utils.js] -[test-window-utils2.js] -[test-windows-common.js] -[test-windows.js] -[test-xhr.js] -[test-xpcom.js] -[test-xul-app.js] diff --git a/addon-sdk/source/test/leak/jetpack-package.ini b/addon-sdk/source/test/leak/jetpack-package.ini deleted file mode 100644 index 0632bdc870..0000000000 --- a/addon-sdk/source/test/leak/jetpack-package.ini +++ /dev/null @@ -1,7 +0,0 @@ -[DEFAULT] -support-files = - leak-utils.js - -[test-leak-window-events.js] -[test-leak-event-dom-closed-window.js] -[test-leak-tab-events.js] diff --git a/addon-sdk/source/test/leak/leak-utils.js b/addon-sdk/source/test/leak/leak-utils.js deleted file mode 100644 index e01255ec8e..0000000000 --- a/addon-sdk/source/test/leak/leak-utils.js +++ /dev/null @@ -1,80 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cu, Ci } = require("chrome"); -const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); -const { SelfSupportBackend } = Cu.import("resource:///modules/SelfSupportBackend.jsm", {}); -const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports; - -// Adapted from the SpecialPowers.exactGC() code. We don't have a -// window to operate on so we cannot use the exact same logic. We -// use 6 GC iterations here as that is what is needed to clean up -// the windows we have tested with. -function gc() { - return new Promise(resolve => { - Cu.forceGC(); - Cu.forceCC(); - let count = 0; - function genGCCallback() { - Cu.forceCC(); - return function() { - if (++count < 5) { - Cu.schedulePreciseGC(genGCCallback()); - } else { - resolve(); - } - } - } - - Cu.schedulePreciseGC(genGCCallback()); - }); -} - -// Execute the given test function and verify that we did not leak windows -// in the process. The test function must return a promise or be a generator. -// If the promise is resolved, or generator completes, with an sdk loader -// object then it will be unloaded after the memory measurements. -exports.asyncWindowLeakTest = function*(assert, asyncTestFunc) { - - // SelfSupportBackend periodically tries to open windows. This can - // mess up our window leak detection below, so turn it off. - SelfSupportBackend.uninit(); - - // Wait for the browser to finish loading. - yield Startup.onceInitialized; - - // Track windows that are opened in an array of weak references. - let weakWindows = []; - function windowObserver(subject, topic) { - let supportsWeak = subject.QueryInterface(Ci.nsISupportsWeakReference); - if (supportsWeak) { - weakWindows.push(Cu.getWeakReference(supportsWeak)); - } - } - Services.obs.addObserver(windowObserver, "domwindowopened", false); - - // Execute the body of the test. - let testLoader = yield asyncTestFunc(assert); - - // Stop tracking new windows and attempt to GC any resources allocated - // by the test body. - Services.obs.removeObserver(windowObserver, "domwindowopened", false); - yield gc(); - - // Check to see if any of the windows we saw survived the GC. We consider - // these leaks. - assert.ok(weakWindows.length > 0, "should see at least one new window"); - for (let i = 0; i < weakWindows.length; ++i) { - assert.equal(weakWindows[i].get(), null, "window " + i + " should be GC'd"); - } - - // Finally, unload the test body's loader if it provided one. We do this - // after our leak detection to avoid free'ing things on unload. Users - // don't tend to unload their addons very often, so we want to find leaks - // that happen while addons are in use. - if (testLoader) { - testLoader.unload(); - } -} diff --git a/addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js b/addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js deleted file mode 100644 index c398462ab4..0000000000 --- a/addon-sdk/source/test/leak/test-leak-event-dom-closed-window.js +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { asyncWindowLeakTest } = require("./leak-utils"); -const { Loader } = require('sdk/test/loader'); -const openWindow = require("sdk/window/utils").open; - -exports["test sdk/event/dom does not leak when attached to closed window"] = function*(assert) { - yield asyncWindowLeakTest(assert, _ => { - return new Promise(resolve => { - let loader = Loader(module); - let { open } = loader.require('sdk/event/dom'); - let w = openWindow(); - w.addEventListener("DOMWindowClose", function windowClosed(evt) { - w.removeEventListener("DOMWindowClose", windowClosed); - // The sdk/event/dom module tries to clean itself up when DOMWindowClose - // is fired. Verify that it doesn't leak if its attached to an - // already closed window either. (See bug 1268898.) - open(w.document, "TestEvent1"); - resolve(loader); - }); - w.close(); - }); - }); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/leak/test-leak-tab-events.js b/addon-sdk/source/test/leak/test-leak-tab-events.js deleted file mode 100644 index 4266c04fc0..0000000000 --- a/addon-sdk/source/test/leak/test-leak-tab-events.js +++ /dev/null @@ -1,46 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { asyncWindowLeakTest } = require("./leak-utils"); -const { Loader } = require('sdk/test/loader'); -const openWindow = require("sdk/window/utils").open; - -exports["test sdk/tab/events does not leak new window"] = function*(assert) { - yield asyncWindowLeakTest(assert, _ => { - return new Promise(resolve => { - let loader = Loader(module); - let { events } = loader.require('sdk/tab/events'); - let w = openWindow(); - w.addEventListener("load", function windowLoaded(evt) { - w.removeEventListener("load", windowLoaded); - w.addEventListener("DOMWindowClose", function windowClosed(evt) { - w.removeEventListener("DOMWindowClose", windowClosed); - resolve(loader); - }); - w.close(); - }); - }); - }); -} - -exports["test sdk/tab/events does not leak when attached to existing window"] = function*(assert) { - yield asyncWindowLeakTest(assert, _ => { - return new Promise(resolve => { - let loader = Loader(module); - let w = openWindow(); - w.addEventListener("load", function windowLoaded(evt) { - w.removeEventListener("load", windowLoaded); - let { events } = loader.require('sdk/tab/events'); - w.addEventListener("DOMWindowClose", function windowClosed(evt) { - w.removeEventListener("DOMWindowClose", windowClosed); - resolve(loader); - }); - w.close(); - }); - }); - }); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/leak/test-leak-window-events.js b/addon-sdk/source/test/leak/test-leak-window-events.js deleted file mode 100644 index ceb20f4755..0000000000 --- a/addon-sdk/source/test/leak/test-leak-window-events.js +++ /dev/null @@ -1,65 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -// Opening new windows in Fennec causes issues -module.metadata = { - engines: { - 'Firefox': '*' - } -}; - -const { asyncWindowLeakTest } = require("./leak-utils.js"); -const { Loader } = require("sdk/test/loader"); -const { open } = require("sdk/window/utils"); - -exports["test window/events for leaks"] = function*(assert) { - yield asyncWindowLeakTest(assert, _ => { - return new Promise((resolve, reject) => { - let loader = Loader(module); - let { events } = loader.require("sdk/window/events"); - let { on, off } = loader.require("sdk/event/core"); - - on(events, "data", function handler(e) { - try { - if (e.type === "load") { - e.target.close(); - } - else if (e.type === "close") { - off(events, "data", handler); - - // Let asyncWindowLeakTest call loader.unload() after the - // leak check. - resolve(loader); - } - } catch (e) { - reject(e); - } - }); - - // Open a window. This will trigger our data events. - open(); - }); - }); -}; - -exports["test window/events for leaks with existing window"] = function*(assert) { - yield asyncWindowLeakTest(assert, _ => { - return new Promise((resolve, reject) => { - let loader = Loader(module); - let w = open(); - w.addEventListener("load", function windowLoaded(evt) { - w.removeEventListener("load", windowLoaded); - let { events } = loader.require("sdk/window/events"); - w.addEventListener("DOMWindowClose", function windowClosed(evt) { - w.removeEventListener("DOMWindowClose", windowClosed); - resolve(loader); - }); - w.close(); - }); - }); - }); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/lib/httpd.js b/addon-sdk/source/test/lib/httpd.js deleted file mode 100644 index e46ca96a0d..0000000000 --- a/addon-sdk/source/test/lib/httpd.js +++ /dev/null @@ -1,5212 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* -* An implementation of an HTTP server both as a loadable script and as an XPCOM -* component. See the accompanying README file for user documentation on -* httpd.js. -*/ - -module.metadata = { - "stability": "experimental" -}; - -const { components, CC, Cc, Ci, Cr, Cu } = require("chrome"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - - -const PR_UINT32_MAX = Math.pow(2, 32) - 1; - -/** True if debugging output is enabled, false otherwise. */ -var DEBUG = false; // non-const *only* so tweakable in server tests - -/** True if debugging output should be timestamped. */ -var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests - -var gGlobalObject = Cc["@mozilla.org/systemprincipal;1"].createInstance(); - -/** -* Asserts that the given condition holds. If it doesn't, the given message is -* dumped, a stack trace is printed, and an exception is thrown to attempt to -* stop execution (which unfortunately must rely upon the exception not being -* accidentally swallowed by the code that uses it). -*/ -function NS_ASSERT(cond, msg) -{ - if (DEBUG && !cond) - { - dumpn("###!!!"); - dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!")); - dumpn("###!!! Stack follows:"); - - var stack = new Error().stack.split(/\n/); - dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n")); - - throw Cr.NS_ERROR_ABORT; - } -} - -/** Constructs an HTTP error object. */ -function HttpError(code, description) -{ - this.code = code; - this.description = description; -} -HttpError.prototype = -{ - toString: function() - { - return this.code + " " + this.description; - } -}; - -/** -* Errors thrown to trigger specific HTTP server responses. -*/ -const HTTP_400 = new HttpError(400, "Bad Request"); -const HTTP_401 = new HttpError(401, "Unauthorized"); -const HTTP_402 = new HttpError(402, "Payment Required"); -const HTTP_403 = new HttpError(403, "Forbidden"); -const HTTP_404 = new HttpError(404, "Not Found"); -const HTTP_405 = new HttpError(405, "Method Not Allowed"); -const HTTP_406 = new HttpError(406, "Not Acceptable"); -const HTTP_407 = new HttpError(407, "Proxy Authentication Required"); -const HTTP_408 = new HttpError(408, "Request Timeout"); -const HTTP_409 = new HttpError(409, "Conflict"); -const HTTP_410 = new HttpError(410, "Gone"); -const HTTP_411 = new HttpError(411, "Length Required"); -const HTTP_412 = new HttpError(412, "Precondition Failed"); -const HTTP_413 = new HttpError(413, "Request Entity Too Large"); -const HTTP_414 = new HttpError(414, "Request-URI Too Long"); -const HTTP_415 = new HttpError(415, "Unsupported Media Type"); -const HTTP_417 = new HttpError(417, "Expectation Failed"); - -const HTTP_500 = new HttpError(500, "Internal Server Error"); -const HTTP_501 = new HttpError(501, "Not Implemented"); -const HTTP_502 = new HttpError(502, "Bad Gateway"); -const HTTP_503 = new HttpError(503, "Service Unavailable"); -const HTTP_504 = new HttpError(504, "Gateway Timeout"); -const HTTP_505 = new HttpError(505, "HTTP Version Not Supported"); - -/** Creates a hash with fields corresponding to the values in arr. */ -function array2obj(arr) -{ - var obj = {}; - for (var i = 0; i < arr.length; i++) - obj[arr[i]] = arr[i]; - return obj; -} - -/** Returns an array of the integers x through y, inclusive. */ -function range(x, y) -{ - var arr = []; - for (var i = x; i <= y; i++) - arr.push(i); - return arr; -} - -/** An object (hash) whose fields are the numbers of all HTTP error codes. */ -const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505))); - - -/** -* The character used to distinguish hidden files from non-hidden files, a la -* the leading dot in Apache. Since that mechanism also hides files from -* easy display in LXR, ls output, etc. however, we choose instead to use a -* suffix character. If a requested file ends with it, we append another -* when getting the file on the server. If it doesn't, we just look up that -* file. Therefore, any file whose name ends with exactly one of the character -* is "hidden" and available for use by the server. -*/ -const HIDDEN_CHAR = "^"; - -/** -* The file name suffix indicating the file containing overridden headers for -* a requested file. -*/ -const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR; - -/** Type used to denote SJS scripts for CGI-like functionality. */ -const SJS_TYPE = "sjs"; - -/** Base for relative timestamps produced by dumpn(). */ -var firstStamp = 0; - -/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */ -function dumpn(str) -{ - if (DEBUG) - { - var prefix = "HTTPD-INFO | "; - if (DEBUG_TIMESTAMP) - { - if (firstStamp === 0) - firstStamp = Date.now(); - - var elapsed = Date.now() - firstStamp; // milliseconds - var min = Math.floor(elapsed / 60000); - var sec = (elapsed % 60000) / 1000; - - if (sec < 10) - prefix += min + ":0" + sec.toFixed(3) + " | "; - else - prefix += min + ":" + sec.toFixed(3) + " | "; - } - - dump(prefix + str + "\n"); - } -} - -/** Dumps the current JS stack if DEBUG. */ -function dumpStack() -{ - // peel off the frames for dumpStack() and Error() - var stack = new Error().stack.split(/\n/).slice(2); - stack.forEach(dumpn); -} - - -/** The XPCOM thread manager. */ -var gThreadManager = null; - -/** The XPCOM prefs service. */ -var gRootPrefBranch = null; -function getRootPrefBranch() -{ - if (!gRootPrefBranch) - { - gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"] - .getService(Ci.nsIPrefBranch); - } - return gRootPrefBranch; -} - -/** -* JavaScript constructors for commonly-used classes; precreating these is a -* speedup over doing the same from base principles. See the docs at -* http://developer.mozilla.org/en/docs/components.Constructor for details. -*/ -const ServerSocket = CC("@mozilla.org/network/server-socket;1", - "nsIServerSocket", - "init"); -const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", - "nsIScriptableInputStream", - "init"); -const Pipe = CC("@mozilla.org/pipe;1", - "nsIPipe", - "init"); -const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", - "nsIFileInputStream", - "init"); -const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1", - "nsIConverterInputStream", - "init"); -const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1", - "nsIWritablePropertyBag2"); -const SupportsString = CC("@mozilla.org/supports-string;1", - "nsISupportsString"); - -/* These two are non-const only so a test can overwrite them. */ -var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", - "setInputStream"); -var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", - "nsIBinaryOutputStream", - "setOutputStream"); - -/** -* Returns the RFC 822/1123 representation of a date. -* -* @param date : Number -* the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT -* @returns string -* the representation of the given date -*/ -function toDateString(date) -{ - // - // rfc1123-date = wkday "," SP date1 SP time SP "GMT" - // date1 = 2DIGIT SP month SP 4DIGIT - // ; day month year (e.g., 02 Jun 1982) - // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - // ; 00:00:00 - 23:59:59 - // wkday = "Mon" | "Tue" | "Wed" - // | "Thu" | "Fri" | "Sat" | "Sun" - // month = "Jan" | "Feb" | "Mar" | "Apr" - // | "May" | "Jun" | "Jul" | "Aug" - // | "Sep" | "Oct" | "Nov" | "Dec" - // - - const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - - /** -* Processes a date and returns the encoded UTC time as a string according to -* the format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toTime(date) - { - var hrs = date.getUTCHours(); - var rv = (hrs < 10) ? "0" + hrs : hrs; - - var mins = date.getUTCMinutes(); - rv += ":"; - rv += (mins < 10) ? "0" + mins : mins; - - var secs = date.getUTCSeconds(); - rv += ":"; - rv += (secs < 10) ? "0" + secs : secs; - - return rv; - } - - /** -* Processes a date and returns the encoded UTC date as a string according to -* the date1 format specified in RFC 2616. -* -* @param date : Date -* the date to process -* @returns string -* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59" -*/ - function toDate1(date) - { - var day = date.getUTCDate(); - var month = date.getUTCMonth(); - var year = date.getUTCFullYear(); - - var rv = (day < 10) ? "0" + day : day; - rv += " " + monthStrings[month]; - rv += " " + year; - - return rv; - } - - date = new Date(date); - - const fmtString = "%wkday%, %date1% %time% GMT"; - var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]); - rv = rv.replace("%time%", toTime(date)); - return rv.replace("%date1%", toDate1(date)); -} - -/** -* Prints out a human-readable representation of the object o and its fields, -* omitting those whose names begin with "_" if showMembers != true (to ignore -* "private" properties exposed via getters/setters). -*/ -function printObj(o, showMembers) -{ - var s = "******************************\n"; - s += "o = {\n"; - for (var i in o) - { - if (typeof(i) != "string" || - (showMembers || (i.length > 0 && i[0] != "_"))) - s+= " " + i + ": " + o[i] + ",\n"; - } - s += " };\n"; - s += "******************************"; - dumpn(s); -} - -/** -* Instantiates a new HTTP server. -*/ -function nsHttpServer() -{ - if (!gThreadManager) - gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService(); - - /** The port on which this server listens. */ - this._port = undefined; - - /** The socket associated with this. */ - this._socket = null; - - /** The handler used to process requests to this server. */ - this._handler = new ServerHandler(this); - - /** Naming information for this server. */ - this._identity = new ServerIdentity(); - - /** -* Indicates when the server is to be shut down at the end of the request. -*/ - this._doQuit = false; - - /** -* True if the socket in this is closed (and closure notifications have been -* sent and processed if the socket was ever opened), false otherwise. -*/ - this._socketClosed = true; - - /** -* Used for tracking existing connections and ensuring that all connections -* are properly cleaned up before server shutdown; increases by 1 for every -* new incoming connection. -*/ - this._connectionGen = 0; - - /** -* Hash of all open connections, indexed by connection number at time of -* creation. -*/ - this._connections = {}; -} -nsHttpServer.prototype = -{ - classID: components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"), - - // NSISERVERSOCKETLISTENER - - /** -* Processes an incoming request coming in on the given socket and contained -* in the given transport. -* -* @param socket : nsIServerSocket -* the socket through which the request was served -* @param trans : nsISocketTransport -* the transport for the request/response -* @see nsIServerSocketListener.onSocketAccepted -*/ - onSocketAccepted: function(socket, trans) - { - dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")"); - - dumpn(">>> new connection on " + trans.host + ":" + trans.port); - - const SEGMENT_SIZE = 8192; - const SEGMENT_COUNT = 1024; - try - { - var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT) - .QueryInterface(Ci.nsIAsyncInputStream); - var output = trans.openOutputStream(0, 0, 0); - } - catch (e) - { - dumpn("*** error opening transport streams: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - var connectionNumber = ++this._connectionGen; - - try - { - var conn = new Connection(input, output, this, socket.port, trans.port, - connectionNumber); - var reader = new RequestReader(conn); - - // XXX add request timeout functionality here! - - // Note: must use main thread here, or we might get a GC that will cause - // threadsafety assertions. We really need to fix XPConnect so that - // you can actually do things in multi-threaded JS. :-( - input.asyncWait(reader, 0, 0, gThreadManager.mainThread); - } - catch (e) - { - // Assume this connection can't be salvaged and bail on it completely; - // don't attempt to close it so that we can assert that any connection - // being closed is in this._connections. - dumpn("*** error in initial request-processing stages: " + e); - trans.close(Cr.NS_BINDING_ABORTED); - return; - } - - this._connections[connectionNumber] = conn; - dumpn("*** starting connection " + connectionNumber); - }, - - /** -* Called when the socket associated with this is closed. -* -* @param socket : nsIServerSocket -* the socket being closed -* @param status : nsresult -* the reason the socket stopped listening (NS_BINDING_ABORTED if the server -* was stopped using nsIHttpServer.stop) -* @see nsIServerSocketListener.onStopListening -*/ - onStopListening: function(socket, status) - { - dumpn(">>> shutting down server on port " + socket.port); - this._socketClosed = true; - if (!this._hasOpenConnections()) - { - dumpn("*** no open connections, notifying async from onStopListening"); - - // Notify asynchronously so that any pending teardown in stop() has a - // chance to run first. - var self = this; - var stopEvent = - { - run: function() - { - dumpn("*** _notifyStopped async callback"); - self._notifyStopped(); - } - }; - gThreadManager.currentThread - .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL); - } - }, - - // NSIHTTPSERVER - - // - // see nsIHttpServer.start - // - start: function(port) - { - this._start(port, "localhost") - }, - - _start: function(port, host) - { - if (this._socket) - throw Cr.NS_ERROR_ALREADY_INITIALIZED; - - this._port = port; - this._doQuit = this._socketClosed = false; - - this._host = host; - - // The listen queue needs to be long enough to handle - // network.http.max-persistent-connections-per-server concurrent connections, - // plus a safety margin in case some other process is talking to - // the server as well. - var prefs = getRootPrefBranch(); - var maxConnections; - try { - // Bug 776860: The original pref was removed in favor of this new one: - maxConnections = prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5; - } - catch(e) { - maxConnections = prefs.getIntPref("network.http.max-connections-per-server") + 5; - } - - try - { - var loopback = true; - if (this._host != "127.0.0.1" && this._host != "localhost") { - var loopback = false; - } - - var socket = new ServerSocket(this._port, - loopback, // true = localhost, false = everybody - maxConnections); - dumpn(">>> listening on port " + socket.port + ", " + maxConnections + - " pending connections"); - socket.asyncListen(this); - this._identity._initialize(socket.port, host, true); - this._socket = socket; - } - catch (e) - { - dumpn("!!! could not start server on port " + port + ": " + e); - throw Cr.NS_ERROR_NOT_AVAILABLE; - } - }, - - // - // see nsIHttpServer.stop - // - stop: function(callback) - { - if (!callback) - throw Cr.NS_ERROR_NULL_POINTER; - if (!this._socket) - throw Cr.NS_ERROR_UNEXPECTED; - - this._stopCallback = typeof callback === "function" - ? callback - : function() { callback.onStopped(); }; - - dumpn(">>> stopping listening on port " + this._socket.port); - this._socket.close(); - this._socket = null; - - // We can't have this identity any more, and the port on which we're running - // this server now could be meaningless the next time around. - this._identity._teardown(); - - this._doQuit = false; - - // socket-close notification and pending request completion happen async - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (file && (!file.exists() || file.isDirectory())) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handler.registerFile(path, file); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // XXX true path validation! - if (path.charAt(0) != "/" || - path.charAt(path.length - 1) != "/" || - (directory && - (!directory.exists() || !directory.isDirectory()))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping - // exists! - - this._handler.registerDirectory(path, directory); - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - this._handler.registerPathHandler(path, handler); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - this._handler.registerPrefixHandler(prefix, handler); - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(code, handler) - { - this._handler.registerErrorHandler(code, handler); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - this._handler.setIndexHandler(handler); - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - this._handler.registerContentType(ext, type); - }, - - // - // see nsIHttpServer.serverIdentity - // - get identity() - { - return this._identity; - }, - - // - // see nsIHttpServer.getState - // - getState: function(path, k) - { - return this._handler._getState(path, k); - }, - - // - // see nsIHttpServer.setState - // - setState: function(path, k, v) - { - return this._handler._setState(path, k, v); - }, - - // - // see nsIHttpServer.getSharedState - // - getSharedState: function(k) - { - return this._handler._getSharedState(k); - }, - - // - // see nsIHttpServer.setSharedState - // - setSharedState: function(k, v) - { - return this._handler._setSharedState(k, v); - }, - - // - // see nsIHttpServer.getObjectState - // - getObjectState: function(k) - { - return this._handler._getObjectState(k); - }, - - // - // see nsIHttpServer.setObjectState - // - setObjectState: function(k, v) - { - return this._handler._setObjectState(k, v); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIServerSocketListener) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NON-XPCOM PUBLIC API - - /** -* Returns true iff this server is not running (and is not in the process of -* serving any requests still to be processed when the server was last -* stopped after being run). -*/ - isStopped: function() - { - return this._socketClosed && !this._hasOpenConnections(); - }, - - // PRIVATE IMPLEMENTATION - - /** True if this server has any open connections to it, false otherwise. */ - _hasOpenConnections: function() - { - // - // If we have any open connections, they're tracked as numeric properties on - // |this._connections|. The non-standard __count__ property could be used - // to check whether there are any properties, but standard-wise, even - // looking forward to ES5, there's no less ugly yet still O(1) way to do - // this. - // - for (var n in this._connections) - return true; - return false; - }, - - /** Calls the server-stopped callback provided when stop() was called. */ - _notifyStopped: function() - { - NS_ASSERT(this._stopCallback !== null, "double-notifying?"); - NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now"); - - // - // NB: We have to grab this now, null out the member, *then* call the - // callback here, or otherwise the callback could (indirectly) futz with - // this._stopCallback by starting and immediately stopping this, at - // which point we'd be nulling out a field we no longer have a right to - // modify. - // - var callback = this._stopCallback; - this._stopCallback = null; - try - { - callback(); - } - catch (e) - { - // not throwing because this is specified as being usually (but not - // always) asynchronous - dump("!!! error running onStopped callback: " + e + "\n"); - } - }, - - /** -* Notifies this server that the given connection has been closed. -* -* @param connection : Connection -* the connection that was closed -*/ - _connectionClosed: function(connection) - { - NS_ASSERT(connection.number in this._connections, - "closing a connection " + this + " that we never added to the " + - "set of open connections?"); - NS_ASSERT(this._connections[connection.number] === connection, - "connection number mismatch? " + - this._connections[connection.number]); - delete this._connections[connection.number]; - - // Fire a pending server-stopped notification if it's our responsibility. - if (!this._hasOpenConnections() && this._socketClosed) - this._notifyStopped(); - }, - - /** -* Requests that the server be shut down when possible. -*/ - _requestQuit: function() - { - dumpn(">>> requesting a quit"); - dumpStack(); - this._doQuit = true; - } -}; - - -// -// RFC 2396 section 3.2.2: -// -// host = hostname | IPv4address -// hostname = *( domainlabel "." ) toplabel [ "." ] -// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum -// toplabel = alpha | alpha *( alphanum | "-" ) alphanum -// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit -// - -const HOST_REGEX = - new RegExp("^(?:" + - // *( domainlabel "." ) - "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" + - // toplabel - "[a-z](?:[a-z0-9-]*[a-z0-9])?" + - "|" + - // IPv4 address - "\\d+\\.\\d+\\.\\d+\\.\\d+" + - ")$", - "i"); - - -/** -* Represents the identity of a server. An identity consists of a set of -* (scheme, host, port) tuples denoted as locations (allowing a single server to -* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any -* host/port). Any incoming request must be to one of these locations, or it -* will be rejected with an HTTP 400 error. One location, denoted as the -* primary location, is the location assigned in contexts where a location -* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests. -* -* A single identity may contain at most one location per unique host/port pair; -* other than that, no restrictions are placed upon what locations may -* constitute an identity. -*/ -function ServerIdentity() -{ - /** The scheme of the primary location. */ - this._primaryScheme = "http"; - - /** The hostname of the primary location. */ - this._primaryHost = "127.0.0.1" - - /** The port number of the primary location. */ - this._primaryPort = -1; - - /** -* The current port number for the corresponding server, stored so that a new -* primary location can always be set if the current one is removed. -*/ - this._defaultPort = -1; - - /** -* Maps hosts to maps of ports to schemes, e.g. the following would represent -* https://example.com:789/ and http://example.org/: -* -* { -* "xexample.com": { 789: "https" }, -* "xexample.org": { 80: "http" } -* } -* -* Note the "x" prefix on hostnames, which prevents collisions with special -* JS names like "prototype". -*/ - this._locations = { "xlocalhost": {} }; -} -ServerIdentity.prototype = -{ - // NSIHTTPSERVERIDENTITY - - // - // see nsIHttpServerIdentity.primaryScheme - // - get primaryScheme() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryScheme; - }, - - // - // see nsIHttpServerIdentity.primaryHost - // - get primaryHost() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryHost; - }, - - // - // see nsIHttpServerIdentity.primaryPort - // - get primaryPort() - { - if (this._primaryPort === -1) - throw Cr.NS_ERROR_NOT_INITIALIZED; - return this._primaryPort; - }, - - // - // see nsIHttpServerIdentity.add - // - add: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - this._locations["x" + host] = entry = {}; - - entry[port] = scheme; - }, - - // - // see nsIHttpServerIdentity.remove - // - remove: function(scheme, host, port) - { - this._validate(scheme, host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return false; - - var present = port in entry; - delete entry[port]; - - if (this._primaryScheme == scheme && - this._primaryHost == host && - this._primaryPort == port && - this._defaultPort !== -1) - { - // Always keep at least one identity in existence at any time, unless - // we're in the process of shutting down (the last condition above). - this._primaryPort = -1; - this._initialize(this._defaultPort, host, false); - } - - return present; - }, - - // - // see nsIHttpServerIdentity.has - // - has: function(scheme, host, port) - { - this._validate(scheme, host, port); - - return "x" + host in this._locations && - scheme === this._locations["x" + host][port]; - }, - - // - // see nsIHttpServerIdentity.has - // - getScheme: function(host, port) - { - this._validate("http", host, port); - - var entry = this._locations["x" + host]; - if (!entry) - return ""; - - return entry[port] || ""; - }, - - // - // see nsIHttpServerIdentity.setPrimary - // - setPrimary: function(scheme, host, port) - { - this._validate(scheme, host, port); - - this.add(scheme, host, port); - - this._primaryScheme = scheme; - this._primaryHost = host; - this._primaryPort = port; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Initializes the primary name for the corresponding server, based on the -* provided port number. -*/ - _initialize: function(port, host, addSecondaryDefault) - { - this._host = host; - if (this._primaryPort !== -1) - this.add("http", host, port); - else - this.setPrimary("http", "localhost", port); - this._defaultPort = port; - - // Only add this if we're being called at server startup - if (addSecondaryDefault && host != "127.0.0.1") - this.add("http", "127.0.0.1", port); - }, - - /** -* Called at server shutdown time, unsets the primary location only if it was -* the default-assigned location and removes the default location from the -* set of locations used. -*/ - _teardown: function() - { - if (this._host != "127.0.0.1") { - // Not the default primary location, nothing special to do here - this.remove("http", "127.0.0.1", this._defaultPort); - } - - // This is a *very* tricky bit of reasoning here; make absolutely sure the - // tests for this code pass before you commit changes to it. - if (this._primaryScheme == "http" && - this._primaryHost == this._host && - this._primaryPort == this._defaultPort) - { - // Make sure we don't trigger the readding logic in .remove(), then remove - // the default location. - var port = this._defaultPort; - this._defaultPort = -1; - this.remove("http", this._host, port); - - // Ensure a server start triggers the setPrimary() path in ._initialize() - this._primaryPort = -1; - } - else - { - // No reason not to remove directly as it's not our primary location - this.remove("http", this._host, this._defaultPort); - } - }, - - /** -* Ensures scheme, host, and port are all valid with respect to RFC 2396. -* -* @throws NS_ERROR_ILLEGAL_VALUE -* if any argument doesn't match the corresponding production -*/ - _validate: function(scheme, host, port) - { - if (scheme !== "http" && scheme !== "https") - { - dumpn("*** server only supports http/https schemes: '" + scheme + "'"); - dumpStack(); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (!HOST_REGEX.test(host)) - { - dumpn("*** unexpected host: '" + host + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - if (port < 0 || port > 65535) - { - dumpn("*** unexpected port: '" + port + "'"); - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - } -}; - - -/** -* Represents a connection to the server (and possibly in the future the thread -* on which the connection is processed). -* -* @param input : nsIInputStream -* stream from which incoming data on the connection is read -* @param output : nsIOutputStream -* stream to write data out the connection -* @param server : nsHttpServer -* the server handling the connection -* @param port : int -* the port on which the server is running -* @param outgoingPort : int -* the outgoing port used by this connection -* @param number : uint -* a serial number used to uniquely identify this connection -*/ -function Connection(input, output, server, port, outgoingPort, number) -{ - dumpn("*** opening new connection " + number + " on port " + outgoingPort); - - /** Stream of incoming data. */ - this.input = input; - - /** Stream for outgoing data. */ - this.output = output; - - /** The server associated with this request. */ - this.server = server; - - /** The port on which the server is running. */ - this.port = port; - - /** The outgoing poort used by this connection. */ - this._outgoingPort = outgoingPort; - - /** The serial number of this connection. */ - this.number = number; - - /** -* The request for which a response is being generated, null if the -* incoming request has not been fully received or if it had errors. -*/ - this.request = null; - - /** State variables for debugging. */ - this._closed = this._processed = false; -} -Connection.prototype = -{ - /** Closes this connection's input/output streams. */ - close: function() - { - dumpn("*** closing connection " + this.number + - " on port " + this._outgoingPort); - - this.input.close(); - this.output.close(); - this._closed = true; - - var server = this.server; - server._connectionClosed(this); - - // If an error triggered a server shutdown, act on it now - if (server._doQuit) - server.stop(function() { /* not like we can do anything better */ }); - }, - - /** -* Initiates processing of this connection, using the data in the given -* request. -* -* @param request : Request -* the request which should be processed -*/ - process: function(request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - - this.request = request; - this.server._handler.handleResponse(this); - }, - - /** -* Initiates processing of this connection, generating a response with the -* given HTTP error code. -* -* @param code : uint -* an HTTP code, so in the range [0, 1000) -* @param request : Request -* incomplete data about the incoming request (since there were errors -* during its processing -*/ - processError: function(code, request) - { - NS_ASSERT(!this._closed && !this._processed); - - this._processed = true; - this.request = request; - this.server._handler.handleError(code, this); - }, - - /** Converts this to a string for debugging purposes. */ - toString: function() - { - return ""; - } -}; - - - -/** Returns an array of count bytes from the given input stream. */ -function readBytes(inputStream, count) -{ - return new BinaryInputStream(inputStream).readByteArray(count); -} - - - -/** Request reader processing states; see RequestReader for details. */ -const READER_IN_REQUEST_LINE = 0; -const READER_IN_HEADERS = 1; -const READER_IN_BODY = 2; -const READER_FINISHED = 3; - - -/** -* Reads incoming request data asynchronously, does any necessary preprocessing, -* and forwards it to the request handler. Processing occurs in three states: -* -* READER_IN_REQUEST_LINE Reading the request's status line -* READER_IN_HEADERS Reading headers in the request -* READER_IN_BODY Reading the body of the request -* READER_FINISHED Entire request has been read and processed -* -* During the first two stages, initial metadata about the request is gathered -* into a Request object. Once the status line and headers have been processed, -* we start processing the body of the request into the Request. Finally, when -* the entire body has been read, we create a Response and hand it off to the -* ServerHandler to be given to the appropriate request handler. -* -* @param connection : Connection -* the connection for the request being read -*/ -function RequestReader(connection) -{ - /** Connection metadata for this request. */ - this._connection = connection; - - /** -* A container providing line-by-line access to the raw bytes that make up the -* data which has been read from the connection but has not yet been acted -* upon (by passing it to the request handler or by extracting request -* metadata from it). -*/ - this._data = new LineData(); - - /** -* The amount of data remaining to be read from the body of this request. -* After all headers in the request have been read this is the value in the -* Content-Length header, but as the body is read its value decreases to zero. -*/ - this._contentLength = 0; - - /** The current state of parsing the incoming request. */ - this._state = READER_IN_REQUEST_LINE; - - /** Metadata constructed from the incoming request for the request handler. */ - this._metadata = new Request(connection.port); - - /** -* Used to preserve state if we run out of line data midway through a -* multi-line header. _lastHeaderName stores the name of the header, while -* _lastHeaderValue stores the value we've seen so far for the header. -* -* These fields are always either both undefined or both strings. -*/ - this._lastHeaderName = this._lastHeaderValue = undefined; -} -RequestReader.prototype = -{ - // NSIINPUTSTREAMCALLBACK - - /** -* Called when more data from the incoming request is available. This method -* then reads the available data from input and deals with that data as -* necessary, depending upon the syntax of already-downloaded data. -* -* @param input : nsIAsyncInputStream -* the stream of incoming data from the connection -*/ - onInputStreamReady: function(input) - { - dumpn("*** onInputStreamReady(input=" + input + ") on thread " + - gThreadManager.currentThread + " (main is " + - gThreadManager.mainThread + ")"); - dumpn("*** this._state == " + this._state); - - // Handle cases where we get more data after a request error has been - // discovered but *before* we can close the connection. - var data = this._data; - if (!data) - return; - - try - { - data.appendBytes(readBytes(input, input.available())); - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** WARNING: unexpected error when reading from socket; will " + - "be treated as if the input stream had been closed"); - dumpn("*** WARNING: actual error was: " + e); - } - - // We've lost a race -- input has been closed, but we're still expecting - // to read more data. available() will throw in this case, and since - // we're dead in the water now, destroy the connection. - dumpn("*** onInputStreamReady called on a closed input, destroying " + - "connection"); - this._connection.close(); - return; - } - - switch (this._state) - { - default: - NS_ASSERT(false, "invalid state: " + this._state); - break; - - case READER_IN_REQUEST_LINE: - if (!this._processRequestLine()) - break; - /* fall through */ - - case READER_IN_HEADERS: - if (!this._processHeaders()) - break; - /* fall through */ - - case READER_IN_BODY: - this._processBody(); - } - - if (this._state != READER_FINISHED) - input.asyncWait(this, 0, 0, gThreadManager.currentThread); - }, - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIInputStreamCallback) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE API - - /** -* Processes unprocessed, downloaded data as a request line. -* -* @returns boolean -* true iff the request line has been fully processed -*/ - _processRequestLine: function() - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - // Servers SHOULD ignore any empty line(s) received where a Request-Line - // is expected (section 4.1). - var data = this._data; - var line = {}; - var readSuccess; - while ((readSuccess = data.readLine(line)) && line.value == "") - dumpn("*** ignoring beginning blank line..."); - - // if we don't have a full line, wait until we do - if (!readSuccess) - return false; - - // we have the first non-blank line - try - { - this._parseRequestLine(line.value); - this._state = READER_IN_HEADERS; - return true; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing request headers. -* -* @returns boolean -* true iff header data in the request has been fully processed -*/ - _processHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - // XXX things to fix here: - // - // - need to support RFC 2047-encoded non-US-ASCII characters - - try - { - var done = this._parseHeaders(); - if (done) - { - var request = this._metadata; - - // XXX this is wrong for requests with transfer-encodings applied to - // them, particularly chunked (which by its nature can have no - // meaningful Content-Length header)! - this._contentLength = request.hasHeader("Content-Length") - ? parseInt(request.getHeader("Content-Length"), 10) - : 0; - dumpn("_processHeaders, Content-length=" + this._contentLength); - - this._state = READER_IN_BODY; - } - return done; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Processes stored data, assuming it is either at the beginning or in -* the middle of processing the request body. -* -* @returns boolean -* true iff the request body has been fully processed -*/ - _processBody: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - // XXX handle chunked transfer-coding request bodies! - - try - { - if (this._contentLength > 0) - { - var data = this._data.purge(); - var count = Math.min(data.length, this._contentLength); - dumpn("*** loading data=" + data + " len=" + data.length + - " excess=" + (data.length - count)); - - var bos = new BinaryOutputStream(this._metadata._bodyOutputStream); - bos.writeByteArray(data, count); - this._contentLength -= count; - } - - dumpn("*** remaining body data len=" + this._contentLength); - if (this._contentLength == 0) - { - this._validateRequest(); - this._state = READER_FINISHED; - this._handleResponse(); - return true; - } - - return false; - } - catch (e) - { - this._handleError(e); - return false; - } - }, - - /** -* Does various post-header checks on the data in this request. -* -* @throws : HttpError -* if the request was malformed in some way -*/ - _validateRequest: function() - { - NS_ASSERT(this._state == READER_IN_BODY); - - dumpn("*** _validateRequest"); - - var metadata = this._metadata; - var headers = metadata._headers; - - // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header - var identity = this._connection.server.identity; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - { - if (!headers.hasHeader("Host")) - { - dumpn("*** malformed HTTP/1.1 or greater request with no Host header!"); - throw HTTP_400; - } - - // If the Request-URI wasn't absolute, then we need to determine our host. - // We have to determine what scheme was used to access us based on the - // server identity data at this point, because the request just doesn't - // contain enough data on its own to do this, sadly. - if (!metadata._host) - { - var host, port; - var hostPort = headers.getHeader("Host"); - var colon = hostPort.indexOf(":"); - if (colon < 0) - { - host = hostPort; - port = ""; - } - else - { - host = hostPort.substring(0, colon); - port = hostPort.substring(colon + 1); - } - - // NB: We allow an empty port here because, oddly, a colon may be - // present even without a port number, e.g. "example.com:"; in this - // case the default port applies. - if (!HOST_REGEX.test(host) || !/^\d*$/.test(port)) - { - dumpn("*** malformed hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - // If we're not given a port, we're stuck, because we don't know what - // scheme to use to look up the correct port here, in general. Since - // the HTTPS case requires a tunnel/proxy and thus requires that the - // requested URI be absolute (and thus contain the necessary - // information), let's assume HTTP will prevail and use that. - port = +port || 80; - - var scheme = identity.getScheme(host, port); - if (!scheme) - { - dumpn("*** unrecognized hostname (" + hostPort + ") in Host " + - "header, 400 time"); - throw HTTP_400; - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - } - } - else - { - NS_ASSERT(metadata._host === undefined, - "HTTP/1.0 doesn't allow absolute paths in the request line!"); - - metadata._scheme = identity.primaryScheme; - metadata._host = identity.primaryHost; - metadata._port = identity.primaryPort; - } - - NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port), - "must have a location we recognize by now!"); - }, - - /** -* Handles responses in case of error, either in the server or in the request. -* -* @param e -* the specific error encountered, which is an HttpError in the case where -* the request is in some way invalid or cannot be fulfilled; if this isn't -* an HttpError we're going to be paranoid and shut down, because that -* shouldn't happen, ever -*/ - _handleError: function(e) - { - // Don't fall back into normal processing! - this._state = READER_FINISHED; - - var server = this._connection.server; - if (e instanceof HttpError) - { - var code = e.code; - } - else - { - dumpn("!!! UNEXPECTED ERROR: " + e + - (e.lineNumber ? ", line " + e.lineNumber : "")); - - // no idea what happened -- be paranoid and shut down - code = 500; - server._requestQuit(); - } - - // make attempted reuse of data an error - this._data = null; - - this._connection.processError(code, this._metadata); - }, - - /** -* Now that we've read the request line and headers, we can actually hand off -* the request to be handled. -* -* This method is called once per request, after the request line and all -* headers and the body, if any, have been received. -*/ - _handleResponse: function() - { - NS_ASSERT(this._state == READER_FINISHED); - - // We don't need the line-based data any more, so make attempted reuse an - // error. - this._data = null; - - this._connection.process(this._metadata); - }, - - - // PARSING - - /** -* Parses the request line for the HTTP request associated with this. -* -* @param line : string -* the request line -*/ - _parseRequestLine: function(line) - { - NS_ASSERT(this._state == READER_IN_REQUEST_LINE); - - dumpn("*** _parseRequestLine('" + line + "')"); - - var metadata = this._metadata; - - // clients and servers SHOULD accept any amount of SP or HT characters - // between fields, even though only a single SP is required (section 19.3) - var request = line.split(/[ \t]+/); - if (!request || request.length != 3) - throw HTTP_400; - - metadata._method = request[0]; - - // get the HTTP version - var ver = request[2]; - var match = ver.match(/^HTTP\/(\d+\.\d+)$/); - if (!match) - throw HTTP_400; - - // determine HTTP version - try - { - metadata._httpVersion = new nsHttpVersion(match[1]); - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0)) - throw "unsupported HTTP version"; - } - catch (e) - { - // we support HTTP/1.0 and HTTP/1.1 only - throw HTTP_501; - } - - - var fullPath = request[1]; - var serverIdentity = this._connection.server.identity; - - var scheme, host, port; - - if (fullPath.charAt(0) != "/") - { - // No absolute paths in the request line in HTTP prior to 1.1 - if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) - throw HTTP_400; - - try - { - var uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(fullPath, null, null); - fullPath = uri.path; - scheme = uri.scheme; - host = metadata._host = uri.asciiHost; - port = uri.port; - if (port === -1) - { - if (scheme === "http") - port = 80; - else if (scheme === "https") - port = 443; - else - throw HTTP_400; - } - } - catch (e) - { - // If the host is not a valid host on the server, the response MUST be a - // 400 (Bad Request) error message (section 5.2). Alternately, the URI - // is malformed. - throw HTTP_400; - } - - if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/") - throw HTTP_400; - } - - var splitter = fullPath.indexOf("?"); - if (splitter < 0) - { - // _queryString already set in ctor - metadata._path = fullPath; - } - else - { - metadata._path = fullPath.substring(0, splitter); - metadata._queryString = fullPath.substring(splitter + 1); - } - - metadata._scheme = scheme; - metadata._host = host; - metadata._port = port; - }, - - /** -* Parses all available HTTP headers in this until the header-ending CRLFCRLF, -* adding them to the store of headers in the request. -* -* @throws -* HTTP_400 if the headers are malformed -* @returns boolean -* true if all headers have now been processed, false otherwise -*/ - _parseHeaders: function() - { - NS_ASSERT(this._state == READER_IN_HEADERS); - - dumpn("*** _parseHeaders"); - - var data = this._data; - - var headers = this._metadata._headers; - var lastName = this._lastHeaderName; - var lastVal = this._lastHeaderValue; - - var line = {}; - while (true) - { - NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)), - lastName === undefined ? - "lastVal without lastName? lastVal: '" + lastVal + "'" : - "lastName without lastVal? lastName: '" + lastName + "'"); - - if (!data.readLine(line)) - { - // save any data we have from the header we might still be processing - this._lastHeaderName = lastName; - this._lastHeaderValue = lastVal; - return false; - } - - var lineText = line.value; - var firstChar = lineText.charAt(0); - - // blank line means end of headers - if (lineText == "") - { - // we're finished with the previous header - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - else - { - // no headers in request -- valid for HTTP/1.0 requests - } - - // either way, we're done processing headers - this._state = READER_IN_BODY; - return true; - } - else if (firstChar == " " || firstChar == "\t") - { - // multi-line header if we've already seen a header line - if (!lastName) - { - // we don't have a header to continue! - throw HTTP_400; - } - - // append this line's text to the value; starts with SP/HT, so no need - // for separating whitespace - lastVal += lineText; - } - else - { - // we have a new header, so set the old one (if one existed) - if (lastName) - { - try - { - headers.setHeader(lastName, lastVal, true); - } - catch (e) - { - dumpn("*** e == " + e); - throw HTTP_400; - } - } - - var colon = lineText.indexOf(":"); // first colon must be splitter - if (colon < 1) - { - // no colon or missing header field-name - throw HTTP_400; - } - - // set header name, value (to be set in the next loop, usually) - lastName = lineText.substring(0, colon); - lastVal = lineText.substring(colon + 1); - } // empty, continuation, start of header - } // while (true) - } -}; - - -/** The character codes for CR and LF. */ -const CR = 0x0D, LF = 0x0A; - -/** -* Calculates the number of characters before the first CRLF pair in array, or -* -1 if the array contains no CRLF pair. -* -* @param array : Array -* an array of numbers in the range [0, 256), each representing a single -* character; the first CRLF is the lowest index i where -* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|, -* if such an |i| exists, and -1 otherwise -* @returns int -* the index of the first CRLF if any were present, -1 otherwise -*/ -function findCRLF(array) -{ - for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1)) - { - if (array[i + 1] == LF) - return i; - } - return -1; -} - - -/** -* A container which provides line-by-line access to the arrays of bytes with -* which it is seeded. -*/ -function LineData() -{ - /** An array of queued bytes from which to get line-based characters. */ - this._data = []; -} -LineData.prototype = -{ - /** -* Appends the bytes in the given array to the internal data cache maintained -* by this. -*/ - appendBytes: function(bytes) - { - Array.prototype.push.apply(this._data, bytes); - }, - - /** -* Removes and returns a line of data, delimited by CRLF, from this. -* -* @param out -* an object whose "value" property will be set to the first line of text -* present in this, sans CRLF, if this contains a full CRLF-delimited line -* of text; if this doesn't contain enough data, the value of the property -* is undefined -* @returns boolean -* true if a full line of data could be read from the data in this, false -* otherwise -*/ - readLine: function(out) - { - var data = this._data; - var length = findCRLF(data); - if (length < 0) - return false; - - // - // We have the index of the CR, so remove all the characters, including - // CRLF, from the array with splice, and convert the removed array into the - // corresponding string, from which we then strip the trailing CRLF. - // - // Getting the line in this matter acknowledges that substring is an O(1) - // operation in SpiderMonkey because strings are immutable, whereas two - // splices, both from the beginning of the data, are less likely to be as - // cheap as a single splice plus two extra character conversions. - // - var line = String.fromCharCode.apply(null, data.splice(0, length + 2)); - out.value = line.substring(0, length); - - return true; - }, - - /** -* Removes the bytes currently within this and returns them in an array. -* -* @returns Array -* the bytes within this when this method is called -*/ - purge: function() - { - var data = this._data; - this._data = []; - return data; - } -}; - - - -/** -* Creates a request-handling function for an nsIHttpRequestHandler object. -*/ -function createHandlerFunc(handler) -{ - return function(metadata, response) { handler.handle(metadata, response); }; -} - - -/** -* The default handler for directories; writes an HTML response containing a -* slightly-formatted directory listing. -*/ -function defaultIndexHandler(metadata, response) -{ - response.setHeader("Content-Type", "text/html", false); - - var path = htmlEscape(decodeURI(metadata.path)); - - // - // Just do a very basic bit of directory listings -- no need for too much - // fanciness, especially since we don't have a style sheet in which we can - // stick rules (don't want to pollute the default path-space). - // - - var body = '\ -\ -' + path + '\ -\ -\ -

' + path + '

\ -
    '; - - var directory = metadata.getProperty("directory").QueryInterface(Ci.nsILocalFile); - NS_ASSERT(directory && directory.isDirectory()); - - var fileList = []; - var files = directory.directoryEntries; - while (files.hasMoreElements()) - { - var f = files.getNext().QueryInterface(Ci.nsIFile); - var name = f.leafName; - if (!f.isHidden() && - (name.charAt(name.length - 1) != HIDDEN_CHAR || - name.charAt(name.length - 2) == HIDDEN_CHAR)) - fileList.push(f); - } - - fileList.sort(fileSort); - - for (var i = 0; i < fileList.length; i++) - { - var file = fileList[i]; - try - { - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - var sep = file.isDirectory() ? "/" : ""; - - // Note: using " to delimit the attribute here because encodeURIComponent - // passes through '. - var item = '
  1. ' + - htmlEscape(name) + sep + - '
  2. '; - - body += item; - } - catch (e) { /* some file system error, ignore the file */ } - } - - body += '
\ -\ -'; - - response.bodyOutputStream.write(body, body.length); -} - -/** -* Sorts a and b (nsIFile objects) into an aesthetically pleasing order. -*/ -function fileSort(a, b) -{ - var dira = a.isDirectory(), dirb = b.isDirectory(); - - if (dira && !dirb) - return -1; - if (dirb && !dira) - return 1; - - var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase(); - return nameb > namea ? -1 : 1; -} - - -/** -* Converts an externally-provided path into an internal path for use in -* determining file mappings. -* -* @param path -* the path to convert -* @param encoded -* true if the given path should be passed through decodeURI prior to -* conversion -* @throws URIError -* if path is incorrectly encoded -*/ -function toInternalPath(path, encoded) -{ - if (encoded) - path = decodeURI(path); - - var comps = path.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - if (comp.charAt(comp.length - 1) == HIDDEN_CHAR) - comps[i] = comp + HIDDEN_CHAR; - } - return comps.join("/"); -} - - -/** -* Adds custom-specified headers for the given file to the given response, if -* any such headers are specified. -* -* @param file -* the file on the disk which is to be written -* @param metadata -* metadata about the incoming request -* @param response -* the Response to which any specified headers/data should be written -* @throws HTTP_500 -* if an error occurred while processing custom-specified headers -*/ -function maybeAddHeaders(file, metadata, response) -{ - var name = file.leafName; - if (name.charAt(name.length - 1) == HIDDEN_CHAR) - name = name.substring(0, name.length - 1); - - var headerFile = file.parent; - headerFile.append(name + HEADERS_SUFFIX); - - if (!headerFile.exists()) - return; - - const PR_RDONLY = 0x01; - var fis = new FileInputStream(headerFile, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0); - lis.QueryInterface(Ci.nsIUnicharLineInputStream); - - var line = {value: ""}; - var more = lis.readLine(line); - - if (!more && line.value == "") - return; - - - // request line - - var status = line.value; - if (status.indexOf("HTTP ") == 0) - { - status = status.substring(5); - var space = status.indexOf(" "); - var code, description; - if (space < 0) - { - code = status; - description = ""; - } - else - { - code = status.substring(0, space); - description = status.substring(space + 1, status.length); - } - - response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description); - - line.value = ""; - more = lis.readLine(line); - } - - // headers - while (more || line.value != "") - { - var header = line.value; - var colon = header.indexOf(":"); - - response.setHeader(header.substring(0, colon), - header.substring(colon + 1, header.length), - false); // allow overriding server-set headers - - line.value = ""; - more = lis.readLine(line); - } - } - catch (e) - { - dumpn("WARNING: error in headers for " + metadata.path + ": " + e); - throw HTTP_500; - } - finally - { - fis.close(); - } -} - - -/** -* An object which handles requests for a server, executing default and -* overridden behaviors as instructed by the code which uses and manipulates it. -* Default behavior includes the paths / and /trace (diagnostics), with some -* support for HTTP error pages for various codes and fallback to HTTP 500 if -* those codes fail for any reason. -* -* @param server : nsHttpServer -* the server in which this handler is being used -*/ -function ServerHandler(server) -{ - // FIELDS - - /** -* The nsHttpServer instance associated with this handler. -*/ - this._server = server; - - /** -* A FileMap object containing the set of path->nsILocalFile mappings for -* all directory mappings set in the server (e.g., "/" for /var/www/html/, -* "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2). -* -* Note carefully: the leading and trailing "/" in each path (not file) are -* removed before insertion to simplify the code which uses this. You have -* been warned! -*/ - this._pathDirectoryMap = new FileMap(); - - /** -* Custom request handlers for the server in which this resides. Path-handler -* pairs are stored as property-value pairs in this property. -* -* @see ServerHandler.prototype._defaultPaths -*/ - this._overridePaths = {}; - - /** -* Custom request handlers for the server in which this resides. Prefix-handler -* pairs are stored as property-value pairs in this property. -*/ - this._overridePrefixes = {}; - - /** -* Custom request handlers for the error handlers in the server in which this -* resides. Path-handler pairs are stored as property-value pairs in this -* property. -* -* @see ServerHandler.prototype._defaultErrors -*/ - this._overrideErrors = {}; - - /** -* Maps file extensions to their MIME types in the server, overriding any -* mapping that might or might not exist in the MIME service. -*/ - this._mimeMappings = {}; - - /** -* The default handler for requests for directories, used to serve directories -* when no index file is present. -*/ - this._indexHandler = defaultIndexHandler; - - /** Per-path state storage for the server. */ - this._state = {}; - - /** Entire-server state storage. */ - this._sharedState = {}; - - /** Entire-server state storage for nsISupports values. */ - this._objectState = {}; -} -ServerHandler.prototype = -{ - // PUBLIC API - - /** -* Handles a request to this server, responding to the request appropriately -* and initiating server shutdown if necessary. -* -* This method never throws an exception. -* -* @param connection : Connection -* the connection for this request -*/ - handleResponse: function(connection) - { - var request = connection.request; - var response = new Response(connection); - - var path = request.path; - dumpn("*** path == " + path); - - try - { - try - { - if (path in this._overridePaths) - { - // explicit paths first, then files based on existing directory mappings, - // then (if the file doesn't exist) built-in server default paths - dumpn("calling override for " + path); - this._overridePaths[path](request, response); - } - else - { - let longestPrefix = ""; - for (let prefix in this._overridePrefixes) - { - if (prefix.length > longestPrefix.length && path.startsWith(prefix)) - { - longestPrefix = prefix; - } - } - if (longestPrefix.length > 0) - { - dumpn("calling prefix override for " + longestPrefix); - this._overridePrefixes[longestPrefix](request, response); - } - else - { - this._handleDefault(request, response); - } - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - if (!(e instanceof HttpError)) - { - dumpn("*** unexpected error: e == " + e); - throw HTTP_500; - } - if (e.code !== 404) - throw e; - - dumpn("*** default: " + (path in this._defaultPaths)); - - response = new Response(connection); - if (path in this._defaultPaths) - this._defaultPaths[path](request, response); - else - throw HTTP_404; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - var errorCode = "internal"; - - try - { - if (!(e instanceof HttpError)) - throw e; - - errorCode = e.code; - dumpn("*** errorCode == " + errorCode); - - response = new Response(connection); - if (e.customErrorHandling) - e.customErrorHandling(response); - this._handleError(errorCode, request, response); - return; - } - catch (e2) - { - dumpn("*** error handling " + errorCode + " error: " + - "e2 == " + e2 + ", shutting down server"); - - connection.server._requestQuit(); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // - // see nsIHttpServer.registerFile - // - registerFile: function(path, file) - { - if (!file) - { - dumpn("*** unregistering '" + path + "' mapping"); - delete this._overridePaths[path]; - return; - } - - dumpn("*** registering '" + path + "' as mapping to " + file.path); - file = file.clone(); - - var self = this; - this._overridePaths[path] = - function(request, response) - { - if (!file.exists()) - throw HTTP_404; - - response.setStatusLine(request.httpVersion, 200, "OK"); - self._writeFileResponse(request, file, response, 0, file.fileSize); - }; - }, - - // - // see nsIHttpServer.registerPathHandler - // - registerPathHandler: function(path, handler) - { - // XXX true path validation! - if (path.charAt(0) != "/") - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePaths, path); - }, - - // - // see nsIHttpServer.registerPrefixHandler - // - registerPrefixHandler: function(prefix, handler) - { - // XXX true prefix validation! - if (!(prefix.startsWith("/") && prefix.endsWith("/"))) - throw Cr.NS_ERROR_INVALID_ARG; - - this._handlerToField(handler, this._overridePrefixes, prefix); - }, - - // - // see nsIHttpServer.registerDirectory - // - registerDirectory: function(path, directory) - { - // strip off leading and trailing '/' so that we can use lastIndexOf when - // determining exactly how a path maps onto a mapped directory -- - // conditional is required here to deal with "/".substring(1, 0) being - // converted to "/".substring(0, 1) per the JS specification - var key = path.length == 1 ? "" : path.substring(1, path.length - 1); - - // the path-to-directory mapping code requires that the first character not - // be "/", or it will go into an infinite loop - if (key.charAt(0) == "/") - throw Cr.NS_ERROR_INVALID_ARG; - - key = toInternalPath(key, false); - - if (directory) - { - dumpn("*** mapping '" + path + "' to the location " + directory.path); - this._pathDirectoryMap.put(key, directory); - } - else - { - dumpn("*** removing mapping for '" + path + "'"); - this._pathDirectoryMap.put(key, null); - } - }, - - // - // see nsIHttpServer.registerErrorHandler - // - registerErrorHandler: function(err, handler) - { - if (!(err in HTTP_ERROR_CODES)) - dumpn("*** WARNING: registering non-HTTP/1.1 error code " + - "(" + err + ") handler -- was this intentional?"); - - this._handlerToField(handler, this._overrideErrors, err); - }, - - // - // see nsIHttpServer.setIndexHandler - // - setIndexHandler: function(handler) - { - if (!handler) - handler = defaultIndexHandler; - else if (typeof(handler) != "function") - handler = createHandlerFunc(handler); - - this._indexHandler = handler; - }, - - // - // see nsIHttpServer.registerContentType - // - registerContentType: function(ext, type) - { - if (!type) - delete this._mimeMappings[ext]; - else - this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type); - }, - - // PRIVATE API - - /** -* Sets or remove (if handler is null) a handler in an object with a key. -* -* @param handler -* a handler, either function or an nsIHttpRequestHandler -* @param dict -* The object to attach the handler to. -* @param key -* The field name of the handler. -*/ - _handlerToField: function(handler, dict, key) - { - // for convenience, handler can be a function if this is run from xpcshell - if (typeof(handler) == "function") - dict[key] = handler; - else if (handler) - dict[key] = createHandlerFunc(handler); - else - delete dict[key]; - }, - - /** -* Handles a request which maps to a file in the local filesystem (if a base -* path has already been set; otherwise the 404 error is thrown). -* -* @param metadata : Request -* metadata for the incoming request -* @param response : Response -* an uninitialized Response to the given request, to be initialized by a -* request handler -* @throws HTTP_### -* if an HTTP error occurred (usually HTTP_404); note that in this case the -* calling code must handle post-processing of the response -*/ - _handleDefault: function(metadata, response) - { - dumpn("*** _handleDefault()"); - - response.setStatusLine(metadata.httpVersion, 200, "OK"); - - var path = metadata.path; - NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">"); - - // determine the actual on-disk file; this requires finding the deepest - // path-to-directory mapping in the requested URL - var file = this._getFileForPath(path); - - // the "file" might be a directory, in which case we either serve the - // contained index.html or make the index handler write the response - if (file.exists() && file.isDirectory()) - { - file.append("index.html"); // make configurable? - if (!file.exists() || file.isDirectory()) - { - metadata._ensurePropertyBag(); - metadata._bag.setPropertyAsInterface("directory", file.parent); - this._indexHandler(metadata, response); - return; - } - } - - // alternately, the file might not exist - if (!file.exists()) - throw HTTP_404; - - var start, end; - if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) && - metadata.hasHeader("Range") && - this._getTypeFromFile(file) !== SJS_TYPE) - { - var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/); - if (!rangeMatch) - throw HTTP_400; - - if (rangeMatch[1] !== undefined) - start = parseInt(rangeMatch[1], 10); - - if (rangeMatch[2] !== undefined) - end = parseInt(rangeMatch[2], 10); - - if (start === undefined && end === undefined) - throw HTTP_400; - - // No start given, so the end is really the count of bytes from the - // end of the file. - if (start === undefined) - { - start = Math.max(0, file.fileSize - end); - end = file.fileSize - 1; - } - - // start and end are inclusive - if (end === undefined || end >= file.fileSize) - end = file.fileSize - 1; - - if (start !== undefined && start >= file.fileSize) { - var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable"); - HTTP_416.customErrorHandling = function(errorResponse) - { - maybeAddHeaders(file, metadata, errorResponse); - }; - throw HTTP_416; - } - - if (end < start) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - start = 0; - end = file.fileSize - 1; - } - else - { - response.setStatusLine(metadata.httpVersion, 206, "Partial Content"); - var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize; - response.setHeader("Content-Range", contentRange); - } - } - else - { - start = 0; - end = file.fileSize - 1; - } - - // finally... - dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " + - start + " to " + end + " inclusive"); - this._writeFileResponse(metadata, file, response, start, end - start + 1); - }, - - /** -* Writes an HTTP response for the given file, including setting headers for -* file metadata. -* -* @param metadata : Request -* the Request for which a response is being generated -* @param file : nsILocalFile -* the file which is to be sent in the response -* @param response : Response -* the response to which the file should be written -* @param offset: uint -* the byte offset to skip to when writing -* @param count: uint -* the number of bytes to write -*/ - _writeFileResponse: function(metadata, file, response, offset, count) - { - const PR_RDONLY = 0x01; - - var type = this._getTypeFromFile(file); - if (type === SJS_TYPE) - { - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - try - { - var sis = new ScriptableInputStream(fis); - var s = Cu.Sandbox(gGlobalObject); - s.importFunction(dump, "dump"); - - // Define a basic key-value state-preservation API across requests, with - // keys initially corresponding to the empty string. - var self = this; - var path = metadata.path; - s.importFunction(function getState(k) - { - return self._getState(path, k); - }); - s.importFunction(function setState(k, v) - { - self._setState(path, k, v); - }); - s.importFunction(function getSharedState(k) - { - return self._getSharedState(k); - }); - s.importFunction(function setSharedState(k, v) - { - self._setSharedState(k, v); - }); - s.importFunction(function getObjectState(k, callback) - { - callback(self._getObjectState(k)); - }); - s.importFunction(function setObjectState(k, v) - { - self._setObjectState(k, v); - }); - s.importFunction(function registerPathHandler(p, h) - { - self.registerPathHandler(p, h); - }); - - // Make it possible for sjs files to access their location - this._setState(path, "__LOCATION__", file.path); - - try - { - // Alas, the line number in errors dumped to console when calling the - // request handler is simply an offset from where we load the SJS file. - // Work around this in a reasonably non-fragile way by dynamically - // getting the line number where we evaluate the SJS file. Don't - // separate these two lines! - var line = new Error().lineNumber; - Cu.evalInSandbox(sis.read(file.fileSize), s); - } - catch (e) - { - dumpn("*** syntax error in SJS at " + file.path + ": " + e); - throw HTTP_500; - } - - try - { - s.handleRequest(metadata, response); - } - catch (e) - { - dump("*** error running SJS at " + file.path + ": " + - e + " on line " + - (e instanceof Error - ? e.lineNumber + " in httpd.js" - : (e.lineNumber - line)) + "\n"); - throw HTTP_500; - } - } - finally - { - fis.close(); - } - } - else - { - try - { - response.setHeader("Last-Modified", - toDateString(file.lastModifiedTime), - false); - } - catch (e) { /* lastModifiedTime threw, ignore */ } - - response.setHeader("Content-Type", type, false); - maybeAddHeaders(file, metadata, response); - response.setHeader("Content-Length", "" + count, false); - - var fis = new FileInputStream(file, PR_RDONLY, 0o444, - Ci.nsIFileInputStream.CLOSE_ON_EOF); - - offset = offset || 0; - count = count || file.fileSize; - NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset"); - NS_ASSERT(count >= 0, "bad count"); - NS_ASSERT(offset + count <= file.fileSize, "bad total data size"); - - try - { - if (offset !== 0) - { - // Seek (or read, if seeking isn't supported) to the correct offset so - // the data sent to the client matches the requested range. - if (fis instanceof Ci.nsISeekableStream) - fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset); - else - new ScriptableInputStream(fis).read(offset); - } - } - catch (e) - { - fis.close(); - throw e; - } - - let writeMore = function writeMore() - { - gThreadManager.currentThread - .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL); - } - - var input = new BinaryInputStream(fis); - var output = new BinaryOutputStream(response.bodyOutputStream); - var writeData = - { - run: function() - { - var chunkSize = Math.min(65536, count); - count -= chunkSize; - NS_ASSERT(count >= 0, "underflow"); - - try - { - var data = input.readByteArray(chunkSize); - NS_ASSERT(data.length === chunkSize, - "incorrect data returned? got " + data.length + - ", expected " + chunkSize); - output.writeByteArray(data, data.length); - if (count === 0) - { - fis.close(); - response.finish(); - } - else - { - writeMore(); - } - } - catch (e) - { - try - { - fis.close(); - } - finally - { - response.finish(); - } - throw e; - } - } - }; - - writeMore(); - - // Now that we know copying will start, flag the response as async. - response.processAsync(); - } - }, - - /** -* Get the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getState: function(path, k) - { - var state = this._state; - if (path in state && k in state[path]) - return state[path][k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for the given path for SJS state -* preservation across requests. -* -* @param path : string -* the path from which the given state is to be retrieved -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setState: function(path, k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - var state = this._state; - if (!(path in state)) - state[path] = {}; - state[path][k] = v; - }, - - /** -* Get the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be returned -* @returns string -* the corresponding value, which is initially the empty string -*/ - _getSharedState: function(k) - { - var state = this._sharedState; - if (k in state) - return state[k]; - return ""; - }, - - /** -* Set the value corresponding to a given key for SJS state preservation -* across requests. -* -* @param k : string -* the key whose corresponding value is to be set -* @param v : string -* the value to be set -*/ - _setSharedState: function(k, v) - { - if (typeof v !== "string") - throw new Error("non-string value passed"); - this._sharedState[k] = v; - }, - - /** -* Returns the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be returned -* @returns nsISupports -* the corresponding object, or null if none was present -*/ - _getObjectState: function(k) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - return this._objectState[k] || null; - }, - - /** -* Sets the object associated with the given key in the server for SJS -* state preservation across requests. -* -* @param k : string -* the key whose corresponding object is to be set -* @param v : nsISupports -* the object to be associated with the given key; may be null -*/ - _setObjectState: function(k, v) - { - if (typeof k !== "string") - throw new Error("non-string key passed"); - if (typeof v !== "object") - throw new Error("non-object value passed"); - if (v && !("QueryInterface" in v)) - { - throw new Error("must pass an nsISupports; use wrappedJSObject to ease " + - "pain when using the server from JS"); - } - - this._objectState[k] = v; - }, - - /** -* Gets a content-type for the given file, first by checking for any custom -* MIME-types registered with this handler for the file's extension, second by -* asking the global MIME service for a content-type, and finally by failing -* over to application/octet-stream. -* -* @param file : nsIFile -* the nsIFile for which to get a file type -* @returns string -* the best content-type which can be determined for the file -*/ - _getTypeFromFile: function(file) - { - try - { - var name = file.leafName; - var dot = name.lastIndexOf("."); - if (dot > 0) - { - var ext = name.slice(dot + 1); - if (ext in this._mimeMappings) - return this._mimeMappings[ext]; - } - return Cc["@mozilla.org/uriloader/external-helper-app-service;1"] - .getService(Ci.nsIMIMEService) - .getTypeFromFile(file); - } - catch (e) - { - return "application/octet-stream"; - } - }, - - /** -* Returns the nsILocalFile which corresponds to the path, as determined using -* all registered path->directory mappings and any paths which are explicitly -* overridden. -* -* @param path : string -* the server path for which a file should be retrieved, e.g. "/foo/bar" -* @throws HttpError -* when the correct action is the corresponding HTTP error (i.e., because no -* mapping was found for a directory in path, the referenced file doesn't -* exist, etc.) -* @returns nsILocalFile -* the file to be sent as the response to a request for the path -*/ - _getFileForPath: function(path) - { - // decode and add underscores as necessary - try - { - path = toInternalPath(path, true); - } - catch (e) - { - throw HTTP_400; // malformed path - } - - // next, get the directory which contains this path - var pathMap = this._pathDirectoryMap; - - // An example progression of tmp for a path "/foo/bar/baz/" might be: - // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", "" - var tmp = path.substring(1); - while (true) - { - // do we have a match for current head of the path? - var file = pathMap.get(tmp); - if (file) - { - // XXX hack; basically disable showing mapping for /foo/bar/ when the - // requested path was /foo/bar, because relative links on the page - // will all be incorrect -- we really need the ability to easily - // redirect here instead - if (tmp == path.substring(1) && - tmp.length != 0 && - tmp.charAt(tmp.length - 1) != "/") - file = null; - else - break; - } - - // if we've finished trying all prefixes, exit - if (tmp == "") - break; - - tmp = tmp.substring(0, tmp.lastIndexOf("/")); - } - - // no mapping applies, so 404 - if (!file) - throw HTTP_404; - - - // last, get the file for the path within the determined directory - var parentFolder = file.parent; - var dirIsRoot = (parentFolder == null); - - // Strategy here is to append components individually, making sure we - // never move above the given directory; this allows paths such as - // "/foo/../bar" but prevents paths such as "/../base-sibling"; - // this component-wise approach also means the code works even on platforms - // which don't use "/" as the directory separator, such as Windows - var leafPath = path.substring(tmp.length + 1); - var comps = leafPath.split("/"); - for (var i = 0, sz = comps.length; i < sz; i++) - { - var comp = comps[i]; - - if (comp == "..") - file = file.parent; - else if (comp == "." || comp == "") - continue; - else - file.append(comp); - - if (!dirIsRoot && file.equals(parentFolder)) - throw HTTP_403; - } - - return file; - }, - - /** -* Writes the error page for the given HTTP error code over the given -* connection. -* -* @param errorCode : uint -* the HTTP error code to be used -* @param connection : Connection -* the connection on which the error occurred -*/ - handleError: function(errorCode, connection) - { - var response = new Response(connection); - - dumpn("*** error in request: " + errorCode); - - this._handleError(errorCode, new Request(connection.port), response); - }, - - /** -* Handles a request which generates the given error code, using the -* user-defined error handler if one has been set, gracefully falling back to -* the x00 status code if the code has no handler, and failing to status code -* 500 if all else fails. -* -* @param errorCode : uint -* the HTTP error which is to be returned -* @param metadata : Request -* metadata for the request, which will often be incomplete since this is an -* error -* @param response : Response -* an uninitialized Response should be initialized when this method -* completes with information which represents the desired error code in the -* ideal case or a fallback code in abnormal circumstances (i.e., 500 is a -* fallback for 505, per HTTP specs) -*/ - _handleError: function(errorCode, metadata, response) - { - if (!metadata) - throw Cr.NS_ERROR_NULL_POINTER; - - var errorX00 = errorCode - (errorCode % 100); - - try - { - if (!(errorCode in HTTP_ERROR_CODES)) - dumpn("*** WARNING: requested invalid error: " + errorCode); - - // RFC 2616 says that we should try to handle an error by its class if we - // can't otherwise handle it -- if that fails, we revert to handling it as - // a 500 internal server error, and if that fails we throw and shut down - // the server - - // actually handle the error - try - { - if (errorCode in this._overrideErrors) - this._overrideErrors[errorCode](metadata, response); - else - this._defaultErrors[errorCode](metadata, response); - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(e); - return; - } - - // don't retry the handler that threw - if (errorX00 == errorCode) - throw HTTP_500; - - dumpn("*** error in handling for error code " + errorCode + ", " + - "falling back to " + errorX00 + "..."); - response = new Response(response._connection); - if (errorX00 in this._overrideErrors) - this._overrideErrors[errorX00](metadata, response); - else if (errorX00 in this._defaultErrors) - this._defaultErrors[errorX00](metadata, response); - else - throw HTTP_500; - } - } - catch (e) - { - if (response.partiallySent()) - { - response.abort(); - return; - } - - // we've tried everything possible for a meaningful error -- now try 500 - dumpn("*** error in handling for error code " + errorX00 + ", falling " + - "back to 500..."); - - try - { - response = new Response(response._connection); - if (500 in this._overrideErrors) - this._overrideErrors[500](metadata, response); - else - this._defaultErrors[500](metadata, response); - } - catch (e2) - { - dumpn("*** multiple errors in default error handlers!"); - dumpn("*** e == " + e + ", e2 == " + e2); - response.abort(e2); - return; - } - } - - response.complete(); - }, - - // FIELDS - - /** -* This object contains the default handlers for the various HTTP error codes. -*/ - _defaultErrors: - { - 400: function(metadata, response) - { - // none of the data in metadata is reliable, so hard-code everything here - response.setStatusLine("1.1", 400, "Bad Request"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Bad request\n"; - response.bodyOutputStream.write(body, body.length); - }, - 403: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 403, "Forbidden"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -403 Forbidden\ -\ -

403 Forbidden

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 404: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 404, "Not Found"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -404 Not Found\ -\ -

404 Not Found

\ -

\ -" + - htmlEscape(metadata.path) + - " was not found.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 416: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 416, - "Requested Range Not Satisfiable"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -\ -416 Requested Range Not Satisfiable\ -\ -

416 Requested Range Not Satisfiable

\ -

The byte range was not valid for the\ -requested resource.\ -

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 500: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, - 500, - "Internal Server Error"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -500 Internal Server Error\ -\ -

500 Internal Server Error

\ -

Something's broken in this server and\ -needs to be fixed.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 501: function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 501, "Not Implemented"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -501 Not Implemented\ -\ -

501 Not Implemented

\ -

This server is not (yet) Apache.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - }, - 505: function(metadata, response) - { - response.setStatusLine("1.1", 505, "HTTP Version Not Supported"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -505 HTTP Version Not Supported\ -\ -

505 HTTP Version Not Supported

\ -

This server only supports HTTP/1.0 and HTTP/1.1\ -connections.

\ -\ -"; - response.bodyOutputStream.write(body, body.length); - } - }, - - /** -* Contains handlers for the default set of URIs contained in this server. -*/ - _defaultPaths: - { - "/": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/html", false); - - var body = "\ -httpd.js\ -\ -

httpd.js

\ -

If you're seeing this page, httpd.js is up and\ -serving requests! Now set a base path and serve some\ -files!

\ -\ -"; - - response.bodyOutputStream.write(body, body.length); - }, - - "/trace": function(metadata, response) - { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - response.setHeader("Content-Type", "text/plain", false); - - var body = "Request-URI: " + - metadata.scheme + "://" + metadata.host + ":" + metadata.port + - metadata.path + "\n\n"; - body += "Request (semantically equivalent, slightly reformatted):\n\n"; - body += metadata.method + " " + metadata.path; - - if (metadata.queryString) - body += "?" + metadata.queryString; - - body += " HTTP/" + metadata.httpVersion + "\r\n"; - - var headEnum = metadata.headers; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n"; - } - - response.bodyOutputStream.write(body, body.length); - } - } -}; - - -/** -* Maps absolute paths to files on the local file system (as nsILocalFiles). -*/ -function FileMap() -{ - /** Hash which will map paths to nsILocalFiles. */ - this._map = {}; -} -FileMap.prototype = -{ - // PUBLIC API - - /** -* Maps key to a clone of the nsILocalFile value if value is non-null; -* otherwise, removes any extant mapping for key. -* -* @param key : string -* string to which a clone of value is mapped -* @param value : nsILocalFile -* the file to map to key, or null to remove a mapping -*/ - put: function(key, value) - { - if (value) - this._map[key] = value.clone(); - else - delete this._map[key]; - }, - - /** -* Returns a clone of the nsILocalFile mapped to key, or null if no such -* mapping exists. -* -* @param key : string -* key to which the returned file maps -* @returns nsILocalFile -* a clone of the mapped file, or null if no mapping exists -*/ - get: function(key) - { - var val = this._map[key]; - return val ? val.clone() : null; - } -}; - - -// Response CONSTANTS - -// token = * -// CHAR = -// CTL = -// separators = "(" | ")" | "<" | ">" | "@" -// | "," | ";" | ":" | "\" | <"> -// | "/" | "[" | "]" | "?" | "=" -// | "{" | "}" | SP | HT -const IS_TOKEN_ARRAY = - [0, 0, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, // 8 - 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 0, 0, 0, 0, 0, 0, 0, // 24 - - 0, 1, 0, 1, 1, 1, 1, 1, // 32 - 0, 0, 1, 1, 0, 1, 1, 0, // 40 - 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 1, 1, 0, 0, 0, 0, 0, 0, // 56 - - 0, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, // 72 - 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 1, 1, 1, 0, 0, 0, 1, 1, // 88 - - 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, // 104 - 1, 1, 1, 1, 1, 1, 1, 1, // 112 - 1, 1, 1, 0, 1, 0, 1]; // 120 - - -/** -* Determines whether the given character code is a CTL. -* -* @param code : uint -* the character code -* @returns boolean -* true if code is a CTL, false otherwise -*/ -function isCTL(code) -{ - return (code >= 0 && code <= 31) || (code == 127); -} - -/** -* Represents a response to an HTTP request, encapsulating all details of that -* response. This includes all headers, the HTTP version, status code and -* explanation, and the entity itself. -* -* @param connection : Connection -* the connection over which this response is to be written -*/ -function Response(connection) -{ - /** The connection over which this response will be written. */ - this._connection = connection; - - /** -* The HTTP version of this response; defaults to 1.1 if not set by the -* handler. -*/ - this._httpVersion = nsHttpVersion.HTTP_1_1; - - /** -* The HTTP code of this response; defaults to 200. -*/ - this._httpCode = 200; - - /** -* The description of the HTTP code in this response; defaults to "OK". -*/ - this._httpDescription = "OK"; - - /** -* An nsIHttpHeaders object in which the headers in this response should be -* stored. This property is null after the status line and headers have been -* written to the network, and it may be modified up until it is cleared, -* except if this._finished is set first (in which case headers are written -* asynchronously in response to a finish() call not preceded by -* flushHeaders()). -*/ - this._headers = new nsHttpHeaders(); - - /** -* Set to true when this response is ended (completely constructed if possible -* and the connection closed); further actions on this will then fail. -*/ - this._ended = false; - - /** -* A stream used to hold data written to the body of this response. -*/ - this._bodyOutputStream = null; - - /** -* A stream containing all data that has been written to the body of this -* response so far. (Async handlers make the data contained in this -* unreliable as a way of determining content length in general, but auxiliary -* saved information can sometimes be used to guarantee reliability.) -*/ - this._bodyInputStream = null; - - /** -* A stream copier which copies data to the network. It is initially null -* until replaced with a copier for response headers; when headers have been -* fully sent it is replaced with a copier for the response body, remaining -* so for the duration of response processing. -*/ - this._asyncCopier = null; - - /** -* True if this response has been designated as being processed -* asynchronously rather than for the duration of a single call to -* nsIHttpRequestHandler.handle. -*/ - this._processAsync = false; - - /** -* True iff finish() has been called on this, signaling that no more changes -* to this may be made. -*/ - this._finished = false; - - /** -* True iff powerSeized() has been called on this, signaling that this -* response is to be handled manually by the response handler (which may then -* send arbitrary data in response, even non-HTTP responses). -*/ - this._powerSeized = false; -} -Response.prototype = -{ - // PUBLIC CONSTRUCTION API - - // - // see nsIHttpResponse.bodyOutputStream - // - get bodyOutputStream() - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - if (!this._bodyOutputStream) - { - var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX, - null); - this._bodyOutputStream = pipe.outputStream; - this._bodyInputStream = pipe.inputStream; - if (this._processAsync || this._powerSeized) - this._startAsyncProcessor(); - } - - return this._bodyOutputStream; - }, - - // - // see nsIHttpResponse.write - // - write: function(data) - { - if (this._finished) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - var dataAsString = String(data); - this.bodyOutputStream.write(dataAsString, dataAsString.length); - }, - - // - // see nsIHttpResponse.setStatusLine - // - setStatusLine: function(httpVersion, code, description) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - if (!(code >= 0 && code < 1000)) - throw Cr.NS_ERROR_INVALID_ARG; - - try - { - var httpVer; - // avoid version construction for the most common cases - if (!httpVersion || httpVersion == "1.1") - httpVer = nsHttpVersion.HTTP_1_1; - else if (httpVersion == "1.0") - httpVer = nsHttpVersion.HTTP_1_0; - else - httpVer = new nsHttpVersion(httpVersion); - } - catch (e) - { - throw Cr.NS_ERROR_INVALID_ARG; - } - - // Reason-Phrase = * - // TEXT = - // - // XXX this ends up disallowing octets which aren't Unicode, I think -- not - // much to do if description is IDL'd as string - if (!description) - description = ""; - for (var i = 0; i < description.length; i++) - if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t") - throw Cr.NS_ERROR_INVALID_ARG; - - // set the values only after validation to preserve atomicity - this._httpDescription = description; - this._httpCode = code; - this._httpVersion = httpVer; - }, - - // - // see nsIHttpResponse.setHeader - // - setHeader: function(name, value, merge) - { - if (!this._headers || this._finished || this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - this._ensureAlive(); - - this._headers.setHeader(name, value, merge); - }, - - // - // see nsIHttpResponse.processAsync - // - processAsync: function() - { - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._processAsync) - return; - this._ensureAlive(); - - dumpn("*** processing connection " + this._connection.number + " async"); - this._processAsync = true; - - /* -* Either the bodyOutputStream getter or this method is responsible for -* starting the asynchronous processor and catching writes of data to the -* response body of async responses as they happen, for the purpose of -* forwarding those writes to the actual connection's output stream. -* If bodyOutputStream is accessed first, calling this method will create -* the processor (when it first is clear that body data is to be written -* immediately, not buffered). If this method is called first, accessing -* bodyOutputStream will create the processor. If only this method is -* called, we'll write nothing, neither headers nor the nonexistent body, -* until finish() is called. Since that delay is easily avoided by simply -* getting bodyOutputStream or calling write(""), we don't worry about it. -*/ - if (this._bodyOutputStream && !this._asyncCopier) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.seizePower - // - seizePower: function() - { - if (this._processAsync) - throw Cr.NS_ERROR_NOT_AVAILABLE; - if (this._finished) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._powerSeized) - return; - this._ensureAlive(); - - dumpn("*** forcefully seizing power over connection " + - this._connection.number + "..."); - - // Purge any already-written data without sending it. We could as easily - // swap out the streams entirely, but that makes it possible to acquire and - // unknowingly use a stale reference, so we require there only be one of - // each stream ever for any response to avoid this complication. - if (this._asyncCopier) - this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED); - this._asyncCopier = null; - if (this._bodyOutputStream) - { - var input = new BinaryInputStream(this._bodyInputStream); - var avail; - while ((avail = input.available()) > 0) - input.readByteArray(avail); - } - - this._powerSeized = true; - if (this._bodyOutputStream) - this._startAsyncProcessor(); - }, - - // - // see nsIHttpResponse.finish - // - finish: function() - { - if (!this._processAsync && !this._powerSeized) - throw Cr.NS_ERROR_UNEXPECTED; - if (this._finished) - return; - - dumpn("*** finishing connection " + this._connection.number); - this._startAsyncProcessor(); // in case bodyOutputStream was never accessed - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - this._finished = true; - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // POST-CONSTRUCTION API (not exposed externally) - - /** -* The HTTP version number of this, as a string (e.g. "1.1"). -*/ - get httpVersion() - { - this._ensureAlive(); - return this._httpVersion.toString(); - }, - - /** -* The HTTP status code of this response, as a string of three characters per -* RFC 2616. -*/ - get httpCode() - { - this._ensureAlive(); - - var codeString = (this._httpCode < 10 ? "0" : "") + - (this._httpCode < 100 ? "0" : "") + - this._httpCode; - return codeString; - }, - - /** -* The description of the HTTP status code of this response, or "" if none is -* set. -*/ - get httpDescription() - { - this._ensureAlive(); - - return this._httpDescription; - }, - - /** -* The headers in this response, as an nsHttpHeaders object. -*/ - get headers() - { - this._ensureAlive(); - - return this._headers; - }, - - // - // see nsHttpHeaders.getHeader - // - getHeader: function(name) - { - this._ensureAlive(); - - return this._headers.getHeader(name); - }, - - /** -* Determines whether this response may be abandoned in favor of a newly -* constructed response. A response may be abandoned only if it is not being -* sent asynchronously and if raw control over it has not been taken from the -* server. -* -* @returns boolean -* true iff no data has been written to the network -*/ - partiallySent: function() - { - dumpn("*** partiallySent()"); - return this._processAsync || this._powerSeized; - }, - - /** -* If necessary, kicks off the remaining request processing needed to be done -* after a request handler performs its initial work upon this response. -*/ - complete: function() - { - dumpn("*** complete()"); - if (this._processAsync || this._powerSeized) - { - NS_ASSERT(this._processAsync ^ this._powerSeized, - "can't both send async and relinquish power"); - return; - } - - NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?"); - - this._startAsyncProcessor(); - - // Now make sure we finish processing this request! - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - }, - - /** -* Abruptly ends processing of this response, usually due to an error in an -* incoming request but potentially due to a bad error handler. Since we -* cannot handle the error in the usual way (giving an HTTP error page in -* response) because data may already have been sent (or because the response -* might be expected to have been generated asynchronously or completely from -* scratch by the handler), we stop processing this response and abruptly -* close the connection. -* -* @param e : Error -* the exception which precipitated this abort, or null if no such exception -* was generated -*/ - abort: function(e) - { - dumpn("*** abort(<" + e + ">)"); - - // This response will be ended by the processor if one was created. - var copier = this._asyncCopier; - if (copier) - { - // We dispatch asynchronously here so that any pending writes of data to - // the connection will be deterministically written. This makes it easier - // to specify exact behavior, and it makes observable behavior more - // predictable for clients. Note that the correctness of this depends on - // callbacks in response to _waitToReadData in WriteThroughCopier - // happening asynchronously with respect to the actual writing of data to - // bodyOutputStream, as they currently do; if they happened synchronously, - // an event which ran before this one could write more data to the - // response body before we get around to canceling the copier. We have - // tests for this in test_seizepower.js, however, and I can't think of a - // way to handle both cases without removing bodyOutputStream access and - // moving its effective write(data, length) method onto Response, which - // would be slower and require more code than this anyway. - gThreadManager.currentThread.dispatch({ - run: function() - { - dumpn("*** canceling copy asynchronously..."); - copier.cancel(Cr.NS_ERROR_UNEXPECTED); - } - }, Ci.nsIThread.DISPATCH_NORMAL); - } - else - { - this.end(); - } - }, - - /** -* Closes this response's network connection, marks the response as finished, -* and notifies the server handler that the request is done being processed. -*/ - end: function() - { - NS_ASSERT(!this._ended, "ending this response twice?!?!"); - - this._connection.close(); - if (this._bodyOutputStream) - this._bodyOutputStream.close(); - - this._finished = true; - this._ended = true; - }, - - // PRIVATE IMPLEMENTATION - - /** -* Sends the status line and headers of this response if they haven't been -* sent and initiates the process of copying data written to this response's -* body to the network. -*/ - _startAsyncProcessor: function() - { - dumpn("*** _startAsyncProcessor()"); - - // Handle cases where we're being called a second time. The former case - // happens when this is triggered both by complete() and by processAsync(), - // while the latter happens when processAsync() in conjunction with sent - // data causes abort() to be called. - if (this._asyncCopier || this._ended) - { - dumpn("*** ignoring second call to _startAsyncProcessor"); - return; - } - - // Send headers if they haven't been sent already and should be sent, then - // asynchronously continue to send the body. - if (this._headers && !this._powerSeized) - { - this._sendHeaders(); - return; - } - - this._headers = null; - this._sendBody(); - }, - - /** -* Signals that all modifications to the response status line and headers are -* complete and then sends that data over the network to the client. Once -* this method completes, a different response to the request that resulted -* in this response cannot be sent -- the only possible action in case of -* error is to abort the response and close the connection. -*/ - _sendHeaders: function() - { - dumpn("*** _sendHeaders()"); - - NS_ASSERT(this._headers); - NS_ASSERT(!this._powerSeized); - - // request-line - var statusLine = "HTTP/" + this.httpVersion + " " + - this.httpCode + " " + - this.httpDescription + "\r\n"; - - // header post-processing - - var headers = this._headers; - headers.setHeader("Connection", "close", false); - headers.setHeader("Server", "httpd.js", false); - if (!headers.hasHeader("Date")) - headers.setHeader("Date", toDateString(Date.now()), false); - - // Any response not being processed asynchronously must have an associated - // Content-Length header for reasons of backwards compatibility with the - // initial server, which fully buffered every response before sending it. - // Beyond that, however, it's good to do this anyway because otherwise it's - // impossible to test behaviors that depend on the presence or absence of a - // Content-Length header. - if (!this._processAsync) - { - dumpn("*** non-async response, set Content-Length"); - - var bodyStream = this._bodyInputStream; - var avail = bodyStream ? bodyStream.available() : 0; - - // XXX assumes stream will always report the full amount of data available - headers.setHeader("Content-Length", "" + avail, false); - } - - - // construct and send response - dumpn("*** header post-processing completed, sending response head..."); - - // request-line - var preambleData = [statusLine]; - - // headers - var headEnum = headers.enumerator; - while (headEnum.hasMoreElements()) - { - var fieldName = headEnum.getNext() - .QueryInterface(Ci.nsISupportsString) - .data; - var values = headers.getHeaderValues(fieldName); - for (var i = 0, sz = values.length; i < sz; i++) - preambleData.push(fieldName + ": " + values[i] + "\r\n"); - } - - // end request-line/headers - preambleData.push("\r\n"); - - var preamble = preambleData.join(""); - - var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null); - responseHeadPipe.outputStream.write(preamble, preamble.length); - - var response = this; - var copyObserver = - { - onStartRequest: function(request, cx) - { - dumpn("*** preamble copying started"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** preamble copying complete " + - "[status=0x" + statusCode.toString(16) + "]"); - - if (!components.isSuccessCode(statusCode)) - { - dumpn("!!! header copying problems: non-success statusCode, " + - "ending response"); - - response.end(); - } - else - { - response._sendBody(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - var headerCopier = this._asyncCopier = - new WriteThroughCopier(responseHeadPipe.inputStream, - this._connection.output, - copyObserver, null); - - responseHeadPipe.outputStream.close(); - - // Forbid setting any more headers or modifying the request line. - this._headers = null; - }, - - /** -* Asynchronously writes the body of the response (or the entire response, if -* seizePower() has been called) to the network. -*/ - _sendBody: function() - { - dumpn("*** _sendBody"); - - NS_ASSERT(!this._headers, "still have headers around but sending body?"); - - // If no body data was written, we're done - if (!this._bodyInputStream) - { - dumpn("*** empty body, response finished"); - this.end(); - return; - } - - var response = this; - var copyObserver = - { - onStartRequest: function(request, context) - { - dumpn("*** onStartRequest"); - }, - - onStopRequest: function(request, cx, statusCode) - { - dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]"); - - if (statusCode === Cr.NS_BINDING_ABORTED) - { - dumpn("*** terminating copy observer without ending the response"); - } - else - { - if (!components.isSuccessCode(statusCode)) - dumpn("*** WARNING: non-success statusCode in onStopRequest"); - - response.end(); - } - }, - - QueryInterface: function(aIID) - { - if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } - }; - - dumpn("*** starting async copier of body data..."); - this._asyncCopier = - new WriteThroughCopier(this._bodyInputStream, this._connection.output, - copyObserver, null); - }, - - /** Ensures that this hasn't been ended. */ - _ensureAlive: function() - { - NS_ASSERT(!this._ended, "not handling response lifetime correctly"); - } -}; - -/** -* Size of the segments in the buffer used in storing response data and writing -* it to the socket. -*/ -Response.SEGMENT_SIZE = 8192; - -/** Serves double duty in WriteThroughCopier implementation. */ -function notImplemented() -{ - throw Cr.NS_ERROR_NOT_IMPLEMENTED; -} - -/** Returns true iff the given exception represents stream closure. */ -function streamClosed(e) -{ - return e === Cr.NS_BASE_STREAM_CLOSED || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED); -} - -/** Returns true iff the given exception represents a blocked stream. */ -function wouldBlock(e) -{ - return e === Cr.NS_BASE_STREAM_WOULD_BLOCK || - (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK); -} - -/** -* Copies data from source to sink as it becomes available, when that data can -* be written to sink without blocking. -* -* @param source : nsIAsyncInputStream -* the stream from which data is to be read -* @param sink : nsIAsyncOutputStream -* the stream to which data is to be copied -* @param observer : nsIRequestObserver -* an observer which will be notified when the copy starts and finishes -* @param context : nsISupports -* context passed to observer when notified of start/stop -* @throws NS_ERROR_NULL_POINTER -* if source, sink, or observer are null -*/ -function WriteThroughCopier(source, sink, observer, context) -{ - if (!source || !sink || !observer) - throw Cr.NS_ERROR_NULL_POINTER; - - /** Stream from which data is being read. */ - this._source = source; - - /** Stream to which data is being written. */ - this._sink = sink; - - /** Observer watching this copy. */ - this._observer = observer; - - /** Context for the observer watching this. */ - this._context = context; - - /** -* True iff this is currently being canceled (cancel has been called, the -* callback may not yet have been made). -*/ - this._canceled = false; - - /** -* False until all data has been read from input and written to output, at -* which point this copy is completed and cancel() is asynchronously called. -*/ - this._completed = false; - - /** Required by nsIRequest, meaningless. */ - this.loadFlags = 0; - /** Required by nsIRequest, meaningless. */ - this.loadGroup = null; - /** Required by nsIRequest, meaningless. */ - this.name = "response-body-copy"; - - /** Status of this request. */ - this.status = Cr.NS_OK; - - /** Arrays of byte strings waiting to be written to output. */ - this._pendingData = []; - - // start copying - try - { - observer.onStartRequest(this, context); - this._waitToReadData(); - this._waitForSinkClosure(); - } - catch (e) - { - dumpn("!!! error starting copy: " + e + - ("lineNumber" in e ? ", line " + e.lineNumber : "")); - dumpn(e.stack); - this.cancel(Cr.NS_ERROR_UNEXPECTED); - } -} -WriteThroughCopier.prototype = -{ - /* nsISupports implementation */ - - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIInputStreamCallback) || - iid.equals(Ci.nsIOutputStreamCallback) || - iid.equals(Ci.nsIRequest) || - iid.equals(Ci.nsISupports)) - { - return this; - } - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // NSIINPUTSTREAMCALLBACK - - /** -* Receives a more-data-in-input notification and writes the corresponding -* data to the output. -* -* @param input : nsIAsyncInputStream -* the input stream on whose data we have been waiting -*/ - onInputStreamReady: function(input) - { - if (this._source === null) - return; - - dumpn("*** onInputStreamReady"); - - // - // Ordinarily we'll read a non-zero amount of data from input, queue it up - // to be written and then wait for further callbacks. The complications in - // this method are the cases where we deviate from that behavior when errors - // occur or when copying is drawing to a finish. - // - // The edge cases when reading data are: - // - // Zero data is read - // If zero data was read, we're at the end of available data, so we can - // should stop reading and move on to writing out what we have (or, if - // we've already done that, onto notifying of completion). - // A stream-closed exception is thrown - // This is effectively a less kind version of zero data being read; the - // only difference is that we notify of completion with that result - // rather than with NS_OK. - // Some other exception is thrown - // This is the least kind result. We don't know what happened, so we - // act as though the stream closed except that we notify of completion - // with the result NS_ERROR_UNEXPECTED. - // - - var bytesWanted = 0, bytesConsumed = -1; - try - { - input = new BinaryInputStream(input); - - bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE); - dumpn("*** input wanted: " + bytesWanted); - - if (bytesWanted > 0) - { - var data = input.readByteArray(bytesWanted); - bytesConsumed = data.length; - this._pendingData.push(String.fromCharCode.apply(String, data)); - } - - dumpn("*** " + bytesConsumed + " bytes read"); - - // Handle the zero-data edge case in the same place as all other edge - // cases are handled. - if (bytesWanted === 0) - throw Cr.NS_BASE_STREAM_CLOSED; - } - catch (e) - { - if (streamClosed(e)) - { - dumpn("*** input stream closed"); - e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED; - } - else - { - dumpn("!!! unexpected error reading from input, canceling: " + e); - e = Cr.NS_ERROR_UNEXPECTED; - } - - this._doneReadingSource(e); - return; - } - - var pendingData = this._pendingData; - - NS_ASSERT(bytesConsumed > 0); - NS_ASSERT(pendingData.length > 0, "no pending data somehow?"); - NS_ASSERT(pendingData[pendingData.length - 1].length > 0, - "buffered zero bytes of data?"); - - NS_ASSERT(this._source !== null); - - // Reading has gone great, and we've gotten data to write now. What if we - // don't have a place to write that data, because output went away just - // before this read? Drop everything on the floor, including new data, and - // cancel at this point. - if (this._sink === null) - { - pendingData.length = 0; - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we've read the data, and we know we have a place to write it. We - // need to queue up the data to be written, but *only* if none is queued - // already -- if data's already queued, the code that actually writes the - // data will make sure to wait on unconsumed pending data. - try - { - if (pendingData.length === 1) - this._waitToWriteData(); - } - catch (e) - { - dumpn("!!! error waiting to write data just read, swallowing and " + - "writing only what we already have: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Whee! We successfully read some data, and it's successfully queued up to - // be written. All that remains now is to wait for more data to read. - try - { - this._waitToReadData(); - } - catch (e) - { - dumpn("!!! error waiting to read more data: " + e); - this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED); - } - }, - - - // NSIOUTPUTSTREAMCALLBACK - - /** -* Callback when data may be written to the output stream without blocking, or -* when the output stream has been closed. -* -* @param output : nsIAsyncOutputStream -* the output stream on whose writability we've been waiting, also known as -* this._sink -*/ - onOutputStreamReady: function(output) - { - if (this._sink === null) - return; - - dumpn("*** onOutputStreamReady"); - - var pendingData = this._pendingData; - if (pendingData.length === 0) - { - // There's no pending data to write. The only way this can happen is if - // we're waiting on the output stream's closure, so we can respond to a - // copying failure as quickly as possible (rather than waiting for data to - // be available to read and then fail to be copied). Therefore, we must - // be done now -- don't bother to attempt to write anything and wrap - // things up. - dumpn("!!! output stream closed prematurely, ending copy"); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - - NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?"); - - // - // Write out the first pending quantum of data. The possible errors here - // are: - // - // The write might fail because we can't write that much data - // Okay, we've written what we can now, so re-queue what's left and - // finish writing it out later. - // The write failed because the stream was closed - // Discard pending data that we can no longer write, stop reading, and - // signal that copying finished. - // Some other error occurred. - // Same as if the stream were closed, but notify with the status - // NS_ERROR_UNEXPECTED so the observer knows something was wonky. - // - - try - { - var quantum = pendingData[0]; - - // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on - // undefined behavior! We're only using this because writeByteArray - // is unusably broken for asynchronous output streams; see bug 532834 - // for details. - var bytesWritten = output.write(quantum, quantum.length); - if (bytesWritten === quantum.length) - pendingData.shift(); - else - pendingData[0] = quantum.substring(bytesWritten); - - dumpn("*** wrote " + bytesWritten + " bytes of data"); - } - catch (e) - { - if (wouldBlock(e)) - { - NS_ASSERT(pendingData.length > 0, - "stream-blocking exception with no data to write?"); - NS_ASSERT(pendingData[0].length > 0, - "stream-blocking exception with empty quantum?"); - this._waitToWriteData(); - return; - } - - if (streamClosed(e)) - dumpn("!!! output stream prematurely closed, signaling error..."); - else - dumpn("!!! unknown error: " + e + ", quantum=" + quantum); - - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // The day is ours! Quantum written, now let's see if we have more data - // still to write. - try - { - if (pendingData.length > 0) - { - this._waitToWriteData(); - return; - } - } - catch (e) - { - dumpn("!!! unexpected error waiting to write pending data: " + e); - this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED); - return; - } - - // Okay, we have no more pending data to write -- but might we get more in - // the future? - if (this._source !== null) - { - /* -* If we might, then wait for the output stream to be closed. (We wait -* only for closure because we have no data to write -- and if we waited -* for a specific amount of data, we would get repeatedly notified for no -* reason if over time the output stream permitted more and more data to -* be written to it without blocking.) -*/ - this._waitForSinkClosure(); - } - else - { - /* -* On the other hand, if we can't have more data because the input -* stream's gone away, then it's time to notify of copy completion. -* Victory! -*/ - this._sink = null; - this._cancelOrDispatchCancelCallback(Cr.NS_OK); - } - }, - - - // NSIREQUEST - - /** Returns true if the cancel observer hasn't been notified yet. */ - isPending: function() - { - return !this._completed; - }, - - /** Not implemented, don't use! */ - suspend: notImplemented, - /** Not implemented, don't use! */ - resume: notImplemented, - - /** -* Cancels data reading from input, asynchronously writes out any pending -* data, and causes the observer to be notified with the given error code when -* all writing has finished. -* -* @param status : nsresult -* the status to pass to the observer when data copying has been canceled -*/ - cancel: function(status) - { - dumpn("*** cancel(" + status.toString(16) + ")"); - - if (this._canceled) - { - dumpn("*** suppressing a late cancel"); - return; - } - - this._canceled = true; - this.status = status; - - // We could be in the middle of absolutely anything at this point. Both - // input and output might still be around, we might have pending data to - // write, and in general we know nothing about the state of the world. We - // therefore must assume everything's in progress and take everything to its - // final steady state (or so far as it can go before we need to finish - // writing out remaining data). - - this._doneReadingSource(status); - }, - - - // PRIVATE IMPLEMENTATION - - /** -* Stop reading input if we haven't already done so, passing e as the status -* when closing the stream, and kick off a copy-completion notice if no more -* data remains to be written. -* -* @param e : nsresult -* the status to be used when closing the input stream -*/ - _doneReadingSource: function(e) - { - dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")"); - - this._finishSource(e); - if (this._pendingData.length === 0) - this._sink = null; - else - NS_ASSERT(this._sink !== null, "null output?"); - - // If we've written out all data read up to this point, then it's time to - // signal completion. - if (this._sink === null) - { - NS_ASSERT(this._pendingData.length === 0, "pending data still?"); - this._cancelOrDispatchCancelCallback(e); - } - }, - - /** -* Stop writing output if we haven't already done so, discard any data that -* remained to be sent, close off input if it wasn't already closed, and kick -* off a copy-completion notice. -* -* @param e : nsresult -* the status to be used when closing input if it wasn't already closed -*/ - _doneWritingToSink: function(e) - { - dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")"); - - this._pendingData.length = 0; - this._sink = null; - this._doneReadingSource(e); - }, - - /** -* Completes processing of this copy: either by canceling the copy if it -* hasn't already been canceled using the provided status, or by dispatching -* the cancel callback event (with the originally provided status, of course) -* if it already has been canceled. -* -* @param status : nsresult -* the status code to use to cancel this, if this hasn't already been -* canceled -*/ - _cancelOrDispatchCancelCallback: function(status) - { - dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")"); - - NS_ASSERT(this._source === null, "should have finished input"); - NS_ASSERT(this._sink === null, "should have finished output"); - NS_ASSERT(this._pendingData.length === 0, "should have no pending data"); - - if (!this._canceled) - { - this.cancel(status); - return; - } - - var self = this; - var event = - { - run: function() - { - dumpn("*** onStopRequest async callback"); - - self._completed = true; - try - { - self._observer.onStopRequest(self, self._context, self.status); - } - catch (e) - { - NS_ASSERT(false, - "how are we throwing an exception here? we control " + - "all the callers! " + e); - } - } - }; - - gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL); - }, - - /** -* Kicks off another wait for more data to be available from the input stream. -*/ - _waitToReadData: function() - { - dumpn("*** _waitToReadData"); - this._source.asyncWait(this, 0, Response.SEGMENT_SIZE, - gThreadManager.mainThread); - }, - - /** -* Kicks off another wait until data can be written to the output stream. -*/ - _waitToWriteData: function() - { - dumpn("*** _waitToWriteData"); - - var pendingData = this._pendingData; - NS_ASSERT(pendingData.length > 0, "no pending data to write?"); - NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?"); - - this._sink.asyncWait(this, 0, pendingData[0].length, - gThreadManager.mainThread); - }, - - /** -* Kicks off a wait for the sink to which data is being copied to be closed. -* We wait for stream closure when we don't have any data to be copied, rather -* than waiting to write a specific amount of data. We can't wait to write -* data because the sink might be infinitely writable, and if no data appears -* in the source for a long time we might have to spin quite a bit waiting to -* write, waiting to write again, &c. Waiting on stream closure instead means -* we'll get just one notification if the sink dies. Note that when data -* starts arriving from the sink we'll resume waiting for data to be written, -* dropping this closure-only callback entirely. -*/ - _waitForSinkClosure: function() - { - dumpn("*** _waitForSinkClosure"); - - this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0, - gThreadManager.mainThread); - }, - - /** -* Closes input with the given status, if it hasn't already been closed; -* otherwise a no-op. -* -* @param status : nsresult -* status code use to close the source stream if necessary -*/ - _finishSource: function(status) - { - dumpn("*** _finishSource(" + status.toString(16) + ")"); - - if (this._source !== null) - { - this._source.closeWithStatus(status); - this._source = null; - } - } -}; - - -/** -* A container for utility functions used with HTTP headers. -*/ -const headerUtils = -{ - /** -* Normalizes fieldName (by converting it to lowercase) and ensures it is a -* valid header field name (although not necessarily one specified in RFC -* 2616). -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not match the field-name production in RFC 2616 -* @returns string -* fieldName converted to lowercase if it is a valid header, for characters -* where case conversion is possible -*/ - normalizeFieldName: function(fieldName) - { - if (fieldName == "") - throw Cr.NS_ERROR_INVALID_ARG; - - for (var i = 0, sz = fieldName.length; i < sz; i++) - { - if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)]) - { - dumpn(fieldName + " is not a valid header field name!"); - throw Cr.NS_ERROR_INVALID_ARG; - } - } - - return fieldName.toLowerCase(); - }, - - /** -* Ensures that fieldValue is a valid header field value (although not -* necessarily as specified in RFC 2616 if the corresponding field name is -* part of the HTTP protocol), normalizes the value if it is, and -* returns the normalized value. -* -* @param fieldValue : string -* a value to be normalized as an HTTP header field value -* @throws NS_ERROR_INVALID_ARG -* if fieldValue does not match the field-value production in RFC 2616 -* @returns string -* fieldValue as a normalized HTTP header field value -*/ - normalizeFieldValue: function(fieldValue) - { - // field-value = *( field-content | LWS ) - // field-content = - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // - // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) - // qdtext = > - // quoted-pair = "\" CHAR - // CHAR = - - // Any LWS that occurs between field-content MAY be replaced with a single - // SP before interpreting the field value or forwarding the message - // downstream (section 4.2); we replace 1*LWS with a single SP - var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " "); - - // remove leading/trailing LWS (which has been converted to SP) - val = val.replace(/^ +/, "").replace(/ +$/, ""); - - // that should have taken care of all CTLs, so val should contain no CTLs - for (var i = 0, len = val.length; i < len; i++) - if (isCTL(val.charCodeAt(i))) - throw Cr.NS_ERROR_INVALID_ARG; - - // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly - // normalize, however, so this can be construed as a tightening of the - // spec and not entirely as a bug - return val; - } -}; - - - -/** -* Converts the given string into a string which is safe for use in an HTML -* context. -* -* @param str : string -* the string to make HTML-safe -* @returns string -* an HTML-safe version of str -*/ -function htmlEscape(str) -{ - // this is naive, but it'll work - var s = ""; - for (var i = 0; i < str.length; i++) - s += "&#" + str.charCodeAt(i) + ";"; - return s; -} - - -/** -* Constructs an object representing an HTTP version (see section 3.1). -* -* @param versionString -* a string of the form "#.#", where # is an non-negative decimal integer with -* or without leading zeros -* @throws -* if versionString does not specify a valid HTTP version number -*/ -function nsHttpVersion(versionString) -{ - var matches = /^(\d+)\.(\d+)$/.exec(versionString); - if (!matches) - throw "Not a valid HTTP version!"; - - /** The major version number of this, as a number. */ - this.major = parseInt(matches[1], 10); - - /** The minor version number of this, as a number. */ - this.minor = parseInt(matches[2], 10); - - if (isNaN(this.major) || isNaN(this.minor) || - this.major < 0 || this.minor < 0) - throw "Not a valid HTTP version!"; -} -nsHttpVersion.prototype = -{ - /** -* Returns the standard string representation of the HTTP version represented -* by this (e.g., "1.1"). -*/ - toString: function () - { - return this.major + "." + this.minor; - }, - - /** -* Returns true if this represents the same HTTP version as otherVersion, -* false otherwise. -* -* @param otherVersion : nsHttpVersion -* the version to compare against this -*/ - equals: function (otherVersion) - { - return this.major == otherVersion.major && - this.minor == otherVersion.minor; - }, - - /** True if this >= otherVersion, false otherwise. */ - atLeast: function(otherVersion) - { - return this.major > otherVersion.major || - (this.major == otherVersion.major && - this.minor >= otherVersion.minor); - } -}; - -nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0"); -nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1"); - - -/** -* An object which stores HTTP headers for a request or response. -* -* Note that since headers are case-insensitive, this object converts headers to -* lowercase before storing them. This allows the getHeader and hasHeader -* methods to work correctly for any case of a header, but it means that the -* values returned by .enumerator may not be equal case-sensitively to the -* values passed to setHeader when adding headers to this. -*/ -function nsHttpHeaders() -{ - /** -* A hash of headers, with header field names as the keys and header field -* values as the values. Header field names are case-insensitive, but upon -* insertion here they are converted to lowercase. Header field values are -* normalized upon insertion to contain no leading or trailing whitespace. -* -* Note also that per RFC 2616, section 4.2, two headers with the same name in -* a message may be treated as one header with the same field name and a field -* value consisting of the separate field values joined together with a "," in -* their original order. This hash stores multiple headers with the same name -* in this manner. -*/ - this._headers = {}; -} -nsHttpHeaders.prototype = -{ - /** -* Sets the header represented by name and value in this. -* -* @param name : string -* the header name -* @param value : string -* the header value -* @throws NS_ERROR_INVALID_ARG -* if name or value is not a valid header component -*/ - setHeader: function(fieldName, fieldValue, merge) - { - var name = headerUtils.normalizeFieldName(fieldName); - var value = headerUtils.normalizeFieldValue(fieldValue); - - // The following three headers are stored as arrays because their real-world - // syntax prevents joining individual headers into a single header using - // ",". See also - if (merge && name in this._headers) - { - if (name === "www-authenticate" || - name === "proxy-authenticate" || - name === "set-cookie") - { - this._headers[name].push(value); - } - else - { - this._headers[name][0] += "," + value; - NS_ASSERT(this._headers[name].length === 1, - "how'd a non-special header have multiple values?") - } - } - else - { - this._headers[name] = [value]; - } - }, - - /** -* Returns the value for the header specified by this. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns string -* the field value for the given header, possibly with non-semantic changes -* (i.e., leading/trailing whitespace stripped, whitespace runs replaced -* with spaces, etc.) at the option of the implementation; multiple -* instances of the header will be combined with a comma, except for -* the three headers noted in the description of getHeaderValues -*/ - getHeader: function(fieldName) - { - return this.getHeaderValues(fieldName).join("\n"); - }, - - /** -* Returns the value for the header specified by fieldName as an array. -* -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @throws NS_ERROR_NOT_AVAILABLE -* if the given header does not exist in this -* @returns [string] -* an array of all the header values in this for the given -* header name. Header values will generally be collapsed -* into a single header by joining all header values together -* with commas, but certain headers (Proxy-Authenticate, -* WWW-Authenticate, and Set-Cookie) violate the HTTP spec -* and cannot be collapsed in this manner. For these headers -* only, the returned array may contain multiple elements if -* that header has been added more than once. -*/ - getHeaderValues: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - - if (name in this._headers) - return this._headers[name]; - else - throw Cr.NS_ERROR_NOT_AVAILABLE; - }, - - /** -* Returns true if a header with the given field name exists in this, false -* otherwise. -* -* @param fieldName : string -* the field name whose existence is to be determined in this -* @throws NS_ERROR_INVALID_ARG -* if fieldName does not constitute a valid header field name -* @returns boolean -* true if the header's present, false otherwise -*/ - hasHeader: function(fieldName) - { - var name = headerUtils.normalizeFieldName(fieldName); - return (name in this._headers); - }, - - /** -* Returns a new enumerator over the field names of the headers in this, as -* nsISupportsStrings. The names returned will be in lowercase, regardless of -* how they were input using setHeader (header names are case-insensitive per -* RFC 2616). -*/ - get enumerator() - { - var headers = []; - for (var i in this._headers) - { - var supports = new SupportsString(); - supports.data = i; - headers.push(supports); - } - - return new nsSimpleEnumerator(headers); - } -}; - - -/** -* Constructs an nsISimpleEnumerator for the given array of items. -* -* @param items : Array -* the items, which must all implement nsISupports -*/ -function nsSimpleEnumerator(items) -{ - this._items = items; - this._nextIndex = 0; -} -nsSimpleEnumerator.prototype = -{ - hasMoreElements: function() - { - return this._nextIndex < this._items.length; - }, - getNext: function() - { - if (!this.hasMoreElements()) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - return this._items[this._nextIndex++]; - }, - QueryInterface: function(aIID) - { - if (Ci.nsISimpleEnumerator.equals(aIID) || - Ci.nsISupports.equals(aIID)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - } -}; - - -/** -* A representation of the data in an HTTP request. -* -* @param port : uint -* the port on which the server receiving this request runs -*/ -function Request(port) -{ - /** Method of this request, e.g. GET or POST. */ - this._method = ""; - - /** Path of the requested resource; empty paths are converted to '/'. */ - this._path = ""; - - /** Query string, if any, associated with this request (not including '?'). */ - this._queryString = ""; - - /** Scheme of requested resource, usually http, always lowercase. */ - this._scheme = "http"; - - /** Hostname on which the requested resource resides. */ - this._host = undefined; - - /** Port number over which the request was received. */ - this._port = port; - - var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null); - - /** Stream from which data in this request's body may be read. */ - this._bodyInputStream = bodyPipe.inputStream; - - /** Stream to which data in this request's body is written. */ - this._bodyOutputStream = bodyPipe.outputStream; - - /** -* The headers in this request. -*/ - this._headers = new nsHttpHeaders(); - - /** -* For the addition of ad-hoc properties and new functionality without having -* to change nsIHttpRequest every time; currently lazily created, as its only -* use is in directory listings. -*/ - this._bag = null; -} -Request.prototype = -{ - // SERVER METADATA - - // - // see nsIHttpRequest.scheme - // - get scheme() - { - return this._scheme; - }, - - // - // see nsIHttpRequest.host - // - get host() - { - return this._host; - }, - - // - // see nsIHttpRequest.port - // - get port() - { - return this._port; - }, - - // REQUEST LINE - - // - // see nsIHttpRequest.method - // - get method() - { - return this._method; - }, - - // - // see nsIHttpRequest.httpVersion - // - get httpVersion() - { - return this._httpVersion.toString(); - }, - - // - // see nsIHttpRequest.path - // - get path() - { - return this._path; - }, - - // - // see nsIHttpRequest.queryString - // - get queryString() - { - return this._queryString; - }, - - // HEADERS - - // - // see nsIHttpRequest.getHeader - // - getHeader: function(name) - { - return this._headers.getHeader(name); - }, - - // - // see nsIHttpRequest.hasHeader - // - hasHeader: function(name) - { - return this._headers.hasHeader(name); - }, - - // - // see nsIHttpRequest.headers - // - get headers() - { - return this._headers.enumerator; - }, - - // - // see nsIPropertyBag.enumerator - // - get enumerator() - { - this._ensurePropertyBag(); - return this._bag.enumerator; - }, - - // - // see nsIHttpRequest.headers - // - get bodyInputStream() - { - return this._bodyInputStream; - }, - - // - // see nsIPropertyBag.getProperty - // - getProperty: function(name) - { - this._ensurePropertyBag(); - return this._bag.getProperty(name); - }, - - - // NSISUPPORTS - - // - // see nsISupports.QueryInterface - // - QueryInterface: function(iid) - { - if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - - // PRIVATE IMPLEMENTATION - - /** Ensures a property bag has been created for ad-hoc behaviors. */ - _ensurePropertyBag: function() - { - if (!this._bag) - this._bag = new WritablePropertyBag(); - } -}; - - -// XPCOM trappings -if ("XPCOMUtils" in this && // Firefox 3.6 doesn't load XPCOMUtils in this scope for some reason... - "generateNSGetFactory" in XPCOMUtils) { - var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]); -} - - - -/** -* Creates a new HTTP server listening for loopback traffic on the given port, -* starts it, and runs the server until the server processes a shutdown request, -* spinning an event loop so that events posted by the server's socket are -* processed. -* -* This method is primarily intended for use in running this script from within -* xpcshell and running a functional HTTP server without having to deal with -* non-essential details. -* -* Note that running multiple servers using variants of this method probably -* doesn't work, simply due to how the internal event loop is spun and stopped. -* -* @note -* This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code); -* you should use this server as a component in Mozilla 1.8. -* @param port -* the port on which the server will run, or -1 if there exists no preference -* for a specific port; note that attempting to use some values for this -* parameter (particularly those below 1024) may cause this method to throw or -* may result in the server being prematurely shut down -* @param basePath -* a local directory from which requests will be served (i.e., if this is -* "/home/jwalden/" then a request to /index.html will load -* /home/jwalden/index.html); if this is omitted, only the default URLs in -* this server implementation will be functional -*/ -function server(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - // if you're running this, you probably want to see debugging info - DEBUG = true; - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", SJS_TYPE); - srv.start(port); - - var thread = gThreadManager.currentThread; - while (!srv.isStopped()) - thread.processNextEvent(true); - - // get rid of any pending requests - while (thread.hasPendingEvents()) - thread.processNextEvent(true); - - DEBUG = false; -} - -function startServerAsync(port, basePath) -{ - if (basePath) - { - var lp = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - lp.initWithPath(basePath); - } - - var srv = new nsHttpServer(); - if (lp) - srv.registerDirectory("/", lp); - srv.registerContentType("sjs", "sjs"); - srv.start(port); - return srv; -} - -exports.nsHttpServer = nsHttpServer; -exports.ScriptableInputStream = ScriptableInputStream; -exports.server = server; -exports.startServerAsync = startServerAsync; diff --git a/addon-sdk/source/test/loader/b2g.js b/addon-sdk/source/test/loader/b2g.js deleted file mode 100644 index 2982a02026..0000000000 --- a/addon-sdk/source/test/loader/b2g.js +++ /dev/null @@ -1,41 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const {Cc, Ci, Cu} = require("chrome"); -const {readURISync} = require("sdk/net/url"); - -const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]. - createInstance(Ci.nsIPrincipal); - - -const FakeCu = function() { - const sandbox = Cu.Sandbox(systemPrincipal, {wantXrays: false}); - sandbox.toString = function() { - return "[object BackstagePass]"; - } - this.sandbox = sandbox; -} -FakeCu.prototype = { - ["import"](url, scope) { - const {sandbox} = this; - sandbox.__URI__ = url; - const target = Cu.createObjectIn(sandbox); - target.toString = sandbox.toString; - Cu.evalInSandbox(`(function(){` + readURISync(url) + `\n})`, - sandbox, "1.8", url).call(target); - // Borrowed from mozJSComponentLoader.cpp to match errors closer. - // https://github.com/mozilla/gecko-dev/blob/f6ca65e8672433b2ce1a0e7c31f72717930b5e27/js/xpconnect/loader/mozJSComponentLoader.cpp#L1205-L1208 - if (!Array.isArray(target.EXPORTED_SYMBOLS)) { - throw Error("EXPORTED_SYMBOLS is not an array."); - } - - for (let key of target.EXPORTED_SYMBOLS) { - scope[key] = target[key]; - } - - return target; - } -}; -exports.FakeCu = FakeCu; diff --git a/addon-sdk/source/test/loader/fixture.js b/addon-sdk/source/test/loader/fixture.js deleted file mode 100644 index ebf91abba3..0000000000 --- a/addon-sdk/source/test/loader/fixture.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.foo = foo; -exports.bar = 2; -print('testing'); diff --git a/addon-sdk/source/test/loader/user-global.js b/addon-sdk/source/test/loader/user-global.js deleted file mode 100644 index 34b233f429..0000000000 --- a/addon-sdk/source/test/loader/user-global.js +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -// Test module to check presense of user defined globals. -// Related to bug 827792. - -exports.getCom = function() { - return com; -}; diff --git a/addon-sdk/source/test/modules/add.js b/addon-sdk/source/test/modules/add.js deleted file mode 100644 index 54729340dd..0000000000 --- a/addon-sdk/source/test/modules/add.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define('modules/add', function () { - return function (a, b) { - return a + b; - }; -}); diff --git a/addon-sdk/source/test/modules/async1.js b/addon-sdk/source/test/modules/async1.js deleted file mode 100644 index b7b60fdc26..0000000000 --- a/addon-sdk/source/test/modules/async1.js +++ /dev/null @@ -1,14 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(['./traditional2', './async2'], function () { - var traditional2 = require('./traditional2'); - return { - name: 'async1', - traditional1Name: traditional2.traditional1Name, - traditional2Name: traditional2.name, - async2Name: require('./async2').name, - async2Traditional2Name: require('./async2').traditional2Name - }; -}); diff --git a/addon-sdk/source/test/modules/async2.js b/addon-sdk/source/test/modules/async2.js deleted file mode 100644 index 802fb2504c..0000000000 --- a/addon-sdk/source/test/modules/async2.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(['./traditional2', 'exports'], function (traditional2, exports) { - exports.name = 'async2'; - exports.traditional2Name = traditional2.name; -}); diff --git a/addon-sdk/source/test/modules/badExportAndReturn.js b/addon-sdk/source/test/modules/badExportAndReturn.js deleted file mode 100644 index 35a5359f60..0000000000 --- a/addon-sdk/source/test/modules/badExportAndReturn.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// This is a bad module, it asks for exports but also returns a value from -// the define defintion function. -define(['exports'], function (exports) { - return 'badExportAndReturn'; -}); - diff --git a/addon-sdk/source/test/modules/badFirst.js b/addon-sdk/source/test/modules/badFirst.js deleted file mode 100644 index fdb03bd4dd..0000000000 --- a/addon-sdk/source/test/modules/badFirst.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(['./badSecond'], function (badSecond) { - return { - name: 'badFirst' - }; -}); diff --git a/addon-sdk/source/test/modules/badSecond.js b/addon-sdk/source/test/modules/badSecond.js deleted file mode 100644 index 8321a85426..0000000000 --- a/addon-sdk/source/test/modules/badSecond.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var first = require('./badFirst'); - -exports.name = 'badSecond'; -exports.badFirstName = first.name; diff --git a/addon-sdk/source/test/modules/blue.js b/addon-sdk/source/test/modules/blue.js deleted file mode 100644 index 14ab149337..0000000000 --- a/addon-sdk/source/test/modules/blue.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(function () { - return { - name: 'blue' - }; -}); diff --git a/addon-sdk/source/test/modules/castor.js b/addon-sdk/source/test/modules/castor.js deleted file mode 100644 index 9209623b5b..0000000000 --- a/addon-sdk/source/test/modules/castor.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(['exports', './pollux'], function(exports, pollux) { - exports.name = 'castor'; - exports.getPolluxName = function () { - return pollux.name; - }; -}); diff --git a/addon-sdk/source/test/modules/cheetah.js b/addon-sdk/source/test/modules/cheetah.js deleted file mode 100644 index 37d12bfc1c..0000000000 --- a/addon-sdk/source/test/modules/cheetah.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(function () { - return function () { - return 'cheetah'; - }; -}); diff --git a/addon-sdk/source/test/modules/color.js b/addon-sdk/source/test/modules/color.js deleted file mode 100644 index 0d946bd999..0000000000 --- a/addon-sdk/source/test/modules/color.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define({ - type: 'color' -}); diff --git a/addon-sdk/source/test/modules/dupe.js b/addon-sdk/source/test/modules/dupe.js deleted file mode 100644 index f87fa555a0..0000000000 --- a/addon-sdk/source/test/modules/dupe.js +++ /dev/null @@ -1,15 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define({ - name: 'dupe' -}); - -// This is wrong and should not be allowed. Only one call to -// define per file. -define([], function () { - return { - name: 'dupe2' - }; -}); diff --git a/addon-sdk/source/test/modules/dupeNested.js b/addon-sdk/source/test/modules/dupeNested.js deleted file mode 100644 index 703948ca74..0000000000 --- a/addon-sdk/source/test/modules/dupeNested.js +++ /dev/null @@ -1,15 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - -define(function () { - // This is wrong and should not be allowed. - define('dupeNested2', { - name: 'dupeNested2' - }); - - return { - name: 'dupeNested' - }; -}); diff --git a/addon-sdk/source/test/modules/dupeSetExports.js b/addon-sdk/source/test/modules/dupeSetExports.js deleted file mode 100644 index 12a1be22bc..0000000000 --- a/addon-sdk/source/test/modules/dupeSetExports.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define({name: "dupeSetExports"}); - -// so this should cause a failure -module.setExports("no no no"); diff --git a/addon-sdk/source/test/modules/exportsEquals.js b/addon-sdk/source/test/modules/exportsEquals.js deleted file mode 100644 index e176316f45..0000000000 --- a/addon-sdk/source/test/modules/exportsEquals.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -module.exports = 4; diff --git a/addon-sdk/source/test/modules/green.js b/addon-sdk/source/test/modules/green.js deleted file mode 100644 index ce2d134b9f..0000000000 --- a/addon-sdk/source/test/modules/green.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define('modules/green', ['./color'], function (color) { - return { - name: 'green', - parentType: color.type - }; -}); diff --git a/addon-sdk/source/test/modules/lion.js b/addon-sdk/source/test/modules/lion.js deleted file mode 100644 index 9c3ac3bfe0..0000000000 --- a/addon-sdk/source/test/modules/lion.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(function(require) { - return 'lion'; -}); diff --git a/addon-sdk/source/test/modules/orange.js b/addon-sdk/source/test/modules/orange.js deleted file mode 100644 index 900a32b082..0000000000 --- a/addon-sdk/source/test/modules/orange.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(['./color'], function (color) { - return { - name: 'orange', - parentType: color.type - }; -}); diff --git a/addon-sdk/source/test/modules/pollux.js b/addon-sdk/source/test/modules/pollux.js deleted file mode 100644 index 61cf66f9ca..0000000000 --- a/addon-sdk/source/test/modules/pollux.js +++ /dev/null @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(['exports', './castor'], function(exports, castor) { - exports.name = 'pollux'; - exports.getCastorName = function () { - return castor.name; - }; -}); diff --git a/addon-sdk/source/test/modules/red.js b/addon-sdk/source/test/modules/red.js deleted file mode 100644 index c47b8e9249..0000000000 --- a/addon-sdk/source/test/modules/red.js +++ /dev/null @@ -1,16 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(function (require) { - // comment fake-outs for require finding. - // require('bad1'); - return { - name: 'red', - parentType: require('./color').type - }; - - /* - require('bad2'); - */ -}); diff --git a/addon-sdk/source/test/modules/setExports.js b/addon-sdk/source/test/modules/setExports.js deleted file mode 100644 index 495c301cd7..0000000000 --- a/addon-sdk/source/test/modules/setExports.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -module.setExports(5); diff --git a/addon-sdk/source/test/modules/subtract.js b/addon-sdk/source/test/modules/subtract.js deleted file mode 100644 index 06e1053ab0..0000000000 --- a/addon-sdk/source/test/modules/subtract.js +++ /dev/null @@ -1,9 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(function () { - return function (a, b) { - return a - b; - } -}); diff --git a/addon-sdk/source/test/modules/tiger.js b/addon-sdk/source/test/modules/tiger.js deleted file mode 100644 index 80479d0199..0000000000 --- a/addon-sdk/source/test/modules/tiger.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -define(function (require, exports) { - exports.name = 'tiger'; - exports.type = require('./types/cat').type; -}); diff --git a/addon-sdk/source/test/modules/traditional1.js b/addon-sdk/source/test/modules/traditional1.js deleted file mode 100644 index 28af8ef30f..0000000000 --- a/addon-sdk/source/test/modules/traditional1.js +++ /dev/null @@ -1,12 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.name = 'traditional1' - -var async1 = require('./async1'); - -exports.traditional2Name = async1.traditional2Name; -exports.traditional1Name = async1.traditional1Name; -exports.async2Name = async1.async2Name; -exports.async2Traditional2Name = async1.async2Traditional2Name; diff --git a/addon-sdk/source/test/modules/traditional2.js b/addon-sdk/source/test/modules/traditional2.js deleted file mode 100644 index 67b64eecbb..0000000000 --- a/addon-sdk/source/test/modules/traditional2.js +++ /dev/null @@ -1,6 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.name = 'traditional2'; -exports.traditional1Name = require('./traditional1').name; diff --git a/addon-sdk/source/test/modules/types/cat.js b/addon-sdk/source/test/modules/types/cat.js deleted file mode 100644 index 41513d6825..0000000000 --- a/addon-sdk/source/test/modules/types/cat.js +++ /dev/null @@ -1,5 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.type = 'cat'; diff --git a/addon-sdk/source/test/page-mod/helpers.js b/addon-sdk/source/test/page-mod/helpers.js deleted file mode 100644 index 3aa3deb0d6..0000000000 --- a/addon-sdk/source/test/page-mod/helpers.js +++ /dev/null @@ -1,117 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cc, Ci } = require("chrome"); -const { setTimeout } = require("sdk/timers"); -const { Loader } = require("sdk/test/loader"); -const { openTab, getBrowserForTab, closeTab } = require("sdk/tabs/utils"); -const { getMostRecentBrowserWindow } = require("sdk/window/utils"); -const { merge } = require("sdk/util/object"); -const httpd = require("../lib/httpd"); -const { cleanUI } = require("sdk/test/utils"); - -const PORT = 8099; -const PATH = '/test-contentScriptWhen.html'; - -function createLoader () { - let options = merge({}, require('@loader/options'), - { id: "testloader", prefixURI: require('../fixtures').url() }); - return Loader(module, null, options); -} -exports.createLoader = createLoader; - -function openNewTab(url) { - return openTab(getMostRecentBrowserWindow(), url, { - inBackground: false - }); -} -exports.openNewTab = openNewTab; - -// an evil function enables the creation of tests -// that depend on delicate event timing. do not use. -function testPageMod(assert, done, testURL, pageModOptions, - testCallback, timeout) { - let loader = createLoader(); - let { PageMod } = loader.require("sdk/page-mod"); - let pageMods = pageModOptions.map(opts => new PageMod(opts)); - let newTab = openNewTab(testURL); - let b = getBrowserForTab(newTab); - - function onPageLoad() { - b.removeEventListener("load", onPageLoad, true); - // Delay callback execute as page-mod content scripts may be executed on - // load event. So page-mod actions may not be already done. - // If we delay even more contentScriptWhen:'end', we may want to modify - // this code again. - setTimeout(testCallback, timeout, - b.contentWindow.wrappedJSObject, // TODO: remove this CPOW - function () { - pageMods.forEach(mod => mod.destroy()); - // XXX leaks reported if we don't close the tab? - closeTab(newTab); - loader.unload(); - done(); - } - ); - } - b.addEventListener("load", onPageLoad, true); - - return pageMods; -} -exports.testPageMod = testPageMod; - -/** - * helper function that creates a PageMod and calls back the appropriate handler - * based on the value of document.readyState at the time contentScript is attached - */ -exports.handleReadyState = function(url, contentScriptWhen, callbacks) { - const loader = Loader(module); - const { PageMod } = loader.require('sdk/page-mod'); - - let pagemod = PageMod({ - include: url, - attachTo: ['existing', 'top'], - contentScriptWhen: contentScriptWhen, - contentScript: "self.postMessage(document.readyState)", - onAttach: worker => { - let { tab } = worker; - worker.on('message', readyState => { - // generate event name from `readyState`, e.g. `"loading"` becomes `onLoading`. - let type = 'on' + readyState[0].toUpperCase() + readyState.substr(1); - - if (type in callbacks) - callbacks[type](tab); - - pagemod.destroy(); - loader.unload(); - }) - } - }); -} - -// serves a slow page which takes 1.5 seconds to load, -// 0.5 seconds in each readyState: uninitialized, loading, interactive. -function contentScriptWhenServer() { - const URL = 'http://localhost:' + PORT + PATH; - - const HTML = `/* polyglot js - - delay both the "DOMContentLoaded" - - and "load" events */`; - - let srv = httpd.startServerAsync(PORT); - - srv.registerPathHandler(PATH, (_, response) => { - response.processAsync(); - response.setHeader('Content-Type', 'text/html', false); - setTimeout(_ => response.finish(), 500); - response.write(HTML); - }) - - srv.URL = URL; - return srv; -} -exports.contentScriptWhenServer = contentScriptWhenServer; diff --git a/addon-sdk/source/test/path/test-path.js b/addon-sdk/source/test/path/test-path.js deleted file mode 100644 index 38037af202..0000000000 --- a/addon-sdk/source/test/path/test-path.js +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// Adapted version of: -// https://github.com/joyent/node/blob/v0.9.1/test/simple/test-path.js - -exports['test path'] = function(assert) { - var system = require('sdk/system'); - var path = require('sdk/fs/path'); - - // Shim process global from node. - var process = Object.create(require('sdk/system')); - process.cwd = process.pathFor.bind(process, 'CurProcD'); - - var isWindows = require('sdk/system').platform.indexOf('win') === 0; - - assert.equal(path.basename(''), ''); - assert.equal(path.basename('/dir/basename.ext'), 'basename.ext'); - assert.equal(path.basename('/basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext/'), 'basename.ext'); - assert.equal(path.basename('basename.ext//'), 'basename.ext'); - - if (isWindows) { - // On Windows a backslash acts as a path separator. - assert.equal(path.basename('\\dir\\basename.ext'), 'basename.ext'); - assert.equal(path.basename('\\basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext\\'), 'basename.ext'); - assert.equal(path.basename('basename.ext\\\\'), 'basename.ext'); - - } else { - // On unix a backslash is just treated as any other character. - assert.equal(path.basename('\\dir\\basename.ext'), '\\dir\\basename.ext'); - assert.equal(path.basename('\\basename.ext'), '\\basename.ext'); - assert.equal(path.basename('basename.ext'), 'basename.ext'); - assert.equal(path.basename('basename.ext\\'), 'basename.ext\\'); - assert.equal(path.basename('basename.ext\\\\'), 'basename.ext\\\\'); - } - - // POSIX filenames may include control characters - // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html - if (!isWindows) { - var controlCharFilename = 'Icon' + String.fromCharCode(13); - assert.equal(path.basename('/a/b/' + controlCharFilename), - controlCharFilename); - } - - assert.equal(path.dirname('/a/b/'), '/a'); - assert.equal(path.dirname('/a/b'), '/a'); - assert.equal(path.dirname('/a'), '/'); - assert.equal(path.dirname(''), '.'); - assert.equal(path.dirname('/'), '/'); - assert.equal(path.dirname('////'), '/'); - - if (isWindows) { - assert.equal(path.dirname('c:\\'), 'c:\\'); - assert.equal(path.dirname('c:\\foo'), 'c:\\'); - assert.equal(path.dirname('c:\\foo\\'), 'c:\\'); - assert.equal(path.dirname('c:\\foo\\bar'), 'c:\\foo'); - assert.equal(path.dirname('c:\\foo\\bar\\'), 'c:\\foo'); - assert.equal(path.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar'); - assert.equal(path.dirname('\\'), '\\'); - assert.equal(path.dirname('\\foo'), '\\'); - assert.equal(path.dirname('\\foo\\'), '\\'); - assert.equal(path.dirname('\\foo\\bar'), '\\foo'); - assert.equal(path.dirname('\\foo\\bar\\'), '\\foo'); - assert.equal(path.dirname('\\foo\\bar\\baz'), '\\foo\\bar'); - assert.equal(path.dirname('c:'), 'c:'); - assert.equal(path.dirname('c:foo'), 'c:'); - assert.equal(path.dirname('c:foo\\'), 'c:'); - assert.equal(path.dirname('c:foo\\bar'), 'c:foo'); - assert.equal(path.dirname('c:foo\\bar\\'), 'c:foo'); - assert.equal(path.dirname('c:foo\\bar\\baz'), 'c:foo\\bar'); - assert.equal(path.dirname('\\\\unc\\share'), '\\\\unc\\share'); - assert.equal(path.dirname('\\\\unc\\share\\foo'), '\\\\unc\\share\\'); - assert.equal(path.dirname('\\\\unc\\share\\foo\\'), '\\\\unc\\share\\'); - assert.equal(path.dirname('\\\\unc\\share\\foo\\bar'), - '\\\\unc\\share\\foo'); - assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\'), - '\\\\unc\\share\\foo'); - assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\baz'), - '\\\\unc\\share\\foo\\bar'); - } - - - assert.equal(path.extname(''), ''); - assert.equal(path.extname('/path/to/file'), ''); - assert.equal(path.extname('/path/to/file.ext'), '.ext'); - assert.equal(path.extname('/path.to/file.ext'), '.ext'); - assert.equal(path.extname('/path.to/file'), ''); - assert.equal(path.extname('/path.to/.file'), ''); - assert.equal(path.extname('/path.to/.file.ext'), '.ext'); - assert.equal(path.extname('/path/to/f.ext'), '.ext'); - assert.equal(path.extname('/path/to/..ext'), '.ext'); - assert.equal(path.extname('file'), ''); - assert.equal(path.extname('file.ext'), '.ext'); - assert.equal(path.extname('.file'), ''); - assert.equal(path.extname('.file.ext'), '.ext'); - assert.equal(path.extname('/file'), ''); - assert.equal(path.extname('/file.ext'), '.ext'); - assert.equal(path.extname('/.file'), ''); - assert.equal(path.extname('/.file.ext'), '.ext'); - assert.equal(path.extname('.path/file.ext'), '.ext'); - assert.equal(path.extname('file.ext.ext'), '.ext'); - assert.equal(path.extname('file.'), '.'); - assert.equal(path.extname('.'), ''); - assert.equal(path.extname('./'), ''); - assert.equal(path.extname('.file.ext'), '.ext'); - assert.equal(path.extname('.file'), ''); - assert.equal(path.extname('.file.'), '.'); - assert.equal(path.extname('.file..'), '.'); - assert.equal(path.extname('..'), ''); - assert.equal(path.extname('../'), ''); - assert.equal(path.extname('..file.ext'), '.ext'); - assert.equal(path.extname('..file'), '.file'); - assert.equal(path.extname('..file.'), '.'); - assert.equal(path.extname('..file..'), '.'); - assert.equal(path.extname('...'), '.'); - assert.equal(path.extname('...ext'), '.ext'); - assert.equal(path.extname('....'), '.'); - assert.equal(path.extname('file.ext/'), '.ext'); - assert.equal(path.extname('file.ext//'), '.ext'); - assert.equal(path.extname('file/'), ''); - assert.equal(path.extname('file//'), ''); - assert.equal(path.extname('file./'), '.'); - assert.equal(path.extname('file.//'), '.'); - - if (isWindows) { - // On windows, backspace is a path separator. - assert.equal(path.extname('.\\'), ''); - assert.equal(path.extname('..\\'), ''); - assert.equal(path.extname('file.ext\\'), '.ext'); - assert.equal(path.extname('file.ext\\\\'), '.ext'); - assert.equal(path.extname('file\\'), ''); - assert.equal(path.extname('file\\\\'), ''); - assert.equal(path.extname('file.\\'), '.'); - assert.equal(path.extname('file.\\\\'), '.'); - - } else { - // On unix, backspace is a valid name component like any other character. - assert.equal(path.extname('.\\'), ''); - assert.equal(path.extname('..\\'), '.\\'); - assert.equal(path.extname('file.ext\\'), '.ext\\'); - assert.equal(path.extname('file.ext\\\\'), '.ext\\\\'); - assert.equal(path.extname('file\\'), ''); - assert.equal(path.extname('file\\\\'), ''); - assert.equal(path.extname('file.\\'), '.\\'); - assert.equal(path.extname('file.\\\\'), '.\\\\'); - } - - // path.join tests - var failures = []; - var joinTests = - // arguments result - [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], - [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], - [['/foo', '../../../bar'], '/bar'], - [['foo', '../../../bar'], '../../bar'], - [['foo/', '../../../bar'], '../../bar'], - [['foo/x', '../../../bar'], '../bar'], - [['foo/x', './bar'], 'foo/x/bar'], - [['foo/x/', './bar'], 'foo/x/bar'], - [['foo/x/', '.', 'bar'], 'foo/x/bar'], - [['./'], './'], - [['.', './'], './'], - [['.', '.', '.'], '.'], - [['.', './', '.'], '.'], - [['.', '/./', '.'], '.'], - [['.', '/////./', '.'], '.'], - [['.'], '.'], - [['', '.'], '.'], - [['', 'foo'], 'foo'], - [['foo', '/bar'], 'foo/bar'], - [['', '/foo'], '/foo'], - [['', '', '/foo'], '/foo'], - [['', '', 'foo'], 'foo'], - [['foo', ''], 'foo'], - [['foo/', ''], 'foo/'], - [['foo', '', '/bar'], 'foo/bar'], - [['./', '..', '/foo'], '../foo'], - [['./', '..', '..', '/foo'], '../../foo'], - [['.', '..', '..', '/foo'], '../../foo'], - [['', '..', '..', '/foo'], '../../foo'], - [['/'], '/'], - [['/', '.'], '/'], - [['/', '..'], '/'], - [['/', '..', '..'], '/'], - [[''], '.'], - [['', ''], '.'], - [[' /foo'], ' /foo'], - [[' ', 'foo'], ' /foo'], - [[' ', '.'], ' '], - [[' ', '/'], ' /'], - [[' ', ''], ' '], - [['/', 'foo'], '/foo'], - [['/', '/foo'], '/foo'], - [['/', '//foo'], '/foo'], - [['/', '', '/foo'], '/foo'], - [['', '/', 'foo'], '/foo'], - [['', '/', '/foo'], '/foo'] - ]; - - // Windows-specific join tests - if (isWindows) { - joinTests = joinTests.concat( - [// UNC path expected - [['//foo/bar'], '//foo/bar/'], - [['\\/foo/bar'], '//foo/bar/'], - [['\\\\foo/bar'], '//foo/bar/'], - // UNC path expected - server and share separate - [['//foo', 'bar'], '//foo/bar/'], - [['//foo/', 'bar'], '//foo/bar/'], - [['//foo', '/bar'], '//foo/bar/'], - // UNC path expected - questionable - [['//foo', '', 'bar'], '//foo/bar/'], - [['//foo/', '', 'bar'], '//foo/bar/'], - [['//foo/', '', '/bar'], '//foo/bar/'], - // UNC path expected - even more questionable - [['', '//foo', 'bar'], '//foo/bar/'], - [['', '//foo/', 'bar'], '//foo/bar/'], - [['', '//foo/', '/bar'], '//foo/bar/'], - // No UNC path expected (no double slash in first component) - [['\\', 'foo/bar'], '/foo/bar'], - [['\\', '/foo/bar'], '/foo/bar'], - [['', '/', '/foo/bar'], '/foo/bar'], - // No UNC path expected (no non-slashes in first component - questionable) - [['//', 'foo/bar'], '/foo/bar'], - [['//', '/foo/bar'], '/foo/bar'], - [['\\\\', '/', '/foo/bar'], '/foo/bar'], - [['//'], '/'], - // No UNC path expected (share name missing - questionable). - [['//foo'], '/foo'], - [['//foo/'], '/foo/'], - [['//foo', '/'], '/foo/'], - [['//foo', '', '/'], '/foo/'], - // No UNC path expected (too many leading slashes - questionable) - [['///foo/bar'], '/foo/bar'], - [['////foo', 'bar'], '/foo/bar'], - [['\\\\\\/foo/bar'], '/foo/bar'], - // Drive-relative vs drive-absolute paths. This merely describes the - // status quo, rather than being obviously right - [['c:'], 'c:.'], - [['c:.'], 'c:.'], - [['c:', ''], 'c:.'], - [['', 'c:'], 'c:.'], - [['c:.', '/'], 'c:./'], - [['c:.', 'file'], 'c:file'], - [['c:', '/'], 'c:/'], - [['c:', 'file'], 'c:/file'] - ]); - } - - // Run the join tests. - joinTests.forEach(function(test) { - var actual = path.join.apply(path, test[0]); - var expected = isWindows ? test[1].replace(/\//g, '\\') : test[1]; - var message = 'path.join(' + test[0].map(JSON.stringify).join(',') + ')' + - '\n expect=' + JSON.stringify(expected) + - '\n actual=' + JSON.stringify(actual); - if (actual !== expected) failures.push('\n' + message); - // assert.equal(actual, expected, message); - }); - assert.equal(failures.length, 0, failures.join('')); - var joinThrowTests = [true, false, 7, null, {}, undefined, [], NaN]; - joinThrowTests.forEach(function(test) { - assert.throws(function() { - path.join(test); - }, TypeError); - assert.throws(function() { - path.resolve(test); - }, TypeError); - }); - - - // path normalize tests - if (isWindows) { - assert.equal(path.normalize('./fixtures///b/../b/c.js'), - 'fixtures\\b\\c.js'); - assert.equal(path.normalize('/foo/../../../bar'), '\\bar'); - assert.equal(path.normalize('a//b//../b'), 'a\\b'); - assert.equal(path.normalize('a//b//./c'), 'a\\b\\c'); - assert.equal(path.normalize('a//b//.'), 'a\\b'); - assert.equal(path.normalize('//server/share/dir/file.ext'), - '\\\\server\\share\\dir\\file.ext'); - } else { - assert.equal(path.normalize('./fixtures///b/../b/c.js'), - 'fixtures/b/c.js'); - assert.equal(path.normalize('/foo/../../../bar'), '/bar'); - assert.equal(path.normalize('a//b//../b'), 'a/b'); - assert.equal(path.normalize('a//b//./c'), 'a/b/c'); - assert.equal(path.normalize('a//b//.'), 'a/b'); - } - - // path.resolve tests - if (isWindows) { - // windows - var resolveTests = - // arguments result - [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], - [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], - [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], - [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], - [['.'], process.cwd()], - [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], - [['c:/', '//'], 'c:\\'], - [['c:/', '//dir'], 'c:\\dir'], - [['c:/', '//server/share'], '\\\\server\\share\\'], - [['c:/', '//server//share'], '\\\\server\\share\\'], - [['c:/', '///some//dir'], 'c:\\some\\dir'] - ]; - } else { - // Posix - var resolveTests = - // arguments result - [[['/var/lib', '../', 'file/'], '/var/file'], - [['/var/lib', '/../', 'file/'], '/file'], - // For some mysterious reasons OSX debug builds resolve incorrectly - // https://tbpl.mozilla.org/php/getParsedLog.php?id=25105489&tree=Mozilla-Inbound - // Disable this tests until Bug 891698 is fixed. - // [['a/b/c/', '../../..'], process.cwd()], - // [['.'], process.cwd()], - [['/some/dir', '.', '/absolute/'], '/absolute']]; - } - var failures = []; - resolveTests.forEach(function(test) { - var actual = path.resolve.apply(path, test[0]); - var expected = test[1]; - var message = 'path.resolve(' + test[0].map(JSON.stringify).join(',') + ')' + - '\n expect=' + JSON.stringify(expected) + - '\n actual=' + JSON.stringify(actual); - if (actual !== expected) failures.push('\n' + message); - // assert.equal(actual, expected, message); - }); - assert.equal(failures.length, 0, failures.join('')); - - // path.isAbsolute tests - if (isWindows) { - assert.equal(path.isAbsolute('//server/file'), true); - assert.equal(path.isAbsolute('\\\\server\\file'), true); - assert.equal(path.isAbsolute('C:/Users/'), true); - assert.equal(path.isAbsolute('C:\\Users\\'), true); - assert.equal(path.isAbsolute('C:cwd/another'), false); - assert.equal(path.isAbsolute('C:cwd\\another'), false); - assert.equal(path.isAbsolute('directory/directory'), false); - assert.equal(path.isAbsolute('directory\\directory'), false); - } else { - assert.equal(path.isAbsolute('/home/foo'), true); - assert.equal(path.isAbsolute('/home/foo/..'), true); - assert.equal(path.isAbsolute('bar/'), false); - assert.equal(path.isAbsolute('./baz'), false); - } - - // path.relative tests - if (isWindows) { - // windows - var relativeTests = - // arguments result - [['c:/blah\\blah', 'd:/games', 'd:\\games'], - ['c:/aaaa/bbbb', 'c:/aaaa', '..'], - ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], - ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], - ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], - ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], - ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], - ['c:/aaaa/bbbb', 'd:\\', 'd:\\']]; - } else { - // posix - var relativeTests = - // arguments result - [['/var/lib', '/var', '..'], - ['/var/lib', '/bin', '../../bin'], - ['/var/lib', '/var/lib', ''], - ['/var/lib', '/var/apache', '../apache'], - ['/var/', '/var/lib', 'lib'], - ['/', '/var/lib', 'var/lib']]; - } - var failures = []; - relativeTests.forEach(function(test) { - var actual = path.relative(test[0], test[1]); - var expected = test[2]; - var message = 'path.relative(' + - test.slice(0, 2).map(JSON.stringify).join(',') + - ')' + - '\n expect=' + JSON.stringify(expected) + - '\n actual=' + JSON.stringify(actual); - if (actual !== expected) failures.push('\n' + message); - }); - assert.equal(failures.length, 0, failures.join('')); - - // path.sep tests - if (isWindows) { - // windows - assert.equal(path.sep, '\\'); - } - else { - // posix - assert.equal(path.sep, '/'); - } - - // path.delimiter tests - if (isWindows) { - // windows - assert.equal(path.delimiter, ';'); - } - else { - // posix - assert.equal(path.delimiter, ':'); - } -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/preferences/common.json b/addon-sdk/source/test/preferences/common.json deleted file mode 100644 index c645e24d5f..0000000000 --- a/addon-sdk/source/test/preferences/common.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "browser.dom.window.dump.enabled": true, - "javascript.options.showInConsole": true, - "devtools.debugger.remote-enabled": true, - "extensions.sdk.console.logLevel": "info", - "extensions.checkCompatibility.nightly": false, - "extensions.update.enabled": false, - "lightweightThemes.update.enabled": false, - "extensions.update.notifyUser": false, - "extensions.enabledScopes": 5, - "extensions.getAddons.cache.enabled": false, - "extensions.installDistroAddons": false, - "extensions.autoDisableScopes": 10, - "app.releaseNotesURL": "http://localhost/app-dummy/", - "app.vendorURL": "http://localhost/app-dummy/" -} diff --git a/addon-sdk/source/test/preferences/e10s-off.json b/addon-sdk/source/test/preferences/e10s-off.json deleted file mode 100644 index 269d7a92a8..0000000000 --- a/addon-sdk/source/test/preferences/e10s-off.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "browser.tabs.remote.autostart": false, - "browser.tabs.remote.autostart.1": false, - "browser.tabs.remote.autostart.2": false -} diff --git a/addon-sdk/source/test/preferences/e10s-on.json b/addon-sdk/source/test/preferences/e10s-on.json deleted file mode 100644 index dd5a78dbfa..0000000000 --- a/addon-sdk/source/test/preferences/e10s-on.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "browser.tabs.remote.autostart": true -} diff --git a/addon-sdk/source/test/preferences/firefox.json b/addon-sdk/source/test/preferences/firefox.json deleted file mode 100644 index 5b8145cec2..0000000000 --- a/addon-sdk/source/test/preferences/firefox.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "browser.startup.homepage": "about:blank", - "startup.homepage_welcome_url": "about:blank", - "devtools.browsertoolbox.panel": "jsdebugger", - "devtools.chrome.enabled": true, - "urlclassifier.updateinterval": 172800, - "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash", - "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update", - "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash", - "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update" -} diff --git a/addon-sdk/source/test/preferences/no-connections.json b/addon-sdk/source/test/preferences/no-connections.json deleted file mode 100644 index 370b7909c7..0000000000 --- a/addon-sdk/source/test/preferences/no-connections.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "toolkit.telemetry.enabled": false, - "toolkit.telemetry.server": "https://localhost/telemetry-dummy/", - "app.update.auto": false, - "app.update.url": "http://localhost/app-dummy/update", - "app.update.enabled": false, - "app.update.staging.enabled": false, - "media.gmp-gmpopenh264.autoupdate": false, - "media.gmp-manager.cert.checkAttributes": false, - "media.gmp-manager.cert.requireBuiltIn": false, - "media.gmp-manager.url": "http://localhost/media-dummy/gmpmanager", - "media.gmp-manager.url.override": "http://localhost/dummy-gmp-manager.xml", - "media.gmp-manager.updateEnabled": false, - "browser.aboutHomeSnippets.updateUrl": "https://localhost/snippet-dummy", - "browser.newtab.url": "about:blank", - "browser.search.update": false, - "browser.search.suggest.enabled": false, - "browser.safebrowsing.phishing.enabled": false, - "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update", - "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash", - "browser.safebrowsing.provider.google.reportURL": "http://localhost/safebrowsing-dummy/malwarereport", - "browser.selfsupport.url": "https://localhost/selfsupport-dummy", - "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash", - "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update", - "browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}", - "browser.newtabpage.directory.ping": "", - "extensions.update.url": "http://localhost/extensions-dummy/updateURL", - "extensions.update.background.url": "http://localhost/extensions-dummy/updateBackgroundURL", - "extensions.blocklist.url": "http://localhost/extensions-dummy/blocklistURL", - "extensions.webservice.discoverURL": "http://localhost/extensions-dummy/discoveryURL", - "extensions.getAddons.maxResults": 0, - "services.blocklist.base": "http://localhost/dummy-kinto/v1", - "geo.wifi.uri": "http://localhost/location-dummy/locationURL", - "browser.search.geoip.url": "http://localhost/location-dummy/locationURL", - "browser.search.isUS": true, - "browser.search.countryCode": "US", - "geo.wifi.uri": "http://localhost/extensions-dummy/geowifiURL", - "geo.wifi.scan": false, - "browser.webapps.checkForUpdates": 0, - "identity.fxaccounts.auth.uri": "http://localhost/fxa-dummy/" -} diff --git a/addon-sdk/source/test/preferences/test-e10s-preferences.js b/addon-sdk/source/test/preferences/test-e10s-preferences.js deleted file mode 100644 index ed09e5fa32..0000000000 --- a/addon-sdk/source/test/preferences/test-e10s-preferences.js +++ /dev/null @@ -1,15 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var extend = require("lodash").extend - -var e10sOn = require("./e10s-on.json"); -var commonPrefs = require("./common.json"); -var testPrefs = require("./test.json"); -var fxPrefs = require("./firefox.json"); -var disconnectionPrefs = require("./no-connections.json"); - -var prefs = extend({}, e10sOn, commonPrefs, testPrefs, disconnectionPrefs, fxPrefs); -module.exports = prefs; diff --git a/addon-sdk/source/test/preferences/test-preferences.js b/addon-sdk/source/test/preferences/test-preferences.js deleted file mode 100644 index 5cdb4a292d..0000000000 --- a/addon-sdk/source/test/preferences/test-preferences.js +++ /dev/null @@ -1,15 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var extend = require("lodash").extend - -var e10sOff = require("./e10s-off.json"); -var commonPrefs = require("./common.json"); -var testPrefs = require("./test.json"); -var fxPrefs = require("./firefox.json"); -var disconnectionPrefs = require("./no-connections.json"); - -var prefs = extend({}, e10sOff, commonPrefs, testPrefs, disconnectionPrefs, fxPrefs); -module.exports = prefs; diff --git a/addon-sdk/source/test/preferences/test.json b/addon-sdk/source/test/preferences/test.json deleted file mode 100644 index d34061fb88..0000000000 --- a/addon-sdk/source/test/preferences/test.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "browser.console.showInPanel": true, - "browser.startup.page": 0, - "browser.firstrun.show.localepicker": false, - "browser.firstrun.show.uidiscovery": false, - "browser.ui.layout.tablet": 0, - "dom.disable_open_during_load": false, - "dom.experimental_forms": true, - "dom.forms.number": true, - "dom.forms.color": true, - "dom.max_script_run_time": 0, - "hangmonitor.timeout": 0, - "dom.max_chrome_script_run_time": 0, - "dom.popup_maximum": -1, - "dom.send_after_paint_to_content": true, - "dom.successive_dialog_time_limit": 0, - "browser.shell.checkDefaultBrowser": false, - "shell.checkDefaultClient": false, - "browser.warnOnQuit": false, - "accessibility.typeaheadfind.autostart": false, - "browser.EULA.override": true, - "gfx.color_management.force_srgb": true, - "network.manage-offline-status": false, - "network.http.speculative-parallel-limit": 0, - "test.mousescroll": true, - "security.default_personal_cert": "Select Automatically", - "network.http.prompt-temp-redirect": false, - "security.warn_viewing_mixed": false, - "extensions.defaultProviders.enabled": true, - "datareporting.policy.dataSubmissionPolicyBypassNotification": true, - "layout.css.report_errors": true, - "layout.css.grid.enabled": true, - "layout.spammy_warnings.enabled": false, - "dom.mozSettings.enabled": true, - "network.http.bypass-cachelock-threshold": 200000, - "geo.provider.testing": true, - "browser.pagethumbnails.capturing_disabled": true, - "browser.download.panel.shown": true, - "general.useragent.updates.enabled": false, - "media.eme.enabled": true, - "media.eme.apiVisible": true, - "dom.ipc.tabs.shutdownTimeoutSecs": 0, - "general.useragent.locale": "en-US", - "intl.locale.matchOS": "en-US", - "dom.indexedDB.experimental": true -} diff --git a/addon-sdk/source/test/private-browsing/helper.js b/addon-sdk/source/test/private-browsing/helper.js deleted file mode 100644 index 4a400b95b6..0000000000 --- a/addon-sdk/source/test/private-browsing/helper.js +++ /dev/null @@ -1,58 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const xulApp = require("sdk/system/xul-app"); -const { open: openWindow, getMostRecentBrowserWindow } = require('sdk/window/utils'); -const { openTab, getTabContentWindow, getActiveTab, setTabURL, closeTab } = require('sdk/tabs/utils'); -const promise = require("sdk/core/promise"); -const windowHelpers = require('sdk/window/helpers'); -const events = require("sdk/system/events"); - -exports.openWebpage = function openWebpage(url, enablePrivate) { - if (xulApp.is("Fennec")) { - let chromeWindow = getMostRecentBrowserWindow(); - let rawTab = openTab(chromeWindow, url, { - isPrivate: enablePrivate - }); - - return { - ready: promise.resolve(getTabContentWindow(rawTab)), - close: function () { - closeTab(rawTab); - // Returns a resolved promise as there is no need to wait - return promise.resolve(); - } - }; - } - else { - let win = openWindow(null, { - features: { - private: enablePrivate - } - }); - let deferred = promise.defer(); - - // Wait for delayed startup code to be executed, in order to ensure - // that the window is really ready - events.on("browser-delayed-startup-finished", function onReady({subject}) { - if (subject == win) { - events.off("browser-delayed-startup-finished", onReady); - deferred.resolve(win); - - let rawTab = getActiveTab(win); - setTabURL(rawTab, url); - deferred.resolve(getTabContentWindow(rawTab)); - } - }, true); - - return { - ready: deferred.promise, - close: function () { - return windowHelpers.close(win); - } - }; - } - return null; -} diff --git a/addon-sdk/source/test/private-browsing/tabs.js b/addon-sdk/source/test/private-browsing/tabs.js deleted file mode 100644 index 8564f07351..0000000000 --- a/addon-sdk/source/test/private-browsing/tabs.js +++ /dev/null @@ -1,25 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { Ci } = require('chrome'); -const { openTab, closeTab } = require('sdk/tabs/utils'); -const { browserWindows } = require('sdk/windows'); -const { isPrivate } = require('sdk/private-browsing'); - -exports.testIsPrivateOnTab = function(assert) { - let window = browserWindows.activeWindow; - assert.ok(!isPrivate(chromeWindow), 'the top level window is not private'); - - let rawTab = openTab(chromeWindow, 'data:text/html,

Hi!

', { - isPrivate: true - }); - - // test that the tab is private - assert.ok(rawTab.browser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing); - assert.ok(isPrivate(rawTab.browser.contentWindow)); - assert.ok(isPrivate(rawTab.browser)); - - closeTab(rawTab); -}; diff --git a/addon-sdk/source/test/private-browsing/windows.js b/addon-sdk/source/test/private-browsing/windows.js deleted file mode 100644 index e6f9c53b56..0000000000 --- a/addon-sdk/source/test/private-browsing/windows.js +++ /dev/null @@ -1,115 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { onFocus, openDialog, open } = require('sdk/window/utils'); -const { open: openPromise, close, focus, promise } = require('sdk/window/helpers'); -const { isPrivate } = require('sdk/private-browsing'); -const { getMode } = require('sdk/private-browsing/utils'); -const { browserWindows: windows } = require('sdk/windows'); -const { defer } = require('sdk/core/promise'); -const tabs = require('sdk/tabs'); -const { getMostRecentBrowserWindow } = require('sdk/window/utils'); -const { cleanUI } = require("sdk/test/utils"); - -// test openDialog() from window/utils with private option -// test isActive state in pwpb case -// test isPrivate on ChromeWindow -exports.testPerWindowPrivateBrowsingGetter = function*(assert) { - let win = openDialog({ private: true }); - - yield promise(win, 'DOMContentLoaded'); - - assert.equal(getMode(win), true, 'Newly opened window is in PB mode'); - assert.ok(isPrivate(win), 'isPrivate(window) is true'); - - yield close(win); -} - -// test open() from window/utils with private feature -// test isActive state in pwpb case -// test isPrivate on ChromeWindow -exports.testPerWindowPrivateBrowsingGetter = function*(assert) { - let win = open('chrome://browser/content/browser.xul', { - features: { - private: true - } - }); - - yield promise(win, 'DOMContentLoaded'); - assert.equal(getMode(win), true, 'Newly opened window is in PB mode'); - assert.ok(isPrivate(win), 'isPrivate(window) is true'); - yield close(win) -} - -exports.testIsPrivateOnWindowOpen = function*(assert) { - let window = yield new Promise(resolve => { - windows.open({ - isPrivate: true, - onOpen: resolve - }); - }); - - assert.equal(isPrivate(window), false, 'isPrivate for a window is true when it should be'); - assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be'); - - yield cleanUI(); -} - -exports.testIsPrivateOnWindowOpenFromPrivate = function(assert, done) { - // open a private window - openPromise(null, { - features: { - private: true, - chrome: true, - titlebar: true, - toolbar: true - } - }).then(focus).then(function(window) { - let { promise, resolve } = defer(); - - assert.equal(isPrivate(window), true, 'the only open window is private'); - - windows.open({ - url: 'about:blank', - onOpen: function(w) { - assert.equal(isPrivate(w), false, 'new test window is not private'); - w.close(() => resolve(window)); - } - }); - - return promise; - }).then(close). - then(done, assert.fail); -}; - -exports.testOpenTabWithPrivateWindow = function*(assert) { - let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true }); - - assert.pass("loading new private window"); - - yield promise(window, 'load').then(focus); - - assert.equal(isPrivate(window), true, 'the focused window is private'); - - yield new Promise(resolve => tabs.open({ - url: 'about:blank', - onOpen: (tab) => { - assert.equal(isPrivate(tab), false, 'the opened tab is not private'); - tab.close(resolve); - } - })); - - yield close(window); -}; - -exports.testIsPrivateOnWindowOff = function(assert, done) { - windows.open({ - onOpen: function(window) { - assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be'); - assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be'); - window.close(done); - } - }) -} diff --git a/addon-sdk/source/test/querystring/test-querystring.js b/addon-sdk/source/test/querystring/test-querystring.js deleted file mode 100644 index 9a4fbe573a..0000000000 --- a/addon-sdk/source/test/querystring/test-querystring.js +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -"use strict"; - -// test using assert -var qs = require('sdk/querystring'); - -// folding block, commented to pass gjslint -// {{{ -// [ wonkyQS, canonicalQS, obj ] -var qsTestCases = [ - ['foo=918854443121279438895193', - 'foo=918854443121279438895193', - {'foo': '918854443121279438895193'}], - ['foo=bar', 'foo=bar', {'foo': 'bar'}], - //['foo=bar&foo=quux', 'foo=bar&foo=quux', {'foo': ['bar', 'quux']}], - ['foo=1&bar=2', 'foo=1&bar=2', {'foo': '1', 'bar': '2'}], - // ['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F', - // 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', - // {'my weird field': 'q1!2"\'w$5&7/z8)?' }], - ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', {'foo=baz': 'bar'}], - ['foo=baz=bar', 'foo=baz%3Dbar', {'foo': 'baz=bar'}], - /* - ['str=foo&arr=1&arr=2&arr=3&somenull=&undef=', - 'str=foo&arr=1&arr=2&arr=3&somenull=&undef=', - { 'str': 'foo', - 'arr': ['1', '2', '3'], - 'somenull': '', - 'undef': ''}], - */ - //[' foo = bar ', '%20foo%20=%20bar%20', {' foo ': ' bar '}], - // disable test that fails ['foo=%zx', 'foo=%25zx', {'foo': '%zx'}], - ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', {'foo': '\ufffd' }] -]; - -// [ wonkyQS, canonicalQS, obj ] -var qsColonTestCases = [ - ['foo:bar', 'foo:bar', {'foo': 'bar'}], - //['foo:bar;foo:quux', 'foo:bar;foo:quux', {'foo': ['bar', 'quux']}], - ['foo:1&bar:2;baz:quux', - 'foo:1%26bar%3A2;baz:quux', - {'foo': '1&bar:2', 'baz': 'quux'}], - ['foo%3Abaz:bar', 'foo%3Abaz:bar', {'foo:baz': 'bar'}], - ['foo:baz:bar', 'foo:baz%3Abar', {'foo': 'baz:bar'}] -]; - -// [wonkyObj, qs, canonicalObj] -var extendedFunction = function() {}; -extendedFunction.prototype = {a: 'b'}; -var qsWeirdObjects = [ - //[{regexp: /./g}, 'regexp=', {'regexp': ''}], - //[{regexp: new RegExp('.', 'g')}, 'regexp=', {'regexp': ''}], - //[{fn: function() {}}, 'fn=', {'fn': ''}], - //[{fn: new Function('')}, 'fn=', {'fn': ''}], - //[{math: Math}, 'math=', {'math': ''}], - //[{e: extendedFunction}, 'e=', {'e': ''}], - //[{d: new Date()}, 'd=', {'d': ''}], - //[{d: Date}, 'd=', {'d': ''}], - //[{f: new Boolean(false), t: new Boolean(true)}, 'f=&t=', {'f': '', 't': ''}], - [{f: false, t: true}, 'f=false&t=true', {'f': 'false', 't': 'true'}], - //[{n: null}, 'n=', {'n': ''}], - //[{nan: NaN}, 'nan=', {'nan': ''}], - //[{inf: Infinity}, 'inf=', {'inf': ''}] -]; -// }}} - -var qsNoMungeTestCases = [ - ['', {}], - //['foo=bar&foo=baz', {'foo': ['bar', 'baz']}], - ['blah=burp', {'blah': 'burp'}], - //['gragh=1&gragh=3&goo=2', {'gragh': ['1', '3'], 'goo': '2'}], - ['frappucino=muffin&goat%5B%5D=scone&pond=moose', - {'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose'}], - ['trololol=yes&lololo=no', {'trololol': 'yes', 'lololo': 'no'}] -]; - -exports['test basic'] = function(assert) { - assert.strictEqual('918854443121279438895193', - qs.parse('id=918854443121279438895193').id, - 'prase id=918854443121279438895193'); -}; - -exports['test that the canonical qs is parsed properly'] = function(assert) { - qsTestCases.forEach(function(testCase) { - assert.deepEqual(testCase[2], qs.parse(testCase[0]), - 'parse ' + testCase[0]); - }); -}; - - -exports['test that the colon test cases can do the same'] = function(assert) { - qsColonTestCases.forEach(function(testCase) { - assert.deepEqual(testCase[2], qs.parse(testCase[0], ';', ':'), - 'parse ' + testCase[0] + ' -> ; :'); - }); -}; - -exports['test the weird objects, that they get parsed properly'] = function(assert) { - qsWeirdObjects.forEach(function(testCase) { - assert.deepEqual(testCase[2], qs.parse(testCase[1]), - 'parse ' + testCase[1]); - }); -}; - -exports['test non munge test cases'] = function(assert) { - qsNoMungeTestCases.forEach(function(testCase) { - assert.deepEqual(testCase[0], qs.stringify(testCase[1], '&', '=', false), - 'stringify ' + JSON.stringify(testCase[1]) + ' -> & ='); - }); -}; - -exports['test the nested qs-in-qs case'] = function(assert) { - var f = qs.parse('a=b&q=x%3Dy%26y%3Dz'); - f.q = qs.parse(f.q); - assert.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } }, - 'parse a=b&q=x%3Dy%26y%3Dz'); -}; - -exports['test nested in colon'] = function(assert) { - var f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':'); - f.q = qs.parse(f.q, ';', ':'); - assert.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } }, - 'parse a:b;q:x%3Ay%3By%3Az -> ; :'); -}; - -exports['test stringifying'] = function(assert) { - qsTestCases.forEach(function(testCase) { - assert.equal(testCase[1], qs.stringify(testCase[2]), - 'stringify ' + JSON.stringify(testCase[2])); - }); - - qsColonTestCases.forEach(function(testCase) { - assert.equal(testCase[1], qs.stringify(testCase[2], ';', ':'), - 'stringify ' + JSON.stringify(testCase[2]) + ' -> ; :'); - }); - - qsWeirdObjects.forEach(function(testCase) { - assert.equal(testCase[1], qs.stringify(testCase[0]), - 'stringify ' + JSON.stringify(testCase[0])); - }); -}; - -exports['test stringifying nested'] = function(assert) { - var f = qs.stringify({ - a: 'b', - q: qs.stringify({ - x: 'y', - y: 'z' - }) - }); - assert.equal(f, 'a=b&q=x%3Dy%26y%3Dz', - JSON.stringify({ - a: 'b', - 'qs.stringify -> q': { - x: 'y', - y: 'z' - } - })); - - var threw = false; - try { qs.parse(undefined); } catch(error) { threw = true; } - assert.ok(!threw, "does not throws on undefined"); -}; - -exports['test nested in colon'] = function(assert) { - var f = qs.stringify({ - a: 'b', - q: qs.stringify({ - x: 'y', - y: 'z' - }, ';', ':') - }, ';', ':'); - assert.equal(f, 'a:b;q:x%3Ay%3By%3Az', - 'stringify ' + JSON.stringify({ - a: 'b', - 'qs.stringify -> q': { - x: 'y', - y: 'z' - } - }) + ' -> ; : '); - - - assert.deepEqual({}, qs.parse(), 'parse undefined'); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/sidebar/utils.js b/addon-sdk/source/test/sidebar/utils.js deleted file mode 100644 index 21002ba49e..0000000000 --- a/addon-sdk/source/test/sidebar/utils.js +++ /dev/null @@ -1,74 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -module.metadata = { - 'engines': { - 'Firefox': '*' - } -}; - -const { Cu } = require('chrome'); -const { getMostRecentBrowserWindow } = require('sdk/window/utils'); -const { fromIterator } = require('sdk/util/array'); - -const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [ - 'menu_socialSidebar', - 'menu_historySidebar', - 'menu_bookmarksSidebar', - 'menu_tabsSidebar', -]; - -function isSidebarShowing(window) { - window = window || getMostRecentBrowserWindow(); - let sidebar = window.document.getElementById('sidebar-box'); - return !sidebar.hidden; -} -exports.isSidebarShowing = isSidebarShowing; - -function getSidebarMenuitems(window) { - window = window || getMostRecentBrowserWindow(); - return fromIterator(window.document.querySelectorAll('#viewSidebarMenu menuitem')); -} -exports.getSidebarMenuitems = getSidebarMenuitems; - -function getExtraSidebarMenuitems() { - let menuitems = getSidebarMenuitems(); - return menuitems.filter(function(mi) { - return BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) < 0; - }); -} -exports.getExtraSidebarMenuitems = getExtraSidebarMenuitems; - -function makeID(id) { - return 'jetpack-sidebar-' + id; -} -exports.makeID = makeID; - -function simulateCommand(ele) { - let window = ele.ownerDocument.defaultView; - let { document } = window; - var evt = document.createEvent('XULCommandEvent'); - evt.initCommandEvent('command', true, true, window, - 0, false, false, false, false, null); - ele.dispatchEvent(evt); -} -exports.simulateCommand = simulateCommand; - -function simulateClick(ele) { - let window = ele.ownerDocument.defaultView; - let { document } = window; - let evt = document.createEvent('MouseEvents'); - evt.initMouseEvent('click', true, true, window, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - ele.dispatchEvent(evt); -} -exports.simulateClick = simulateClick; - -// OSX and Windows exhibit different behaviors when 'checked' is false, -// so compare against the consistent 'true'. See bug 894809. -function isChecked(node) { - return node.getAttribute('checked') === 'true'; -}; -exports.isChecked = isChecked; diff --git a/addon-sdk/source/test/tabs/test-fennec-tabs.js b/addon-sdk/source/test/tabs/test-fennec-tabs.js deleted file mode 100644 index d7e362d416..0000000000 --- a/addon-sdk/source/test/tabs/test-fennec-tabs.js +++ /dev/null @@ -1,595 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { Cc, Ci } = require('chrome'); -const { Loader, LoaderWithHookedConsole } = require('sdk/test/loader'); -const timer = require('sdk/timers'); -const tabs = require('sdk/tabs'); -const windows = require('sdk/windows'); -const { set: setPref } = require("sdk/preferences/service"); -const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; - -const tabsLen = tabs.length; -const URL = 'data:text/html;charset=utf-8,#title#'; - -// Fennec error message dispatched on all currently unimplement tab features, -// that match LoaderWithHookedConsole messages object pattern -const ERR_FENNEC_MSG = { - type: "error", - msg: "This method is not yet supported by Fennec" -}; - -// TEST: tab unloader -exports.testAutomaticDestroy = function(assert, done) { - let called = false; - - let loader2 = Loader(module); - let loader3 = Loader(module); - let tabs2 = loader2.require('sdk/tabs'); - let tabs3 = loader3.require('sdk/tabs'); - let tabs2Len = tabs2.length; - - tabs2.on('open', function onOpen(tab) { - assert.fail("an onOpen listener was called that should not have been"); - called = true; - }); - tabs2.on('ready', function onReady(tab) { - assert.fail("an onReady listener was called that should not have been"); - called = true; - }); - tabs2.on('select', function onSelect(tab) { - assert.fail("an onSelect listener was called that should not have been"); - called = true; - }); - tabs2.on('close', function onClose(tab) { - assert.fail("an onClose listener was called that should not have been"); - called = true; - }); - loader2.unload(); - - tabs3.on('open', function onOpen(tab) { - assert.pass("an onOpen listener was called for tabs3"); - - tab.on('ready', function onReady(tab) { - assert.fail("an onReady listener was called that should not have been"); - called = true; - }); - tab.on('select', function onSelect(tab) { - assert.fail("an onSelect listener was called that should not have been"); - called = true; - }); - tab.on('close', function onClose(tab) { - assert.fail("an onClose listener was called that should not have been"); - called = true; - }); - }); - tabs3.open(URL.replace(/#title#/, 'tabs3')); - loader3.unload(); - - // Fire a tab event and ensure that the destroyed tab is inactive - tabs.once('open', function(tab) { - assert.pass('tabs.once("open") works!'); - - assert.equal(tabs2Len, tabs2.length, "tabs2 length was not changed"); - assert.equal(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length"); - - tab.once('ready', function() { - assert.pass('tab.once("ready") works!'); - - tab.once('close', function() { - assert.pass('tab.once("close") works!'); - - timer.setTimeout(function () { - assert.ok(!called, "Unloaded tab module is destroyed and inactive"); - - // end test - done(); - }); - }); - - tab.close(); - }); - }); - - tabs.open('data:text/html;charset=utf-8,foo'); -}; - -// TEST: tab properties -exports.testTabProperties = function(assert, done) { - let url = "data:text/html;charset=utf-8,foofoo"; - let tabsLen = tabs.length; - tabs.open({ - url: url, - onReady: function(tab) { - assert.equal(tab.title, "foo", "title of the new tab matches"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tab.style, null, "style of the new tab matches"); - assert.equal(tab.index, tabsLen, "index of the new tab matches"); - assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); - assert.notEqual(tab.id, null, "a tab object always has an id property"); - - tab.close(function() { - loader.unload(); - - // end test - done(); - }); - } - }); -}; - -// TEST: tabs iterator and length property -exports.testTabsIteratorAndLength = function(assert, done) { - let newTabs = []; - let startCount = 0; - for (let t of tabs) startCount++; - - assert.equal(startCount, tabs.length, "length property is correct"); - - let url = "data:text/html;charset=utf-8,testTabsIteratorAndLength"; - tabs.open({url: url, onOpen: tab => newTabs.push(tab)}); - tabs.open({url: url, onOpen: tab => newTabs.push(tab)}); - tabs.open({ - url: url, - onOpen: function(tab) { - let count = 0; - for (let t of tabs) count++; - assert.equal(count, startCount + 3, "iterated tab count matches"); - assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property"); - - let newTabsLength = newTabs.length; - newTabs.forEach(t => t.close(function() { - if (--newTabsLength > 0) return; - - tab.close(done); - })); - } - }); -}; - -// TEST: tab.url setter -exports.testTabLocation = function(assert, done) { - let url1 = "data:text/html;charset=utf-8,foo"; - let url2 = "data:text/html;charset=utf-8,bar"; - - tabs.on('ready', function onReady(tab) { - if (tab.url != url2) - return; - - tabs.removeListener('ready', onReady); - assert.pass("tab loaded the correct url"); - - tab.close(done); - }); - - tabs.open({ - url: url1, - onOpen: function(tab) { - tab.url = url2; - } - }); -}; - -// TEST: tab.move() -exports.testTabMove = function(assert, done) { - let { loader, messages } = LoaderWithHookedConsole(); - let tabs = loader.require('sdk/tabs'); - - let url = "data:text/html;charset=utf-8,testTabMove"; - - tabs.open({ - url: url, - onOpen: function(tab1) { - assert.ok(tab1.index >= 0, "opening a tab returns a tab w/ valid index"); - - tabs.open({ - url: url, - onOpen: function(tab) { - let i = tab.index; - assert.ok(tab.index > tab1.index, "2nd tab has valid index"); - tab.index = 0; - assert.equal(tab.index, i, "tab index after move matches"); - assert.equal(JSON.stringify(messages), - JSON.stringify([ERR_FENNEC_MSG]), - "setting tab.index logs error"); - // end test - tab1.close(() => tab.close(function() { - loader.unload(); - done(); - })); - } - }); - } - }); -}; - -// TEST: open tab with default options -exports.testTabsOpen_alt = function(assert, done) { - let { loader, messages } = LoaderWithHookedConsole(); - let tabs = loader.require('sdk/tabs'); - let url = "data:text/html;charset=utf-8,default"; - - tabs.open({ - url: url, - onReady: function(tab) { - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tabs.activeTab, tab, "URL of active tab in the current window matches"); - assert.equal(tab.isPinned, false, "The new tab is not pinned"); - assert.equal(messages.length, 1, "isPinned logs error"); - - // end test - tab.close(function() { - loader.unload(); - done(); - }); - } - }); -}; - -// TEST: open pinned tab -exports.testOpenPinned_alt = function(assert, done) { - let { loader, messages } = LoaderWithHookedConsole(); - let tabs = loader.require('sdk/tabs'); - let url = "about:blank"; - - tabs.open({ - url: url, - isPinned: true, - onOpen: function(tab) { - assert.equal(tab.isPinned, false, "The new tab is pinned"); - // We get two error message: one for tabs.open's isPinned argument - // and another one for tab.isPinned - assert.equal(JSON.stringify(messages), - JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), - "isPinned logs error"); - - // end test - tab.close(function() { - loader.unload(); - done(); - }); - } - }); -}; - -// TEST: pin/unpin opened tab -exports.testPinUnpin_alt = function(assert, done) { - let { loader, messages } = LoaderWithHookedConsole(); - let tabs = loader.require('sdk/tabs'); - let url = "data:text/html;charset=utf-8,default"; - - tabs.open({ - url: url, - onOpen: function(tab) { - tab.pin(); - assert.equal(tab.isPinned, false, "The tab was pinned correctly"); - assert.equal(JSON.stringify(messages), - JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), - "tab.pin() logs error"); - - // Clear console messages for the following test - messages.length = 0; - - tab.unpin(); - assert.equal(tab.isPinned, false, "The tab was unpinned correctly"); - assert.equal(JSON.stringify(messages), - JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]), - "tab.unpin() logs error"); - - // end test - tab.close(function() { - loader.unload(); - done(); - }); - } - }); -}; - -// TEST: open tab in background -exports.testInBackground = function(assert, done) { - let activeUrl = tabs.activeTab.url; - let url = "data:text/html;charset=utf-8,background"; - let window = windows.browserWindows.activeWindow; - tabs.once('ready', function onReady(tab) { - assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed"); - assert.equal(tab.url, url, "URL of the new background tab matches"); - assert.equal(windows.browserWindows.activeWindow, window, "a new window was not opened"); - assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); - - // end test - tab.close(done); - }); - - tabs.open({ - url: url, - inBackground: true - }); -}; - -// TEST: open tab in new window -exports.testOpenInNewWindow = function(assert, done) { - let url = "data:text/html;charset=utf-8,newwindow"; - let window = windows.browserWindows.activeWindow; - - tabs.open({ - url: url, - inNewWindow: true, - onReady: function(tab) { - assert.equal(windows.browserWindows.length, 1, "a new window was not opened"); - assert.equal(windows.browserWindows.activeWindow, window, "old window is active"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tabs.activeTab, tab, "tab is the activeTab"); - - tab.close(done); - } - }); -}; - -// TEST: onOpen event handler -exports.testTabsEvent_onOpen = function(assert, done) { - let url = URL.replace('#title#', 'testTabsEvent_onOpen'); - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('open', listener1); - - // add listener via collection add - tabs.on('open', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('open', listener1); - tabs.removeListener('open', listener2); - - // ends test - tab.close(done); - }); - - tabs.open(url); -}; - -// TEST: onClose event handler -exports.testTabsEvent_onClose = function(assert, done) { - let url = "data:text/html;charset=utf-8,onclose"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - } - tabs.on('close', listener1); - - // add listener via collection add - tabs.on('close', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('close', listener1); - tabs.removeListener('close', listener2); - - // end test - done(); - }); - - tabs.on('ready', function onReady(tab) { - tabs.removeListener('ready', onReady); - tab.close(); - }); - - tabs.open(url); -}; - -// TEST: onClose event handler when a window is closed -exports.testTabsEvent_onCloseWindow = function(assert, done) { - let closeCount = 0, individualCloseCount = 0; - function listener() { - closeCount++; - } - tabs.on('close', listener); - - // One tab is already open with the window - let openTabs = 0; - function testCasePossiblyLoaded(tab) { - tab.close(function() { - if (++openTabs == 3) { - tabs.removeListener("close", listener); - - assert.equal(closeCount, 3, "Correct number of close events received"); - assert.equal(individualCloseCount, 3, - "Each tab with an attached onClose listener received a close " + - "event when the window was closed"); - - done(); - } - }); - } - - tabs.open({ - url: "data:text/html;charset=utf-8,tab2", - onOpen: testCasePossiblyLoaded, - onClose: () => individualCloseCount++ - }); - - tabs.open({ - url: "data:text/html;charset=utf-8,tab3", - onOpen: testCasePossiblyLoaded, - onClose: () => individualCloseCount++ - }); - - tabs.open({ - url: "data:text/html;charset=utf-8,tab4", - onOpen: testCasePossiblyLoaded, - onClose: () => individualCloseCount++ - }); -}; - -// TEST: onReady event handler -exports.testTabsEvent_onReady = function(assert, done) { - let url = "data:text/html;charset=utf-8,onready"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('ready', listener1); - - // add listener via collection add - tabs.on('ready', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('ready', listener1); - tabs.removeListener('ready', listener2); - - // end test - tab.close(done); - }); - - tabs.open(url); -}; - -// TEST: onActivate event handler -exports.testTabsEvent_onActivate = function(assert, done) { - let url = "data:text/html;charset=utf-8,onactivate"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('activate', listener1); - - // add listener via collection add - tabs.on('activate', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - assert.equal(tab, tabs.activeTab, 'the active tab is correct'); - tabs.removeListener('activate', listener1); - tabs.removeListener('activate', listener2); - - // end test - tab.close(done); - }); - - tabs.open(url); -}; - -// TEST: onDeactivate event handler -exports.testTabsEvent_onDeactivate = function(assert, done) { - let url = "data:text/html;charset=utf-8,ondeactivate"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('deactivate', listener1); - - // add listener via collection add - tabs.on('deactivate', function listener2(tab) { - assert.equal(++eventCount, 2, 'both listeners notified'); - assert.notEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab'); - tabs.removeListener('deactivate', listener1); - tabs.removeListener('deactivate', listener2); - - // end test - tab.close(done); - }); - - tabs.on('activate', function onActivate(tab) { - tabs.removeListener('activate', onActivate); - tabs.open("data:text/html;charset=utf-8,foo"); - tab.close(); - }); - - tabs.open(url); -}; - -// TEST: per-tab event handlers -exports.testPerTabEvents = function(assert, done) { - let eventCount = 0; - - tabs.open({ - url: "data:text/html;charset=utf-8,foo", - onOpen: function(tab) { - // add listener via property assignment - function listener1() { - eventCount++; - }; - tab.on('ready', listener1); - - // add listener via collection add - tab.on('ready', function listener2() { - assert.equal(eventCount, 1, "both listeners notified"); - tab.removeListener('ready', listener1); - tab.removeListener('ready', listener2); - - // end test - tab.close(done); - }); - } - }); -}; - -exports.testUniqueTabIds = function(assert, done) { - var tabs = require('sdk/tabs'); - var tabIds = {}; - var steps = [ - function (index) { - tabs.open({ - url: "data:text/html;charset=utf-8,foo", - onOpen: function(tab) { - tabIds['tab1'] = tab.id; - next(index); - } - }); - }, - function (index) { - tabs.open({ - url: "data:text/html;charset=utf-8,bar", - onOpen: function(tab) { - tabIds['tab2'] = tab.id; - next(index); - } - }); - }, - function (index) { - assert.notEqual(tabIds.tab1, tabIds.tab2, "Tab ids should be unique."); - done(); - } - ]; - - function next(index) { - if (index === steps.length) { - return; - } - let fn = steps[index]; - index++; - fn(index); - } - - next(0); -} - -exports.testOnLoadEventWithDOM = function(assert, done) { - let count = 0; - let title = 'testOnLoadEventWithDOM'; - - tabs.open({ - url: 'data:text/html;charset=utf-8,' + title + '', - inBackground: true, - onLoad: function(tab) { - assert.equal(tab.title, title, 'tab passed in as arg, load called'); - - if (++count > 1) { - assert.pass('onLoad event called on reload'); - tab.close(done); - } - else { - assert.pass('first onLoad event occured'); - tab.reload(); - } - } - }); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/tabs/test-firefox-tabs.js b/addon-sdk/source/test/tabs/test-firefox-tabs.js deleted file mode 100644 index 368ed02ba9..0000000000 --- a/addon-sdk/source/test/tabs/test-firefox-tabs.js +++ /dev/null @@ -1,1305 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { Cc, Ci } = require('chrome'); -const { Loader } = require('sdk/test/loader'); -const systemEvents = require("sdk/system/events"); -const { setTimeout, setImmediate } = require('sdk/timers'); -const { modelFor } = require('sdk/model/core'); -const { viewFor } = require('sdk/view/core'); -const { getOwnerWindow } = require('sdk/tabs/utils'); -const { windows, onFocus, getMostRecentBrowserWindow } = require('sdk/window/utils'); -const { open, focus, close } = require('sdk/window/helpers'); -const { observer: windowObserver } = require("sdk/windows/observer"); -const tabs = require('sdk/tabs'); -const { browserWindows } = require('sdk/windows'); -const { set: setPref, get: getPref, reset: resetPref } = require("sdk/preferences/service"); -const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; -const OPEN_IN_NEW_WINDOW_PREF = 'browser.link.open_newwindow'; -const DISABLE_POPUP_PREF = 'dom.disable_open_during_load'; -const fixtures = require("../fixtures"); -const { base64jpeg } = fixtures; -const { cleanUI, before, after } = require("sdk/test/utils"); -const { wait } = require('../event/helpers'); - -// Bug 682681 - tab.title should never be empty -exports.testBug682681_aboutURI = function(assert, done) { - let url = 'chrome://browser/locale/tabbrowser.properties'; - let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]. - getService(Ci.nsIStringBundleService). - createBundle(url); - let emptyTabTitle = stringBundle.GetStringFromName('tabs.emptyTabTitle'); - - tabs.on('ready', function onReady(tab) { - tabs.removeListener('ready', onReady); - - assert.equal(tab.title, - emptyTabTitle, - "title of about: tab is not blank"); - - tab.close(done); - }); - - // open a about: url - tabs.open({ - url: "about:blank", - inBackground: true - }); -}; - -// related to Bug 682681 -exports.testTitleForDataURI = function(assert, done) { - tabs.open({ - url: "data:text/html;charset=utf-8,tab", - inBackground: true, - onReady: function(tab) { - assert.equal(tab.title, "tab", "data: title is not Connecting..."); - tab.close(done); - } - }); -}; - -// TEST: 'BrowserWindow' instance creation on tab 'activate' event -// See bug 648244: there was a infinite loop. -exports.testBrowserWindowCreationOnActivate = function(assert, done) { - let windows = require("sdk/windows").browserWindows; - let gotActivate = false; - - tabs.once('activate', function onActivate(eventTab) { - assert.ok(windows.activeWindow, "Is able to fetch activeWindow"); - gotActivate = true; - }); - - open().then(function(window) { - assert.ok(gotActivate, "Received activate event"); - return close(window); - }).then(done).then(null, assert.fail); -} - -// TEST: tab unloader -exports.testAutomaticDestroyEventOpen = function(assert, done) { - let called = false; - let loader = Loader(module); - let tabs2 = loader.require("sdk/tabs"); - tabs2.on('open', _ => called = true); - - // Fire a tab event and ensure that the destroyed tab is inactive - tabs.once('open', tab => { - setTimeout(_ => { - assert.ok(!called, "Unloaded tab module is destroyed and inactive"); - tab.close(done); - }); - }); - - loader.unload(); - tabs.open("data:text/html;charset=utf-8,testAutomaticDestroyEventOpen"); -}; - -exports.testAutomaticDestroyEventActivate = function(assert, done) { - let called = false; - let loader = Loader(module); - let tabs2 = loader.require("sdk/tabs"); - tabs2.on('activate', _ => called = true); - - // Fire a tab event and ensure that the destroyed tab is inactive - tabs.once('activate', tab => { - setTimeout(_ => { - assert.ok(!called, "Unloaded tab module is destroyed and inactive"); - tab.close(done); - }); - }); - - loader.unload(); - tabs.open("data:text/html;charset=utf-8,testAutomaticDestroyEventActivate"); -}; - -exports.testAutomaticDestroyEventDeactivate = function(assert, done) { - let called = false; - let currentTab = tabs.activeTab; - let loader = Loader(module); - let tabs2 = loader.require("sdk/tabs"); - - tabs.open({ - url: "data:text/html;charset=utf-8,testAutomaticDestroyEventDeactivate", - onActivate: _ => setTimeout(_ => { - tabs2.on('deactivate', _ => called = true); - - // Fire a tab event and ensure that the destroyed tab is inactive - tabs.once('deactivate', tab => { - setTimeout(_ => { - assert.ok(!called, "Unloaded tab module is destroyed and inactive"); - tab.close(done); - }); - }); - - loader.unload(); - currentTab.activate(); - }) - }); -}; - -exports.testAutomaticDestroyEventClose = function(assert, done) { - let called = false; - let loader = Loader(module); - let tabs2 = loader.require("sdk/tabs"); - - tabs.open({ - url: "data:text/html;charset=utf-8,testAutomaticDestroyEventClose", - onReady: tab => { - tabs2.on('close', _ => called = true); - - // Fire a tab event and ensure that the destroyed tab is inactive - tabs.once('close', tab => { - setTimeout(_ => { - assert.ok(!called, "Unloaded tab module is destroyed and inactive"); - done(); - }); - }); - - loader.unload(); - tab.close(); - } - }); -}; - -exports.testTabPropertiesInNewWindow = function(assert, done) { - const { LoaderWithFilteredConsole } = require("sdk/test/loader"); - let loader = LoaderWithFilteredConsole(module, function(type, message) { - return true; - }); - - let tabs = loader.require('sdk/tabs'); - let { viewFor } = loader.require('sdk/view/core'); - - let count = 0; - function onReadyOrLoad (tab) { - if (count++) { - close(getOwnerWindow(viewFor(tab))).then(done).then(null, assert.fail); - } - } - - let url = "data:text/html;charset=utf-8,foofoo"; - tabs.open({ - inNewWindow: true, - url: url, - onReady: function(tab) { - assert.equal(tab.title, "foo", "title of the new tab matches"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); - assert.equal(tab.style, null, "style of the new tab matches"); - assert.equal(tab.index, 0, "index of the new tab matches"); - assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); - assert.notEqual(tab.id, null, "a tab object always has an id property."); - - onReadyOrLoad(tab); - }, - onLoad: function(tab) { - assert.equal(tab.title, "foo", "title of the new tab matches"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); - assert.equal(tab.style, null, "style of the new tab matches"); - assert.equal(tab.index, 0, "index of the new tab matches"); - assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); - assert.notEqual(tab.id, null, "a tab object always has an id property."); - - onReadyOrLoad(tab); - } - }); -}; - -exports.testTabPropertiesInSameWindow = function(assert, done) { - const { LoaderWithFilteredConsole } = require("sdk/test/loader"); - let loader = LoaderWithFilteredConsole(module, function(type, message) { - return true; - }); - - let tabs = loader.require('sdk/tabs'); - - // Get current count of tabs so we know the index of the - // new tab, bug 893846 - let tabCount = tabs.length; - let count = 0; - function onReadyOrLoad (tab) { - if (count++) { - tab.close(done); - } - } - - let url = "data:text/html;charset=utf-8,foofoo"; - tabs.open({ - url: url, - onReady: function(tab) { - assert.equal(tab.title, "foo", "title of the new tab matches"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); - assert.equal(tab.style, null, "style of the new tab matches"); - assert.equal(tab.index, tabCount, "index of the new tab matches"); - assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); - assert.notEqual(tab.id, null, "a tab object always has an id property."); - - onReadyOrLoad(tab); - }, - onLoad: function(tab) { - assert.equal(tab.title, "foo", "title of the new tab matches"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tab.favicon, undefined, "favicon of the new tab is undefined"); - assert.equal(tab.style, null, "style of the new tab matches"); - assert.equal(tab.index, tabCount, "index of the new tab matches"); - assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); - assert.notEqual(tab.id, null, "a tab object always has an id property."); - - onReadyOrLoad(tab); - } - }); -}; - -// TEST: tab properties -exports.testTabContentTypeAndReload = function(assert, done) { - open().then(focus).then(function(window) { - let url = "data:text/html;charset=utf-8,foofoo"; - let urlXML = "data:text/xml;charset=utf-8,bar"; - tabs.open({ - url: url, - onReady: function(tab) { - if (tab.url === url) { - assert.equal(tab.contentType, "text/html"); - tab.url = urlXML; - } - else { - assert.equal(tab.contentType, "text/xml"); - close(window).then(done).then(null, assert.fail); - } - } - }); - }); -}; - -// TEST: tabs iterator and length property -exports.testTabsIteratorAndLength = function(assert, done) { - open(null, { features: { chrome: true, toolbar: true } }).then(focus).then(function(window) { - let startCount = 0; - for (let t of tabs) startCount++; - assert.equal(startCount, tabs.length, "length property is correct"); - let url = "data:text/html;charset=utf-8,default"; - - tabs.open(url); - tabs.open(url); - tabs.open({ - url: url, - onOpen: function(tab) { - let count = 0; - for (let t of tabs) count++; - assert.equal(count, startCount + 3, "iterated tab count matches"); - assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property"); - - close(window).then(done).then(null, assert.fail); - } - }); - }); -}; - -// TEST: tab.url setter -exports.testTabLocation = function(assert, done) { - open().then(focus).then(function(window) { - let url1 = "data:text/html;charset=utf-8,foo"; - let url2 = "data:text/html;charset=utf-8,bar"; - - tabs.on('ready', function onReady(tab) { - if (tab.url != url2) - return; - tabs.removeListener('ready', onReady); - assert.pass("tab.load() loaded the correct url"); - close(window).then(done).then(null, assert.fail); - }); - - tabs.open({ - url: url1, - onOpen: function(tab) { - tab.url = url2 - } - }); - }); -}; - -// TEST: tab.close() -exports.testTabClose = function(assert, done) { - let testName = "testTabClose"; - let url = "data:text/html;charset=utf-8," + testName; - - assert.notEqual(tabs.activeTab.url, url, "tab is not the active tab"); - tabs.once('ready', function onReady(tab) { - assert.equal(tabs.activeTab.url, tab.url, "tab is now the active tab"); - assert.equal(url, tab.url, "tab url is the test url"); - let secondOnCloseCalled = false; - - // Bug 699450: Multiple calls to tab.close should not throw - tab.close(() => secondOnCloseCalled = true); - try { - tab.close(function () { - assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); - assert.ok(secondOnCloseCalled, - "The immediate second call to tab.close happened"); - assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); - - done(); - }); - } - catch(e) { - assert.fail("second call to tab.close() thrown an exception: " + e); - } - }); - - tabs.open(url); -}; - -// TEST: tab.move() -exports.testTabMove = function(assert, done) { - open().then(focus).then(function(window) { - let url = "data:text/html;charset=utf-8,foo"; - - tabs.open({ - url: url, - onOpen: function(tab) { - assert.equal(tab.index, 1, "tab index before move matches"); - tab.index = 0; - assert.equal(tab.index, 0, "tab index after move matches"); - close(window).then(done).then(null, assert.fail); - } - }); - }).then(null, assert.fail); -}; - -exports.testIgnoreClosing = function*(assert) { - let url = "data:text/html;charset=utf-8,foobar"; - let originalWindow = getMostRecentBrowserWindow(); - - let window = yield open().then(focus); - - assert.equal(tabs.length, 2, "should be two windows open each with one tab"); - - yield new Promise(resolve => { - tabs.once("ready", (tab) => { - let win = tab.window; - assert.equal(win.tabs.length, 2, "should be two tabs in the new window"); - assert.equal(tabs.length, 3, "should be three tabs in total"); - - tab.close(() => { - assert.equal(win.tabs.length, 1, "should be one tab in the new window"); - assert.equal(tabs.length, 2, "should be two tabs in total"); - resolve(); - }); - }); - - tabs.open(url); - }); -}; - -// TEST: open tab with default options -exports.testOpen = function(assert, done) { - let url = "data:text/html;charset=utf-8,default"; - tabs.open({ - url: url, - onReady: function(tab) { - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(tab.isPinned, false, "The new tab is not pinned"); - - tab.close(done); - } - }); -}; - -// TEST: opening a pinned tab -exports.testOpenPinned = function(assert, done) { - let url = "data:text/html;charset=utf-8,default"; - tabs.open({ - url: url, - isPinned: true, - onOpen: function(tab) { - assert.equal(tab.isPinned, true, "The new tab is pinned"); - tab.close(done); - } - }); -}; - -// TEST: pin/unpin opened tab -exports.testPinUnpin = function(assert, done) { - let url = "data:text/html;charset=utf-8,default"; - tabs.open({ - url: url, - inBackground: true, - onOpen: function(tab) { - tab.pin(); - assert.equal(tab.isPinned, true, "The tab was pinned correctly"); - tab.unpin(); - assert.equal(tab.isPinned, false, "The tab was unpinned correctly"); - tab.close(done); - } - }); -} - -// TEST: open tab in background -exports.testInBackground = function(assert, done) { - assert.equal(tabs.length, 1, "Should be one tab"); - - let window = getMostRecentBrowserWindow(); - let activeUrl = tabs.activeTab.url; - let url = "data:text/html;charset=utf-8,background"; - assert.equal(getMostRecentBrowserWindow(), window, "getMostRecentBrowserWindow() matches this window"); - tabs.on('ready', function onReady(tab) { - tabs.removeListener('ready', onReady); - assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed"); - assert.equal(tab.url, url, "URL of the new background tab matches"); - assert.equal(getMostRecentBrowserWindow(), window, "a new window was not opened"); - assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); - tab.close(done); - }); - - tabs.open({ - url: url, - inBackground: true - }); -} - -// TEST: open tab in new window -exports.testOpenInNewWindow = function(assert, done) { - let startWindowCount = windows().length; - - let url = "data:text/html;charset=utf-8,testOpenInNewWindow"; - tabs.open({ - url: url, - inNewWindow: true, - onReady: function(tab) { - let newWindow = getOwnerWindow(viewFor(tab)); - assert.equal(windows().length, startWindowCount + 1, "a new window was opened"); - - onFocus(newWindow).then(function() { - assert.equal(getMostRecentBrowserWindow(), newWindow, "new window is active"); - assert.equal(tab.url, url, "URL of the new tab matches"); - assert.equal(newWindow.content.location, url, "URL of new tab in new window matches"); - assert.equal(tabs.activeTab.url, url, "URL of activeTab matches"); - - return close(newWindow).then(done); - }).then(null, assert.fail); - } - }); - -} - -// Test tab.open inNewWindow + onOpen combination -exports.testOpenInNewWindowOnOpen = function(assert, done) { - let startWindowCount = windows().length; - - let url = "data:text/html;charset=utf-8,newwindow"; - tabs.open({ - url: url, - inNewWindow: true, - onOpen: function(tab) { - let newWindow = getOwnerWindow(viewFor(tab)); - - onFocus(newWindow).then(function() { - assert.equal(windows().length, startWindowCount + 1, "a new window was opened"); - assert.equal(getMostRecentBrowserWindow(), newWindow, "new window is active"); - - close(newWindow).then(done).then(null, assert.fail); - }); - } - }); -}; - -// TEST: onOpen event handler -exports.testTabsEvent_onOpen = function(assert, done) { - open().then(focus).then(window => { - let url = "data:text/html;charset=utf-8,1"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('open', listener1); - - // add listener via collection add - tabs.on('open', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('open', listener1); - tabs.removeListener('open', listener2); - close(window).then(done).then(null, assert.fail); - }); - - tabs.open(url); - }).then(null, assert.fail); -}; - -// TEST: onClose event handler -exports.testTabsEvent_onClose = function*(assert) { - let window = yield open().then(focus); - let url = "data:text/html;charset=utf-8,onclose"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - } - tabs.on("close", listener1); - - yield new Promise(resolve => { - // add listener via collection add - tabs.on("close", function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener("close", listener2); - resolve(); - }); - - tabs.on('ready', function onReady(tab) { - tabs.removeListener('ready', onReady); - tab.close(); - }); - - tabs.open(url); - }); - - tabs.removeListener("close", listener1); - assert.pass("done test!"); - - yield close(window); - assert.pass("window was closed!"); -}; - -// TEST: onClose event handler when a window is closed -exports.testTabsEvent_onCloseWindow = function(assert, done) { - let closeCount = 0; - let individualCloseCount = 0; - - open().then(focus).then(window => { - assert.pass('opened a new window'); - - tabs.on("close", function listener() { - if (++closeCount == 4) { - tabs.removeListener("close", listener); - } - }); - - function endTest() { - if (++individualCloseCount < 3) { - assert.pass('tab closed ' + individualCloseCount); - return; - } - - assert.equal(closeCount, 4, "Correct number of close events received"); - assert.equal(individualCloseCount, 3, - "Each tab with an attached onClose listener received a close " + - "event when the window was closed"); - - done(); - } - - // One tab is already open with the window - let openTabs = 1; - function testCasePossiblyLoaded() { - if (++openTabs == 4) { - window.close(); - } - assert.pass('tab opened ' + openTabs); - } - - tabs.open({ - url: "data:text/html;charset=utf-8,tab2", - onOpen: testCasePossiblyLoaded, - onClose: endTest - }); - - tabs.open({ - url: "data:text/html;charset=utf-8,tab3", - onOpen: testCasePossiblyLoaded, - onClose: endTest - }); - - tabs.open({ - url: "data:text/html;charset=utf-8,tab4", - onOpen: testCasePossiblyLoaded, - onClose: endTest - }); - }).then(null, assert.fail); -} - -// TEST: onReady event handler -exports.testTabsEvent_onReady = function(assert, done) { - open().then(focus).then(window => { - let url = "data:text/html;charset=utf-8,onready"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('ready', listener1); - - // add listener via collection add - tabs.on('ready', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('ready', listener1); - tabs.removeListener('ready', listener2); - close(window).then(done); - }); - - tabs.open(url); - }).then(null, assert.fail); -}; - -// TEST: onActivate event handler -exports.testTabsEvent_onActivate = function(assert, done) { - open().then(focus).then(window => { - let url = "data:text/html;charset=utf-8,onactivate"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - }; - tabs.on('activate', listener1); - - // add listener via collection add - tabs.on('activate', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('activate', listener1); - tabs.removeListener('activate', listener2); - close(window).then(done).then(null, assert.fail); - }); - - tabs.open(url); - }).then(null, assert.fail); -}; - -// onDeactivate event handler -exports.testTabsEvent_onDeactivate = function*(assert) { - let window = yield open().then(focus); - - let url = "data:text/html;charset=utf-8,ondeactivate"; - let eventCount = 0; - - // add listener via property assignment - function listener1(tab) { - eventCount++; - assert.pass("listener1 was called " + eventCount); - }; - tabs.on('deactivate', listener1); - - yield new Promise(resolve => { - // add listener via collection add - tabs.on('deactivate', function listener2(tab) { - assert.equal(++eventCount, 2, "both listeners notified"); - tabs.removeListener('deactivate', listener2); - resolve(); - }); - - tabs.on('open', function onOpen(tab) { - assert.pass("tab opened"); - tabs.removeListener('open', onOpen); - tabs.open("data:text/html;charset=utf-8,foo"); - }); - - tabs.open(url); - }); - - tabs.removeListener('deactivate', listener1); - assert.pass("listeners were removed"); -}; - -// pinning -exports.testTabsEvent_pinning = function(assert, done) { - open().then(focus).then(window => { - let url = "data:text/html;charset=utf-8,1"; - - tabs.on('open', function onOpen(tab) { - tabs.removeListener('open', onOpen); - tab.pin(); - }); - - tabs.on('pinned', function onPinned(tab) { - tabs.removeListener('pinned', onPinned); - assert.ok(tab.isPinned, "notified tab is pinned"); - tab.unpin(); - }); - - tabs.on('unpinned', function onUnpinned(tab) { - tabs.removeListener('unpinned', onUnpinned); - assert.ok(!tab.isPinned, "notified tab is not pinned"); - close(window).then(done).then(null, assert.fail); - }); - - tabs.open(url); - }).then(null, assert.fail); -}; - -// TEST: per-tab event handlers -exports.testPerTabEvents = function*(assert) { - let window = yield open().then(focus); - let eventCount = 0; - - let tab = yield new Promise(resolve => { - tabs.open({ - url: "data:text/html;charset=utf-8,foo", - onOpen: (tab) => { - assert.pass("the tab was opened"); - - // add listener via property assignment - function listener1() { - eventCount++; - }; - tab.on('ready', listener1); - - // add listener via collection add - tab.on('ready', function listener2() { - assert.equal(eventCount, 1, "listener1 called before listener2"); - tab.removeListener('ready', listener1); - tab.removeListener('ready', listener2); - assert.pass("removed listeners"); - eventCount++; - resolve(); - }); - } - }); - }); - - assert.equal(eventCount, 2, "both listeners were notified."); -}; - -exports.testAttachOnMultipleDocuments = function (assert, done) { - // Example of attach that process multiple tab documents - open().then(focus).then(window => { - let firstLocation = "data:text/html;charset=utf-8,foobar"; - let secondLocation = "data:text/html;charset=utf-8,bar"; - let thirdLocation = "data:text/html;charset=utf-8,fox"; - let onReadyCount = 0; - let worker1 = null; - let worker2 = null; - let detachEventCount = 0; - - tabs.open({ - url: firstLocation, - onReady: function (tab) { - onReadyCount++; - if (onReadyCount == 1) { - worker1 = tab.attach({ - contentScript: 'self.on("message", ' + - ' function () { return self.postMessage(document.location.href); }' + - ');', - onMessage: function (msg) { - assert.equal(msg, firstLocation, - "Worker url is equal to the 1st document"); - tab.url = secondLocation; - }, - onDetach: function () { - detachEventCount++; - assert.pass("Got worker1 detach event"); - assert.throws(function () { - worker1.postMessage("ex-1"); - }, - /Couldn't find the worker/, - "postMessage throw because worker1 is destroyed"); - checkEnd(); - } - }); - worker1.postMessage("new-doc-1"); - } - else if (onReadyCount == 2) { - - worker2 = tab.attach({ - contentScript: 'self.on("message", ' + - ' function () { return self.postMessage(document.location.href); }' + - ');', - onMessage: function (msg) { - assert.equal(msg, secondLocation, - "Worker url is equal to the 2nd document"); - tab.url = thirdLocation; - }, - onDetach: function () { - detachEventCount++; - assert.pass("Got worker2 detach event"); - assert.throws(function () { - worker2.postMessage("ex-2"); - }, - /Couldn't find the worker/, - "postMessage throw because worker2 is destroyed"); - checkEnd(); - } - }); - worker2.postMessage("new-doc-2"); - } - else if (onReadyCount == 3) { - tab.close(); - } - } - }); - - function checkEnd() { - if (detachEventCount != 2) - return; - - assert.pass("Got all detach events"); - - close(window).then(done).then(null, assert.fail); - } - }).then(null, assert.fail); -} - - -exports.testAttachWrappers = function (assert, done) { - // Check that content script has access to wrapped values by default - open().then(focus).then(window => { - let document = "data:text/html;charset=utf-8,"; - let count = 0; - - tabs.open({ - url: document, - onReady: function (tab) { - let worker = tab.attach({ - contentScript: 'try {' + - ' self.postMessage(!("globalJSVar" in window));' + - ' self.postMessage(typeof window.globalJSVar == "undefined");' + - '} catch(e) {' + - ' self.postMessage(e.message);' + - '}', - onMessage: function (msg) { - assert.equal(msg, true, "Worker has wrapped objects ("+count+")"); - if (count++ == 1) - close(window).then(done).then(null, assert.fail); - } - }); - } - }); - }).then(null, assert.fail); -} - -/* -// We do not offer unwrapped access to DOM since bug 601295 landed -// See 660780 to track progress of unwrap feature -exports.testAttachUnwrapped = function (assert, done) { - // Check that content script has access to unwrapped values through unsafeWindow - openBrowserWindow(function(window, browser) { - let document = "data:text/html;charset=utf-8,"; - let count = 0; - - tabs.open({ - url: document, - onReady: function (tab) { - let worker = tab.attach({ - contentScript: 'try {' + - ' self.postMessage(unsafeWindow.globalJSVar);' + - '} catch(e) {' + - ' self.postMessage(e.message);' + - '}', - onMessage: function (msg) { - assert.equal(msg, true, "Worker has access to javascript content globals ("+count+")"); - close(window).then(done); - } - }); - } - }); - - }); -} -*/ - -exports['test window focus changes active tab'] = function(assert, done) { - let url1 = "data:text/html;charset=utf-8," + encodeURIComponent("test window focus changes active tab

Window #1"); - - let win1 = openBrowserWindow(function() { - assert.pass("window 1 is open"); - - let win2 = openBrowserWindow(function() { - assert.pass("window 2 is open"); - - focus(win2).then(function() { - tabs.on("activate", function onActivate(tab) { - tabs.removeListener("activate", onActivate); - - if (tab.readyState === 'uninitialized') { - tab.once('ready', whenReady); - } - else { - whenReady(tab); - } - - function whenReady(tab) { - assert.pass("activate was called on windows focus change."); - assert.equal(tab.url, url1, 'the activated tab url is correct'); - - return close(win2).then(function() { - assert.pass('window 2 was closed'); - return close(win1); - }).then(done).then(null, assert.fail); - } - }); - - win1.focus(); - }); - }, "data:text/html;charset=utf-8,test window focus changes active tab

Window #2"); - }, url1); -}; - -exports['test ready event on new window tab'] = function(assert, done) { - let uri = encodeURI("data:text/html;charset=utf-8,Waiting for ready event!"); - - require("sdk/tabs").on("ready", function onReady(tab) { - if (tab.url === uri) { - require("sdk/tabs").removeListener("ready", onReady); - assert.pass("ready event was emitted"); - close(window).then(done).then(null, assert.fail); - } - }); - - let window = openBrowserWindow(function(){}, uri); -}; - -exports['test unique tab ids'] = function(assert, done) { - var windows = require('sdk/windows').browserWindows; - var { all, defer } = require('sdk/core/promise'); - - function openWindow() { - let deferred = defer(); - let win = windows.open({ - url: "data:text/html;charset=utf-8,foo", - }); - - win.on('open', function(window) { - assert.ok(window.tabs.length); - assert.ok(window.tabs.activeTab); - assert.ok(window.tabs.activeTab.id); - deferred.resolve({ - id: window.tabs.activeTab.id, - win: win - }); - }); - - return deferred.promise; - } - - var one = openWindow(), two = openWindow(); - all([one, two]).then(function(results) { - assert.notEqual(results[0].id, results[1].id, "tab Ids should not be equal."); - results[0].win.close(function() { - results[1].win.close(function () { - done(); - }); - }); - }); -} - -// related to Bug 671305 -exports.testOnLoadEventWithDOM = function(assert, done) { - let count = 0; - let title = 'testOnLoadEventWithDOM'; - - // open a about: url - tabs.open({ - url: 'data:text/html;charset=utf-8,' + title + '', - inBackground: true, - onLoad: function(tab) { - assert.equal(tab.title, title, 'tab passed in as arg, load called'); - - if (++count > 1) { - assert.pass('onLoad event called on reload'); - tab.close(done); - } - else { - assert.pass('first onLoad event occured'); - tab.reload(); - } - } - }); -}; - -// related to Bug 671305 -exports.testOnLoadEventWithImage = function(assert, done) { - let count = 0; - - tabs.open({ - url: base64jpeg, - inBackground: true, - onLoad: function(tab) { - if (++count > 1) { - assert.pass('onLoad event called on reload with image'); - tab.close(done); - } - else { - assert.pass('first onLoad event occured'); - tab.reload(); - } - } - }); -}; - -exports.testNoDeadObjects = function(assert, done) { - let loader = Loader(module); - let myTabs = loader.require("sdk/tabs"); - - // Load a tab, unload our modules, and navigate the tab to trigger an event - // on it. This would throw a dead object exception if our modules didn't - // clean up their event handlers on unload. - tabs.open({ - url: "data:text/html;charset=utf-8,one", - onLoad: function(tab) { - // 2. Arrange to nuke the sandboxes and then trigger the load event - // on the tab once the loader is kaput. - systemEvents.on("sdk:loader:destroy", function onUnload() { - systemEvents.off("sdk:loader:destroy", onUnload, true); - // Defer this carnage till the end of the event queue, to avoid nuking - // the sandboxes from under the modules as they're being cleaned up. - setTimeout(function() { - // 3. Arrange to close the tab once the second page loads. - tab.on("load", function() { - tab.close(function() { - let { viewFor } = loader.require("sdk/view/core"); - assert.equal(viewFor(tab), undefined, "didn't retain the closed tab"); - done(); - }); - }); - - // Trigger a load event on the tab, to give the now-unloaded - // myTabs a chance to choke on it. - tab.url = "data:text/html;charset=utf-8,two"; - }, 0); - }, true); - - // 1. Start unloading the modules. Defer till the end of the event - // queue, in case myTabs is attaching its own handlers here too. - // We want it to latch on before we pull the rug from under it. - setTimeout(function() { - loader.unload(); - }, 0); - } - }); -}; - -exports.testTabDestroy = function(assert, done) { - let loader = Loader(module); - let myTabs = loader.require("sdk/tabs"); - let { modelFor: myModelFor } = loader.require("sdk/model/core"); - let { viewFor: myViewFor } = loader.require("sdk/view/core"); - let myFirstTab = myTabs.activeTab; - - myTabs.open({ - url: "data:text/html;charset=utf-8,destroy", - onReady: (myTab) => setImmediate(() => { - let tab = modelFor(myViewFor(myTab)); - - function badListener(event, tab) { - // Ignore events for the other tabs - if (tab != myTab) - return; - assert.fail("Should not have seen the " + event + " listener called"); - } - - assert.ok(myTab, "Should have a tab in the test loader."); - assert.equal(myViewFor(myTab), viewFor(tab), "Should have the right view."); - assert.equal(myTabs.length, 2, "Should have the right number of global tabs."); - assert.equal(myTab.window.tabs.length, 2, "Should have the right number of window tabs."); - assert.equal(myTabs.activeTab, myTab, "Globally active tab is correct."); - assert.equal(myTab.window.tabs.activeTab, myTab, "Window active tab is correct."); - - assert.equal(myTabs[1], myTab, "Global tabs list contains tab."); - assert.equal(myTab.window.tabs[1], myTab, "Window tabs list contains tab."); - - myTab.once("ready", badListener.bind(null, "tab ready")); - myTab.once("deactivate", badListener.bind(null, "tab deactivate")); - myTab.once("activate", badListener.bind(null, "tab activate")); - myTab.once("close", badListener.bind(null, "tab close")); - - myTab.destroy(); - - myTab.once("ready", badListener.bind(null, "new tab ready")); - myTab.once("deactivate", badListener.bind(null, "new tab deactivate")); - myTab.once("activate", badListener.bind(null, "new tab activate")); - myTab.once("close", badListener.bind(null, "new tab close")); - - myTabs.once("ready", badListener.bind(null, "tabs ready")); - myTabs.once("deactivate", badListener.bind(null, "tabs deactivate")); - myTabs.once("activate", badListener.bind(null, "tabs activate")); - myTabs.once("close", badListener.bind(null, "tabs close")); - - assert.equal(myViewFor(myTab), viewFor(tab), "Should have the right view."); - assert.equal(myModelFor(viewFor(tab)), myTab, "Can still reach the tab object."); - assert.equal(myTabs.length, 2, "Should have the right number of global tabs."); - assert.equal(myTab.window.tabs.length, 2, "Should have the right number of window tabs."); - assert.equal(myTabs.activeTab, myTab, "Globally active tab is correct."); - assert.equal(myTab.window.tabs.activeTab, myTab, "Window active tab is correct."); - - assert.equal(myTabs[1], myTab, "Global tabs list still contains tab."); - assert.equal(myTab.window.tabs[1], myTab, "Window tabs list still contains tab."); - - assert.equal(myTab.url, undefined, "url property is not usable"); - assert.equal(myTab.contentType, undefined, "contentType property is not usable"); - assert.equal(myTab.title, undefined, "title property is not usable"); - assert.equal(myTab.id, undefined, "id property is not usable"); - assert.equal(myTab.index, undefined, "index property is not usable"); - - myTab.pin(); - assert.ok(!tab.isPinned, "pin method shouldn't work"); - - tabs.once("activate", () => setImmediate(() => { - assert.equal(myTabs.activeTab, myFirstTab, "Globally active tab is correct."); - assert.equal(myTab.window.tabs.activeTab, myFirstTab, "Window active tab is correct."); - - let sawActivate = false; - tabs.once("activate", () => setImmediate(() => { - sawActivate = true; - - assert.equal(myTabs.activeTab, myTab, "Globally active tab is correct."); - assert.equal(myTab.window.tabs.activeTab, myTab, "Window active tab is correct."); - - // This shouldn't have any effect - myTab.close(); - - tab.once("ready", () => setImmediate(() => { - tab.close(() => { - loader.unload(); - done(); - }); - })); - tab.url = "data:text/html;charset=utf-8,destroy2"; - })); - - myTab.activate(); - setImmediate(() => { - assert.ok(!sawActivate, "activate method shouldn't have done anything"); - - tab.activate(); - }); - })); - myFirstTab.activate(); - }) - }) -}; - -// related to bug 942511 -// https://bugzilla.mozilla.org/show_bug.cgi?id=942511 -exports['test active tab properties defined on popup closed'] = function (assert, done) { - setPref(OPEN_IN_NEW_WINDOW_PREF, 2); - setPref(DISABLE_POPUP_PREF, false); - - let tabID = ""; - let popupClosed = false; - - tabs.open({ - url: 'about:blank', - onReady: function (tab) { - tabID = tab.id; - tab.attach({ - contentScript: 'var popup = window.open("about:blank");' + - 'popup.close();' - }); - - windowObserver.once('close', () => { - popupClosed = true; - }); - - windowObserver.on('activate', () => { - // Only when the 'activate' event is fired after the popup was closed. - if (popupClosed) { - popupClosed = false; - let activeTabID = tabs.activeTab.id; - if (activeTabID) { - assert.equal(tabID, activeTabID, 'ActiveTab properties are correct'); - } - else { - assert.fail('ActiveTab properties undefined on popup closed'); - } - tab.close(done); - } - }); - } - }); -}; - -// related to bugs 922956 and 989288 -// https://bugzilla.mozilla.org/show_bug.cgi?id=922956 -// https://bugzilla.mozilla.org/show_bug.cgi?id=989288 -exports["test tabs ready and close after window.open"] = function*(assert, done) { - // ensure popups open in a new window and disable popup blocker - setPref(OPEN_IN_NEW_WINDOW_PREF, 2); - setPref(DISABLE_POPUP_PREF, false); - - // open windows to trigger observers - tabs.activeTab.attach({ - contentScript: "window.open('about:blank');" + - "window.open('about:blank', '', " + - "'width=800,height=600,resizable=no,status=no,location=no');" - }); - - let tab1 = yield wait(tabs, "ready"); - assert.pass("first tab ready has occured"); - - let tab2 = yield wait(tabs, "ready"); - assert.pass("second tab ready has occured"); - - tab1.close(); - yield wait(tabs, "close"); - assert.pass("first tab close has occured"); - - tab2.close(); - yield wait(tabs, "close"); - assert.pass("second tab close has occured"); -}; - -// related to bug #939496 -exports["test tab open event for new window"] = function(assert, done) { - // ensure popups open in a new window and disable popup blocker - setPref(OPEN_IN_NEW_WINDOW_PREF, 2); - setPref(DISABLE_POPUP_PREF, false); - - tabs.once('open', function onOpen(window) { - assert.pass("tab open has occured"); - window.close(done); - }); - - // open window to trigger observers - browserWindows.open("about:logo"); -}; - -after(exports, function*(name, assert) { - resetPopupPrefs(); - yield cleanUI(); -}); - -const resetPopupPrefs = () => { - resetPref(OPEN_IN_NEW_WINDOW_PREF); - resetPref(DISABLE_POPUP_PREF); -}; - -/******************* helpers *********************/ - -// Utility function to open a new browser window. -function openBrowserWindow(callback, url) { - let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - let urlString = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - urlString.data = url; - let window = ww.openWindow(null, "chrome://browser/content/browser.xul", - "_blank", "chrome,all,dialog=no", urlString); - - if (callback) { - window.addEventListener("load", function onLoad(event) { - if (event.target && event.target.defaultView == window) { - window.removeEventListener("load", onLoad, true); - let browsers = window.document.getElementsByTagName("tabbrowser"); - try { - setTimeout(function () { - callback(window, browsers[0]); - }, 10); - } - catch (e) { - console.exception(e); - } - } - }, true); - } - - return window; -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/tabs/utils.js b/addon-sdk/source/test/tabs/utils.js deleted file mode 100644 index 4981a4d087..0000000000 --- a/addon-sdk/source/test/tabs/utils.js +++ /dev/null @@ -1,24 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { openTab: makeTab, getTabContentWindow } = require("sdk/tabs/utils"); - -function openTab(rawWindow, url) { - return new Promise(resolve => { - let tab = makeTab(rawWindow, url); - let window = getTabContentWindow(tab); - if (window.document.readyState == "complete") { - return resolve(); - } - - window.addEventListener("load", function onLoad() { - window.removeEventListener("load", onLoad, true); - resolve(); - }, true); - - return null; - }) -} -exports.openTab = openTab; diff --git a/addon-sdk/source/test/test-addon-bootstrap.js b/addon-sdk/source/test/test-addon-bootstrap.js deleted file mode 100644 index 01e2d15fc3..0000000000 --- a/addon-sdk/source/test/test-addon-bootstrap.js +++ /dev/null @@ -1,97 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cu, Cc, Ci } = require("chrome"); -const { create, evaluate } = require("./fixtures/bootstrap/utils"); - -const ROOT = require.resolve("sdk/base64").replace("/sdk/base64.js", ""); - -// Note: much of this test code is from -// http://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm -const BOOTSTRAP_REASONS = { - APP_STARTUP : 1, - APP_SHUTDOWN : 2, - ADDON_ENABLE : 3, - ADDON_DISABLE : 4, - ADDON_INSTALL : 5, - ADDON_UNINSTALL : 6, - ADDON_UPGRADE : 7, - ADDON_DOWNGRADE : 8 -}; - -exports["test install/startup/shutdown/uninstall all return a promise"] = function(assert) { - let uri = require.resolve("./fixtures/addon/bootstrap.js"); - let id = "test-min-boot@jetpack"; - let bootstrapScope = create({ - id: id, - uri: uri - }); - - // As we don't want our caller to control the JS version used for the - // bootstrap file, we run loadSubScript within the context of the - // sandbox with the latest JS version set explicitly. - bootstrapScope.ROOT = ROOT; - - evaluate({ - uri: uri, - scope: bootstrapScope - }); - - let addon = { - id: id, - version: "0.0.1", - resourceURI: { - spec: uri.replace("bootstrap.js", "") - } - }; - - let install = bootstrapScope.install(addon, BOOTSTRAP_REASONS.ADDON_INSTALL); - yield install.then(() => assert.pass("install returns a promise")); - - let startup = bootstrapScope.startup(addon, BOOTSTRAP_REASONS.ADDON_INSTALL); - yield startup.then(() => assert.pass("startup returns a promise")); - - let shutdown = bootstrapScope.shutdown(addon, BOOTSTRAP_REASONS.ADDON_DISABLE); - yield shutdown.then(() => assert.pass("shutdown returns a promise")); - - // calling shutdown multiple times is fine - shutdown = bootstrapScope.shutdown(addon, BOOTSTRAP_REASONS.ADDON_DISABLE); - yield shutdown.then(() => assert.pass("shutdown returns working promise on multiple calls")); - - let uninstall = bootstrapScope.uninstall(addon, BOOTSTRAP_REASONS.ADDON_UNINSTALL); - yield uninstall.then(() => assert.pass("uninstall returns a promise")); -} - -exports["test minimal bootstrap.js"] = function*(assert) { - let uri = require.resolve("./fixtures/addon/bootstrap.js"); - let bootstrapScope = create({ - id: "test-min-boot@jetpack", - uri: uri - }); - - // As we don't want our caller to control the JS version used for the - // bootstrap file, we run loadSubScript within the context of the - // sandbox with the latest JS version set explicitly. - bootstrapScope.ROOT = ROOT; - - assert.equal(typeof bootstrapScope.install, "undefined", "install DNE"); - assert.equal(typeof bootstrapScope.startup, "undefined", "startup DNE"); - assert.equal(typeof bootstrapScope.shutdown, "undefined", "shutdown DNE"); - assert.equal(typeof bootstrapScope.uninstall, "undefined", "uninstall DNE"); - - evaluate({ - uri: uri, - scope: bootstrapScope - }); - - assert.equal(typeof bootstrapScope.install, "function", "install exists"); - assert.equal(typeof bootstrapScope.startup, "function", "startup exists"); - assert.equal(typeof bootstrapScope.shutdown, "function", "shutdown exists"); - assert.equal(typeof bootstrapScope.uninstall, "function", "uninstall exists"); - - bootstrapScope.shutdown(null, BOOTSTRAP_REASONS.ADDON_DISABLE); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-addon-extras.js b/addon-sdk/source/test/test-addon-extras.js deleted file mode 100644 index 1910db05e1..0000000000 --- a/addon-sdk/source/test/test-addon-extras.js +++ /dev/null @@ -1,70 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Ci, Cu, Cc, components } = require("chrome"); -const self = require("sdk/self"); -const { before, after } = require("sdk/test/utils"); -const fixtures = require("./fixtures"); -const { Loader } = require("sdk/test/loader"); -const { merge } = require("sdk/util/object"); - -exports["test changing result from addon extras in panel"] = function(assert, done) { - let loader = Loader(module, null, null, { - modules: { - "sdk/self": merge({}, self, { - data: merge({}, self.data, {url: fixtures.url}) - }) - } - }); - - const { Panel } = loader.require("sdk/panel"); - const { events } = loader.require("sdk/content/sandbox/events"); - const { on } = loader.require("sdk/event/core"); - const { isAddonContent } = loader.require("sdk/content/utils"); - - var result = 1; - var extrasVal = { - test: function() { - return result; - } - }; - - on(events, "content-script-before-inserted", ({ window, worker }) => { - assert.pass("content-script-before-inserted"); - - if (isAddonContent({ contentURL: window.location.href })) { - let extraStuff = Cu.cloneInto(extrasVal, window, { - cloneFunctions: true - }); - getUnsafeWindow(window).extras = extraStuff; - - assert.pass("content-script-before-inserted done!"); - } - }); - - let panel = Panel({ - contentURL: "./test-addon-extras.html" - }); - - panel.port.once("result1", (result) => { - assert.equal(result, 1, "result is a number"); - result = true; - panel.port.emit("get-result"); - }); - - panel.port.once("result2", (result) => { - assert.equal(result, true, "result is a boolean"); - loader.unload(); - done(); - }); - - panel.port.emit("get-result"); -} - -function getUnsafeWindow (win) { - return win.wrappedJSObject || win; -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-addon-installer.js b/addon-sdk/source/test/test-addon-installer.js deleted file mode 100644 index bb39cca2db..0000000000 --- a/addon-sdk/source/test/test-addon-installer.js +++ /dev/null @@ -1,230 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cc, Ci, Cu } = require("chrome"); -const { pathFor } = require("sdk/system"); -const AddonInstaller = require("sdk/addon/installer"); -const { on, off } = require("sdk/system/events"); -const { setTimeout } = require("sdk/timers"); -const fs = require("sdk/io/fs"); -const path = require("sdk/fs/path"); -const { OS } = require("resource://gre/modules/osfile.jsm"); -const { toFilename } = require("sdk/url"); - -// Retrieve the path to the OS temporary directory: -const tmpDir = pathFor("TmpD"); - -const profilePath = pathFor("ProfD"); -const corruptXPIPath = path.join(profilePath, "sdk-corrupt.xpi"); -const testFolderURL = module.uri.split('test-addon-installer.js')[0]; -const ADDON_URL = toFilename(testFolderURL + "fixtures/addon-install-unit-test@mozilla.com.xpi"); - -exports["test Install"] = function*(assert) { - var ADDON_PATH = OS.Path.join(OS.Constants.Path.tmpDir, "install-test.xpi"); - - assert.pass("Copying test add-on " + ADDON_URL + " to " + ADDON_PATH); - - yield OS.File.copy(ADDON_URL, ADDON_PATH); - - assert.pass("Copied test add-on to " + ADDON_PATH); - - // Save all events distpatched by bootstrap.js of the installed addon - let events = []; - function eventsObserver({ data }) { - events.push(data); - } - on("addon-install-unit-test", eventsObserver); - - // Install the test addon - yield AddonInstaller.install(ADDON_PATH).then((id) => { - assert.equal(id, "addon-install-unit-test@mozilla.com", "`id` is valid"); - - // Now uninstall it - return AddonInstaller.uninstall(id).then(function () { - // Ensure that bootstrap.js methods of the addon have been called - // successfully and in the right order - let expectedEvents = ["install", "startup", "shutdown", "uninstall"]; - assert.equal(JSON.stringify(events), - JSON.stringify(expectedEvents), - "addon's bootstrap.js functions have been called"); - - off("addon-install-unit-test", eventsObserver); - }); - }, (code) => { - assert.fail("Install failed: "+code); - off("addon-install-unit-test", eventsObserver); - }); - - assert.pass("Add-on was uninstalled."); - - yield OS.File.remove(ADDON_PATH); - - assert.pass("Removed the temp file"); -}; - -exports["test Failing Install With Invalid Path"] = function (assert, done) { - AddonInstaller.install("invalid-path").then( - function onInstalled(id) { - assert.fail("Unexpected success"); - done(); - }, - function onFailure(code) { - assert.equal(code, AddonInstaller.ERROR_FILE_ACCESS, - "Got expected error code"); - done(); - } - ); -}; - -exports["test Failing Install With Invalid File"] = function (assert, done) { - const content = "bad xpi"; - const path = corruptXPIPath; - - fs.writeFile(path, content, (error) => { - assert.equal(fs.readFileSync(path).toString(), - content, - "contet was written"); - - AddonInstaller.install(path).then( - () => { - assert.fail("Unexpected success"); - fs.unlink(path, done); - }, - (code) => { - assert.equal(code, AddonInstaller.ERROR_CORRUPT_FILE, - "Got expected error code"); - fs.unlink(path, done); - } - ); - }); -} - -exports["test Update"] = function*(assert) { - var ADDON_PATH = OS.Path.join(OS.Constants.Path.tmpDir, "update-test.xpi"); - - assert.pass("Copying test add-on " + ADDON_URL + " to " + ADDON_PATH); - - yield OS.File.copy(ADDON_URL, ADDON_PATH); - - assert.pass("Copied test add-on to " + ADDON_PATH); - - // Save all events distpatched by bootstrap.js of the installed addon - let events = []; - let iteration = 1; - let eventsObserver = ({data}) => events.push(data); - on("addon-install-unit-test", eventsObserver); - - yield new Promise(resolve => { - function onInstalled(id) { - let prefix = "[" + iteration + "] "; - assert.equal(id, "addon-install-unit-test@mozilla.com", - prefix + "`id` is valid"); - - // On 2nd and 3rd iteration, we receive uninstall events from the last - // previously installed addon - let expectedEvents = - iteration == 1 - ? ["install", "startup"] - : ["shutdown", "uninstall", "install", "startup"]; - assert.equal(JSON.stringify(events), - JSON.stringify(expectedEvents), - prefix + "addon's bootstrap.js functions have been called"); - - if (iteration++ < 3) { - next(); - } - else { - events = []; - AddonInstaller.uninstall(id).then(function() { - let expectedEvents = ["shutdown", "uninstall"]; - assert.equal(JSON.stringify(events), - JSON.stringify(expectedEvents), - prefix + "addon's bootstrap.js functions have been called"); - - off("addon-install-unit-test", eventsObserver); - resolve(); - }); - } - } - function onFailure(code) { - assert.fail("Install failed: "+code); - off("addon-install-unit-test", eventsObserver); - resolve(); - } - - function next() { - events = []; - AddonInstaller.install(ADDON_PATH).then(onInstalled, onFailure); - } - - next(); - }); - - assert.pass("Add-on was uninstalled."); - - yield OS.File.remove(ADDON_PATH); - - assert.pass("Removed the temp file"); -}; - -exports['test Uninstall failure'] = function (assert, done) { - AddonInstaller.uninstall('invalid-addon-path').then( - () => assert.fail('Addon uninstall should not resolve successfully'), - () => assert.pass('Addon correctly rejected invalid uninstall') - ).then(done, assert.fail); -}; - -exports['test Addon Disable and Enable'] = function*(assert) { - var ADDON_PATH = OS.Path.join(OS.Constants.Path.tmpDir, "disable-enable-test.xpi"); - - assert.pass("Copying test add-on " + ADDON_URL + " to " + ADDON_PATH); - - yield OS.File.copy(ADDON_URL, ADDON_PATH); - - assert.pass("Copied test add-on to " + ADDON_PATH); - - let ensureActive = (addonId) => AddonInstaller.isActive(addonId).then(state => { - assert.equal(state, true, 'Addon should be enabled by default'); - return addonId; - }); - let ensureInactive = (addonId) => AddonInstaller.isActive(addonId).then(state => { - assert.equal(state, false, 'Addon should be disabled after disabling'); - return addonId; - }); - - yield AddonInstaller.install(ADDON_PATH) - .then(ensureActive) - .then(AddonInstaller.enable) // should do nothing, yet not fail - .then(ensureActive) - .then(AddonInstaller.disable) - .then(ensureInactive) - .then(AddonInstaller.disable) // should do nothing, yet not fail - .then(ensureInactive) - .then(AddonInstaller.enable) - .then(ensureActive) - .then(AddonInstaller.uninstall); - - assert.pass("Add-on was uninstalled."); - - yield OS.File.remove(ADDON_PATH); - - assert.pass("Removed the temp file"); -}; - -exports['test Disable failure'] = function (assert, done) { - AddonInstaller.disable('not-an-id').then( - () => assert.fail('Addon disable should not resolve successfully'), - () => assert.pass('Addon correctly rejected invalid disable') - ).then(done, assert.fail); -}; - -exports['test Enable failure'] = function (assert, done) { - AddonInstaller.enable('not-an-id').then( - () => assert.fail('Addon enable should not resolve successfully'), - () => assert.pass('Addon correctly rejected invalid enable') - ).then(done, assert.fail); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-addon-window.js b/addon-sdk/source/test/test-addon-window.js deleted file mode 100644 index 8cb07bb07d..0000000000 --- a/addon-sdk/source/test/test-addon-window.js +++ /dev/null @@ -1,22 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -var { Loader } = require('sdk/test/loader'); - -exports.testReady = function(assert, done) { - let loader = Loader(module); - let { ready, window } = loader.require('sdk/addon/window'); - let windowIsReady = false; - - ready.then(function() { - assert.equal(windowIsReady, false, 'ready promise was resolved only once'); - windowIsReady = true; - - loader.unload(); - done(); - }).then(null, assert.fail); -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-api-utils.js b/addon-sdk/source/test/test-api-utils.js deleted file mode 100644 index 12f2bf44f3..0000000000 --- a/addon-sdk/source/test/test-api-utils.js +++ /dev/null @@ -1,316 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const apiUtils = require("sdk/deprecated/api-utils"); - -exports.testValidateOptionsEmpty = function (assert) { - let val = apiUtils.validateOptions(null, {}); - - assert.deepEqual(val, {}); - - val = apiUtils.validateOptions(null, { foo: {} }); - assert.deepEqual(val, {}); - - val = apiUtils.validateOptions({}, {}); - assert.deepEqual(val, {}); - - val = apiUtils.validateOptions({}, { foo: {} }); - assert.deepEqual(val, {}); -}; - -exports.testValidateOptionsNonempty = function (assert) { - let val = apiUtils.validateOptions({ foo: 123 }, {}); - assert.deepEqual(val, {}); - - val = apiUtils.validateOptions({ foo: 123, bar: 456 }, - { foo: {}, bar: {}, baz: {} }); - - assert.deepEqual(val, { foo: 123, bar: 456 }); -}; - -exports.testValidateOptionsMap = function (assert) { - let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, { - foo: { map: v => v * v }, - bar: { map: v => undefined } - }); - assert.deepEqual(val, { foo: 9, bar: undefined }); -}; - -exports.testValidateOptionsMapException = function (assert) { - let val = apiUtils.validateOptions({ foo: 3 }, { - foo: { map: function () { throw new Error(); }} - }); - assert.deepEqual(val, { foo: 3 }); -}; - -exports.testValidateOptionsOk = function (assert) { - let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, { - foo: { ok: v => v }, - bar: { ok: v => v } - }); - assert.deepEqual(val, { foo: 3, bar: 2 }); - - assert.throws( - () => apiUtils.validateOptions({ foo: 2, bar: 2 }, { - bar: { ok: v => v > 2 } - }), - /^The option "bar" is invalid/, - "ok should raise exception on invalid option" - ); - - assert.throws( - () => apiUtils.validateOptions(null, { foo: { ok: v => v }}), - /^The option "foo" is invalid/, - "ok should raise exception on invalid option" - ); -}; - -exports.testValidateOptionsIs = function (assert) { - let opts = { - array: [], - boolean: true, - func: function () {}, - nul: null, - number: 1337, - object: {}, - string: "foo", - undef1: undefined - }; - let requirements = { - array: { is: ["array"] }, - boolean: { is: ["boolean"] }, - func: { is: ["function"] }, - nul: { is: ["null"] }, - number: { is: ["number"] }, - object: { is: ["object"] }, - string: { is: ["string"] }, - undef1: { is: ["undefined"] }, - undef2: { is: ["undefined"] } - }; - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - assert.throws( - () => apiUtils.validateOptions(null, { - foo: { is: ["object", "number"] } - }), - /^The option "foo" must be one of the following types: object, number/, - "Invalid type should raise exception" - ); -}; - -exports.testValidateOptionsIsWithExportedValue = function (assert) { - let { string, number, boolean, object } = apiUtils; - - let opts = { - boolean: true, - number: 1337, - object: {}, - string: "foo" - }; - let requirements = { - string: { is: string }, - number: { is: number }, - boolean: { is: boolean }, - object: { is: object } - }; - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - // Test the types are optional by default - val = apiUtils.validateOptions({foo: 'bar'}, requirements); - assert.deepEqual(val, {}); -}; - -exports.testValidateOptionsIsWithEither = function (assert) { - let { string, number, boolean, either } = apiUtils; - let text = { is: either(string, number) }; - - let requirements = { - text: text, - boolOrText: { is: either(text, boolean) } - }; - - let val = apiUtils.validateOptions({text: 12}, requirements); - assert.deepEqual(val, {text: 12}); - - val = apiUtils.validateOptions({text: "12"}, requirements); - assert.deepEqual(val, {text: "12"}); - - val = apiUtils.validateOptions({boolOrText: true}, requirements); - assert.deepEqual(val, {boolOrText: true}); - - val = apiUtils.validateOptions({boolOrText: "true"}, requirements); - assert.deepEqual(val, {boolOrText: "true"}); - - val = apiUtils.validateOptions({boolOrText: 1}, requirements); - assert.deepEqual(val, {boolOrText: 1}); - - assert.throws( - () => apiUtils.validateOptions({text: true}, requirements), - /^The option "text" must be one of the following types/, - "Invalid type should raise exception" - ); - - assert.throws( - () => apiUtils.validateOptions({boolOrText: []}, requirements), - /^The option "boolOrText" must be one of the following types/, - "Invalid type should raise exception" - ); -}; - -exports.testValidateOptionsWithRequiredAndOptional = function (assert) { - let { string, number, required, optional } = apiUtils; - - let opts = { - number: 1337, - string: "foo" - }; - - let requirements = { - string: required(string), - number: number - }; - - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - val = apiUtils.validateOptions({string: "foo"}, requirements); - assert.deepEqual(val, {string: "foo"}); - - assert.throws( - () => apiUtils.validateOptions({number: 10}, requirements), - /^The option "string" must be one of the following types/, - "Invalid type should raise exception" - ); - - // Makes string optional - requirements.string = optional(requirements.string); - - val = apiUtils.validateOptions({number: 10}, requirements), - assert.deepEqual(val, {number: 10}); - -}; - - - -exports.testValidateOptionsWithExportedValue = function (assert) { - let { string, number, boolean, object } = apiUtils; - - let opts = { - boolean: true, - number: 1337, - object: {}, - string: "foo" - }; - let requirements = { - string: string, - number: number, - boolean: boolean, - object: object - }; - let val = apiUtils.validateOptions(opts, requirements); - assert.deepEqual(val, opts); - - // Test the types are optional by default - val = apiUtils.validateOptions({foo: 'bar'}, requirements); - assert.deepEqual(val, {}); -}; - - -exports.testValidateOptionsMapIsOk = function (assert) { - let [map, is, ok] = [false, false, false]; - let val = apiUtils.validateOptions({ foo: 1337 }, { - foo: { - map: v => v.toString(), - is: ["string"], - ok: v => v.length > 0 - } - }); - assert.deepEqual(val, { foo: "1337" }); - - let requirements = { - foo: { - is: ["object"], - ok: () => assert.fail("is should have caused us to throw by now") - } - }; - assert.throws( - () => apiUtils.validateOptions(null, requirements), - /^The option "foo" must be one of the following types: object/, - "is should be used before ok is called" - ); -}; - -exports.testValidateOptionsErrorMsg = function (assert) { - assert.throws( - () => apiUtils.validateOptions(null, { - foo: { ok: v => v, msg: "foo!" } - }), - /^foo!/, - "ok should raise exception with customized message" - ); -}; - -exports.testValidateMapWithMissingKey = function (assert) { - let val = apiUtils.validateOptions({ }, { - foo: { - map: v => v || "bar" - } - }); - assert.deepEqual(val, { foo: "bar" }); - - val = apiUtils.validateOptions({ }, { - foo: { - map: v => { throw "bar" } - } - }); - assert.deepEqual(val, { }); -}; - -exports.testValidateMapWithMissingKeyAndThrown = function (assert) { - let val = apiUtils.validateOptions({}, { - bar: { - map: function(v) { throw "bar" } - }, - baz: { - map: v => "foo" - } - }); - assert.deepEqual(val, { baz: "foo" }); -}; - -exports.testAddIterator = function testAddIterator (assert) { - let obj = {}; - let keys = ["foo", "bar", "baz"]; - let vals = [1, 2, 3]; - let keysVals = [["foo", 1], ["bar", 2], ["baz", 3]]; - apiUtils.addIterator( - obj, - function keysValsGen() { - for (let keyVal of keysVals) - yield keyVal; - } - ); - - let keysItr = []; - for (let key in obj) - keysItr.push(key); - - assert.equal(keysItr.length, keys.length, - "the keys iterator returns the correct number of items"); - for (let i = 0; i < keys.length; i++) - assert.equal(keysItr[i], keys[i], "the key is correct"); - - let valsItr = []; - for each (let val in obj) - valsItr.push(val); - assert.equal(valsItr.length, vals.length, - "the vals iterator returns the correct number of items"); - for (let i = 0; i < vals.length; i++) - assert.equal(valsItr[i], vals[i], "the val is correct"); - -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-array.js b/addon-sdk/source/test/test-array.js deleted file mode 100644 index 161d8033dc..0000000000 --- a/addon-sdk/source/test/test-array.js +++ /dev/null @@ -1,103 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict' - -const array = require('sdk/util/array'); - -exports.testHas = function(assert) { - var testAry = [1, 2, 3]; - assert.equal(array.has([1, 2, 3], 1), true); - assert.equal(testAry.length, 3); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); - assert.equal(testAry[2], 3); - assert.equal(array.has(testAry, 2), true); - assert.equal(array.has(testAry, 3), true); - assert.equal(array.has(testAry, 4), false); - assert.equal(array.has(testAry, '1'), false); -}; -exports.testHasAny = function(assert) { - var testAry = [1, 2, 3]; - assert.equal(array.hasAny([1, 2, 3], [1]), true); - assert.equal(array.hasAny([1, 2, 3], [1, 5]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 1]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 2]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 3]), true); - assert.equal(array.hasAny([1, 2, 3], [5, 4]), false); - assert.equal(testAry.length, 3); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); - assert.equal(testAry[2], 3); - assert.equal(array.hasAny(testAry, [2]), true); - assert.equal(array.hasAny(testAry, [3]), true); - assert.equal(array.hasAny(testAry, [4]), false); - assert.equal(array.hasAny(testAry), false); - assert.equal(array.hasAny(testAry, '1'), false); -}; - -exports.testAdd = function(assert) { - var testAry = [1]; - assert.equal(array.add(testAry, 1), false); - assert.equal(testAry.length, 1); - assert.equal(testAry[0], 1); - assert.equal(array.add(testAry, 2), true); - assert.equal(testAry.length, 2); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); -}; - -exports.testRemove = function(assert) { - var testAry = [1, 2]; - assert.equal(array.remove(testAry, 3), false); - assert.equal(testAry.length, 2); - assert.equal(testAry[0], 1); - assert.equal(testAry[1], 2); - assert.equal(array.remove(testAry, 2), true); - assert.equal(testAry.length, 1); - assert.equal(testAry[0], 1); -}; - -exports.testFlatten = function(assert) { - assert.equal(array.flatten([1, 2, 3]).length, 3); - assert.equal(array.flatten([1, [2, 3]]).length, 3); - assert.equal(array.flatten([1, [2, [3]]]).length, 3); - assert.equal(array.flatten([[1], [[2, [3]]]]).length, 3); -}; - -exports.testUnique = function(assert) { - var Class = function () {}; - var A = {}; - var B = new Class(); - var C = [ 1, 2, 3 ]; - var D = {}; - var E = new Class(); - - assert.deepEqual(array.unique([1,2,3,1,2]), [1,2,3]); - assert.deepEqual(array.unique([1,1,1,4,9,5,5]), [1,4,9,5]); - assert.deepEqual(array.unique([A, A, A, B, B, D]), [A,B,D]); - assert.deepEqual(array.unique([A, D, A, E, E, D, A, A, C]), [A, D, E, C]) -}; - -exports.testUnion = function(assert) { - var Class = function () {}; - var A = {}; - var B = new Class(); - var C = [ 1, 2, 3 ]; - var D = {}; - var E = new Class(); - - assert.deepEqual(array.union([1, 2, 3],[7, 1, 2]), [1, 2, 3, 7]); - assert.deepEqual(array.union([1, 1, 1, 4, 9, 5, 5], [10, 1, 5]), [1, 4, 9, 5, 10]); - assert.deepEqual(array.union([A, B], [A, D]), [A, B, D]); - assert.deepEqual(array.union([A, D], [A, E], [E, D, A], [A, C]), [A, D, E, C]); -}; - -exports.testFind = function(assert) { - let isOdd = (x) => x % 2; - assert.equal(array.find([2, 4, 5, 7, 8, 9], isOdd), 5); - assert.equal(array.find([2, 4, 6, 8], isOdd), undefined); - assert.equal(array.find([2, 4, 6, 8], isOdd, null), null); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-base64.js b/addon-sdk/source/test/test-base64.js deleted file mode 100644 index b969413f92..0000000000 --- a/addon-sdk/source/test/test-base64.js +++ /dev/null @@ -1,100 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const base64 = require("sdk/base64"); - -const text = "Awesome!"; -const b64text = "QXdlc29tZSE="; - -const utf8text = "\u2713 à la mode"; -const badutf8text = "\u0013 à la mode"; -const b64utf8text = "4pyTIMOgIGxhIG1vZGU="; - -// 1 MB string -const longtext = 'fff'.repeat(333333); -const b64longtext = 'ZmZm'.repeat(333333); - -exports["test base64.encode"] = function (assert) { - assert.equal(base64.encode(text), b64text, "encode correctly") -} - -exports["test base64.decode"] = function (assert) { - assert.equal(base64.decode(b64text), text, "decode correctly") -} - -exports["test base64.encode Unicode"] = function (assert) { - - assert.equal(base64.encode(utf8text, "utf-8"), b64utf8text, - "encode correctly Unicode strings.") -} - -exports["test base64.decode Unicode"] = function (assert) { - - assert.equal(base64.decode(b64utf8text, "utf-8"), utf8text, - "decode correctly Unicode strings.") -} - -exports["test base64.encode long string"] = function (assert) { - - assert.equal(base64.encode(longtext), b64longtext, "encode long strings") -} - -exports["test base64.decode long string"] = function (assert) { - - assert.equal(base64.decode(b64longtext), longtext, "decode long strings") -} - -exports["test base64.encode treats input as octet string"] = function (assert) { - - assert.equal(base64.encode("\u0066"), "Zg==", - "treat octet string as octet string") - assert.equal(base64.encode("\u0166"), "Zg==", - "treat non-octet string as octet string") - assert.equal(base64.encode("\uff66"), "Zg==", - "encode non-octet string as octet string") -} - -exports["test base64.encode with wrong charset"] = function (assert) { - - assert.throws(function() { - base64.encode(utf8text, "utf-16"); - }, "The charset argument can be only 'utf-8'"); - - assert.throws(function() { - base64.encode(utf8text, ""); - }, "The charset argument can be only 'utf-8'"); - - assert.throws(function() { - base64.encode(utf8text, 8); - }, "The charset argument can be only 'utf-8'"); - -} - -exports["test base64.decode with wrong charset"] = function (assert) { - - assert.throws(function() { - base64.decode(utf8text, "utf-16"); - }, "The charset argument can be only 'utf-8'"); - - assert.throws(function() { - base64.decode(utf8text, ""); - }, "The charset argument can be only 'utf-8'"); - - assert.throws(function() { - base64.decode(utf8text, 8); - }, "The charset argument can be only 'utf-8'"); - -} - -exports["test encode/decode Unicode without utf-8 as charset"] = function (assert) { - - assert.equal(base64.decode(base64.encode(utf8text)), badutf8text, - "Unicode strings needs 'utf-8' charset or will be mangled" - ); - -} - -require("test").run(exports); diff --git a/addon-sdk/source/test/test-bootstrap.js b/addon-sdk/source/test/test-bootstrap.js deleted file mode 100644 index 52a713e617..0000000000 --- a/addon-sdk/source/test/test-bootstrap.js +++ /dev/null @@ -1,19 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Request } = require("sdk/request"); - -exports.testBootstrapExists = function (assert, done) { - Request({ - url: "resource://gre/modules/sdk/bootstrap.js", - onComplete: function (response) { - if (response.text) - assert.pass("the bootstrap file was found"); - done(); - } - }).get(); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-browser-events.js b/addon-sdk/source/test/test-browser-events.js deleted file mode 100644 index 9402f1ec5a..0000000000 --- a/addon-sdk/source/test/test-browser-events.js +++ /dev/null @@ -1,102 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - engines: { - "Firefox": "*" - } -}; - -const { Loader } = require("sdk/test/loader"); -const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils"); -const { setTimeout } = require("sdk/timers"); - -exports["test browser events"] = function(assert, done) { - let loader = Loader(module); - let { events } = loader.require("sdk/browser/events"); - let { on, off } = loader.require("sdk/event/core"); - let actual = []; - - on(events, "data", function handler(e) { - actual.push(e); - if (e.type === "load") window.close(); - if (e.type === "close") { - // Unload the module so that all listeners set by observer are removed. - - let [ ready, load, close ] = actual; - - assert.equal(ready.type, "DOMContentLoaded"); - assert.equal(ready.target, window, "window ready"); - - assert.equal(load.type, "load"); - assert.equal(load.target, window, "window load"); - - assert.equal(close.type, "close"); - assert.equal(close.target, window, "window load"); - - // Note: If window is closed right after this GC won't have time - // to claim loader and there for this listener, there for it's safer - // to remove listener. - off(events, "data", handler); - loader.unload(); - done(); - } - }); - - // Open window and close it to trigger observers. - let window = open(); -}; - -exports["test browser events ignore other wins"] = function(assert, done) { - let loader = Loader(module); - let { events: windowEvents } = loader.require("sdk/window/events"); - let { events: browserEvents } = loader.require("sdk/browser/events"); - let { on, off } = loader.require("sdk/event/core"); - let actualBrowser = []; - let actualWindow = []; - - function browserEventHandler(e) { - return actualBrowser.push(e); - } - on(browserEvents, "data", browserEventHandler); - on(windowEvents, "data", function handler(e) { - actualWindow.push(e); - // Delay close so that if "load" is also emitted on `browserEvents` - // `browserEventHandler` will be invoked. - if (e.type === "load") setTimeout(window.close); - if (e.type === "close") { - assert.deepEqual(actualBrowser, [], "browser events were not triggered"); - let [ open, ready, load, close ] = actualWindow; - - assert.equal(open.type, "open"); - assert.equal(open.target, window, "window is open"); - - - - assert.equal(ready.type, "DOMContentLoaded"); - assert.equal(ready.target, window, "window ready"); - - assert.equal(load.type, "load"); - assert.equal(load.target, window, "window load"); - - assert.equal(close.type, "close"); - assert.equal(close.target, window, "window load"); - - - // Note: If window is closed right after this GC won't have time - // to claim loader and there for this listener, there for it's safer - // to remove listener. - off(windowEvents, "data", handler); - off(browserEvents, "data", browserEventHandler); - loader.unload(); - done(); - } - }); - - // Open window and close it to trigger observers. - let window = open("data:text/html,not a browser"); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-buffer.js b/addon-sdk/source/test/test-buffer.js deleted file mode 100644 index e55bf2b2fc..0000000000 --- a/addon-sdk/source/test/test-buffer.js +++ /dev/null @@ -1,563 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - - -/* - * Many of these tests taken from Joyent's Node - * https://github.com/joyent/node/blob/master/test/simple/test-buffer.js - */ - -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -const { Buffer, TextEncoder, TextDecoder } = require('sdk/io/buffer'); -const { safeMerge } = require('sdk/util/object'); - -const ENCODINGS = ['utf-8']; - -exports.testBufferMain = function (assert) { - let b = Buffer('abcdef'); - - // try to create 0-length buffers - new Buffer(''); - new Buffer(0); - // test encodings supported by node; - // this is different than what node supports, details - // in buffer.js - ENCODINGS.forEach(enc => { - new Buffer('', enc); - assert.pass('Creating a buffer with ' + enc + ' does not throw'); - }); - - ENCODINGS.forEach(function(encoding) { - // Does not work with utf8 - if (encoding === 'utf-8') return; - var b = new Buffer(10); - b.write('あいうえお', encoding); - assert.equal(b.toString(encoding), 'あいうえお', - 'encode and decodes buffer with ' + encoding); - }); - - // invalid encoding for Buffer.toString - assert.throws(() => { - b.toString('invalid'); - }, RangeError, 'invalid encoding for Buffer.toString'); - - // try to toString() a 0-length slice of a buffer, both within and without the - // valid buffer range - assert.equal(new Buffer('abc').toString('utf8', 0, 0), '', - 'toString 0-length buffer, valid range'); - assert.equal(new Buffer('abc').toString('utf8', -100, -100), '', - 'toString 0-length buffer, invalid range'); - assert.equal(new Buffer('abc').toString('utf8', 100, 100), '', - 'toString 0-length buffer, invalid range'); - - // try toString() with a object as a encoding - assert.equal(new Buffer('abc').toString({toString: function() { - return 'utf8'; - }}), 'abc', 'toString with object as an encoding'); - - // test for buffer overrun - var buf = new Buffer([0, 0, 0, 0, 0]); // length: 5 - var sub = buf.slice(0, 4); // length: 4 - var written = sub.write('12345', 'utf8'); - assert.equal(written, 4, 'correct bytes written in slice'); - assert.equal(buf[4], 0, 'correct origin buffer value'); - - // Check for fractional length args, junk length args, etc. - // https://github.com/joyent/node/issues/1758 - Buffer(3.3).toString(); // throws bad argument error in commit 43cb4ec - assert.equal(Buffer(-1).length, 0); - assert.equal(Buffer(NaN).length, 0); - assert.equal(Buffer(3.3).length, 3); - assert.equal(Buffer({length: 3.3}).length, 3); - assert.equal(Buffer({length: 'BAM'}).length, 0); - - // Make sure that strings are not coerced to numbers. - assert.equal(Buffer('99').length, 2); - assert.equal(Buffer('13.37').length, 5); -}; - -exports.testIsEncoding = function (assert) { - ENCODINGS.map(encoding => { - assert.ok(Buffer.isEncoding(encoding), - 'Buffer.isEncoding ' + encoding + ' truthy'); - }); - ['not-encoding', undefined, null, 100, {}].map(encoding => { - assert.ok(!Buffer.isEncoding(encoding), - 'Buffer.isEncoding ' + encoding + ' falsy'); - }); -}; - -exports.testBufferCopy = function (assert) { - // counter to ensure unique value is always copied - var cntr = 0; - var b = Buffer(1024); // safe constructor - - assert.strictEqual(1024, b.length); - b[0] = -1; - assert.strictEqual(b[0], 255); - - var shimArray = []; - for (var i = 0; i < 1024; i++) { - b[i] = i % 256; - shimArray[i] = i % 256; - } - - compareBuffers(assert, b, shimArray, 'array notation'); - - var c = new Buffer(512); - assert.strictEqual(512, c.length); - // copy 512 bytes, from 0 to 512. - b.fill(++cntr); - c.fill(++cntr); - var copied = b.copy(c, 0, 0, 512); - assert.strictEqual(512, copied, - 'copied ' + copied + ' bytes from b into c'); - - compareBuffers(assert, b, c, 'copied to other buffer'); - - // copy c into b, without specifying sourceEnd - b.fill(++cntr); - c.fill(++cntr); - var copied = c.copy(b, 0, 0); - assert.strictEqual(c.length, copied, - 'copied ' + copied + ' bytes from c into b w/o sourceEnd'); - compareBuffers(assert, b, c, - 'copied to other buffer without specifying sourceEnd'); - - // copy c into b, without specifying sourceStart - b.fill(++cntr); - c.fill(++cntr); - var copied = c.copy(b, 0); - assert.strictEqual(c.length, copied, - 'copied ' + copied + ' bytes from c into b w/o sourceStart'); - compareBuffers(assert, b, c, - 'copied to other buffer without specifying sourceStart'); - - // copy longer buffer b to shorter c without targetStart - b.fill(++cntr); - c.fill(++cntr); - - var copied = b.copy(c); - assert.strictEqual(c.length, copied, - 'copied ' + copied + ' bytes from b into c w/o targetStart'); - compareBuffers(assert, b, c, - 'copy long buffer to shorter buffer without targetStart'); - - // copy starting near end of b to c - b.fill(++cntr); - c.fill(++cntr); - var copied = b.copy(c, 0, b.length - Math.floor(c.length / 2)); - assert.strictEqual(Math.floor(c.length / 2), copied, - 'copied ' + copied + ' bytes from end of b into beg. of c'); - - let successStatus = true; - for (var i = 0; i < Math.floor(c.length / 2); i++) { - if (b[b.length - Math.floor(c.length / 2) + i] !== c[i]) - successStatus = false; - } - - for (var i = Math.floor(c.length /2) + 1; i < c.length; i++) { - if (c[c.length-1] !== c[i]) - successStatus = false; - } - assert.ok(successStatus, - 'Copied bytes from end of large buffer into beginning of small buffer'); - - // try to copy 513 bytes, and check we don't overrun c - b.fill(++cntr); - c.fill(++cntr); - var copied = b.copy(c, 0, 0, 513); - assert.strictEqual(c.length, copied, - 'copied ' + copied + ' bytes from b trying to overrun c'); - compareBuffers(assert, b, c, - 'copying to buffer that would overflow'); - - // copy 768 bytes from b into b - b.fill(++cntr); - b.fill(++cntr, 256); - var copied = b.copy(b, 0, 256, 1024); - assert.strictEqual(768, copied, - 'copied ' + copied + ' bytes from b into b'); - - compareBuffers(assert, b, shimArray.map(()=>cntr), - 'copy partial buffer to itself'); - - // copy string longer than buffer length (failure will segfault) - var bb = new Buffer(10); - bb.fill('hello crazy world'); - - // copy throws at negative sourceStart - assert.throws(function() { - Buffer(5).copy(Buffer(5), 0, -1); - }, RangeError, 'buffer copy throws at negative sourceStart'); - - // check sourceEnd resets to targetEnd if former is greater than the latter - b.fill(++cntr); - c.fill(++cntr); - var copied = b.copy(c, 0, 0, 1025); - assert.strictEqual(copied, c.length, - 'copied ' + copied + ' bytes from b into c'); - compareBuffers(assert, b, c, 'copying should reset sourceEnd if targetEnd if sourceEnd > targetEnd'); - - // throw with negative sourceEnd - assert.throws(function() { - b.copy(c, 0, 0, -1); - }, RangeError, 'buffer copy throws at negative sourceEnd'); - - // when sourceStart is greater than sourceEnd, zero copied - assert.equal(b.copy(c, 0, 100, 10), 0); - - // when targetStart > targetLength, zero copied - assert.equal(b.copy(c, 512, 0, 10), 0); - - // try to copy 0 bytes worth of data into an empty buffer - b.copy(new Buffer(0), 0, 0, 0); - - // try to copy 0 bytes past the end of the target buffer - b.copy(new Buffer(0), 1, 1, 1); - b.copy(new Buffer(1), 1, 1, 1); - - // try to copy 0 bytes from past the end of the source buffer - b.copy(new Buffer(1), 0, 2048, 2048); -}; - -exports.testBufferWrite = function (assert) { - let b = Buffer(1024); - b.fill(0); - - // try to write a 0-length string beyond the end of b - assert.throws(function() { - b.write('', 2048); - }, RangeError, 'writing a 0-length string beyond buffer throws'); - // throw when writing to negative offset - assert.throws(function() { - b.write('a', -1); - }, RangeError, 'writing negative offset on buffer throws'); - - // throw when writing past bounds from the pool - assert.throws(function() { - b.write('a', 2048); - }, RangeError, 'writing past buffer bounds from pool throws'); - - // testing for smart defaults and ability to pass string values as offset - - // previous write API was the following: - // write(string, encoding, offset, length) - // this is planned on being removed in node v0.13, - // we will not support it - var writeTest = new Buffer('abcdes'); - writeTest.write('n', 'utf8'); -// writeTest.write('o', 'utf8', '1'); - writeTest.write('d', '2', 'utf8'); - writeTest.write('e', 3, 'utf8'); -// writeTest.write('j', 'utf8', 4); - assert.equal(writeTest.toString(), 'nbdees', - 'buffer write API alternative syntax works'); -}; - -exports.testBufferWriteEncoding = function (assert) { - - // Node #1210 Test UTF-8 string includes null character - var buf = new Buffer('\0'); - assert.equal(buf.length, 1); - buf = new Buffer('\0\0'); - assert.equal(buf.length, 2); - - buf = new Buffer(2); - var written = buf.write(''); // 0byte - assert.equal(written, 0); - written = buf.write('\0'); // 1byte (v8 adds null terminator) - assert.equal(written, 1); - written = buf.write('a\0'); // 1byte * 2 - assert.equal(written, 2); - // TODO, these tests write 0, possibly due to character encoding -/* - written = buf.write('あ'); // 3bytes - assert.equal(written, 0); - written = buf.write('\0あ'); // 1byte + 3bytes - assert.equal(written, 1); -*/ - written = buf.write('\0\0あ'); // 1byte * 2 + 3bytes - buf = new Buffer(10); - written = buf.write('あいう'); // 3bytes * 3 (v8 adds null terminator) - assert.equal(written, 9); - written = buf.write('あいう\0'); // 3bytes * 3 + 1byte - assert.equal(written, 10); -}; - -exports.testBufferWriteWithMaxLength = function (assert) { - // Node #243 Test write() with maxLength - var buf = new Buffer(4); - buf.fill(0xFF); - var written = buf.write('abcd', 1, 2, 'utf8'); - assert.equal(written, 2); - assert.equal(buf[0], 0xFF); - assert.equal(buf[1], 0x61); - assert.equal(buf[2], 0x62); - assert.equal(buf[3], 0xFF); - - buf.fill(0xFF); - written = buf.write('abcd', 1, 4); - assert.equal(written, 3); - assert.equal(buf[0], 0xFF); - assert.equal(buf[1], 0x61); - assert.equal(buf[2], 0x62); - assert.equal(buf[3], 0x63); - - buf.fill(0xFF); - // Ignore legacy API - /* - written = buf.write('abcd', 'utf8', 1, 2); // legacy style - console.log(buf); - assert.equal(written, 2); - assert.equal(buf[0], 0xFF); - assert.equal(buf[1], 0x61); - assert.equal(buf[2], 0x62); - assert.equal(buf[3], 0xFF); - */ -}; - -exports.testBufferSlice = function (assert) { - var asciiString = 'hello world'; - var offset = 100; - var b = Buffer(1024); - b.fill(0); - - for (var i = 0; i < asciiString.length; i++) { - b[i] = asciiString.charCodeAt(i); - } - var asciiSlice = b.toString('utf8', 0, asciiString.length); - assert.equal(asciiString, asciiSlice); - - var written = b.write(asciiString, offset, 'utf8'); - assert.equal(asciiString.length, written); - asciiSlice = b.toString('utf8', offset, offset + asciiString.length); - assert.equal(asciiString, asciiSlice); - - var sliceA = b.slice(offset, offset + asciiString.length); - var sliceB = b.slice(offset, offset + asciiString.length); - compareBuffers(assert, sliceA, sliceB, - 'slicing is idempotent'); - - let sliceTest = true; - for (var j = 0; j < 100; j++) { - var slice = b.slice(100, 150); - if (50 !== slice.length) - sliceTest = false; - for (var i = 0; i < 50; i++) { - if (b[100 + i] !== slice[i]) - sliceTest = false; - } - } - assert.ok(sliceTest, 'massive slice runs do not affect buffer'); - - // Single argument slice - let testBuf = new Buffer('abcde'); - assert.equal('bcde', testBuf.slice(1).toString(), 'single argument slice'); - - // slice(0,0).length === 0 - assert.equal(0, Buffer('hello').slice(0, 0).length, 'slice(0,0) === 0'); - - var buf = new Buffer('0123456789'); - assert.equal(buf.slice(-10, 10), '0123456789', 'buffer slice range correct'); - assert.equal(buf.slice(-20, 10), '0123456789', 'buffer slice range correct'); - assert.equal(buf.slice(-20, -10), '', 'buffer slice range correct'); - assert.equal(buf.slice(0, -1), '012345678', 'buffer slice range correct'); - assert.equal(buf.slice(2, -2), '234567', 'buffer slice range correct'); - assert.equal(buf.slice(0, 65536), '0123456789', 'buffer slice range correct'); - assert.equal(buf.slice(65536, 0), '', 'buffer slice range correct'); - - sliceTest = true; - for (var i = 0, s = buf.toString(); i < buf.length; ++i) { - if (buf.slice(-i) != s.slice(-i)) sliceTest = false; - if (buf.slice(0, -i) != s.slice(0, -i)) sliceTest = false; - } - assert.ok(sliceTest, 'buffer.slice should be consistent'); - - // Make sure modifying a sliced buffer, affects original and vice versa - b.fill(0); - let sliced = b.slice(0, 10); - let babyslice = sliced.slice(0, 5); - - for (let i = 0; i < sliced.length; i++) - sliced[i] = 'jetpack'.charAt(i); - - compareBuffers(assert, b, sliced, - 'modifying sliced buffer affects original'); - - compareBuffers(assert, b, babyslice, - 'modifying sliced buffer affects child-sliced buffer'); - - for (let i = 0; i < sliced.length; i++) - b[i] = 'odinmonkey'.charAt(i); - - compareBuffers(assert, b, sliced, - 'modifying original buffer affects sliced'); - - compareBuffers(assert, b, babyslice, - 'modifying original buffer affects grandchild sliced buffer'); -}; - -exports.testSlicingParents = function (assert) { - let root = Buffer(5); - let child = root.slice(0, 4); - let grandchild = child.slice(0, 3); - - assert.equal(root.parent, undefined, 'a new buffer should not have a parent'); - - // make sure a zero length slice doesn't set the .parent attribute - assert.equal(root.slice(0,0).parent, undefined, - '0-length slice should not have a parent'); - - assert.equal(child.parent, root, - 'a valid slice\'s parent should be the original buffer (child)'); - - assert.equal(grandchild.parent, root, - 'a valid slice\'s parent should be the original buffer (grandchild)'); -}; - -exports.testIsBuffer = function (assert) { - let buffer = new Buffer('content', 'utf8'); - assert.ok(Buffer.isBuffer(buffer), 'isBuffer truthy on buffers'); - assert.ok(!Buffer.isBuffer({}), 'isBuffer falsy on objects'); - assert.ok(!Buffer.isBuffer(new Uint8Array()), - 'isBuffer falsy on Uint8Array'); - assert.ok(Buffer.isBuffer(buffer.slice(0)), 'Buffer#slice should be a new buffer'); -}; - -exports.testBufferConcat = function (assert) { - let zero = []; - let one = [ new Buffer('asdf') ]; - let long = []; - for (let i = 0; i < 10; i++) long.push(new Buffer('asdf')); - - let flatZero = Buffer.concat(zero); - let flatOne = Buffer.concat(one); - let flatLong = Buffer.concat(long); - let flatLongLen = Buffer.concat(long, 40); - - assert.equal(flatZero.length, 0); - assert.equal(flatOne.toString(), 'asdf'); - assert.equal(flatOne, one[0]); - assert.equal(flatLong.toString(), (new Array(10+1).join('asdf'))); - assert.equal(flatLongLen.toString(), (new Array(10+1).join('asdf'))); -}; - -exports.testBufferByteLength = function (assert) { - let str = '\u00bd + \u00bc = \u00be'; - assert.equal(Buffer.byteLength(str), 12, - 'correct byteLength of string'); - - assert.equal(14, Buffer.byteLength('Il était tué')); - assert.equal(14, Buffer.byteLength('Il était tué', 'utf8')); - // We do not currently support these encodings - /* - ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { - assert.equal(24, Buffer.byteLength('Il était tué', encoding)); - }); - assert.equal(12, Buffer.byteLength('Il était tué', 'ascii')); - assert.equal(12, Buffer.byteLength('Il était tué', 'binary')); - */ -}; - -exports.testTextEncoderDecoder = function (assert) { - assert.ok(TextEncoder, 'TextEncoder exists'); - assert.ok(TextDecoder, 'TextDecoder exists'); -}; - -exports.testOverflowedBuffers = function (assert) { - assert.throws(function() { - new Buffer(0xFFFFFFFF); - }, RangeError, 'correctly throws buffer overflow'); - - assert.throws(function() { - new Buffer(0xFFFFFFFFF); - }, RangeError, 'correctly throws buffer overflow'); - - assert.throws(function() { - var buf = new Buffer(8); - buf.readFloatLE(0xffffffff); - }, RangeError, 'correctly throws buffer overflow with readFloatLE'); - - assert.throws(function() { - var buf = new Buffer(8); - buf.writeFloatLE(0.0, 0xffffffff); - }, RangeError, 'correctly throws buffer overflow with writeFloatLE'); - - //ensure negative values can't get past offset - assert.throws(function() { - var buf = new Buffer(8); - buf.readFloatLE(-1); - }, RangeError, 'correctly throws with readFloatLE negative values'); - - assert.throws(function() { - var buf = new Buffer(8); - buf.writeFloatLE(0.0, -1); - }, RangeError, 'correctly throws with writeFloatLE with negative values'); - - assert.throws(function() { - var buf = new Buffer(8); - buf.readFloatLE(-1); - }, RangeError, 'correctly throws with readFloatLE with negative values'); -}; - -exports.testReadWriteDataTypeErrors = function (assert) { - var buf = new Buffer(0); - assert.throws(function() { buf.readUInt8(0); }, RangeError, - 'readUInt8(0) throws'); - assert.throws(function() { buf.readInt8(0); }, RangeError, - 'readInt8(0) throws'); - - [16, 32].forEach(function(bits) { - var buf = new Buffer(bits / 8 - 1); - assert.throws(function() { buf['readUInt' + bits + 'BE'](0); }, - RangeError, - 'readUInt' + bits + 'BE'); - - assert.throws(function() { buf['readUInt' + bits + 'LE'](0); }, - RangeError, - 'readUInt' + bits + 'LE'); - - assert.throws(function() { buf['readInt' + bits + 'BE'](0); }, - RangeError, - 'readInt' + bits + 'BE()'); - - assert.throws(function() { buf['readInt' + bits + 'LE'](0); }, - RangeError, - 'readInt' + bits + 'LE()'); - }); -}; - -safeMerge(exports, require('./buffers/test-write-types')); -safeMerge(exports, require('./buffers/test-read-types')); - -function compareBuffers (assert, buf1, buf2, message) { - let status = true; - for (let i = 0; i < Math.min(buf1.length, buf2.length); i++) { - if (buf1[i] !== buf2[i]) - status = false; - } - assert.ok(status, 'buffer successfully copied: ' + message); -} -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-byte-streams.js b/addon-sdk/source/test/test-byte-streams.js deleted file mode 100644 index 7d45130aa2..0000000000 --- a/addon-sdk/source/test/test-byte-streams.js +++ /dev/null @@ -1,169 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const byteStreams = require("sdk/io/byte-streams"); -const file = require("sdk/io/file"); -const { pathFor } = require("sdk/system"); -const { Loader } = require("sdk/test/loader"); - -const STREAM_CLOSED_ERROR = new RegExp("The stream is closed and cannot be used."); - -// This should match the constant of the same name in byte-streams.js. -const BUFFER_BYTE_LEN = 0x8000; - -exports.testWriteRead = function (assert) { - let fname = dataFileFilename(); - - // Write a small string less than the stream's buffer size... - let str = "exports.testWriteRead data!"; - let stream = open(assert, fname, true); - assert.ok(!stream.closed, "stream.closed after open should be false"); - stream.write(str); - stream.close(); - assert.ok(stream.closed, "Stream should be closed after stream.close"); - assert.throws(() => stream.write("This shouldn't be written!"), - STREAM_CLOSED_ERROR, - "stream.write after close should raise error"); - - // ... and read it. - stream = open(assert, fname); - assert.equal(stream.read(), str, - "stream.read should return string written"); - assert.equal(stream.read(), "", - "stream.read at EOS should return empty string"); - stream.close(); - assert.ok(stream.closed, "Stream should be closed after stream.close"); - assert.throws(() => stream.read(), - STREAM_CLOSED_ERROR, - "stream.read after close should raise error"); - - file.remove(fname); -}; - -// Write a big string many times the size of the stream's buffer and read it. -exports.testWriteReadBig = function (assert) { - let str = ""; - let bufLen = BUFFER_BYTE_LEN; - let fileSize = bufLen * 10; - for (let i = 0; i < fileSize; i++) - str += i % 10; - let fname = dataFileFilename(); - let stream = open(assert, fname, true); - stream.write(str); - stream.close(); - stream = open(assert, fname); - assert.equal(stream.read(), str, - "stream.read should return string written"); - stream.close(); - file.remove(fname); -}; - -// The same, but write and read in chunks. -exports.testWriteReadChunks = function (assert) { - let str = ""; - let bufLen = BUFFER_BYTE_LEN; - let fileSize = bufLen * 10; - for (let i = 0; i < fileSize; i++) - str += i % 10; - let fname = dataFileFilename(); - let stream = open(assert, fname, true); - let i = 0; - while (i < str.length) { - // Use a chunk length that spans buffers. - let chunk = str.substr(i, bufLen + 1); - stream.write(chunk); - i += bufLen + 1; - } - stream.close(); - stream = open(assert, fname); - let readStr = ""; - bufLen = BUFFER_BYTE_LEN; - let readLen = bufLen + 1; - do { - var frag = stream.read(readLen); - readStr += frag; - } while (frag); - stream.close(); - assert.equal(readStr, str, - "stream.write and read in chunks should work as expected"); - file.remove(fname); -}; - -exports.testReadLengths = function (assert) { - let fname = dataFileFilename(); - let str = "exports.testReadLengths data!"; - let stream = open(assert, fname, true); - stream.write(str); - stream.close(); - - stream = open(assert, fname); - assert.equal(stream.read(str.length * 1000), str, - "stream.read with big byte length should return string " + - "written"); - stream.close(); - - stream = open(assert, fname); - assert.equal(stream.read(0), "", - "string.read with zero byte length should return empty " + - "string"); - stream.close(); - - stream = open(assert, fname); - assert.equal(stream.read(-1), "", - "string.read with negative byte length should return " + - "empty string"); - stream.close(); - - file.remove(fname); -}; - -exports.testTruncate = function (assert) { - let fname = dataFileFilename(); - let str = "exports.testReadLengths data!"; - let stream = open(assert, fname, true); - stream.write(str); - stream.close(); - - stream = open(assert, fname); - assert.equal(stream.read(), str, - "stream.read should return string written"); - stream.close(); - - stream = open(assert, fname, true); - stream.close(); - - stream = open(assert, fname); - assert.equal(stream.read(), "", - "stream.read after truncate should be empty"); - stream.close(); - - file.remove(fname); -}; - -exports.testUnload = function (assert) { - let loader = Loader(module); - let file = loader.require("sdk/io/file"); - - let filename = dataFileFilename("temp-b"); - let stream = file.open(filename, "wb"); - - loader.unload(); - assert.ok(stream.closed, "Stream should be closed after module unload"); -}; - -// Returns the name of a file that should be used to test writing and reading. -function dataFileFilename() { - return file.join(pathFor("ProfD"), "test-byte-streams-data"); -} - -// Opens and returns the given file and ensures it's of the correct class. -function open(assert, filename, forWriting) { - let stream = file.open(filename, forWriting ? "wb" : "b"); - let klass = forWriting ? "ByteWriter" : "ByteReader"; - assert.ok(stream instanceof byteStreams[klass], - "Opened stream should be a " + klass); - return stream; -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-child_process.js b/addon-sdk/source/test/test-child_process.js deleted file mode 100644 index 4cfd9ec493..0000000000 --- a/addon-sdk/source/test/test-child_process.js +++ /dev/null @@ -1,545 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -const { spawn, exec, execFile, fork } = require('sdk/system/child_process'); -const { env, platform, pathFor } = require('sdk/system'); -const { isNumber } = require('sdk/lang/type'); -const { after } = require('sdk/test/utils'); -const { emit } = require('sdk/event/core'); -const PROFILE_DIR= pathFor('ProfD'); -const isWindows = platform.toLowerCase().indexOf('win') === 0; -const { getScript, cleanUp } = require('./fixtures/child-process-scripts'); - -// We use direct paths to these utilities as we currently cannot -// call non-absolute paths to utilities in subprocess.jsm -const CAT_PATH = isWindows ? 'C:\\Windows\\System32\\more.com' : '/bin/cat'; - -exports.testExecCallbackSuccess = function (assert, done) { - exec(isWindows ? 'DIR /A-D' : 'ls -al', { - cwd: PROFILE_DIR - }, function (err, stdout, stderr) { - assert.ok(!err, 'no errors found'); - assert.equal(stderr, '', 'stderr is empty'); - assert.ok(/extensions\.ini/.test(stdout), 'stdout output of `ls -al` finds files'); - - if (isWindows) { - // `DIR /A-D` does not display directories on WIN - assert.ok(!//.test(stdout), - 'passing arguments in `exec` works'); - } - else { - // `ls -al` should list all the priviledge information on Unix - assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), - 'passing arguments in `exec` works'); - } - done(); - }); -}; - -exports.testExecCallbackError = function (assert, done) { - exec('not-real-command', { cwd: PROFILE_DIR }, function (err, stdout, stderr) { - assert.ok(/not-real-command/.test(err.toString()), - 'error contains error message'); - assert.ok(err.lineNumber >= 0, 'error contains lineNumber'); - assert.ok(/resource:\/\//.test(err.fileName), 'error contains fileName'); - assert.ok(err.code && isNumber(err.code), 'non-zero error code property on error'); - assert.equal(err.signal, null, - 'null signal property when not manually terminated'); - assert.equal(stdout, '', 'stdout is empty'); - assert.ok(/not-real-command/.test(stderr), 'stderr contains error message'); - done(); - }); -}; - -exports.testExecOptionsEnvironment = function (assert, done) { - getScript('check-env').then(envScript => { - exec(envScript, { - cwd: PROFILE_DIR, - env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' } - }, function (err, stdout, stderr) { - assert.equal(stderr, '', 'stderr is empty'); - assert.ok(!err, 'received `cwd` option'); - assert.ok(/my-value-test/.test(stdout), - 'receives environment option'); - done(); - }); - }).then(null, assert.fail); -}; - -exports.testExecOptionsTimeout = function (assert, done) { - let count = 0; - getScript('wait').then(script => { - let child = exec(script, { timeout: 100 }, (err, stdout, stderr) => { - assert.equal(err.killed, true, 'error has `killed` property as true'); - assert.equal(err.code, null, 'error has `code` as null'); - assert.equal(err.signal, 'SIGTERM', - 'error has `signal` as SIGTERM by default'); - assert.equal(stdout, '', 'stdout is empty'); - assert.equal(stderr, '', 'stderr is empty'); - if (++count === 3) complete(); - }); - - function exitHandler (code, signal) { - assert.equal(code, null, 'error has `code` as null'); - assert.equal(signal, 'SIGTERM', - 'error has `signal` as SIGTERM by default'); - if (++count === 3) complete(); - } - - function closeHandler (code, signal) { - assert.equal(code, null, 'error has `code` as null'); - assert.equal(signal, 'SIGTERM', - 'error has `signal` as SIGTERM by default'); - if (++count === 3) complete(); - } - - child.on('exit', exitHandler); - child.on('close', closeHandler); - - function complete () { - child.off('exit', exitHandler); - child.off('close', closeHandler); - done(); - } - }).then(null, assert.fail); -}; - -exports.testExecFileCallbackSuccess = function (assert, done) { - getScript('args').then(script => { - execFile(script, ['--myargs', '-j', '-s'], { cwd: PROFILE_DIR }, function (err, stdout, stderr) { - assert.ok(!err, 'no errors found'); - assert.equal(stderr, '', 'stderr is empty'); - // Trim output since different systems have different new line output - assert.equal(stdout.trim(), '--myargs -j -s'.trim(), 'passes in correct arguments'); - done(); - }); - }).then(null, assert.fail); -}; - -exports.testExecFileCallbackError = function (assert, done) { - execFile('not-real-command', { cwd: PROFILE_DIR }, function (err, stdout, stderr) { - assert.ok(/Executable not found/.test(err.message), - `error '${err.message}' contains error message`); - assert.ok(err.lineNumber >= 0, 'error contains lineNumber'); - assert.ok(/resource:\/\//.test(err.fileName), 'error contains fileName'); - assert.equal(stdout, '', 'stdout is empty'); - assert.equal(stderr, '', 'stdout is empty'); - done(); - }); -}; - -exports.testExecFileOptionsEnvironment = function (assert, done) { - getScript('check-env').then(script => { - execFile(script, { - cwd: PROFILE_DIR, - env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' } - }, function (err, stdout, stderr) { - assert.equal(stderr, '', 'stderr is empty'); - assert.ok(!err, 'received `cwd` option'); - assert.ok(/my-value-test/.test(stdout), - 'receives environment option'); - done(); - }); - }).then(null, assert.fail); -}; - -exports.testExecFileOptionsTimeout = function (assert, done) { - let count = 0; - getScript('wait').then(script => { - let child = execFile(script, { timeout: 100 }, (err, stdout, stderr) => { - assert.equal(err.killed, true, 'error has `killed` property as true'); - assert.equal(err.code, null, 'error has `code` as null'); - assert.equal(err.signal, 'SIGTERM', - 'error has `signal` as SIGTERM by default'); - assert.equal(stdout, '', 'stdout is empty'); - assert.equal(stderr, '', 'stderr is empty'); - if (++count === 3) complete(); - }); - - function exitHandler (code, signal) { - assert.equal(code, null, 'error has `code` as null'); - assert.equal(signal, 'SIGTERM', - 'error has `signal` as SIGTERM by default'); - if (++count === 3) complete(); - } - - function closeHandler (code, signal) { - assert.equal(code, null, 'error has `code` as null'); - assert.equal(signal, 'SIGTERM', - 'error has `signal` as SIGTERM by default'); - if (++count === 3) complete(); - } - - child.on('exit', exitHandler); - child.on('close', closeHandler); - - function complete () { - child.off('exit', exitHandler); - child.off('close', closeHandler); - done(); - } - }).then(null, assert.fail); -}; - -/** - * Not necessary to test for both `exec` and `execFile`, but - * it is necessary to test both when the buffer is larger - * and smaller than buffer size used by the subprocess library (1024) - */ -exports.testExecFileOptionsMaxBufferLargeStdOut = function (assert, done) { - let count = 0; - let stdoutChild; - - // Creates a buffer of 2000 to stdout, greater than 1024 - getScript('large-out').then(script => { - stdoutChild = execFile(script, ['10000'], { maxBuffer: 50 }, (err, stdout, stderr) => { - assert.ok(/stdout maxBuffer exceeded/.test(err.toString()), - 'error contains stdout maxBuffer exceeded message'); - assert.ok(stdout.length >= 50, 'stdout has full buffer'); - assert.equal(stderr, '', 'stderr is empty'); - if (++count === 3) complete(); - }); - stdoutChild.on('exit', exitHandler); - stdoutChild.on('close', closeHandler); - }).then(null, assert.fail); - - function exitHandler (code, signal) { - assert.equal(code, null, 'Exit code is null in exit handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler'); - if (++count === 3) complete(); - } - - function closeHandler (code, signal) { - assert.equal(code, null, 'Exit code is null in close handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler'); - if (++count === 3) complete(); - } - - function complete () { - stdoutChild.off('exit', exitHandler); - stdoutChild.off('close', closeHandler); - done(); - } -}; - -exports.testExecFileOptionsMaxBufferLargeStdOErr = function (assert, done) { - let count = 0; - let stderrChild; - // Creates a buffer of 2000 to stderr, greater than 1024 - getScript('large-err').then(script => { - stderrChild = execFile(script, ['10000'], { maxBuffer: 50 }, (err, stdout, stderr) => { - assert.ok(/stderr maxBuffer exceeded/.test(err.toString()), - 'error contains stderr maxBuffer exceeded message'); - assert.ok(stderr.length >= 50, 'stderr has full buffer'); - assert.equal(stdout, '', 'stdout is empty'); - if (++count === 3) complete(); - }); - stderrChild.on('exit', exitHandler); - stderrChild.on('close', closeHandler); - }).then(null, assert.fail); - - function exitHandler (code, signal) { - assert.equal(code, null, 'Exit code is null in exit handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler'); - if (++count === 3) complete(); - } - - function closeHandler (code, signal) { - assert.equal(code, null, 'Exit code is null in close handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler'); - if (++count === 3) complete(); - } - - function complete () { - stderrChild.off('exit', exitHandler); - stderrChild.off('close', closeHandler); - done(); - } -}; - -/** - * When total buffer is < process buffer (1024), the process will exit - * and not get a chance to be killed for violating the maxBuffer, - * although the error will still be sent through (node behaviour) - */ -exports.testExecFileOptionsMaxBufferSmallStdOut = function (assert, done) { - let count = 0; - let stdoutChild; - - // Creates a buffer of 60 to stdout, less than 1024 - getScript('large-out').then(script => { - stdoutChild = execFile(script, ['60'], { maxBuffer: 50 }, (err, stdout, stderr) => { - assert.ok(/stdout maxBuffer exceeded/.test(err.toString()), - 'error contains stdout maxBuffer exceeded message'); - assert.ok(stdout.length >= 50, 'stdout has full buffer'); - assert.equal(stderr, '', 'stderr is empty'); - if (++count === 3) complete(); - }); - stdoutChild.on('exit', exitHandler); - stdoutChild.on('close', closeHandler); - }).then(null, assert.fail); - - function exitHandler (code, signal) { - // Sometimes the buffer limit is hit before the process closes successfully - // on both OSX/Windows - if (code === null) { - assert.equal(code, null, 'Exit code is null in exit handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler'); - } - else { - assert.equal(code, 0, 'Exit code is 0 in exit handler'); - assert.equal(signal, null, 'Signal is null in exit handler'); - } - if (++count === 3) complete(); - } - - function closeHandler (code, signal) { - // Sometimes the buffer limit is hit before the process closes successfully - // on both OSX/Windows - if (code === null) { - assert.equal(code, null, 'Exit code is null in close handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler'); - } - else { - assert.equal(code, 0, 'Exit code is 0 in close handler'); - assert.equal(signal, null, 'Signal is null in close handler'); - } - if (++count === 3) complete(); - } - - function complete () { - stdoutChild.off('exit', exitHandler); - stdoutChild.off('close', closeHandler); - done(); - } -}; - -exports.testExecFileOptionsMaxBufferSmallStdErr = function (assert, done) { - let count = 0; - let stderrChild; - // Creates a buffer of 60 to stderr, less than 1024 - getScript('large-err').then(script => { - stderrChild = execFile(script, ['60'], { maxBuffer: 50 }, (err, stdout, stderr) => { - assert.ok(/stderr maxBuffer exceeded/.test(err.toString()), - 'error contains stderr maxBuffer exceeded message'); - assert.ok(stderr.length >= 50, 'stderr has full buffer'); - assert.equal(stdout, '', 'stdout is empty'); - if (++count === 3) complete(); - }); - stderrChild.on('exit', exitHandler); - stderrChild.on('close', closeHandler); - }).then(null, assert.fail); - - function exitHandler (code, signal) { - // Sometimes the buffer limit is hit before the process closes successfully - // on both OSX/Windows - if (code === null) { - assert.equal(code, null, 'Exit code is null in exit handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler'); - } - else { - assert.equal(code, 0, 'Exit code is 0 in exit handler'); - assert.equal(signal, null, 'Signal is null in exit handler'); - } - if (++count === 3) complete(); - } - - function closeHandler (code, signal) { - // Sometimes the buffer limit is hit before the process closes successfully - // on both OSX/Windows - if (code === null) { - assert.equal(code, null, 'Exit code is null in close handler'); - assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in close handler'); - } - else { - assert.equal(code, 0, 'Exit code is 0 in close handler'); - assert.equal(signal, null, 'Signal is null in close handler'); - } - if (++count === 3) complete(); - } - - function complete () { - stderrChild.off('exit', exitHandler); - stderrChild.off('close', closeHandler); - done(); - } -}; - -exports.testChildExecFileKillSignal = function (assert, done) { - getScript('wait').then(script => { - execFile(script, { - killSignal: 'beepbeep', - timeout: 10 - }, function (err, stdout, stderr) { - assert.equal(err.signal, 'beepbeep', 'correctly used custom killSignal'); - done(); - }); - }).then(null, assert.fail); -}; - -exports.testChildProperties = function (assert, done) { - getScript('check-env').then(script => { - let child = spawn(script, { - env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' } - }); - - if (isWindows) - assert.ok(true, 'Windows environment does not have `pid`'); - else - assert.ok(child.pid > 0, 'Child has a pid'); - }).then(done, assert.fail); -}; - -exports.testChildStdinStreamLarge = function (assert, done) { - let REPEAT = 2000; - let allData = ''; - // Use direct paths to more/cat, as we do not currently support calling non-files - // from subprocess.jsm - let child = spawn(CAT_PATH); - - child.stdout.on('data', onData); - child.on('close', onClose); - - for (let i = 0; i < REPEAT; i++) - emit(child.stdin, 'data', '12345\n'); - - emit(child.stdin, 'end'); - - function onData (data) { - allData += data; - } - - function onClose (code, signal) { - child.stdout.off('data', onData); - child.off('close', onClose); - assert.equal(code, 0, 'exited succesfully'); - assert.equal(signal, null, 'no kill signal given'); - assert.equal(allData.replace(/\W/g, '').length, '12345'.length * REPEAT, - 'all data processed from stdin'); - done(); - } -}; - -exports.testChildStdinStreamSmall = function (assert, done) { - let allData = ''; - let child = spawn(CAT_PATH); - child.stdout.on('data', onData); - child.on('close', onClose); - - emit(child.stdin, 'data', '12345'); - emit(child.stdin, 'end'); - - function onData (data) { - allData += data; - } - - function onClose (code, signal) { - child.stdout.off('data', onData); - child.off('close', onClose); - assert.equal(code, 0, 'exited succesfully'); - assert.equal(signal, null, 'no kill signal given'); - assert.equal(allData.trim(), '12345', 'all data processed from stdin'); - done(); - } -}; -/* - * This tests failures when an error is thrown attempting to - * spawn the process, like an invalid command - */ -exports.testChildEventsSpawningError= function (assert, done) { - let handlersCalled = 0; - let child = execFile('i-do-not-exist', (err, stdout, stderr) => { - assert.ok(err, 'error was passed into callback'); - assert.equal(stdout, '', 'stdout is empty') - assert.equal(stderr, '', 'stderr is empty'); - if (++handlersCalled === 3) complete(); - }); - - child.on('error', handleError); - child.on('exit', handleExit); - child.on('close', handleClose); - - function handleError (e) { - assert.ok(e, 'error passed into error handler'); - if (++handlersCalled === 3) complete(); - } - - function handleClose (code, signal) { - assert.equal(code, -1, - 'process was never spawned, therefore exit code is -1'); - assert.equal(signal, null, 'signal should be null'); - if (++handlersCalled === 3) complete(); - } - - function handleExit (code, signal) { - assert.fail('Close event should not be called on init failure'); - } - - function complete () { - child.off('error', handleError); - child.off('exit', handleExit); - child.off('close', handleClose); - done(); - } -}; - -exports.testSpawnOptions = function (assert, done) { - let count = 0; - let envStdout = ''; - let cwdStdout = ''; - let checkEnv, checkPwd, envChild, cwdChild; - getScript('check-env').then(script => { - checkEnv = script; - return getScript('check-pwd'); - }).then(script => { - checkPwd = script; - - envChild = spawn(checkEnv, { - env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' } - }); - cwdChild = spawn(checkPwd, { cwd: PROFILE_DIR }); - - // Do these need to be unbound? - envChild.stdout.on('data', data => envStdout += data); - cwdChild.stdout.on('data', data => cwdStdout += data); - - envChild.on('close', envClose); - cwdChild.on('close', cwdClose); - }).then(null, assert.fail); - - function envClose () { - assert.equal(envStdout.trim(), 'my-value-test', 'spawn correctly passed in ENV'); - if (++count === 2) complete(); - } - - function cwdClose () { - // Check for PROFILE_DIR in the output because - // some systems resolve symbolic links, and on OSX - // /var -> /private/var - let isCorrectPath = ~cwdStdout.trim().indexOf(PROFILE_DIR); - assert.ok(isCorrectPath, 'spawn correctly passed in cwd'); - if (++count === 2) complete(); - } - - function complete () { - envChild.off('close', envClose); - cwdChild.off('close', cwdClose); - done(); - } -}; - -exports.testFork = function (assert) { - assert.throws(function () { - fork(); - }, /not currently supported/, 'fork() correctly throws an unsupported error'); -}; - -after(exports, cleanUp); - -require("sdk/test").run(exports); - -// Test disabled because of bug 979675 -module.exports = {}; diff --git a/addon-sdk/source/test/test-chrome.js b/addon-sdk/source/test/test-chrome.js deleted file mode 100644 index 3d1f29dd0e..0000000000 --- a/addon-sdk/source/test/test-chrome.js +++ /dev/null @@ -1,84 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -var chrome = require('chrome'); - -const FIXTURES_URL = module.uri.substr(0, module.uri.lastIndexOf('/') + 1) + - 'fixtures/chrome-worker/' - -exports['test addEventListener'] = function(assert, done) { - let uri = FIXTURES_URL + 'addEventListener.js'; - - let worker = new chrome.ChromeWorker(uri); - worker.addEventListener('message', function(event) { - assert.equal(event.data, 'Hello', 'message received'); - worker.terminate(); - done(); - }); -}; - -exports['test onmessage'] = function(assert, done) { - let uri = FIXTURES_URL + 'onmessage.js'; - - let worker = new chrome.ChromeWorker(uri); - worker.onmessage = function(event) { - assert.equal(event.data, 'ok', 'message received'); - worker.terminate(); - done(); - }; - worker.postMessage('ok'); -}; - -exports['test setTimeout'] = function(assert, done) { - let uri = FIXTURES_URL + 'setTimeout.js'; - - let worker = new chrome.ChromeWorker(uri); - worker.onmessage = function(event) { - assert.equal(event.data, 'ok', 'setTimeout fired'); - worker.terminate(); - done(); - }; -}; - -exports['test jsctypes'] = function(assert, done) { - let uri = FIXTURES_URL + 'jsctypes.js'; - - let worker = new chrome.ChromeWorker(uri); - worker.onmessage = function(event) { - assert.equal(event.data, 'function', 'ctypes.open is a function'); - worker.terminate(); - done(); - }; -}; - -exports['test XMLHttpRequest'] = function(assert, done) { - let uri = FIXTURES_URL + 'xhr.js'; - - let worker = new chrome.ChromeWorker(uri); - worker.onmessage = function(event) { - assert.equal(event.data, 'ok', 'XMLHttpRequest works'); - worker.terminate(); - done(); - }; -}; - -exports['test onerror'] = function(assert, done) { - let uri = FIXTURES_URL + 'onerror.js'; - - let worker = new chrome.ChromeWorker(uri); - worker.onerror = function(event) { - assert.equal(event.filename, uri, 'event reports the correct uri'); - assert.equal(event.lineno, 6, 'event reports the correct line number'); - assert.equal(event.target, worker, 'event reports the correct worker'); - assert.ok(event.message.match(/ok/), - 'event contains the exception message'); - // Call preventDefault in order to avoid being displayed in JS console. - event.preventDefault(); - worker.terminate(); - done(); - }; -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-clipboard.js b/addon-sdk/source/test/test-clipboard.js deleted file mode 100644 index f7ffd05be8..0000000000 --- a/addon-sdk/source/test/test-clipboard.js +++ /dev/null @@ -1,170 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -require("sdk/clipboard"); - -const { Cc, Ci } = require("chrome"); - -const imageTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools); -const io = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); -const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].getService(Ci.nsIAppShellService); - -const XHTML_NS = "http://www.w3.org/1999/xhtml"; -const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" + - "AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" + - "N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" + - "bWRR9AAAAABJRU5ErkJggg%3D%3D"; - -const { base64jpeg } = require("./fixtures"); - -const { platform } = require("sdk/system"); -// For Windows, Mac and Linux, platform returns the following: winnt, darwin and linux. -var isWindows = platform.toLowerCase().indexOf("win") == 0; - -// Test the typical use case, setting & getting with no flavors specified -exports["test With No Flavor"] = function(assert) { - var contents = "hello there"; - var flavor = "text"; - var fullFlavor = "text/unicode"; - var clip = require("sdk/clipboard"); - - // Confirm we set the clipboard - assert.ok(clip.set(contents)); - - // Confirm flavor is set - assert.equal(clip.currentFlavors[0], flavor); - - // Confirm we set the clipboard - assert.equal(clip.get(), contents); - - // Confirm we can get the clipboard using the flavor - assert.equal(clip.get(flavor), contents); - - // Confirm we can still get the clipboard using the full flavor - assert.equal(clip.get(fullFlavor), contents); -}; - -// Test the slightly less common case where we specify the flavor -exports["test With Flavor"] = function(assert) { - var contents = "hello there"; - var contentsText = "hello there"; - - // On windows, HTML clipboard includes extra data. - // The values are from widget/windows/nsDataObj.cpp. - var contentsWindowsHtml = "\n" + - contents + - "\n\n"; - - var flavor = "html"; - var fullFlavor = "text/html"; - var unicodeFlavor = "text"; - var unicodeFullFlavor = "text/unicode"; - var clip = require("sdk/clipboard"); - - assert.ok(clip.set(contents, flavor)); - - assert.equal(clip.currentFlavors[0], unicodeFlavor); - assert.equal(clip.currentFlavors[1], flavor); - assert.equal(clip.get(), contentsText); - assert.equal(clip.get(flavor), isWindows ? contentsWindowsHtml : contents); - assert.equal(clip.get(fullFlavor), isWindows ? contentsWindowsHtml : contents); - assert.equal(clip.get(unicodeFlavor), contentsText); - assert.equal(clip.get(unicodeFullFlavor), contentsText); -}; - -// Test that the typical case still works when we specify the flavor to set -exports["test With Redundant Flavor"] = function(assert) { - var contents = "hello there"; - var flavor = "text"; - var fullFlavor = "text/unicode"; - var clip = require("sdk/clipboard"); - - assert.ok(clip.set(contents, flavor)); - assert.equal(clip.currentFlavors[0], flavor); - assert.equal(clip.get(), contents); - assert.equal(clip.get(flavor), contents); - assert.equal(clip.get(fullFlavor), contents); -}; - -exports["test Not In Flavor"] = function(assert) { - var contents = "hello there"; - var flavor = "html"; - var clip = require("sdk/clipboard"); - - assert.ok(clip.set(contents)); - // If there's nothing on the clipboard with this flavor, should return null - assert.equal(clip.get(flavor), null); -}; - -exports["test Set Image"] = function(assert) { - var clip = require("sdk/clipboard"); - var flavor = "image"; - var fullFlavor = "image/png"; - - assert.ok(clip.set(base64png, flavor), "clipboard set"); - assert.equal(clip.currentFlavors[0], flavor, "flavor is set"); -}; - -exports["test Get Image"] = function* (assert) { - var clip = require("sdk/clipboard"); - - clip.set(base64png, "image"); - - var contents = clip.get(); - const hiddenWindow = appShellService.hiddenDOMWindow; - const Image = hiddenWindow.Image; - const canvas = hiddenWindow.document.createElementNS(XHTML_NS, "canvas"); - let context = canvas.getContext("2d"); - - const imageURLToPixels = (imageURL) => { - return new Promise((resolve) => { - let img = new Image(); - - img.onload = function() { - context.drawImage(this, 0, 0); - - let pixels = Array.join(context.getImageData(0, 0, 32, 32).data); - resolve(pixels); - }; - - img.src = imageURL; - }); - }; - - let [base64pngPixels, clipboardPixels] = yield Promise.all([ - imageURLToPixels(base64png), imageURLToPixels(contents), - ]); - - assert.ok(base64pngPixels === clipboardPixels, - "Image gets from clipboard equals to image sets to the clipboard"); -}; - -exports["test Set Image Type Not Supported"] = function(assert) { - var clip = require("sdk/clipboard"); - var flavor = "image"; - - assert.throws(function () { - clip.set(base64jpeg, flavor); - }, "Invalid flavor for image/jpeg"); - -}; - -// Notice that `imageTools.decodeImageData`, used by `clipboard.set` method for -// images, write directly to the javascript console the error in case the image -// is corrupt, even if the error is catched. -// -// See: http://mxr.mozilla.org/mozilla-central/source/image/src/Decoder.cpp#136 -exports["test Set Image Type Wrong Data"] = function(assert) { - var clip = require("sdk/clipboard"); - var flavor = "image"; - - var wrongPNG = "data:image/png" + base64jpeg.substr(15); - - assert.throws(function () { - clip.set(wrongPNG, flavor); - }, "Unable to decode data given in a valid image."); -}; - -require("sdk/test").run(exports) diff --git a/addon-sdk/source/test/test-collection.js b/addon-sdk/source/test/test-collection.js deleted file mode 100644 index d723c14ce0..0000000000 --- a/addon-sdk/source/test/test-collection.js +++ /dev/null @@ -1,128 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const collection = require("sdk/util/collection"); - -exports.testAddRemove = function (assert) { - let coll = new collection.Collection(); - compare(assert, coll, []); - addRemove(assert, coll, [], false); -}; - -exports.testAddRemoveBackingArray = function (assert) { - let items = ["foo"]; - let coll = new collection.Collection(items); - compare(assert, coll, items); - addRemove(assert, coll, items, true); - - items = ["foo", "bar"]; - coll = new collection.Collection(items); - compare(assert, coll, items); - addRemove(assert, coll, items, true); -}; - -exports.testProperty = function (assert) { - let obj = makeObjWithCollProp(); - compare(assert, obj.coll, []); - addRemove(assert, obj.coll, [], false); - - // Test single-value set. - let items = ["foo"]; - obj.coll = items[0]; - compare(assert, obj.coll, items); - addRemove(assert, obj.coll, items, false); - - // Test array set. - items = ["foo", "bar"]; - obj.coll = items; - compare(assert, obj.coll, items); - addRemove(assert, obj.coll, items, false); -}; - -exports.testPropertyBackingArray = function (assert) { - let items = ["foo"]; - let obj = makeObjWithCollProp(items); - compare(assert, obj.coll, items); - addRemove(assert, obj.coll, items, true); - - items = ["foo", "bar"]; - obj = makeObjWithCollProp(items); - compare(assert, obj.coll, items); - addRemove(assert, obj.coll, items, true); -}; - -// Adds some values to coll and then removes them. initialItems is an array -// containing coll's initial items. isBacking is true if initialItems is coll's -// backing array; the point is that updates to coll should affect initialItems -// if that's the case. -function addRemove(assert, coll, initialItems, isBacking) { - let items = isBacking ? initialItems : initialItems.slice(0); - let numInitialItems = items.length; - - // Test add(val). - let numInsertions = 5; - for (let i = 0; i < numInsertions; i++) { - compare(assert, coll, items); - coll.add(i); - if (!isBacking) - items.push(i); - } - compare(assert, coll, items); - - // Add the items we just added to make sure duplicates aren't added. - for (let i = 0; i < numInsertions; i++) - coll.add(i); - compare(assert, coll, items); - - // Test remove(val). Do a kind of shuffled remove. Remove item 1, then - // item 0, 3, 2, 5, 4, ... - for (let i = 0; i < numInsertions; i++) { - let val = i % 2 ? i - 1 : - i === numInsertions - 1 ? i : i + 1; - coll.remove(val); - if (!isBacking) - items.splice(items.indexOf(val), 1); - compare(assert, coll, items); - } - assert.equal(coll.length, numInitialItems, - "All inserted items should be removed"); - - // Remove the items we just removed. coll should be unchanged. - for (let i = 0; i < numInsertions; i++) - coll.remove(i); - compare(assert, coll, items); - - // Test add and remove([val1, val2]). - let newItems = [0, 1]; - coll.add(newItems); - compare(assert, coll, isBacking ? items : items.concat(newItems)); - coll.remove(newItems); - compare(assert, coll, items); - assert.equal(coll.length, numInitialItems, - "All inserted items should be removed"); -} - -// Asserts that the items in coll are the items of array. -function compare(assert, coll, array) { - assert.equal(coll.length, array.length, - "Collection length should be correct"); - let numItems = 0; - for (let item in coll) { - assert.equal(item, array[numItems], "Items should be equal"); - numItems++; - } - assert.equal(numItems, array.length, - "Number of items in iteration should be correct"); -} - -// Returns a new object with a collection property named "coll". backingArray, -// if defined, will create the collection with that backing array. -function makeObjWithCollProp(backingArray) { - let obj = {}; - collection.addCollectionProperty(obj, "coll", backingArray); - return obj; -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-commonjs-test-adapter.js b/addon-sdk/source/test/test-commonjs-test-adapter.js deleted file mode 100644 index 936bea9185..0000000000 --- a/addon-sdk/source/test/test-commonjs-test-adapter.js +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -exports["test custom `Assert`'s"] = require("./commonjs-test-adapter/asserts"); - -// Disabling this check since it is not yet supported by jetpack. -// if (module == require.main) - require("test").run(exports); diff --git a/addon-sdk/source/test/test-content-events.js b/addon-sdk/source/test/test-content-events.js deleted file mode 100644 index 059c356c4a..0000000000 --- a/addon-sdk/source/test/test-content-events.js +++ /dev/null @@ -1,92 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const { Loader } = require("sdk/test/loader"); -const { getMostRecentBrowserWindow, getInnerId } = require("sdk/window/utils"); -const { openTab, closeTab, getBrowserForTab } = require("sdk/tabs/utils"); -const { defer } = require("sdk/core/promise"); -const { curry, identity, partial } = require("sdk/lang/functional"); - -const { nuke } = require("sdk/loader/sandbox"); - -const { open: openWindow, close: closeWindow } = require('sdk/window/helpers'); - -const openBrowserWindow = partial(openWindow, null, {features: {toolbar: true}}); - -var when = curry(function(options, tab) { - let type = options.type || options; - let capture = options.capture || false; - let target = getBrowserForTab(tab); - let { promise, resolve } = defer(); - - target.addEventListener(type, function handler(event) { - if (!event.target.defaultView.frameElement) { - target.removeEventListener(type, handler, capture); - resolve(tab); - } - }, capture); - - return promise; -}); - -var use = use = value => () => value; - - -var open = curry((url, window) => openTab(window, url)); -var close = function(tab) { - let promise = when("pagehide", tab); - closeTab(tab); - return promise; -} - -exports["test dead object errors"] = function(assert, done) { - let system = require("sdk/system/events"); - let loader = Loader(module); - let { events } = loader.require("sdk/content/events"); - - // The dead object error is properly reported on console but - // doesn't raise any test's exception - function onMessage({ subject }) { - let message = subject.wrappedJSObject; - let { level } = message; - let text = String(message.arguments[0]); - - if (level === "error" && text.includes("can't access dead object")) - fail(text); - } - - let cleanup = () => system.off("console-api-log-event", onMessage); - let fail = (reason) => { - cleanup(); - assert.fail(reason); - } - - loader.unload(); - - nuke(loader.sharedGlobalSandbox); - - system.on("console-api-log-event", onMessage, true); - - openBrowserWindow(). - then(closeWindow). - then(() => assert.pass("checking dead object errors")). - then(cleanup). - then(done, fail); -}; - -// ignore *-document-global-created events that are not very consistent. -// only allow data uris that we create to ignore unwanted events, e.g., -// about:blank, http:// requests from Fennec's `about:`home page that displays -// add-ons a user could install, local `searchplugins`, other chrome uris -// Calls callback if passes filter -function eventFilter (type, target, callback) { - if (target.URL.startsWith("data:text/html,") && - type !== "chrome-document-global-created" && - type !== "content-document-global-created") - - callback(); -} -require("test").run(exports); diff --git a/addon-sdk/source/test/test-content-script.js b/addon-sdk/source/test/test-content-script.js deleted file mode 100644 index d3c7e1bbc0..0000000000 --- a/addon-sdk/source/test/test-content-script.js +++ /dev/null @@ -1,845 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const hiddenFrames = require("sdk/frame/hidden-frame"); -const { create: makeFrame } = require("sdk/frame/utils"); -const { window } = require("sdk/addon/window"); -const { Loader } = require('sdk/test/loader'); -const { URL } = require("sdk/url"); -const testURI = require("./fixtures").url("test.html"); -const testHost = URL(testURI).scheme + '://' + URL(testURI).host; - -/* - * Utility function that allow to easily run a proxy test with a clean - * new HTML document. See first unit test for usage. - */ -function createProxyTest(html, callback) { - return function (assert, done) { - let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html); - let principalLoaded = false; - - let element = makeFrame(window.document, { - nodeName: "iframe", - type: "content", - allowJavascript: true, - allowPlugins: true, - allowAuth: true, - uri: testURI - }); - - element.addEventListener("DOMContentLoaded", onDOMReady, false); - - function onDOMReady() { - // Reload frame after getting principal from `testURI` - if (!principalLoaded) { - element.setAttribute("src", url); - principalLoaded = true; - return; - } - - assert.equal(element.getAttribute("src"), url, "correct URL loaded"); - element.removeEventListener("DOMContentLoaded", onDOMReady, - false); - let xrayWindow = element.contentWindow; - let rawWindow = xrayWindow.wrappedJSObject; - - let isDone = false; - let helper = { - xrayWindow: xrayWindow, - rawWindow: rawWindow, - createWorker: function (contentScript) { - return createWorker(assert, xrayWindow, contentScript, helper.done); - }, - done: function () { - if (isDone) - return; - isDone = true; - element.parentNode.removeChild(element); - done(); - } - }; - callback(helper, assert); - } - }; -} - -function createWorker(assert, xrayWindow, contentScript, done) { - let loader = Loader(module); - let Worker = loader.require("sdk/content/worker").Worker; - let worker = Worker({ - window: xrayWindow, - contentScript: [ - 'new ' + function () { - assert = function assert(v, msg) { - self.port.emit("assert", {assertion:v, msg:msg}); - } - done = function done() { - self.port.emit("done"); - } - }, - contentScript - ] - }); - - worker.port.on("done", done); - worker.port.on("assert", function (data) { - assert.ok(data.assertion, data.msg); - }); - - return worker; -} - -/* Examples for the `createProxyTest` uses */ - -var html = ""; - -exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) { - // You can get access to regular `test` object in second argument of - // `createProxyTest` method: - assert.ok(helper.rawWindow.documentGlobal, - "You have access to a raw window reference via `helper.rawWindow`"); - assert.ok(!("documentGlobal" in helper.xrayWindow), - "You have access to an XrayWrapper reference via `helper.xrayWindow`"); - - // If you do not create a Worker, you have to call helper.done(), - // in order to say when your test is finished - helper.done(); -}); - -exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) { - - helper.createWorker( - "new " + function WorkerScope() { - assert(true, "You can do assertions in your content script"); - // And if you create a worker, you either have to call `done` - // from content script or helper.done() - done(); - } - ); - -}); - -exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) { - - let worker = helper.createWorker( - "new " + function WorkerScope() { - self.port.emit("foo"); - } - ); - - worker.port.on("foo", function () { - assert.pass("You can use events"); - // And terminate your test with helper.done: - helper.done(); - }); - -}); - -/* Disabled due to bug 1038432 -// Bug 714778: There was some issue around `toString` functions -// that ended up being shared between content scripts -exports["test Shared To String Proxies"] = createProxyTest("", function(helper) { - - let worker = helper.createWorker( - 'new ' + function ContentScriptScope() { - // We ensure that `toString` can't be modified so that nothing could - // leak to/from the document and between content scripts - // It only applies to JS proxies, there isn't any such issue with xrays. - //document.location.toString = function foo() {}; - document.location.toString.foo = "bar"; - assert("foo" in document.location.toString, "document.location.toString can be modified"); - assert(document.location.toString() == "data:text/html;charset=utf-8,", - "First document.location.toString()"); - self.postMessage("next"); - } - ); - worker.on("message", function () { - helper.createWorker( - 'new ' + function ContentScriptScope2() { - assert(!("foo" in document.location.toString), - "document.location.toString is different for each content script"); - assert(document.location.toString() == "data:text/html;charset=utf-8,", - "Second document.location.toString()"); - done(); - } - ); - }); -}); -*/ - -// Ensure that postMessage is working correctly across documents with an iframe -var html = ' -

- -

- - A targetted link. - -

- -

- -

- -

- - A link with no ID and an anchor, used by PredicateContext tests. - -

- -

- - - -

-

- - - - - A complex nested structure. - - - - -

- -

- -

- -

- -

- -

- -

- -

-

This content is editable.

-

- - diff --git a/addon-sdk/source/test/test-context-menu.js b/addon-sdk/source/test/test-context-menu.js deleted file mode 100644 index f1a9555454..0000000000 --- a/addon-sdk/source/test/test-context-menu.js +++ /dev/null @@ -1,3763 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - 'use strict'; - -require("sdk/context-menu"); - -const { defer } = require("sdk/core/promise"); -const { isTravisCI } = require("sdk/test/utils"); -const packaging = require('@loader/options'); - -// These should match the same constants in the module. -const OVERFLOW_THRESH_DEFAULT = 10; -const OVERFLOW_THRESH_PREF = - "extensions.addon-sdk.context-menu.overflowThreshold"; - -const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html"); -const data = require("./fixtures"); - -const { TestHelper } = require("./context-menu/test-helper.js") - -// Tests that when present the separator is placed before the separator from -// the old context-menu module -exports.testSeparatorPosition = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - // Create the old separator - let oldSeparator = test.contextMenuPopup.ownerDocument.createElement("menuseparator"); - oldSeparator.id = "jetpack-context-menu-separator"; - test.contextMenuPopup.appendChild(oldSeparator); - - // Create an item. - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - assert.equal(test.contextMenuSeparator.nextSibling.nextSibling, oldSeparator, - "New separator should appear before the old one"); - test.contextMenuPopup.removeChild(oldSeparator); - test.done(); - }); -}; - -// Destroying items that were previously created should cause them to be absent -// from the menu. -exports.testConstructDestroy = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - // Create an item. - let item = new loader.cm.Item({ label: "item" }); - assert.equal(item.parentMenu, loader.cm.contentContextMenu, - "item's parent menu should be correct"); - - test.showMenu(null, function (popup) { - - // It should be present when the menu is shown. - test.checkMenu([item], [], []); - popup.hidePopup(); - - // Destroy the item. Multiple destroys should be harmless. - item.destroy(); - item.destroy(); - test.showMenu(null, function (popup) { - - // It should be removed from the menu. - test.checkMenu([item], [], [item]); - test.done(); - }); - }); -}; - - -// Destroying an item twice should not cause an error. -exports.testDestroyTwice = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ label: "item" }); - item.destroy(); - item.destroy(); - - test.pass("Destroying an item twice should not cause an error."); - test.done(); -}; - - -// CSS selector contexts should cause their items to be present in the menu -// when the menu is invoked on nodes that match the selectors. -exports.testSelectorContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - data: "item", - context: loader.cm.SelectorContext("img") - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// CSS selector contexts should cause their items to be present in the menu -// when the menu is invoked on nodes that have ancestors that match the -// selectors. -exports.testSelectorAncestorContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - data: "item", - context: loader.cm.SelectorContext("a[href]") - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#span-link", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// CSS selector contexts should cause their items to be absent from the menu -// when the menu is not invoked on nodes that match or have ancestors that -// match the selectors. -exports.testSelectorContextNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - data: "item", - context: loader.cm.SelectorContext("img") - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); -}; - - -// Page contexts should cause their items to be present in the menu when the -// menu is not invoked on an active element. -exports.testPageContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ - label: "item 0" - }), - new loader.cm.Item({ - label: "item 1", - context: undefined - }), - new loader.cm.Item({ - label: "item 2", - context: loader.cm.PageContext() - }), - new loader.cm.Item({ - label: "item 3", - context: [loader.cm.PageContext()] - }) - ]; - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Page contexts should cause their items to be absent from the menu when the -// menu is invoked on an active element. -exports.testPageContextNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ - label: "item 0" - }), - new loader.cm.Item({ - label: "item 1", - context: undefined - }), - new loader.cm.Item({ - label: "item 2", - context: loader.cm.PageContext() - }), - new loader.cm.Item({ - label: "item 3", - context: [loader.cm.PageContext()] - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); - }); -}; - - -// Selection contexts should cause items to appear when a selection exists. -exports.testSelectionContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.withTestDoc(function (window, doc) { - window.getSelection().selectAllChildren(doc.body); - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// Selection contexts should cause items to appear when a selection exists in -// a text field. -exports.testSelectionContextMatchInTextField = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.withTestDoc(function (window, doc) { - test.selectRange("#textfield", 0, null); - test.showMenu("#textfield", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// Selection contexts should not cause items to appear when a selection does -// not exist in a text field. -exports.testSelectionContextNoMatchInTextField = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.withTestDoc(function (window, doc) { - test.selectRange("#textfield", 0, 0); - test.showMenu("#textfield", function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); - }); -}; - - -// Selection contexts should not cause items to appear when a selection does -// not exist. -exports.testSelectionContextNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); -}; - - -// Selection contexts should cause items to appear when a selection exists even -// for newly opened pages -exports.testSelectionContextInNewTab = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.withTestDoc(function (window, doc) { - let link = doc.getElementById("targetlink"); - link.click(); - - let tablistener = event => { - this.tabBrowser.tabContainer.removeEventListener("TabOpen", tablistener, false); - let tab = event.target; - let browser = tab.linkedBrowser; - this.loadFrameScript(browser); - this.delayedEventListener(browser, "load", () => { - let window = browser.contentWindow; - let doc = browser.contentDocument; - window.getSelection().selectAllChildren(doc.body); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - popup.hidePopup(); - - test.tabBrowser.removeTab(test.tabBrowser.selectedTab); - test.tabBrowser.selectedTab = test.tab; - - test.showMenu(null, function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); - }); - }, true); - }; - this.tabBrowser.tabContainer.addEventListener("TabOpen", tablistener, false); - }); -}; - - -// Selection contexts should work when right clicking a form button -exports.testSelectionContextButtonMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.withTestDoc(function (window, doc) { - window.getSelection().selectAllChildren(doc.body); - test.showMenu("#button", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -//Selection contexts should work when right clicking a form button -exports.testSelectionContextButtonNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "item", - context: loader.cm.SelectionContext() - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#button", function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); - }); -}; - - -// URL contexts should cause items to appear on pages that match. -exports.testURLContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - loader.cm.Item({ - label: "item 0", - context: loader.cm.URLContext(TEST_DOC_URL) - }), - loader.cm.Item({ - label: "item 1", - context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"]) - }), - loader.cm.Item({ - label: "item 2", - context: loader.cm.URLContext([new RegExp(".*\\.html")]) - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// URL contexts should not cause items to appear on pages that do not match. -exports.testURLContextNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - loader.cm.Item({ - label: "item 0", - context: loader.cm.URLContext("*.bogus.com") - }), - loader.cm.Item({ - label: "item 1", - context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"]) - }), - loader.cm.Item({ - label: "item 2", - context: loader.cm.URLContext([new RegExp(".*\\.js")]) - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); - }); -}; - - -// Loading a new page in the same tab should correctly start a new worker for -// any content scripts -exports.testPageReload = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ - label: "Item", - contentScript: "var doc = document; self.on('context', node => doc.body.getAttribute('showItem') == 'true');" - }); - - test.withTestDoc(function (window, doc) { - // Set a flag on the document that the item uses - doc.body.setAttribute("showItem", "true"); - - test.showMenu(null, function (popup) { - // With the attribute true the item should be visible in the menu - test.checkMenu([item], [], []); - test.hideMenu(function() { - let browser = this.tabBrowser.getBrowserForTab(this.tab) - test.delayedEventListener(browser, "load", function() { - test.delayedEventListener(browser, "load", function() { - window = browser.contentWindow; - doc = window.document; - - // Set a flag on the document that the item uses - doc.body.setAttribute("showItem", "false"); - - test.showMenu(null, function (popup) { - // In the new document with the attribute false the item should be - // hidden, but if the contentScript hasn't been reloaded it will - // still see the old value - test.checkMenu([item], [item], []); - - test.done(); - }); - }, true); - browser.loadURI(TEST_DOC_URL, null, null); - }, true); - // Required to make sure we load a new page in history rather than - // just reloading the current page which would unload it - browser.loadURI("about:blank", null, null); - }); - }); - }); -}; - -// Closing a page after it's been used with a worker should cause the worker -// to be destroyed -/*exports.testWorkerDestroy = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let loadExpected = false; - - let item = loader.cm.Item({ - label: "item", - contentScript: 'self.postMessage("loaded"); self.on("detach", function () { console.log("saw detach"); self.postMessage("detach") });', - onMessage: function (msg) { - switch (msg) { - case "loaded": - assert.ok(loadExpected, "Should have seen the load event at the right time"); - loadExpected = false; - break; - case "detach": - test.done(); - break; - } - } - }); - - test.withTestDoc(function (window, doc) { - loadExpected = true; - test.showMenu(null, function (popup) { - assert.ok(!loadExpected, "Should have seen a message"); - - test.checkMenu([item], [], []); - - test.closeTab(); - }); - }); -};*/ - - -// Content contexts that return true should cause their items to be present -// in the menu. -exports.testContentContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => true);' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); -}; - - -// Content contexts that return false should cause their items to be absent -// from the menu. -exports.testContentContextNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => false);' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); -}; - - -// Content contexts that return undefined should cause their items to be absent -// from the menu. -exports.testContentContextUndefined = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", function () {});' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); -}; - - -// Content contexts that return an empty string should cause their items to be -// absent from the menu and shouldn't wipe the label -exports.testContentContextEmptyString = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => "");' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [item], []); - assert.equal(item.label, "item", "Label should still be correct"); - test.done(); - }); -}; - - -// If any content contexts returns true then their items should be present in -// the menu. -exports.testMultipleContentContextMatch1 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => true); ' + - 'self.on("context", () => false);', - onMessage: function() { - test.fail("Should not have called the second context listener"); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); -}; - - -// If any content contexts returns true then their items should be present in -// the menu. -exports.testMultipleContentContextMatch2 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => false); ' + - 'self.on("context", () => true);' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); -}; - - -// If any content contexts returns a string then their items should be present -// in the menu. -exports.testMultipleContentContextString1 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => "new label"); ' + - 'self.on("context", () => false);' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - assert.equal(item.label, "new label", "Label should have changed"); - test.done(); - }); -}; - - -// If any content contexts returns a string then their items should be present -// in the menu. -exports.testMultipleContentContextString2 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => false); ' + - 'self.on("context", () => "new label");' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - assert.equal(item.label, "new label", "Label should have changed"); - test.done(); - }); -}; - - -// If many content contexts returns a string then the first should take effect -exports.testMultipleContentContextString3 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", () => "new label 1"); ' + - 'self.on("context", () => "new label 2");' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - assert.equal(item.label, "new label 1", "Label should have changed"); - test.done(); - }); -}; - - -// Content contexts that return true should cause their items to be present -// in the menu when context clicking an active element. -exports.testContentContextMatchActiveElement = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ - label: "item 1", - contentScript: 'self.on("context", () => true);' - }), - new loader.cm.Item({ - label: "item 2", - context: undefined, - contentScript: 'self.on("context", () => true);' - }), - // These items will always be hidden by the declarative usage of PageContext - new loader.cm.Item({ - label: "item 3", - context: loader.cm.PageContext(), - contentScript: 'self.on("context", () => true);' - }), - new loader.cm.Item({ - label: "item 4", - context: [loader.cm.PageContext()], - contentScript: 'self.on("context", () => true);' - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, [items[2], items[3]], []); - test.done(); - }); - }); -}; - - -// Content contexts that return false should cause their items to be absent -// from the menu when context clicking an active element. -exports.testContentContextNoMatchActiveElement = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ - label: "item 1", - contentScript: 'self.on("context", () => false);' - }), - new loader.cm.Item({ - label: "item 2", - context: undefined, - contentScript: 'self.on("context", () => false);' - }), - // These items will always be hidden by the declarative usage of PageContext - new loader.cm.Item({ - label: "item 3", - context: loader.cm.PageContext(), - contentScript: 'self.on("context", () => false);' - }), - new loader.cm.Item({ - label: "item 4", - context: [loader.cm.PageContext()], - contentScript: 'self.on("context", () => false);' - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); - }); -}; - - -// Content contexts that return undefined should cause their items to be absent -// from the menu when context clicking an active element. -exports.testContentContextNoMatchActiveElement = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ - label: "item 1", - contentScript: 'self.on("context", () => {});' - }), - new loader.cm.Item({ - label: "item 2", - context: undefined, - contentScript: 'self.on("context", () => {});' - }), - // These items will always be hidden by the declarative usage of PageContext - new loader.cm.Item({ - label: "item 3", - context: loader.cm.PageContext(), - contentScript: 'self.on("context", () => {});' - }), - new loader.cm.Item({ - label: "item 4", - context: [loader.cm.PageContext()], - contentScript: 'self.on("context", () => {});' - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); - }); -}; - - -// Content contexts that return a string should cause their items to be present -// in the menu and the items' labels to be updated. -exports.testContentContextMatchString = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "first label", - contentScript: 'self.on("context", () => "second label");' - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - assert.equal(item.label, "second label", - "item's label should be updated"); - test.done(); - }); -}; - - -// Ensure that contentScriptFile is working correctly -exports.testContentScriptFile = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - let { defer, all } = require("sdk/core/promise"); - let itemScript = [defer(), defer()]; - let menuShown = defer(); - let menuPromises = itemScript.concat(menuShown).map(({promise}) => promise); - - // Reject remote files - assert.throws(function() { - new loader.cm.Item({ - label: "item", - contentScriptFile: "http://mozilla.com/context-menu.js" - }); - }, - /The `contentScriptFile` option must be a local URL or an array of URLs/, - "Item throws when contentScriptFile is a remote URL"); - - // But accept files from data folder - let item = new loader.cm.Item({ - label: "item", - contentScriptFile: data.url("test-contentScriptFile.js"), - onMessage: (message) => { - assert.equal(message, "msg from contentScriptFile", - "contentScriptFile loaded with absolute url"); - itemScript[0].resolve(); - } - }); - - let item2 = new loader.cm.Item({ - label: "item2", - contentScriptFile: "./test-contentScriptFile.js", - onMessage: (message) => { - assert.equal(message, "msg from contentScriptFile", - "contentScriptFile loaded with relative url"); - itemScript[1].resolve(); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item, item2], [], []); - menuShown.resolve(); - }); - - all(menuPromises).then(() => test.done()); -}; - - -// The args passed to context listeners should be correct. -exports.testContentContextArgs = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - let callbacks = 0; - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'self.on("context", function (node) {' + - ' self.postMessage(node.tagName);' + - ' return false;' + - '});', - onMessage: function (tagName) { - assert.equal(tagName, "HTML", "node should be an HTML element"); - if (++callbacks == 2) test.done(); - } - }); - - test.showMenu(null, function () { - if (++callbacks == 2) test.done(); - }); -}; - -// Multiple contexts imply intersection, not union. -exports.testMultipleContexts = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()], - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#span-link", function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); - }); -}; - -// Once a context is removed, it should no longer cause its item to appear. -exports.testRemoveContext = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let ctxt = loader.cm.SelectorContext("img"); - let item = new loader.cm.Item({ - label: "item", - context: ctxt - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - - // The item should be present at first. - test.checkMenu([item], [], []); - popup.hidePopup(); - - // Remove the img context and check again. - item.context.remove(ctxt); - test.showMenu("#image", function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); - }); - }); -}; - -// Once a context is removed, it should no longer cause its item to appear. -exports.testSetContextRemove = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let ctxt = loader.cm.SelectorContext("img"); - let item = new loader.cm.Item({ - label: "item", - context: ctxt - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - - // The item should be present at first. - test.checkMenu([item], [], []); - popup.hidePopup(); - - // Remove the img context and check again. - item.context = []; - test.showMenu("#image", function (popup) { - test.checkMenu([item], [item], []); - test.done(); - }); - }); - }); -}; - -// Once a context is added, it should affect whether the item appears. -exports.testAddContext = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let ctxt = loader.cm.SelectorContext("img"); - let item = new loader.cm.Item({ - label: "item" - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - - // The item should not be present at first. - test.checkMenu([item], [item], []); - popup.hidePopup(); - - // Add the img context and check again. - item.context.add(ctxt); - test.showMenu("#image", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); - }); -}; - -// Once a context is added, it should affect whether the item appears. -exports.testSetContextAdd = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let ctxt = loader.cm.SelectorContext("img"); - let item = new loader.cm.Item({ - label: "item" - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - - // The item should not be present at first. - test.checkMenu([item], [item], []); - popup.hidePopup(); - - // Add the img context and check again. - item.context = [ctxt]; - test.showMenu("#image", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); - }); -}; - -// Lots of items should overflow into the overflow submenu. -exports.testOverflow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) { - let item = new loader.cm.Item({ label: "item " + i }); - items.push(item); - } - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Module unload should cause all items to be removed. -exports.testUnload = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - - // The menu should contain the item. - test.checkMenu([item], [], []); - popup.hidePopup(); - - // Unload the module. - loader.unload(); - test.showMenu(null, function (popup) { - - // The item should be removed from the menu. - test.checkMenu([item], [], [item]); - test.done(); - }); - }); -}; - - -// Using multiple module instances to add items without causing overflow should -// work OK. Assumes OVERFLOW_THRESH_DEFAULT >= 2. -exports.testMultipleModulesAdd = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - // Use each module to add an item, then unload each module in turn. - let item0 = new loader0.cm.Item({ label: "item 0" }); - let item1 = new loader1.cm.Item({ label: "item 1" }); - - test.showMenu(null, function (popup) { - - // The menu should contain both items. - test.checkMenu([item0, item1], [], []); - popup.hidePopup(); - - // Unload the first module. - loader0.unload(); - test.showMenu(null, function (popup) { - - // The first item should be removed from the menu. - test.checkMenu([item0, item1], [], [item0]); - popup.hidePopup(); - - // Unload the second module. - loader1.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([item0, item1], [], [item0, item1]); - test.done(); - }); - }); - }); -}; - - -// Using multiple module instances to add items causing overflow should work OK. -exports.testMultipleModulesAddOverflow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - // Use module 0 to add OVERFLOW_THRESH_DEFAULT items. - let items0 = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { - let item = new loader0.cm.Item({ label: "item 0 " + i }); - items0.push(item); - } - - // Use module 1 to add one item. - let item1 = new loader1.cm.Item({ label: "item 1" }); - - let allItems = items0.concat(item1); - - test.showMenu(null, function (popup) { - - // The menu should contain all items in overflow. - test.checkMenu(allItems, [], []); - popup.hidePopup(); - - // Unload the first module. - loader0.unload(); - test.showMenu(null, function (popup) { - - // The first items should be removed from the menu, which should not - // overflow. - test.checkMenu(allItems, [], items0); - popup.hidePopup(); - - // Unload the second module. - loader1.unload(); - test.showMenu(null, function (popup) { - - // All items should be removed from the menu. - test.checkMenu(allItems, [], allItems); - test.done(); - }); - }); - }); -}; - - -// Using multiple module instances to modify the menu without causing overflow -// should work OK. This test creates two loaders and: -// loader0 create item -> loader1 create item -> loader0.unload -> -// loader1.unload -exports.testMultipleModulesDiffContexts1 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item0 = new loader0.cm.Item({ - label: "item 0", - context: loader0.cm.SelectorContext("img") - }); - - let item1 = new loader1.cm.Item({ label: "item 1" }); - - test.showMenu(null, function (popup) { - - // The menu should contain item1. - test.checkMenu([item0, item1], [item0], []); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // item0 should be removed from the menu. - test.checkMenu([item0, item1], [], [item0]); - popup.hidePopup(); - - // Unload module 1. - loader1.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([item0, item1], [], [item0, item1]); - test.done(); - }); - }); - }); -}; - - -// Using multiple module instances to modify the menu without causing overflow -// should work OK. This test creates two loaders and: -// loader1 create item -> loader0 create item -> loader0.unload -> -// loader1.unload -exports.testMultipleModulesDiffContexts2 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item1 = new loader1.cm.Item({ label: "item 1" }); - - let item0 = new loader0.cm.Item({ - label: "item 0", - context: loader0.cm.SelectorContext("img") - }); - - test.showMenu(null, function (popup) { - - // The menu should contain item1. - test.checkMenu([item0, item1], [item0], []); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // item0 should be removed from the menu. - test.checkMenu([item0, item1], [], [item0]); - popup.hidePopup(); - - // Unload module 1. - loader1.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([item0, item1], [], [item0, item1]); - test.done(); - }); - }); - }); -}; - - -// Using multiple module instances to modify the menu without causing overflow -// should work OK. This test creates two loaders and: -// loader0 create item -> loader1 create item -> loader1.unload -> -// loader0.unload -exports.testMultipleModulesDiffContexts3 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item0 = new loader0.cm.Item({ - label: "item 0", - context: loader0.cm.SelectorContext("img") - }); - - let item1 = new loader1.cm.Item({ label: "item 1" }); - - test.showMenu(null, function (popup) { - - // The menu should contain item1. - test.checkMenu([item0, item1], [item0], []); - popup.hidePopup(); - - // Unload module 1. - loader1.unload(); - test.showMenu(null, function (popup) { - - // item1 should be removed from the menu. - test.checkMenu([item0, item1], [item0], [item1]); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([item0, item1], [], [item0, item1]); - test.done(); - }); - }); - }); -}; - - -// Using multiple module instances to modify the menu without causing overflow -// should work OK. This test creates two loaders and: -// loader1 create item -> loader0 create item -> loader1.unload -> -// loader0.unload -exports.testMultipleModulesDiffContexts4 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item1 = new loader1.cm.Item({ label: "item 1" }); - - let item0 = new loader0.cm.Item({ - label: "item 0", - context: loader0.cm.SelectorContext("img") - }); - - test.showMenu(null, function (popup) { - - // The menu should contain item1. - test.checkMenu([item0, item1], [item0], []); - popup.hidePopup(); - - // Unload module 1. - loader1.unload(); - test.showMenu(null, function (popup) { - - // item1 should be removed from the menu. - test.checkMenu([item0, item1], [item0], [item1]); - popup.hidePopup(); - - // Unload module 0. - loader0.unload(); - test.showMenu(null, function (popup) { - - // Both items should be removed from the menu. - test.checkMenu([item0, item1], [], [item0, item1]); - test.done(); - }); - }); - }); -}; - - -// Test interactions between a loaded module, unloading another module, and the -// menu separator and overflow submenu. -exports.testMultipleModulesAddRemove = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item = new loader0.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - - // The menu should contain the item. - test.checkMenu([item], [], []); - popup.hidePopup(); - - // Remove the item. - item.destroy(); - test.showMenu(null, function (popup) { - - // The item should be removed from the menu. - test.checkMenu([item], [], [item]); - popup.hidePopup(); - - // Unload module 1. - loader1.unload(); - test.showMenu(null, function (popup) { - - // There shouldn't be any errors involving the menu separator or - // overflow submenu. - test.checkMenu([item], [], [item]); - test.done(); - }); - }); - }); -}; - - -// Checks that the order of menu items is correct when adding/removing across -// multiple modules. All items from a single module should remain in a group -exports.testMultipleModulesOrder = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - // Use each module to add an item, then unload each module in turn. - let item0 = new loader0.cm.Item({ label: "item 0" }); - let item1 = new loader1.cm.Item({ label: "item 1" }); - - test.showMenu(null, function (popup) { - - // The menu should contain both items. - test.checkMenu([item0, item1], [], []); - popup.hidePopup(); - - let item2 = new loader0.cm.Item({ label: "item 2" }); - - test.showMenu(null, function (popup) { - - // The new item should be grouped with the same items from loader0. - test.checkMenu([item0, item2, item1], [], []); - popup.hidePopup(); - - let item3 = new loader1.cm.Item({ label: "item 3" }); - - test.showMenu(null, function (popup) { - - // Same again - test.checkMenu([item0, item2, item1, item3], [], []); - test.done(); - }); - }); - }); -}; - - -// Checks that the order of menu items is correct when adding/removing across -// multiple modules when overflowing. All items from a single module should -// remain in a group -exports.testMultipleModulesOrderOverflow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let prefs = loader0.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - // Use each module to add an item, then unload each module in turn. - let item0 = new loader0.cm.Item({ label: "item 0" }); - let item1 = new loader1.cm.Item({ label: "item 1" }); - - test.showMenu(null, function (popup) { - - // The menu should contain both items. - test.checkMenu([item0, item1], [], []); - popup.hidePopup(); - - let item2 = new loader0.cm.Item({ label: "item 2" }); - - test.showMenu(null, function (popup) { - - // The new item should be grouped with the same items from loader0. - test.checkMenu([item0, item2, item1], [], []); - popup.hidePopup(); - - let item3 = new loader1.cm.Item({ label: "item 3" }); - - test.showMenu(null, function (popup) { - - // Same again - test.checkMenu([item0, item2, item1, item3], [], []); - test.done(); - }); - }); - }); -}; - - -// Checks that if a module's items are all hidden then the overflow menu doesn't -// get hidden -exports.testMultipleModulesOverflowHidden = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let prefs = loader0.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - // Use each module to add an item, then unload each module in turn. - let item0 = new loader0.cm.Item({ label: "item 0" }); - let item1 = new loader1.cm.Item({ - label: "item 1", - context: loader1.cm.SelectorContext("a") - }); - - test.showMenu(null, function (popup) { - // One should be hidden - test.checkMenu([item0, item1], [item1], []); - test.done(); - }); -}; - - -// Checks that if a module's items are all hidden then the overflow menu doesn't -// get hidden (reverse order to above) -exports.testMultipleModulesOverflowHidden2 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let prefs = loader0.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - // Use each module to add an item, then unload each module in turn. - let item0 = new loader0.cm.Item({ - label: "item 0", - context: loader0.cm.SelectorContext("a") - }); - let item1 = new loader1.cm.Item({ label: "item 1" }); - - test.showMenu(null, function (popup) { - // One should be hidden - test.checkMenu([item0, item1], [item0], []); - test.done(); - }); -}; - - -// Checks that we don't overflow if there are more items than the overflow -// threshold but not all of them are visible -exports.testOverflowIgnoresHidden = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let prefs = loader.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 2); - - let allItems = [ - new loader.cm.Item({ - label: "item 0" - }), - new loader.cm.Item({ - label: "item 1" - }), - new loader.cm.Item({ - label: "item 2", - context: loader.cm.SelectorContext("a") - }) - ]; - - test.showMenu(null, function (popup) { - // One should be hidden - test.checkMenu(allItems, [allItems[2]], []); - test.done(); - }); -}; - - -// Checks that we don't overflow if there are more items than the overflow -// threshold but not all of them are visible -exports.testOverflowIgnoresHiddenMultipleModules1 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let prefs = loader0.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 2); - - let allItems = [ - new loader0.cm.Item({ - label: "item 0" - }), - new loader0.cm.Item({ - label: "item 1" - }), - new loader1.cm.Item({ - label: "item 2", - context: loader1.cm.SelectorContext("a") - }), - new loader1.cm.Item({ - label: "item 3", - context: loader1.cm.SelectorContext("a") - }) - ]; - - test.showMenu(null, function (popup) { - // One should be hidden - test.checkMenu(allItems, [allItems[2], allItems[3]], []); - test.done(); - }); -}; - - -// Checks that we don't overflow if there are more items than the overflow -// threshold but not all of them are visible -exports.testOverflowIgnoresHiddenMultipleModules2 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let prefs = loader0.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 2); - - let allItems = [ - new loader0.cm.Item({ - label: "item 0" - }), - new loader0.cm.Item({ - label: "item 1", - context: loader0.cm.SelectorContext("a") - }), - new loader1.cm.Item({ - label: "item 2" - }), - new loader1.cm.Item({ - label: "item 3", - context: loader1.cm.SelectorContext("a") - }) - ]; - - test.showMenu(null, function (popup) { - // One should be hidden - test.checkMenu(allItems, [allItems[1], allItems[3]], []); - test.done(); - }); -}; - - -// Checks that we don't overflow if there are more items than the overflow -// threshold but not all of them are visible -exports.testOverflowIgnoresHiddenMultipleModules3 = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let prefs = loader0.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 2); - - let allItems = [ - new loader0.cm.Item({ - label: "item 0", - context: loader0.cm.SelectorContext("a") - }), - new loader0.cm.Item({ - label: "item 1", - context: loader0.cm.SelectorContext("a") - }), - new loader1.cm.Item({ - label: "item 2" - }), - new loader1.cm.Item({ - label: "item 3" - }) - ]; - - test.showMenu(null, function (popup) { - // One should be hidden - test.checkMenu(allItems, [allItems[0], allItems[1]], []); - test.done(); - }); -}; - - -// Tests that we transition between overflowing to non-overflowing to no items -// and back again -exports.testOverflowTransition = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let prefs = loader.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 2); - - let pItems = [ - new loader.cm.Item({ - label: "item 0", - context: loader.cm.SelectorContext("p") - }), - new loader.cm.Item({ - label: "item 1", - context: loader.cm.SelectorContext("p") - }) - ]; - - let aItems = [ - new loader.cm.Item({ - label: "item 2", - context: loader.cm.SelectorContext("a") - }), - new loader.cm.Item({ - label: "item 3", - context: loader.cm.SelectorContext("a") - }) - ]; - - let allItems = pItems.concat(aItems); - - test.withTestDoc(function (window, doc) { - test.showMenu("#link", function (popup) { - // The menu should contain all items and will overflow - test.checkMenu(allItems, [], []); - popup.hidePopup(); - - test.showMenu("#text", function (popup) { - // Only contains hald the items and will not overflow - test.checkMenu(allItems, aItems, []); - popup.hidePopup(); - - test.showMenu(null, function (popup) { - // None of the items will be visible - test.checkMenu(allItems, allItems, []); - popup.hidePopup(); - - test.showMenu("#text", function (popup) { - // Only contains hald the items and will not overflow - test.checkMenu(allItems, aItems, []); - popup.hidePopup(); - - test.showMenu("#link", function (popup) { - // The menu should contain all items and will overflow - test.checkMenu(allItems, [], []); - popup.hidePopup(); - - test.showMenu(null, function (popup) { - // None of the items will be visible - test.checkMenu(allItems, allItems, []); - popup.hidePopup(); - - test.showMenu("#link", function (popup) { - // The menu should contain all items and will overflow - test.checkMenu(allItems, [], []); - test.done(); - }); - }); - }); - }); - }); - }); - }); - }); -}; - - -// An item's command listener should work. -exports.testItemCommand = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - data: "item data", - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function (data) { - assert.equal(this, item, "`this` inside onMessage should be item"); - assert.equal(data.tagName, "HTML", "node should be an HTML element"); - assert.equal(data.data, item.data, "data should be item data"); - test.done(); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - let elt = test.getItemElt(popup, item); - - // create a command event - let evt = elt.ownerDocument.createEvent('Event'); - evt.initEvent('command', true, true); - elt.dispatchEvent(evt); - }); -}; - - -// A menu's click listener should work and receive bubbling 'command' events from -// sub-items appropriately. This also tests menus and ensures that when a CSS -// selector context matches the clicked node's ancestor, the matching ancestor -// is passed to listeners as the clicked node. -exports.testMenuCommand = function (assert, done) { - // Create a top-level menu, submenu, and item, like this: - // topMenu -> submenu -> item - // Click the item and make sure the click bubbles. - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "submenu item", - data: "submenu item data", - context: loader.cm.SelectorContext("a"), - }); - - let submenu = new loader.cm.Menu({ - label: "submenu", - context: loader.cm.SelectorContext("a"), - items: [item] - }); - - let topMenu = new loader.cm.Menu({ - label: "top menu", - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function (data) { - assert.equal(this, topMenu, "`this` inside top menu should be menu"); - assert.equal(data.tagName, "A", "Clicked node should be anchor"); - assert.equal(data.data, item.data, - "Clicked item data should be correct"); - test.done(); - }, - items: [submenu], - context: loader.cm.SelectorContext("a") - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#span-link", function (popup) { - test.checkMenu([topMenu], [], []); - let topMenuElt = test.getItemElt(popup, topMenu); - let topMenuPopup = topMenuElt.firstChild; - let submenuElt = test.getItemElt(topMenuPopup, submenu); - let submenuPopup = submenuElt.firstChild; - let itemElt = test.getItemElt(submenuPopup, item); - - // create a command event - let evt = itemElt.ownerDocument.createEvent('Event'); - evt.initEvent('command', true, true); - itemElt.dispatchEvent(evt); - }); - }); -}; - - -// Click listeners should work when multiple modules are loaded. -exports.testItemCommandMultipleModules = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item0 = loader0.cm.Item({ - label: "loader 0 item", - contentScript: 'self.on("click", self.postMessage);', - onMessage: function () { - test.fail("loader 0 item should not emit click event"); - } - }); - let item1 = loader1.cm.Item({ - label: "loader 1 item", - contentScript: 'self.on("click", self.postMessage);', - onMessage: function () { - test.pass("loader 1 item clicked as expected"); - test.done(); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item0, item1], [], []); - let item1Elt = test.getItemElt(popup, item1); - - // create a command event - let evt = item1Elt.ownerDocument.createEvent('Event'); - evt.initEvent('command', true, true); - item1Elt.dispatchEvent(evt); - }); -}; - - - - -// An item's click listener should work. -exports.testItemClick = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - data: "item data", - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function (data) { - assert.equal(this, item, "`this` inside onMessage should be item"); - assert.equal(data.tagName, "HTML", "node should be an HTML element"); - assert.equal(data.data, item.data, "data should be item data"); - test.done(); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - let elt = test.getItemElt(popup, item); - elt.click(); - }); -}; - - -// A menu's click listener should work and receive bubbling clicks from -// sub-items appropriately. This also tests menus and ensures that when a CSS -// selector context matches the clicked node's ancestor, the matching ancestor -// is passed to listeners as the clicked node. -exports.testMenuClick = function (assert, done) { - // Create a top-level menu, submenu, and item, like this: - // topMenu -> submenu -> item - // Click the item and make sure the click bubbles. - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "submenu item", - data: "submenu item data", - context: loader.cm.SelectorContext("a"), - }); - - let submenu = new loader.cm.Menu({ - label: "submenu", - context: loader.cm.SelectorContext("a"), - items: [item] - }); - - let topMenu = new loader.cm.Menu({ - label: "top menu", - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function (data) { - assert.equal(this, topMenu, "`this` inside top menu should be menu"); - assert.equal(data.tagName, "A", "Clicked node should be anchor"); - assert.equal(data.data, item.data, - "Clicked item data should be correct"); - test.done(); - }, - items: [submenu], - context: loader.cm.SelectorContext("a") - }); - - test.withTestDoc(function (window, doc) { - test.showMenu("#span-link", function (popup) { - test.checkMenu([topMenu], [], []); - let topMenuElt = test.getItemElt(popup, topMenu); - let topMenuPopup = topMenuElt.firstChild; - let submenuElt = test.getItemElt(topMenuPopup, submenu); - let submenuPopup = submenuElt.firstChild; - let itemElt = test.getItemElt(submenuPopup, item); - itemElt.click(); - }); - }); -}; - -// Click listeners should work when multiple modules are loaded. -exports.testItemClickMultipleModules = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let item0 = loader0.cm.Item({ - label: "loader 0 item", - contentScript: 'self.on("click", self.postMessage);', - onMessage: function () { - test.fail("loader 0 item should not emit click event"); - } - }); - let item1 = loader1.cm.Item({ - label: "loader 1 item", - contentScript: 'self.on("click", self.postMessage);', - onMessage: function () { - test.pass("loader 1 item clicked as expected"); - test.done(); - } - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item0, item1], [], []); - let item1Elt = test.getItemElt(popup, item1); - item1Elt.click(); - }); -}; - - -// Adding a separator to a submenu should work OK. -exports.testSeparator = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = new loader.cm.Menu({ - label: "submenu", - items: [new loader.cm.Separator()] - }); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// The parentMenu option should work -exports.testParentMenu = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = new loader.cm.Menu({ - label: "submenu", - items: [loader.cm.Item({ label: "item 1" })], - parentMenu: loader.cm.contentContextMenu - }); - - let item = loader.cm.Item({ - label: "item 2", - parentMenu: menu, - }); - - assert.equal(menu.items[1], item, "Item should be in the sub menu"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Existing context menu modifications should apply to new windows. -exports.testNewWindow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ label: "item" }); - - test.withNewWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// When a new window is opened, items added by an unloaded module should not -// be present in the menu. -exports.testNewWindowMultipleModules = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - popup.hidePopup(); - loader.unload(); - test.withNewWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([item], [], [item]); - test.done(); - }); - }); - }); -}; - - -// Existing context menu modifications should not apply to new private windows. -exports.testNewPrivateWindow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - popup.hidePopup(); - - test.withNewPrivateWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([], [], []); - test.done(); - }); - }); - }); -}; - - -// Existing context menu modifications should apply to new private windows when -// private browsing support is enabled. -exports.testNewPrivateEnabledWindow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newPrivateLoader(); - - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - popup.hidePopup(); - - test.withNewPrivateWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); - }); -}; - - -// Existing context menu modifications should apply to new private windows when -// private browsing support is enabled unless unloaded. -exports.testNewPrivateEnabledWindowUnloaded = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newPrivateLoader(); - - let item = new loader.cm.Item({ label: "item" }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - popup.hidePopup(); - - loader.unload(); - - test.withNewPrivateWindow(function () { - test.showMenu(null, function (popup) { - test.checkMenu([], [], []); - test.done(); - }); - }); - }); -}; - - -// Items in the context menu should be sorted according to locale. -exports.testSorting = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - // Make an unsorted items list. It'll look like this: - // item 1, item 0, item 3, item 2, item 5, item 4, ... - let items = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) { - items.push(new loader.cm.Item({ label: "item " + (i + 1) })); - items.push(new loader.cm.Item({ label: "item " + i })); - } - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Items in the overflow menu should be sorted according to locale. -exports.testSortingOverflow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - // Make an unsorted items list. It'll look like this: - // item 1, item 0, item 3, item 2, item 5, item 4, ... - let items = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) { - items.push(new loader.cm.Item({ label: "item " + (i + 1) })); - items.push(new loader.cm.Item({ label: "item " + i })); - } - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Multiple modules shouldn't interfere with sorting. -exports.testSortingMultipleModules = function (assert, done) { - let test = new TestHelper(assert, done); - let loader0 = test.newLoader(); - let loader1 = test.newLoader(); - - let items0 = []; - let items1 = []; - for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { - if (i % 2) { - let item = new loader0.cm.Item({ label: "item " + i }); - items0.push(item); - } - else { - let item = new loader1.cm.Item({ label: "item " + i }); - items1.push(item); - } - } - let allItems = items0.concat(items1); - - test.showMenu(null, function (popup) { - - // All items should be present and sorted. - test.checkMenu(allItems, [], []); - popup.hidePopup(); - loader0.unload(); - loader1.unload(); - test.showMenu(null, function (popup) { - - // All items should be removed. - test.checkMenu(allItems, [], allItems); - test.done(); - }); - }); -}; - - -// Content click handlers and context handlers should be able to communicate, -// i.e., they're eval'ed in the same worker and sandbox. -exports.testContentCommunication = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ - label: "item", - contentScript: 'var potato;' + - 'self.on("context", function () {' + - ' potato = "potato";' + - ' return true;' + - '});' + - 'self.on("click", function () {' + - ' self.postMessage(potato);' + - '});', - }); - - item.on("message", function (data) { - assert.equal(data, "potato", "That's a lot of potatoes!"); - test.done(); - }); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - let elt = test.getItemElt(popup, item); - elt.click(); - }); -}; - - -// When the context menu is invoked on a tab that was already open when the -// module was loaded, it should contain the expected items and content workers -// should function as expected. -exports.testLoadWithOpenTab = function (assert, done) { - let test = new TestHelper(assert, done); - test.withTestDoc(function (window, doc) { - let loader = test.newLoader(); - let item = new loader.cm.Item({ - label: "item", - contentScript: - 'self.on("click", () => self.postMessage("click"));', - onMessage: function (msg) { - if (msg === "click") - test.done(); - } - }); - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.getItemElt(popup, item).click(); - }); - }); -}; - -// Bug 732716: Ensure that the node given in `click` event works fine -// (i.e. is correctly wrapped) -exports.testDrawImageOnClickNode = function (assert, done) { - let test = new TestHelper(assert, done); - test.withTestDoc(function (window, doc) { - let loader = test.newLoader(); - let item = new loader.cm.Item({ - label: "item", - context: loader.cm.SelectorContext("img"), - contentScript: "new " + function() { - self.on("click", function (img, data) { - let ctx = document.createElement("canvas").getContext("2d"); - ctx.drawImage(img, 1, 1, 1, 1); - self.postMessage("done"); - }); - }, - onMessage: function (msg) { - if (msg === "done") - test.done(); - } - }); - test.showMenu("#image", function (popup) { - test.checkMenu([item], [], []); - test.getItemElt(popup, item).click(); - }); - }); -}; - - -// Setting an item's label before the menu is ever shown should correctly change -// its label. -exports.testSetLabelBeforeShow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ] - items[0].label = "z"; - assert.equal(items[0].label, "z"); - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Setting an item's label after the menu is shown should correctly change its -// label. -exports.testSetLabelAfterShow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ]; - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - popup.hidePopup(); - - items[0].label = "z"; - assert.equal(items[0].label, "z"); - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Setting an item's label before the menu is ever shown should correctly change -// its label. -exports.testSetLabelBeforeShowOverflow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let prefs = loader.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ] - items[0].label = "z"; - assert.equal(items[0].label, "z"); - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); -}; - - -// Setting an item's label after the menu is shown should correctly change its -// label. -exports.testSetLabelAfterShowOverflow = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let prefs = loader.loader.require("sdk/preferences/service"); - prefs.set(OVERFLOW_THRESH_PREF, 0); - - let items = [ - new loader.cm.Item({ label: "a" }), - new loader.cm.Item({ label: "b" }) - ]; - - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - popup.hidePopup(); - - items[0].label = "z"; - assert.equal(items[0].label, "z"); - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Setting the label of an item in a Menu should work. -exports.testSetLabelMenuItem = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [loader.cm.Item({ label: "a" })] - }); - menu.items[0].label = "z"; - - assert.equal(menu.items[0].label, "z"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Menu.addItem() should work. -exports.testMenuAddItem = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "item 0" }) - ] - }); - menu.addItem(loader.cm.Item({ label: "item 1" })); - menu.addItem(loader.cm.Item({ label: "item 2" })); - - assert.equal(menu.items.length, 3, - "menu should have correct number of items"); - for (let i = 0; i < 3; i++) { - assert.equal(menu.items[i].label, "item " + i, - "item label should be correct"); - assert.equal(menu.items[i].parentMenu, menu, - "item's parent menu should be correct"); - } - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Adding the same item twice to a menu should work as expected. -exports.testMenuAddItemTwice = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [] - }); - let subitem = loader.cm.Item({ label: "item 1" }) - menu.addItem(subitem); - menu.addItem(loader.cm.Item({ label: "item 0" })); - menu.addItem(subitem); - - assert.equal(menu.items.length, 2, - "menu should have correct number of items"); - for (let i = 0; i < 2; i++) { - assert.equal(menu.items[i].label, "item " + i, - "item label should be correct"); - } - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Menu.removeItem() should work. -exports.testMenuRemoveItem = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let subitem = loader.cm.Item({ label: "item 1" }); - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "item 0" }), - subitem, - loader.cm.Item({ label: "item 2" }) - ] - }); - - // Removing twice should be harmless. - menu.removeItem(subitem); - menu.removeItem(subitem); - - assert.equal(subitem.parentMenu, null, - "item's parent menu should be correct"); - - assert.equal(menu.items.length, 2, - "menu should have correct number of items"); - assert.equal(menu.items[0].label, "item 0", - "item label should be correct"); - assert.equal(menu.items[1].label, "item 2", - "item label should be correct"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Adding an item currently contained in one menu to another menu should work. -exports.testMenuItemSwap = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let subitem = loader.cm.Item({ label: "item" }); - let menu0 = loader.cm.Menu({ - label: "menu 0", - items: [subitem] - }); - let menu1 = loader.cm.Menu({ - label: "menu 1", - items: [] - }); - menu1.addItem(subitem); - - assert.equal(menu0.items.length, 0, - "menu should have correct number of items"); - - assert.equal(menu1.items.length, 1, - "menu should have correct number of items"); - assert.equal(menu1.items[0].label, "item", - "item label should be correct"); - - assert.equal(subitem.parentMenu, menu1, - "item's parent menu should be correct"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu0, menu1], [menu0], []); - test.done(); - }); -}; - - -// Destroying an item should remove it from its parent menu. -exports.testMenuItemDestroy = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let subitem = loader.cm.Item({ label: "item" }); - let menu = loader.cm.Menu({ - label: "menu", - items: [subitem] - }); - subitem.destroy(); - - assert.equal(menu.items.length, 0, - "menu should have correct number of items"); - assert.equal(subitem.parentMenu, null, - "item's parent menu should be correct"); - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [menu], []); - test.done(); - }); -}; - - -// Setting Menu.items should work. -exports.testMenuItemsSetter = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "old item 0" }), - loader.cm.Item({ label: "old item 1" }) - ] - }); - menu.items = [ - loader.cm.Item({ label: "new item 0" }), - loader.cm.Item({ label: "new item 1" }), - loader.cm.Item({ label: "new item 2" }) - ]; - - assert.equal(menu.items.length, 3, - "menu should have correct number of items"); - for (let i = 0; i < 3; i++) { - assert.equal(menu.items[i].label, "new item " + i, - "item label should be correct"); - assert.equal(menu.items[i].parentMenu, menu, - "item's parent menu should be correct"); - } - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], []); - test.done(); - }); -}; - - -// Setting Item.data should work. -exports.testItemDataSetter = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = loader.cm.Item({ label: "old item 0", data: "old" }); - item.data = "new"; - - assert.equal(item.data, "new", "item should have correct data"); - - test.showMenu(null, function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); -}; - - -// Open the test doc, load the module, make sure items appear when context- -// clicking the iframe. -exports.testAlreadyOpenIframe = function (assert, done) { - let test = new TestHelper(assert, done); - test.withTestDoc(function (window, doc) { - let loader = test.newLoader(); - let item = new loader.cm.Item({ - label: "item" - }); - test.showMenu("#iframe", function (popup) { - test.checkMenu([item], [], []); - test.done(); - }); - }); -}; - - -// Tests that a missing label throws an exception -exports.testItemNoLabel = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - try { - new loader.cm.Item({}); - assert.ok(false, "Should have seen exception"); - } - catch (e) { - assert.ok(true, "Should have seen exception"); - } - - try { - new loader.cm.Item({ label: null }); - assert.ok(false, "Should have seen exception"); - } - catch (e) { - assert.ok(true, "Should have seen exception"); - } - - try { - new loader.cm.Item({ label: undefined }); - assert.ok(false, "Should have seen exception"); - } - catch (e) { - assert.ok(true, "Should have seen exception"); - } - - try { - new loader.cm.Item({ label: "" }); - assert.ok(false, "Should have seen exception"); - } - catch (e) { - assert.ok(true, "Should have seen exception"); - } - - test.done(); -} - -/* bug 1302854 - disabled this subtest as it is intermittent -// Tests that items can have an empty data property -exports.testItemNoData = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - function checkData(data) { - assert.equal(data, undefined, "Data should be undefined"); - } - - let item1 = new loader.cm.Item({ - label: "item 1", - contentScript: 'self.on("click", (node, data) => self.postMessage(data))', - onMessage: checkData - }); - let item2 = new loader.cm.Item({ - label: "item 2", - data: null, - contentScript: 'self.on("click", (node, data) => self.postMessage(data))', - onMessage: checkData - }); - let item3 = new loader.cm.Item({ - label: "item 3", - data: undefined, - contentScript: 'self.on("click", (node, data) => self.postMessage(data))', - onMessage: checkData - }); - - assert.equal(item1.data, undefined, "Should be no defined data"); - assert.equal(item2.data, null, "Should be no defined data"); - assert.equal(item3.data, undefined, "Should be no defined data"); - - test.showMenu(null, function (popup) { - test.checkMenu([item1, item2, item3], [], []); - - let itemElt = test.getItemElt(popup, item1); - itemElt.click(); - - test.hideMenu(function() { - test.showMenu(null, function (popup) { - let itemElt = test.getItemElt(popup, item2); - itemElt.click(); - - test.hideMenu(function() { - test.showMenu(null, function (popup) { - let itemElt = test.getItemElt(popup, item3); - itemElt.click(); - - test.done(); - }); - }); - }); - }); - }); -} -*/ - - -exports.testItemNoAccessKey = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item1 = new loader.cm.Item({ label: "item 1" }); - let item2 = new loader.cm.Item({ label: "item 2", accesskey: null }); - let item3 = new loader.cm.Item({ label: "item 3", accesskey: undefined }); - - assert.equal(item1.accesskey, undefined, "Should be no defined image"); - assert.equal(item2.accesskey, null, "Should be no defined image"); - assert.equal(item3.accesskey, undefined, "Should be no defined image"); - - test.showMenu(). - then((popup) => test.checkMenu([item1, item2, item3], [], [])). - then(test.done). - catch(assert.fail); -} - - -// Test accesskey support. -exports.testItemAccessKey = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item = new loader.cm.Item({ label: "item", accesskey: "i" }); - assert.equal(item.accesskey, "i", "Should have set the image to i"); - - let menu = new loader.cm.Menu({ label: "menu", accesskey: "m", items: [ - loader.cm.Item({ label: "subitem" }) - ]}); - assert.equal(menu.accesskey, "m", "Should have set the accesskey to m"); - - test.showMenu().then((popup) => { - test.checkMenu([item, menu], [], []); - - let accesskey = "e"; - menu.accesskey = item.accesskey = accesskey; - assert.equal(item.accesskey, accesskey, "Should have set the accesskey to " + accesskey); - assert.equal(menu.accesskey, accesskey, "Should have set the accesskey to " + accesskey); - test.checkMenu([item, menu], [], []); - - item.accesskey = null; - menu.accesskey = null; - assert.equal(item.accesskey, null, "Should have set the accesskey to " + accesskey); - assert.equal(menu.accesskey, null, "Should have set the accesskey to " + accesskey); - test.checkMenu([item, menu], [], []); - }). - then(test.done). - catch(assert.fail); -}; - - -// Tests that items without an image don't attempt to show one -exports.testItemNoImage = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let item1 = new loader.cm.Item({ label: "item 1" }); - let item2 = new loader.cm.Item({ label: "item 2", image: null }); - let item3 = new loader.cm.Item({ label: "item 3", image: undefined }); - - assert.equal(item1.image, undefined, "Should be no defined image"); - assert.equal(item2.image, null, "Should be no defined image"); - assert.equal(item3.image, undefined, "Should be no defined image"); - - test.showMenu(null, function (popup) { - test.checkMenu([item1, item2, item3], [], []); - - test.done(); - }); -} - - -// Test image support. -exports.testItemImage = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let imageURL = data.url("moz_favicon.ico"); - let item = new loader.cm.Item({ label: "item", image: imageURL }); - let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [ - loader.cm.Item({ label: "subitem" }) - ]}); - assert.equal(item.image, imageURL, "Should have set the image correctly"); - assert.equal(menu.image, imageURL, "Should have set the image correctly"); - - test.showMenu(null, function (popup) { - test.checkMenu([item, menu], [], []); - - let imageURL2 = data.url("dummy.ico"); - item.image = imageURL2; - menu.image = imageURL2; - assert.equal(item.image, imageURL2, "Should have set the image correctly"); - assert.equal(menu.image, imageURL2, "Should have set the image correctly"); - test.checkMenu([item, menu], [], []); - - item.image = null; - menu.image = null; - assert.equal(item.image, null, "Should have set the image correctly"); - assert.equal(menu.image, null, "Should have set the image correctly"); - test.checkMenu([item, menu], [], []); - - test.done(); - }); -}; - -// Test image URL validation. -exports.testItemImageValidURL = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - assert.throws(function(){ - new loader.cm.Item({ - label: "item 1", - image: "foo" - }) - }, /Image URL validation failed/ - ); - - assert.throws(function(){ - new loader.cm.Item({ - label: "item 2", - image: false - }) - }, /Image URL validation failed/ - ); - - assert.throws(function(){ - new loader.cm.Item({ - label: "item 3", - image: 0 - }) - }, /Image URL validation failed/ - ); - - let imageURL = data.url("moz_favicon.ico"); - let item4 = new loader.cm.Item({ label: "item 4", image: imageURL }); - let item5 = new loader.cm.Item({ label: "item 5", image: null }); - let item6 = new loader.cm.Item({ label: "item 6", image: undefined }); - - assert.equal(item4.image, imageURL, "Should be proper image URL"); - assert.equal(item5.image, null, "Should be null image"); - assert.equal(item6.image, undefined, "Should be undefined image"); - - test.done(); -}; - - -// Menu.destroy should destroy the item tree rooted at that menu. -exports.testMenuDestroy = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let menu = loader.cm.Menu({ - label: "menu", - items: [ - loader.cm.Item({ label: "item 0" }), - loader.cm.Menu({ - label: "item 1", - items: [ - loader.cm.Item({ label: "subitem 0" }), - loader.cm.Item({ label: "subitem 1" }), - loader.cm.Item({ label: "subitem 2" }) - ] - }), - loader.cm.Item({ label: "item 2" }) - ] - }); - menu.destroy(); - - /*let numRegistryEntries = 0; - loader.globalScope.browserManager.browserWins.forEach(function (bwin) { - for (let itemID in bwin.items) - numRegistryEntries++; - }); - assert.equal(numRegistryEntries, 0, "All items should be unregistered.");*/ - - test.showMenu(null, function (popup) { - test.checkMenu([menu], [], [menu]); - test.done(); - }); -}; - -// Checks that if a menu contains sub items that are hidden then the menu is -// hidden too. Also checks that content scripts and contexts work for sub items. -exports.testSubItemContextNoMatchHideMenu = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - loader.cm.Menu({ - label: "menu 1", - items: [ - loader.cm.Item({ - label: "subitem 1", - context: loader.cm.SelectorContext(".foo") - }) - ] - }), - loader.cm.Menu({ - label: "menu 2", - items: [ - loader.cm.Item({ - label: "subitem 2", - contentScript: 'self.on("context", () => false);' - }) - ] - }), - loader.cm.Menu({ - label: "menu 3", - items: [ - loader.cm.Item({ - label: "subitem 3", - context: loader.cm.SelectorContext(".foo") - }), - loader.cm.Item({ - label: "subitem 4", - contentScript: 'self.on("context", () => false);' - }) - ] - }) - ]; - - test.showMenu(null, function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); -}; - - -// Checks that if a menu contains a combination of hidden and visible sub items -// then the menu is still visible too. -exports.testSubItemContextMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let hiddenItems = [ - loader.cm.Item({ - label: "subitem 3", - context: loader.cm.SelectorContext(".foo") - }), - loader.cm.Item({ - label: "subitem 6", - contentScript: 'self.on("context", () => false);' - }) - ]; - - let items = [ - loader.cm.Menu({ - label: "menu 1", - items: [ - loader.cm.Item({ - label: "subitem 1", - context: loader.cm.URLContext(TEST_DOC_URL) - }) - ] - }), - loader.cm.Menu({ - label: "menu 2", - items: [ - loader.cm.Item({ - label: "subitem 2", - contentScript: 'self.on("context", () => true);' - }) - ] - }), - loader.cm.Menu({ - label: "menu 3", - items: [ - hiddenItems[0], - loader.cm.Item({ - label: "subitem 4", - contentScript: 'self.on("context", () => true);' - }) - ] - }), - loader.cm.Menu({ - label: "menu 4", - items: [ - loader.cm.Item({ - label: "subitem 5", - context: loader.cm.URLContext(TEST_DOC_URL) - }), - hiddenItems[1] - ] - }), - loader.cm.Menu({ - label: "menu 5", - items: [ - loader.cm.Item({ - label: "subitem 7", - context: loader.cm.URLContext(TEST_DOC_URL) - }), - loader.cm.Item({ - label: "subitem 8", - contentScript: 'self.on("context", () => true);' - }) - ] - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, hiddenItems, []); - test.done(); - }); - }); -}; - - -// Child items should default to visible, not to PageContext -exports.testSubItemDefaultVisible = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [ - loader.cm.Menu({ - label: "menu 1", - context: loader.cm.SelectorContext("img"), - items: [ - loader.cm.Item({ - label: "subitem 1" - }), - loader.cm.Item({ - label: "subitem 2", - context: loader.cm.SelectorContext("img") - }), - loader.cm.Item({ - label: "subitem 3", - context: loader.cm.SelectorContext("a") - }) - ] - }) - ]; - - // subitem 3 will be hidden - let hiddenItems = [items[0].items[2]]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, hiddenItems, []); - test.done(); - }); - }); -}; - -// Tests that the click event on sub menuitem -// tiggers the click event for the sub menuitem and the parent menu -exports.testSubItemClick = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let state = 0; - - let items = [ - loader.cm.Menu({ - label: "menu 1", - items: [ - loader.cm.Item({ - label: "subitem 1", - data: "foobar", - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function(msg) { - assert.equal(msg.tagName, "HTML", "should have seen the right node"); - assert.equal(msg.data, "foobar", "should have seen the right data"); - assert.equal(state, 0, "should have seen the event at the right time"); - state++; - } - }) - ], - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function(msg) { - assert.equal(msg.tagName, "HTML", "should have seen the right node"); - assert.equal(msg.data, "foobar", "should have seen the right data"); - assert.equal(state, 1, "should have seen the event at the right time"); - - test.done(); - } - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - - let topMenuElt = test.getItemElt(popup, items[0]); - let topMenuPopup = topMenuElt.firstChild; - let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]); - itemElt.click(); - }); - }); -}; - -// Tests that the command event on sub menuitem -// tiggers the click event for the sub menuitem and the parent menu -exports.testSubItemCommand = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let state = 0; - - let items = [ - loader.cm.Menu({ - label: "menu 1", - items: [ - loader.cm.Item({ - label: "subitem 1", - data: "foobar", - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function(msg) { - assert.equal(msg.tagName, "HTML", "should have seen the right node"); - assert.equal(msg.data, "foobar", "should have seen the right data"); - assert.equal(state, 0, "should have seen the event at the right time"); - state++; - } - }) - ], - contentScript: 'self.on("click", function (node, data) {' + - ' self.postMessage({' + - ' tagName: node.tagName,' + - ' data: data' + - ' });' + - '});', - onMessage: function(msg) { - assert.equal(msg.tagName, "HTML", "should have seen the right node"); - assert.equal(msg.data, "foobar", "should have seen the right data"); - assert.equal(state, 1, "should have seen the event at the right time"); - state++ - - test.done(); - } - }) - ]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - - let topMenuElt = test.getItemElt(popup, items[0]); - let topMenuPopup = topMenuElt.firstChild; - let itemElt = test.getItemElt(topMenuPopup, items[0].items[0]); - - // create a command event - let evt = itemElt.ownerDocument.createEvent('Event'); - evt.initEvent('command', true, true); - itemElt.dispatchEvent(evt); - }); - }); -}; - -// Tests that opening a context menu for an outer frame when an inner frame -// has a selection doesn't activate the SelectionContext -exports.testSelectionInInnerFrameNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let state = 0; - - let items = [ - loader.cm.Item({ - label: "test item", - context: loader.cm.SelectionContext() - }) - ]; - - test.withTestDoc(function (window, doc) { - let frame = doc.getElementById("iframe"); - frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body); - - test.showMenu(null, function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); - }); -}; - -// Tests that opening a context menu for an inner frame when the inner frame -// has a selection does activate the SelectionContext -exports.testSelectionInInnerFrameMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let state = 0; - - let items = [ - loader.cm.Item({ - label: "test item", - context: loader.cm.SelectionContext() - }) - ]; - - test.withTestDoc(function (window, doc) { - let frame = doc.getElementById("iframe"); - frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body); - - test.showMenu(["#iframe", "#text"], function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Tests that opening a context menu for an inner frame when the outer frame -// has a selection doesn't activate the SelectionContext -exports.testSelectionInOuterFrameNoMatch = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let state = 0; - - let items = [ - loader.cm.Item({ - label: "test item", - context: loader.cm.SelectionContext() - }) - ]; - - test.withTestDoc(function (window, doc) { - let frame = doc.getElementById("iframe"); - window.getSelection().selectAllChildren(doc.body); - - test.showMenu(["#iframe", "#text"], function (popup) { - test.checkMenu(items, items, []); - test.done(); - }); - }); -}; - - -// Test that the return value of the predicate function determines if -// item is shown -exports.testPredicateContextControl = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let itemTrue = loader.cm.Item({ - label: "visible", - context: loader.cm.PredicateContext(function () { return true; }) - }); - - let itemFalse = loader.cm.Item({ - label: "hidden", - context: loader.cm.PredicateContext(function () { return false; }) - }); - - test.showMenu(null, function (popup) { - test.checkMenu([itemTrue, itemFalse], [itemFalse], []); - test.done(); - }); -}; - -// Test that the data object has the correct document type -exports.testPredicateContextDocumentType = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.equal(data.documentType, 'text/html'); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has the correct document URL -exports.testPredicateContextDocumentURL = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.equal(data.documentURL, TEST_DOC_URL); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Test that the data object has the correct element name -exports.testPredicateContextTargetName = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.targetName, "input"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#button", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Test that the data object has the correct ID -exports.testPredicateContextTargetIDSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.targetID, "button"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#button", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has the correct ID -exports.testPredicateContextTargetIDNotSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.targetID, null); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu(".predicate-test-a", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object is showing editable correctly for regular text inputs -exports.testPredicateContextTextBoxIsEditable = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, true); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#textbox", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object is showing editable correctly for readonly text inputs -exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, false); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#readonly-textbox", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object is showing editable correctly for disabled text inputs -exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, false); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#disabled-textbox", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object is showing editable correctly for text areas -exports.testPredicateContextTextAreaIsEditable = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, true); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#textfield", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that non-text inputs are not considered editable -exports.testPredicateContextButtonIsNotEditable = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, false); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#button", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Test that the data object is showing editable correctly -exports.testPredicateContextNonInputIsNotEditable = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, false); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Test that the data object is showing editable correctly for HTML contenteditable elements -exports.testPredicateContextEditableElement = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.isEditable, true); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#editable", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Test that the data object does not have a selection when there is none -exports.testPredicateContextNoSelectionInPage = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.selectionText, null); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object includes the selected page text -exports.testPredicateContextSelectionInPage = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - // since we might get whitespace - assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1, - 'Expected "Some text.", got "' + data.selectionText + '"'); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - window.getSelection().selectAllChildren(doc.getElementById("text")); - test.showMenu(null, function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object includes the selected input text -exports.testPredicateContextSelectionInTextBox = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - // since we might get whitespace - assert.strictEqual(data.selectionText, "t v"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - let textbox = doc.getElementById("textbox"); - test.selectRange("#textbox", 3, 6); - test.showMenu("#textbox", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has the correct src for an image -exports.testPredicateContextTargetSrcSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - let image; - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.srcURL, image.src); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - image = doc.getElementById("image"); - test.showMenu("#image", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has no src for a link -exports.testPredicateContextTargetSrcNotSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.srcURL, null); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#link", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - - -// Test that the data object has the correct link set -exports.testPredicateContextTargetLinkSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - let image; - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu(".predicate-test-a", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has no link for an image -exports.testPredicateContextTargetLinkNotSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.linkURL, null); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has the correct link for a nested image -exports.testPredicateContextTargetLinkSetNestedImage = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.linkURL, TEST_DOC_URL + "#nested-image"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#predicate-test-nested-image", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has the correct link for a complex nested structure -exports.testPredicateContextTargetLinkSetNestedStructure = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.linkURL, TEST_DOC_URL + "#nested-structure"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#predicate-test-nested-structure", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has the value for an input textbox -exports.testPredicateContextTargetValueSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - let image; - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.value, "test value"); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#textbox", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -// Test that the data object has no value for an image -exports.testPredicateContextTargetValueNotSet = function (assert, done) { - let test = new TestHelper(assert, done); - let loader = test.newLoader(); - - let items = [loader.cm.Item({ - label: "item", - context: loader.cm.PredicateContext(function (data) { - assert.strictEqual(data.value, null); - return true; - }) - })]; - - test.withTestDoc(function (window, doc) { - test.showMenu("#image", function (popup) { - test.checkMenu(items, [], []); - test.done(); - }); - }); -}; - -if (isTravisCI) { - module.exports = { - "test skip on jpm": (assert) => assert.pass("skipping this file with jpm") - }; -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-context-menu@2.js b/addon-sdk/source/test/test-context-menu@2.js deleted file mode 100644 index 78c496220a..0000000000 --- a/addon-sdk/source/test/test-context-menu@2.js +++ /dev/null @@ -1,1350 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cc, Ci } = require("chrome"); -const {openWindow, closeWindow, openTab, closeTab, - openContextMenu, closeContextMenu, select, - readNode, captureContextMenu, withTab, withItems } = require("./context-menu/util"); -const {when} = require("sdk/dom/events"); -const {Item, Menu, Separator, Contexts, Readers } = require("sdk/context-menu@2"); -const prefs = require("sdk/preferences/service"); -const { before, after } = require('sdk/test/utils'); - -const testPageURI = require.resolve("./test-context-menu").replace(".js", ".html"); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - -const data = input => - `data:text/html;charset=utf-8,${encodeURIComponent(input)}` - -const menugroup = (...children) => Object.assign({ - tagName: "menugroup", - namespaceURI: XUL_NS, - style: "-moz-box-orient: vertical;", - className: "sdk-context-menu-extension" -}, children.length ? {children} : {}); - -const menuseparator = () => ({ - tagName: "menuseparator", - namespaceURI: XUL_NS, - className: "sdk-context-menu-separator" -}) - -const menuitem = properties => Object.assign({ - tagName: "menuitem", - namespaceURI: XUL_NS, - className: "sdk-context-menu-item menuitem-iconic" -}, properties); - -const menu = (properties, ...children) => Object.assign({ - tagName: "menu", - namespaceURI: XUL_NS, - className: "sdk-context-menu menu-iconic" -}, properties, { - children: [Object.assign({tagName: "menupopup", namespaceURI: XUL_NS}, - children.length ? {children} : {})] -}); - -// Destroying items that were previously created should cause them to be absent -// from the menu. -exports["test create / destroy menu item"] = withTab(function*(assert) { - const item = new Item({ - label: "test-1" - }); - - const before = yield captureContextMenu("h1"); - - assert.deepEqual(before, - menugroup(menuseparator(), - menuitem({label: "test-1"})), - "context menu contains separator & added item"); - - item.destroy(); - - const after = yield captureContextMenu("h1"); - assert.deepEqual(after, menugroup(), - "all items were removed children are present"); -}, data`

hello

`); - - -/* Bug 1115419 - Disable occasionally failing test until we - figure out why it fails. -// Items created should be present on all browser windows. -exports["test menu item in new window"] = function*(assert) { - const isMenuPopulated = function*(tab) { - const state = yield captureContextMenu("h1", tab); - assert.deepEqual(state, - menugroup(menuseparator(), - menuitem({label: "multi-window"})), - "created menu item is present") - }; - - const isMenuEmpty = function*(tab) { - const state = yield captureContextMenu("h1", tab); - assert.deepEqual(state, menugroup(), "no sdk items present"); - }; - - const item = new Item({ label: "multi-window" }); - - const tab1 = yield openTab(`data:text/html,

hello

`); - yield* isMenuPopulated(tab1); - - const window2 = yield openWindow(); - assert.pass("window is ready"); - - const tab2 = yield openTab(`data:text/html,

hello window-2

`, window2); - assert.pass("tab is ready"); - - yield* isMenuPopulated(tab2); - - item.destroy(); - - yield* isMenuEmpty(tab2); - yield closeWindow(window2); - - yield* isMenuEmpty(tab1); - - yield closeTab(tab1); -}; -*/ - - -// Multilpe items can be created and destroyed at different points -// in time & they should not affect each other. -exports["test multiple items"] = withTab(function*(assert) { - const item1 = new Item({ label: "one" }); - - const step1 = yield captureContextMenu("h1"); - assert.deepEqual(step1, - menugroup(menuseparator(), - menuitem({label: "one"})), - "item1 is present"); - - const item2 = new Item({ label: "two" }); - const step2 = yield captureContextMenu("h1"); - - assert.deepEqual(step2, - menugroup(menuseparator(), - menuitem({label: "one"}), - menuitem({label: "two"})), - "both items where present"); - - item1.destroy(); - - const step3 = yield captureContextMenu("h1"); - assert.deepEqual(step3, - menugroup(menuseparator(), - menuitem({label: "two"})), - "one items left"); - - item2.destroy(); - - const step4 = yield captureContextMenu("h1"); - assert.deepEqual(step4, menugroup(), "no items left"); -}, data`

Multiple Items

`); - -// Destroying an item twice should not cause an error. -exports["test destroy twice"] = withTab(function*(assert) { - const item = new Item({ label: "destroy" }); - const withItem = yield captureContextMenu("h2"); - assert.deepEqual(withItem, - menugroup(menuseparator(), - menuitem({label:"destroy"})), - "Item is added"); - - item.destroy(); - - const withoutItem = yield captureContextMenu("h2"); - assert.deepEqual(withoutItem, menugroup(), "Item was removed"); - - item.destroy(); - assert.pass("Destroying an item twice should not cause an error."); -}, "data:text/html,

item destroy

"); - -// CSS selector contexts should cause their items to be absent from the menu -// when the menu is not invoked on nodes that match selectors. -exports["test selector context"] = withTab(function*(assert) { - const item = new Item({ - context: [new Contexts.Selector("body b")], - label: "bold" - }); - - const match = yield captureContextMenu("b"); - assert.deepEqual(match, - menugroup(menuseparator(), - menuitem({label: "bold"})), - "item mathched context"); - - const noMatch = yield captureContextMenu("i"); - assert.deepEqual(noMatch, menugroup(), "item did not match context"); - - item.destroy(); - - const cleared = yield captureContextMenu("b"); - assert.deepEqual(cleared, menugroup(), "item was removed"); -}, data`onetwo`); - -// CSS selector contexts should cause their items to be absent in the menu -// when the menu is invoked even on nodes that have ancestors that match the -// selectors. -exports["test parent selector don't match children"] = withTab(function*(assert) { - const item = new Item({ - label: "parent match", - context: [new Contexts.Selector("a[href]")] - }); - - const match = yield captureContextMenu("a"); - assert.deepEqual(match, - menugroup(menuseparator(), - menuitem({label: "parent match"})), - "item mathched context"); - - const noMatch = yield captureContextMenu("strong"); - assert.deepEqual(noMatch, menugroup(), "item did not mathch context"); - - item.destroy(); - - const destroyed = yield captureContextMenu("a"); - assert.deepEqual(destroyed, menugroup(), "no items left"); -}, data`This text must be long & bold!`); - -// Page contexts should cause their items to be present in the menu when the -// menu is not invoked on an active element. -exports["test page context match"] = withTab(function*(assert) { - const isPageMatch = (tree, description="page context matched") => - assert.deepEqual(tree, - menugroup(menuseparator(), - menuitem({label: "page match"}), - menuitem({label: "any match"})), - description); - - const isntPageMatch = (tree, description="page context did not match") => - assert.deepEqual(tree, - menugroup(menuseparator(), - menuitem({label: "any match"})), - description); - - yield* withItems({ - pageMatch: new Item({ - label: "page match", - context: [new Contexts.Page()], - }), - anyMatch: new Item({ - label: "any match" - }) - }, function*({pageMatch, anyMatch}) { - for (let tagName of [null, "p", "h3"]) { - isPageMatch((yield captureContextMenu(tagName)), - `Page context matches ${tagName} passive element`); - } - - for (let tagName of ["button", "canvas", "img", "input", "textarea", - "select", "menu", "embed" ,"object", "video", "audio", - "applet"]) - { - isntPageMatch((yield captureContextMenu(tagName)), - `Page context does not match <${tagName}/> active element`); - } - - for (let selector of ["span"]) - { - isntPageMatch((yield captureContextMenu(selector)), - `Page context does not match decedents of active element`); - } - }); -}, -data` - - - -

paragraph

- -

hi

-
-
-
-
-
-
-
-
-
-
-
-
-`); - -// Page context does not match if if there is a selection. -exports["test page context doesn't match on selection"] = withTab(function*(assert) { - const isPageMatch = (tree, description="page context matched") => - assert.deepEqual(tree, - menugroup(menuseparator(), - menuitem({label: "page match"}), - menuitem({label: "any match"})), - description); - - const isntPageMatch = (tree, description="page context did not match") => - assert.deepEqual(tree, - menugroup(menuseparator(), - menuitem({label: "any match"})), - description); - - yield* withItems({ - pageMatch: new Item({ - label: "page match", - context: [new Contexts.Page()], - }), - anyMatch: new Item({ - label: "any match" - }) - }, function*({pageMatch, anyMatch}) { - yield select("b"); - isntPageMatch((yield captureContextMenu("i")), - "page context does not match if there is a selection"); - - yield select(null); - isPageMatch((yield captureContextMenu("i")), - "page context match if there is no selection"); - }); -}, data`onetwo`); - -exports["test selection context"] = withTab(function*(assert) { - yield* withItems({ - item: new Item({ - label: "selection", - context: [new Contexts.Selection()] - }) - }, function*({item}) { - assert.deepEqual((yield captureContextMenu()), - menugroup(), - "item does not match if there is no selection"); - - yield select("b"); - - assert.deepEqual((yield captureContextMenu()), - menugroup(menuseparator(), - menuitem({label: "selection"})), - "item matches if there is a selection"); - }); -}, data`onetwo`); - -exports["test selection context in textarea"] = withTab(function*(assert) { - yield* withItems({ - item: new Item({ - label: "selection", - context: [new Contexts.Selection()] - }) - }, function*({item}) { - assert.deepEqual((yield captureContextMenu()), - menugroup(), - "does not match if there's no selection"); - - yield select({target:"textarea", start:0, end:5}); - - assert.deepEqual((yield captureContextMenu("b")), - menugroup(), - "does not match if target isn't input with selection"); - - assert.deepEqual((yield captureContextMenu("textarea")), - menugroup(menuseparator(), - menuitem({label: "selection"})), - "matches if target is input with selected text"); - - yield select({target: "textarea", start: 0, end: 0}); - - assert.deepEqual((yield captureContextMenu("textarea")), - menugroup(), - "does not match when selection is cleared"); - }); -}, data`!!`); - -exports["test url contexts"] = withTab(function*(assert) { - yield* withItems({ - a: new Item({ - label: "a", - context: [new Contexts.URL(testPageURI)] - }), - b: new Item({ - label: "b", - context: [new Contexts.URL("*.bogus.com")] - }), - c: new Item({ - label: "c", - context: [new Contexts.URL("*.bogus.com"), - new Contexts.URL(testPageURI)] - }), - d: new Item({ - label: "d", - context: [new Contexts.URL(/.*\.html/)] - }), - e: new Item({ - label: "e", - context: [new Contexts.URL("http://*"), - new Contexts.URL(testPageURI)] - }), - f: new Item({ - label: "f", - context: [new Contexts.URL("http://*").required, - new Contexts.URL(testPageURI)] - }), - }, function*(_) { - assert.deepEqual((yield captureContextMenu()), - menugroup(menuseparator(), - menuitem({label: "a"}), - menuitem({label: "c"}), - menuitem({label: "d"}), - menuitem({label: "e"})), - "shows only matching items"); - }); -}, testPageURI); - -exports["test iframe context"] = withTab(function*(assert) { - yield* withItems({ - page: new Item({ - label: "page", - context: [new Contexts.Page()] - }), - iframe: new Item({ - label: "iframe", - context: [new Contexts.Frame()] - }), - h2: new Item({ - label: "element", - context: [new Contexts.Selector("*")] - }) - }, function(_) { - assert.deepEqual((yield captureContextMenu("iframe")), - menugroup(menuseparator(), - menuitem({label: "page"}), - menuitem({label: "iframe"}), - menuitem({label: "element"})), - "matching items are present"); - - assert.deepEqual((yield captureContextMenu("h1")), - menugroup(menuseparator(), - menuitem({label: "page"}), - menuitem({label: "element"})), - "only matching items are present"); - - }); - -}, -data`

hello

-

-

-

-

-

-

-

-

- -`; -exports["test predicate context"] = withTab(function*(assert) { - const test = function*(selector, expect) { - var isMatch = false; - test.return = (target) => { - return isMatch = expect(target); - } - assert.deepEqual((yield captureContextMenu(selector)), - isMatch ? menugroup(menuseparator(), - menuitem({label:"predicate"})) : - menugroup(), - isMatch ? `predicate item matches ${selector}` : - `predicate item doesn't match ${selector}`); - }; - test.predicate = target => test.return(target); - - yield* withItems({ - item: new Item({ - label: "predicate", - read: { - mediaType: new Readers.MediaType(), - link: new Readers.LinkURL(), - isPage: new Readers.isPage(), - isFrame: new Readers.isFrame(), - isEditable: new Readers.isEditable(), - tagName: new Readers.Query("tagName"), - appCodeName: new Readers.Query("ownerDocument.defaultView.navigator.appCodeName"), - width: new Readers.Attribute("width"), - src: new Readers.SrcURL(), - url: new Readers.PageURL(), - selection: new Readers.Selection() - }, - context: [Contexts.Predicate(test.predicate)] - }) - }, function*(items) { - yield* test("strong p", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: true, - isFrame: false, - isEditable: false, - tagName: "P", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "pagraph read test"); - return true; - }); - - yield* test("a span", target => { - assert.deepEqual(target, { - mediaType: null, - link: "./link", - isPage: false, - isFrame: false, - isEditable: false, - tagName: "SPAN", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "video tag test"); - return false; - }); - - yield* test("h3", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: true, - isFrame: false, - isEditable: false, - tagName: "H3", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "video tag test"); - return false; - }); - - yield select("h3"); - - yield* test("a span", target => { - assert.deepEqual(target, { - mediaType: null, - link: "./link", - isPage: false, - isFrame: false, - isEditable: false, - tagName: "SPAN", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: "hi", - }, "test selection with link"); - return true; - }); - - yield select(null); - - - yield* test("button", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "BUTTON", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test button"); - return true; - }); - - yield* test("canvas", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "CANVAS", - appCodeName: "Mozilla", - width: "50", - src: null, - url: predicateTestURL, - selection: null, - }, "test button"); - return true; - }); - - yield* test("img", target => { - assert.deepEqual(target, { - mediaType: "image", - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "IMG", - appCodeName: "Mozilla", - width: "50", - src: "./no.png", - url: predicateTestURL, - selection: null, - }, "test image"); - return true; - }); - - yield* test("code", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: true, - tagName: "CODE", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test content editable"); - return false; - }); - - yield* test("input[readonly=true]", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test readonly input"); - return false; - }); - - yield* test("input[disabled=true]", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test disabled input"); - return false; - }); - - yield select({target: "input#text", start: 0, end: 5 }); - - yield* test("input#text", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: true, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: "test ", - }, "test editable input"); - return false; - }); - - yield select({target: "input#text", start:0, end: 0}); - - yield* test("input[type=submit]", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test submit input"); - return false; - }); - - yield* test("input[type=radio]", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test radio input"); - return false; - }); - - yield* test("input[type=checkbox]", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test checkbox input"); - return false; - }); - - yield* test("input[type=foo]", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: true, - tagName: "INPUT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test unrecognized input"); - return false; - }); - - yield* test("textarea", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: true, - tagName: "TEXTAREA", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test textarea"); - return false; - }); - - - yield* test("iframe", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: true, - isFrame: true, - isEditable: false, - tagName: "BODY", - appCodeName: "Mozilla", - width: null, - src: null, - url: `data:text/html,Bye`, - selection: null, - }, "test iframe"); - return true; - }); - - yield* test("select", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "SELECT", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test select"); - return true; - }); - - yield* test("menu", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "MENU", - appCodeName: "Mozilla", - width: null, - src: null, - url: predicateTestURL, - selection: null, - }, "test menu"); - return false; - }); - - yield* test("video", target => { - assert.deepEqual(target, { - mediaType: "video", - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "VIDEO", - appCodeName: "Mozilla", - width: "50", - src: null, - url: predicateTestURL, - selection: null, - }, "test video"); - return true; - }); - - yield* test("audio", target => { - assert.deepEqual(target, { - mediaType: "audio", - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "AUDIO", - appCodeName: "Mozilla", - width: "10", - src: null, - url: predicateTestURL, - selection: null, - }, "test audio"); - return true; - }); - - yield* test("object", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "OBJECT", - appCodeName: "Mozilla", - width: "10", - src: null, - url: predicateTestURL, - selection: null, - }, "test object"); - return true; - }); - - yield* test("embed", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "EMBED", - appCodeName: "Mozilla", - width: "10", - src: null, - url: predicateTestURL, - selection: null, - }, "test embed"); - return true; - }); - - yield* test("applet", target => { - assert.deepEqual(target, { - mediaType: null, - link: null, - isPage: false, - isFrame: false, - isEditable: false, - tagName: "APPLET", - appCodeName: "Mozilla", - width: "30", - src: null, - url: predicateTestURL, - selection: null, - }, "test applet"); - return false; - }); - - }); -}, predicateTestURL); - -exports["test extractor reader"] = withTab(function*(assert) { - const test = function*(selector, expect) { - var isMatch = false; - test.return = (target) => { - return isMatch = expect(target); - } - assert.deepEqual((yield captureContextMenu(selector)), - isMatch ? menugroup(menuseparator(), - menuitem({label:"extractor"})) : - menugroup(), - isMatch ? `predicate item matches ${selector}` : - `predicate item doesn't match ${selector}`); - }; - test.predicate = target => test.return(target); - - - yield* withItems({ - item: new Item({ - label: "extractor", - context: [Contexts.Predicate(test.predicate)], - read: { - tagName: Readers.Query("tagName"), - selector: Readers.Extractor(target => { - let node = target; - let path = []; - while (node) { - if (node.id) { - path.unshift(`#${node.id}`); - node = null; - } - else { - path.unshift(node.localName); - node = node.parentElement; - } - } - return path.join(" > "); - }) - } - }) - }, function*(_) { - yield* test("footer", target => { - assert.deepEqual(target, { - tagName: "FOOTER", - selector: "html > body > nav > footer" - }, "test footer"); - return false; - }); - - - }); -}, data` - - -
-
First title
-
-

First paragraph

-

Second paragraph

-
-
-
-
Second title
-
-

First paragraph

-

Second paragraph

-
-
- -`); - -exports["test items overflow"] = withTab(function*(assert) { - yield* withItems({ - i1: new Item({label: "item-1"}), - i2: new Item({label: "item-2"}), - i3: new Item({label: "item-3"}), - i4: new Item({label: "item-4"}), - i5: new Item({label: "item-5"}), - i6: new Item({label: "item-6"}), - i7: new Item({label: "item-7"}), - i8: new Item({label: "item-8"}), - i9: new Item({label: "item-9"}), - i10: new Item({label: "item-10"}), - }, function*(_) { - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menu({ - className: "sdk-context-menu-overflow-menu", - label: "Add-ons", - accesskey: "A", - }, menuitem({label: "item-1"}), - menuitem({label: "item-2"}), - menuitem({label: "item-3"}), - menuitem({label: "item-4"}), - menuitem({label: "item-5"}), - menuitem({label: "item-6"}), - menuitem({label: "item-7"}), - menuitem({label: "item-8"}), - menuitem({label: "item-9"}), - menuitem({label: "item-10"}))), - "context menu has an overflow"); - }); - - prefs.set("extensions.addon-sdk.context-menu.overflowThreshold", 3); - - yield* withItems({ - i1: new Item({label: "item-1"}), - i2: new Item({label: "item-2"}), - }, function*(_) { - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menuitem({label: "item-1"}), - menuitem({label: "item-2"})), - "two items do not overflow"); - }); - - yield* withItems({ - one: new Item({label: "one"}), - two: new Item({label: "two"}), - three: new Item({label: "three"}) - }, function*(_) { - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menu({className: "sdk-context-menu-overflow-menu", - label: "Add-ons", - accesskey: "A"}, - menuitem({label: "one"}), - menuitem({label: "two"}), - menuitem({label: "three"}))), - "three items overflow"); - }); - - prefs.reset("extensions.addon-sdk.context-menu.overflowThreshold"); - - yield* withItems({ - one: new Item({label: "one"}), - two: new Item({label: "two"}), - three: new Item({label: "three"}) - }, function*(_) { - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menuitem({label: "one"}), - menuitem({label: "two"}), - menuitem({label: "three"})), - "three items no longer overflow"); - }); -}, data`

Hello

`); - - -exports["test context menus"] = withTab(function*(assert) { - const one = new Item({ - label: "one", - context: [Contexts.Selector("p")], - read: {tagName: Readers.Query("tagName")} - }); - - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menuitem({label: "one"})), - "item is present"); - - const two = new Item({ - label: "two", - read: {tagName: Readers.Query("tagName")} - }); - - - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menuitem({label: "one"}), - menuitem({label: "two"})), - "both items are present"); - - const groupLevel1 = new Menu({label: "Level 1"}, - [one]); - - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menuitem({label: "two"}), - menu({label: "Level 1"}, - menuitem({label: "one"}))), - "first item moved to group"); - - assert.deepEqual((yield captureContextMenu("h1")), - menugroup(menuseparator(), - menuitem({label: "two"})), - "menu is hidden since only item does not match"); - - - const groupLevel2 = new Menu({label: "Level 2" }, [groupLevel1]); - - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menuitem({label: "two"}), - menu({label: "Level 2"}, - menu({label: "Level 1"}, - menuitem({label: "one"})))), - "top level menu moved to submenu"); - - assert.deepEqual((yield captureContextMenu("h1")), - menugroup(menuseparator(), - menuitem({label: "two"})), - "menu is hidden since only item does not match"); - - - const contextGroup = new Menu({ - label: "H1 Group", - context: [Contexts.Selector("h1")] - }, [ - two, - new Separator(), - new Item({ label: "three" }) - ]); - - - assert.deepEqual((yield captureContextMenu("p")), - menugroup(menuseparator(), - menu({label: "Level 2"}, - menu({label: "Level 1"}, - menuitem({label: "one"})))), - "nested menu is rendered"); - - assert.deepEqual((yield captureContextMenu("h1")), - menugroup(menuseparator(), - menu({label: "H1 Group"}, - menuitem({label: "two"}), - menuseparator(), - menuitem({label: "three"}))), - "new contextual menu rendered"); - - yield* withItems({one, two, - groupLevel1, groupLevel2, contextGroup}, function*() { - - }); - - assert.deepEqual((yield captureContextMenu("p")), - menugroup(), - "everyhing matching p was desposed"); - - assert.deepEqual((yield captureContextMenu("h1")), - menugroup(), - "everyhing matching h1 was desposed"); - -}, data`

Title

Content

`); - -exports["test unloading"] = withTab(function*(assert) { - const { Loader } = require("sdk/test/loader"); - const loader = Loader(module); - - const {Item, Menu, Separator, Contexts, Readers } = loader.require("sdk/context-menu@2"); - - const item = new Item({label: "item"}); - const group = new Menu({label: "menu"}, - [new Separator(), - new Item({label: "sub-item"})]); - assert.deepEqual((yield captureContextMenu()), - menugroup(menuseparator(), - menuitem({label: "item"}), - menu({label: "menu"}, - menuseparator(), - menuitem({label: "sub-item"}))), - "all items rendered"); - - - loader.unload(); - - assert.deepEqual((yield captureContextMenu()), - menugroup(), - "all items disposed"); -}, data``); - -if (require("@loader/options").isNative) { - module.exports = { - "test skip on jpm": (assert) => assert.pass("skipping this file with jpm") - }; -} - -before(exports, (name, assert) => { - // Make sure Java doesn't activate - prefs.set("plugin.state.java", 0); -}); - -after(exports, (name, assert) => { - prefs.reset("plugin.state.java"); -}); - -require("sdk/test").run(module.exports); diff --git a/addon-sdk/source/test/test-cuddlefish.js b/addon-sdk/source/test/test-cuddlefish.js deleted file mode 100644 index c92eaa6249..0000000000 --- a/addon-sdk/source/test/test-cuddlefish.js +++ /dev/null @@ -1,78 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { Cc, Ci, Cu, CC, Cr, Cm, ChromeWorker, components } = require("chrome"); - -const packaging = require("@loader/options"); -const app = require('sdk/system/xul-app'); -const { resolve } = require; - -const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. - getService(Ci.mozIJSSubScriptLoader); -const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); - -function loadSandbox(uri) { - let proto = { - sandboxPrototype: { - loadSandbox: loadSandbox, - ChromeWorker: ChromeWorker - } - }; - let sandbox = Cu.Sandbox(systemPrincipal, proto); - // Create a fake commonjs environnement just to enable loading loader.js - // correctly - sandbox.exports = {}; - sandbox.module = { uri: uri, exports: sandbox.exports }; - sandbox.require = function (id) { - if (id !== "chrome") - throw new Error("Bootstrap sandbox `require` method isn't implemented."); - - return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, - CC: CC, components: components, - ChromeWorker: ChromeWorker }); - }; - scriptLoader.loadSubScript(uri, sandbox, 'UTF-8'); - return sandbox; -} - -exports['test loader'] = function(assert) { - let { Loader, Require, unload, override } = loadSandbox(resolve('sdk/loader/cuddlefish.js')).exports; - var prints = []; - function print(message) { - prints.push(message); - } - - let loader = Loader(override(packaging, { - globals: { - print: print, - foo: 1 - } - })); - let require = Require(loader, module); - - var fixture = require('./loader/fixture'); - - assert.equal(fixture.foo, 1, 'custom globals must work.'); - assert.equal(fixture.bar, 2, 'exports are set'); - - assert.equal(prints[0], 'testing', 'global print must be injected.'); - - var unloadsCalled = ''; - - require("sdk/system/unload").when(function(reason) { - assert.equal(reason, 'test', 'unload reason is passed'); - unloadsCalled += 'a'; - }); - require('sdk/system/unload.js').when(function() { - unloadsCalled += 'b'; - }); - - unload(loader, 'test'); - - assert.equal(unloadsCalled, 'ba', - 'loader.unload() must call listeners in LIFO order.'); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-deprecate.js b/addon-sdk/source/test/test-deprecate.js deleted file mode 100644 index c1bd443c61..0000000000 --- a/addon-sdk/source/test/test-deprecate.js +++ /dev/null @@ -1,160 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const deprecate = require("sdk/util/deprecate"); -const { LoaderWithHookedConsole } = require("sdk/test/loader"); -const { get, set } = require("sdk/preferences/service"); -const PREFERENCE = "devtools.errorconsole.deprecation_warnings"; - -exports["test Deprecate Usage"] = function testDeprecateUsage(assert) { - set(PREFERENCE, true); - let { loader, messages } = LoaderWithHookedConsole(module); - let deprecate = loader.require("sdk/util/deprecate"); - - function functionIsDeprecated() { - deprecate.deprecateUsage("foo"); - } - - functionIsDeprecated(); - - assert.equal(messages.length, 1, "only one error is dispatched"); - assert.equal(messages[0].type, "error", "the console message is an error"); - - let msg = messages[0].msg; - - assert.ok(msg.indexOf("foo") !== -1, - "message contains the given message"); - assert.ok(msg.indexOf("functionIsDeprecated") !== -1, - "message contains name of the caller function"); - assert.ok(msg.indexOf(module.uri) !== -1, - "message contains URI of the caller module"); - - loader.unload(); -} - -exports["test Deprecate Function"] = function testDeprecateFunction(assert) { - set(PREFERENCE, true); - let { loader, messages } = LoaderWithHookedConsole(module); - let deprecate = loader.require("sdk/util/deprecate"); - - let self = {}; - let arg1 = "foo"; - let arg2 = {}; - - function originalFunction(a1, a2) { - assert.equal(this, self); - assert.equal(a1, arg1); - assert.equal(a2, arg2); - }; - - let deprecateFunction = deprecate.deprecateFunction(originalFunction, - "bar"); - - deprecateFunction.call(self, arg1, arg2); - - assert.equal(messages.length, 1, "only one error is dispatched"); - assert.equal(messages[0].type, "error", "the console message is an error"); - - let msg = messages[0].msg; - assert.ok(msg.indexOf("bar") !== -1, "message contains the given message"); - assert.ok(msg.indexOf("testDeprecateFunction") !== -1, - "message contains name of the caller function"); - assert.ok(msg.indexOf(module.uri) !== -1, - "message contains URI of the caller module"); - - loader.unload(); -} - -exports.testDeprecateEvent = function(assert, done) { - set(PREFERENCE, true); - let { loader, messages } = LoaderWithHookedConsole(module); - let deprecate = loader.require("sdk/util/deprecate"); - - let { on, emit } = loader.require('sdk/event/core'); - let testObj = {}; - testObj.on = deprecate.deprecateEvent(on.bind(null, testObj), 'BAD', ['fire']); - - testObj.on('fire', function() { - testObj.on('water', function() { - assert.equal(messages.length, 1, "only one error is dispatched"); - loader.unload(); - done(); - }) - assert.equal(messages.length, 1, "only one error is dispatched"); - emit(testObj, 'water'); - }); - - assert.equal(messages.length, 1, "only one error is dispatched"); - assert.equal(messages[0].type, "error", "the console message is an error"); - let msg = messages[0].msg; - assert.ok(msg.indexOf("BAD") !== -1, "message contains the given message"); - assert.ok(msg.indexOf("deprecateEvent") !== -1, - "message contains name of the caller function"); - assert.ok(msg.indexOf(module.uri) !== -1, - "message contains URI of the caller module"); - - emit(testObj, 'fire'); -} - -exports.testDeprecateSettingToggle = function (assert) { - let { loader, messages } = LoaderWithHookedConsole(module); - let deprecate = loader.require("sdk/util/deprecate"); - - function fn () { deprecate.deprecateUsage("foo"); } - - set(PREFERENCE, false); - fn(); - assert.equal(messages.length, 0, 'no deprecation warnings'); - - set(PREFERENCE, true); - fn(); - assert.equal(messages.length, 1, 'deprecation warnings when toggled'); - - set(PREFERENCE, false); - fn(); - assert.equal(messages.length, 1, 'no new deprecation warnings'); -}; - -exports.testDeprecateSetting = function (assert, done) { - // Set devtools.errorconsole.deprecation_warnings to false - set(PREFERENCE, false); - - let { loader, messages } = LoaderWithHookedConsole(module); - let deprecate = loader.require("sdk/util/deprecate"); - - // deprecateUsage test - function functionIsDeprecated() { - deprecate.deprecateUsage("foo"); - } - functionIsDeprecated(); - - assert.equal(messages.length, 0, - "no errors dispatched on deprecateUsage"); - - // deprecateFunction test - function originalFunction() {}; - - let deprecateFunction = deprecate.deprecateFunction(originalFunction, - "bar"); - deprecateFunction(); - - assert.equal(messages.length, 0, - "no errors dispatched on deprecateFunction"); - - // deprecateEvent - let { on, emit } = loader.require('sdk/event/core'); - let testObj = {}; - testObj.on = deprecate.deprecateEvent(on.bind(null, testObj), 'BAD', ['fire']); - - testObj.on('fire', () => { - assert.equal(messages.length, 0, - "no errors dispatched on deprecateEvent"); - done(); - }); - - emit(testObj, 'fire'); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-dev-panel.js b/addon-sdk/source/test/test-dev-panel.js deleted file mode 100644 index fb786b0430..0000000000 --- a/addon-sdk/source/test/test-dev-panel.js +++ /dev/null @@ -1,426 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "engines": { - "Firefox": "*" - } -}; - -const { Tool } = require("dev/toolbox"); -const { Panel } = require("dev/panel"); -const { Class } = require("sdk/core/heritage"); -const { openToolbox, closeToolbox, getCurrentPanel } = require("dev/utils"); -const { MessageChannel } = require("sdk/messaging"); -const { when } = require("sdk/dom/events-shimmed"); -const { viewFor } = require("sdk/view/core"); -const { createView } = require("dev/panel/view"); - -const iconURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADqmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPnhtcC5kaWQ6M0ExNEY4NjZBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpEZXJpdmVkRnJvbSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6M0ExNEY4NjNBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6M0ExNEY4NjRBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3N0UmVmOmRvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6M0ExNEY4NjVBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2g8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+ChetDDYAAACaSURBVEgNY2AYboAR3UNnzpx5DxQTQBP/YGJiIogmBuYSq54Ji2Z0S0BKsInBtGKTwxDDZhHMAKrSdLOIqq7GZxjj+fPnBf7+/bseqMgBn0IK5A4wMzMHMtHYEpD7HEB2gOLIAcSjMXCgW2IYtYjsqBwNutGgg4fAaGKABwWpDLoG3QFSXUeG+gNMoEoJqJGWloErPjIcR54WALqPHeiJgl15AAAAAElFTkSuQmCC"; -const makeHTML = fn => - "data:text/html;charset=utf-8,"; - - -const test = function(unit) { - return function*(assert) { - assert.isRendered = (panel, toolbox) => { - const doc = toolbox.doc; - assert.ok(doc.querySelector("[value='" + panel.label + "']"), - "panel.label is found in the developer toolbox DOM"); - assert.ok(doc.querySelector("[tooltiptext='" + panel.tooltip + "']"), - "panel.tooltip is found in the developer toolbox DOM"); - - assert.ok(doc.querySelector("#toolbox-panel-" + panel.id), - "toolbar panel with a matching id is present"); - }; - - - yield* unit(assert); - }; -}; - -exports["test Panel API"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "test panel", - tooltip: "my test panel", - icon: iconURI, - url: makeHTML(() => { - document.documentElement.innerHTML = "hello world"; - }), - setup: function({debuggee}) { - this.debuggee = debuggee; - assert.equal(this.readyState, "uninitialized", - "at construction time panel document is not inited"); - }, - dispose: function() { - delete this.debuggee; - } - }); - assert.ok(MyPanel, "panel is defined"); - - const myTool = new Tool({ - panels: { - myPanel: MyPanel - } - }); - assert.ok(myTool, "tool is defined"); - - - var toolbox = yield openToolbox(MyPanel); - var panel = yield getCurrentPanel(toolbox); - assert.ok(panel instanceof MyPanel, "is instance of MyPanel"); - - assert.isRendered(panel, toolbox); - - if (panel.readyState === "uninitialized") { - yield panel.ready(); - assert.equal(panel.readyState, "interactive", "panel is ready"); - } - - yield panel.loaded(); - assert.equal(panel.readyState, "complete", "panel is loaded"); - - yield closeToolbox(); - - assert.equal(panel.readyState, "destroyed", "panel is destroyed"); - - myTool.destroy(); -}); - -exports["test forbid remote https docs"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "test https panel", - tooltip: "my test panel", - icon: iconURI, - url: "https://mozilla.org", - }); - - assert.throws(() => { - new Tool({ panels: { myPanel: MyPanel } }); - }, - /The `options.url` must be a valid local URI/, - "can't use panel with remote URI"); -}); - -exports["test forbid remote http docs"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "test http panel", - tooltip: "my test panel", - icon: iconURI, - url: "http://arewefastyet.com/", - }); - - assert.throws(() => { - new Tool({ panels: { myPanel: MyPanel } }); - }, - /The `options.url` must be a valid local URI/, - "can't use panel with remote URI"); -}); - -exports["test forbid remote ftp docs"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "test ftp panel", - tooltip: "my test panel", - icon: iconURI, - url: "ftp://ftp.mozilla.org/", - }); - - assert.throws(() => { - new Tool({ panels: { myPanel: MyPanel } }); - }, - /The `options.url` must be a valid local URI/, - "can't use panel with remote URI"); -}); - - -exports["test Panel communication"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "communication", - tooltip: "test palen communication", - icon: iconURI, - url: makeHTML(() => { - window.addEventListener("message", event => { - if (event.source === window) { - var port = event.ports[0]; - port.start(); - port.postMessage("ping"); - port.onmessage = (event) => { - if (event.data === "pong") { - port.postMessage("bye"); - port.close(); - } - }; - } - }); - }), - dispose: function() { - delete this.port; - } - }); - - - const myTool = new Tool({ - panels: { - myPanel: MyPanel - } - }); - - - const toolbox = yield openToolbox(MyPanel); - const panel = yield getCurrentPanel(toolbox); - assert.ok(panel instanceof MyPanel, "is instance of MyPanel"); - - assert.isRendered(panel, toolbox); - - yield panel.ready(); - const { port1, port2 } = new MessageChannel(); - panel.port = port1; - panel.postMessage("connect", [port2]); - panel.port.start(); - - const ping = yield when(panel.port, "message"); - - assert.equal(ping.data, "ping", "received ping from panel doc"); - - panel.port.postMessage("pong"); - - const bye = yield when(panel.port, "message"); - - assert.equal(bye.data, "bye", "received bye from panel doc"); - - panel.port.close(); - - yield closeToolbox(); - - assert.equal(panel.readyState, "destroyed", "panel is destroyed"); - myTool.destroy(); -}); - -exports["test communication with debuggee"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "debuggee", - tooltip: "test debuggee", - icon: iconURI, - url: makeHTML(() => { - window.addEventListener("message", event => { - if (event.source === window) { - var debuggee = event.ports[0]; - var port = event.ports[1]; - debuggee.start(); - port.start(); - - - debuggee.onmessage = (event) => { - port.postMessage(event.data); - }; - port.onmessage = (event) => { - debuggee.postMessage(event.data); - }; - } - }); - }), - setup: function({debuggee}) { - this.debuggee = debuggee; - }, - onReady: function() { - const { port1, port2 } = new MessageChannel(); - this.port = port1; - this.port.start(); - this.debuggee.start(); - - this.postMessage("connect", [this.debuggee, port2]); - }, - dispose: function() { - this.port.close(); - this.debuggee.close(); - - delete this.port; - delete this.debuggee; - } - }); - - - const myTool = new Tool({ - panels: { - myPanel: MyPanel - } - }); - - - const toolbox = yield openToolbox(MyPanel); - const panel = yield getCurrentPanel(toolbox); - assert.ok(panel instanceof MyPanel, "is instance of MyPanel"); - - assert.isRendered(panel, toolbox); - - yield panel.ready(); - const intro = yield when(panel.port, "message"); - - assert.equal(intro.data.from, "root", "intro message from root"); - - panel.port.postMessage({ - to: "root", - type: "echo", - text: "ping" - }); - - const pong = yield when(panel.port, "message"); - - assert.deepEqual(pong.data, { - to: "root", - from: "root", - type: "echo", - text: "ping" - }, "received message back from root"); - - yield closeToolbox(); - - assert.equal(panel.readyState, "destroyed", "panel is destroyed"); - - myTool.destroy(); -}); - - -exports["test viewFor panel"] = test(function*(assert) { - const url = "data:text/html;charset=utf-8,viewFor"; - const MyPanel = Class({ - extends: Panel, - label: "view for panel", - tooltip: "my panel view", - icon: iconURI, - url: url - }); - - const myTool = new Tool({ - panels: { - myPanel: MyPanel - } - }); - - - const toolbox = yield openToolbox(MyPanel); - const panel = yield getCurrentPanel(toolbox); - assert.ok(panel instanceof MyPanel, "is instance of MyPanel"); - - const frame = viewFor(panel); - - assert.equal(frame.nodeName.toLowerCase(), "iframe", - "viewFor(panel) returns associated iframe"); - - yield panel.loaded(); - - assert.equal(frame.contentDocument.URL, url, "is expected iframe"); - - yield closeToolbox(); - - myTool.destroy(); -}); - - -exports["test createView panel"] = test(function*(assert) { - var frame = null; - var panel = null; - - const url = "data:text/html;charset=utf-8,createView"; - const id = Math.random().toString(16).substr(2); - const MyPanel = Class({ - extends: Panel, - label: "create view", - tooltip: "panel creator", - icon: iconURI, - url: url - }); - - createView.define(MyPanel, (instance, document) => { - var view = document.createElement("iframe"); - view.setAttribute("type", "content"); - - // save instances for later asserts - frame = view; - panel = instance; - - return view; - }); - - const myTool = new Tool({ - panels: { - myPanel: MyPanel - } - }); - - const toolbox = yield openToolbox(MyPanel); - const myPanel = yield getCurrentPanel(toolbox); - - assert.equal(myPanel, panel, - "panel passed to createView is one instantiated"); - assert.equal(viewFor(panel), frame, - "createView has created an iframe"); - - yield panel.loaded(); - - assert.equal(frame.contentDocument.URL, url, "is expected iframe"); - - yield closeToolbox(); - - myTool.destroy(); -}); - - -exports["test ports is an optional"] = test(function*(assert) { - const MyPanel = Class({ - extends: Panel, - label: "no-port", - icon: iconURI, - url: makeHTML(() => { - window.addEventListener("message", event => { - if (event.ports.length) { - event.ports[0].postMessage(window.firstPacket); - } else { - window.firstPacket = event.data; - } - }); - }) - }); - - - const myTool = new Tool({ - panels: { - myPanel: MyPanel - } - }); - - - const toolbox = yield openToolbox(MyPanel); - const panel = yield getCurrentPanel(toolbox); - assert.ok(panel instanceof MyPanel, "is instance of MyPanel"); - - assert.isRendered(panel, toolbox); - - yield panel.ready(); - - const { port1, port2 } = new MessageChannel(); - port1.start(); - - panel.postMessage("hi"); - panel.postMessage("bye", [port2]); - - const packet = yield when(port1, "message"); - - assert.equal(packet.data, "hi", "got first packet back"); - - yield closeToolbox(); - - assert.equal(panel.readyState, "destroyed", "panel is destroyed"); - - myTool.destroy(); -}); - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-diffpatcher.js b/addon-sdk/source/test/test-diffpatcher.js deleted file mode 100644 index 47126c930e..0000000000 --- a/addon-sdk/source/test/test-diffpatcher.js +++ /dev/null @@ -1,8 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -exports["test diffpatcher"] = require("diffpatcher/test/index"); -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-dispatcher.js b/addon-sdk/source/test/test-dispatcher.js deleted file mode 100644 index 437d751761..0000000000 --- a/addon-sdk/source/test/test-dispatcher.js +++ /dev/null @@ -1,76 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { dispatcher } = require("sdk/util/dispatcher"); - -exports["test dispatcher API"] = assert => { - const dispatch = dispatcher(); - - assert.equal(typeof(dispatch), "function", - "dispatch is a function"); - - assert.equal(typeof(dispatch.define), "function", - "dispatch.define is a function"); - - assert.equal(typeof(dispatch.implement), "function", - "dispatch.implement is a function"); - - assert.equal(typeof(dispatch.when), "function", - "dispatch.when is a function"); -}; - -exports["test dispatcher"] = assert => { - const isDuck = dispatcher(); - - const quacks = x => x && typeof(x.quack) === "function"; - - const Duck = function() {}; - const Goose = function() {}; - - const True = _ => true; - const False = _ => false; - - - - isDuck.define(Goose, False); - isDuck.define(Duck, True); - isDuck.when(quacks, True); - - assert.equal(isDuck(new Goose()), false, - "Goose ain't duck"); - - assert.equal(isDuck(new Duck()), true, - "Ducks are ducks"); - - assert.equal(isDuck({ quack: () => "Quaaaaaack!" }), true, - "It's a duck if it quacks"); - - - assert.throws(() => isDuck({}), /Type does not implements method/, "not implemneted"); - - isDuck.define(Object, False); - - assert.equal(isDuck({}), false, - "Ain't duck if it does not quacks!"); -}; - -exports["test redefining fails"] = assert => { - const isPM = dispatcher(); - const isAfternoon = time => time.getHours() > 12; - - isPM.when(isAfternoon, _ => true); - - assert.equal(isPM(new Date(Date.parse("Jan 23, 1985, 13:20:00"))), true, - "yeap afternoon"); - assert.equal(isPM({ getHours: _ => 17 }), true, - "seems like afternoon"); - - assert.throws(() => isPM.when(isAfternoon, x => x > 12 && x < 24), - /Already implemented for the given predicate/, - "can't redefine on same predicate"); - -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-disposable.js b/addon-sdk/source/test/test-disposable.js deleted file mode 100644 index 3204c2479b..0000000000 --- a/addon-sdk/source/test/test-disposable.js +++ /dev/null @@ -1,393 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Loader } = require("sdk/test/loader"); -const { Class } = require("sdk/core/heritage"); -const { Disposable } = require("sdk/core/disposable"); -const { Cc, Ci, Cu } = require("chrome"); -const { setTimeout } = require("sdk/timers"); - -exports["test disposeDisposable"] = assert => { - let loader = Loader(module); - - const { Disposable, disposeDisposable } = loader.require("sdk/core/disposable"); - const { isWeak, WeakReference } = loader.require("sdk/core/reference"); - - let disposals = 0; - - const Foo = Class({ - extends: Disposable, - implements: [WeakReference], - dispose(...params) { - disposeDisposable(this); - disposals = disposals + 1; - } - }); - - const f1 = new Foo(); - assert.equal(isWeak(f1), true, "f1 has WeakReference support"); - - f1.dispose(); - assert.equal(disposals, 1, "disposed on dispose"); - - loader.unload("uninstall"); - assert.equal(disposals, 1, "after disposeDisposable, dispose is not called anymore"); -}; - -exports["test destroy reasons"] = assert => { - let disposals = 0; - - const Foo = Class({ - extends: Disposable, - dispose: function() { - disposals = disposals + 1; - } - }); - - const f1 = new Foo(); - - f1.destroy(); - assert.equal(disposals, 1, "disposed on destroy"); - f1.destroy(); - assert.equal(disposals, 1, "second destroy is ignored"); - - disposals = 0; - const f2 = new Foo(); - - f2.destroy("uninstall"); - assert.equal(disposals, 1, "uninstall invokes disposal"); - f2.destroy("uninstall") - f2.destroy(); - assert.equal(disposals, 1, "disposal happens just once"); - - disposals = 0; - const f3 = new Foo(); - - f3.destroy("shutdown"); - assert.equal(disposals, 0, "shutdown invoke disposal"); - f3.destroy("shutdown"); - f3.destroy(); - assert.equal(disposals, 0, "shutdown disposal happens just once"); - - disposals = 0; - const f4 = new Foo(); - - f4.destroy("disable"); - assert.equal(disposals, 1, "disable invokes disposal"); - f4.destroy("disable") - f4.destroy(); - assert.equal(disposals, 1, "destroy happens just once"); - - disposals = 0; - const f5 = new Foo(); - - f5.destroy("disable"); - assert.equal(disposals, 1, "disable invokes disposal"); - f5.destroy("disable") - f5.destroy(); - assert.equal(disposals, 1, "destroy happens just once"); - - disposals = 0; - const f6 = new Foo(); - - f6.destroy("upgrade"); - assert.equal(disposals, 1, "upgrade invokes disposal"); - f6.destroy("upgrade") - f6.destroy(); - assert.equal(disposals, 1, "destroy happens just once"); - - disposals = 0; - const f7 = new Foo(); - - f7.destroy("downgrade"); - assert.equal(disposals, 1, "downgrade invokes disposal"); - f7.destroy("downgrade") - f7.destroy(); - assert.equal(disposals, 1, "destroy happens just once"); - - - disposals = 0; - const f8 = new Foo(); - - f8.destroy("whatever"); - assert.equal(disposals, 1, "unrecognized reason invokes disposal"); - f8.destroy("meh") - f8.destroy(); - assert.equal(disposals, 1, "destroy happens just once"); -}; - -exports["test different unload hooks"] = assert => { - const { uninstall, shutdown, disable, upgrade, - downgrade, dispose } = require("sdk/core/disposable"); - const UberUnload = Class({ - extends: Disposable, - setup: function() { - this.log = []; - } - }); - - uninstall.define(UberUnload, x => x.log.push("uninstall")); - shutdown.define(UberUnload, x => x.log.push("shutdown")); - disable.define(UberUnload, x => x.log.push("disable")); - upgrade.define(UberUnload, x => x.log.push("upgrade")); - downgrade.define(UberUnload, x => x.log.push("downgrade")); - dispose.define(UberUnload, x => x.log.push("dispose")); - - const u1 = new UberUnload(); - u1.destroy("uninstall"); - u1.destroy(); - u1.destroy("shutdown"); - assert.deepEqual(u1.log, ["uninstall"], "uninstall hook invoked"); - - const u2 = new UberUnload(); - u2.destroy("shutdown"); - u2.destroy(); - u2.destroy("uninstall"); - assert.deepEqual(u2.log, ["shutdown"], "shutdown hook invoked"); - - const u3 = new UberUnload(); - u3.destroy("disable"); - u3.destroy(); - u3.destroy("uninstall"); - assert.deepEqual(u3.log, ["disable"], "disable hook invoked"); - - const u4 = new UberUnload(); - u4.destroy("upgrade"); - u4.destroy(); - u4.destroy("uninstall"); - assert.deepEqual(u4.log, ["upgrade"], "upgrade hook invoked"); - - const u5 = new UberUnload(); - u5.destroy("downgrade"); - u5.destroy(); - u5.destroy("uninstall"); - assert.deepEqual(u5.log, ["downgrade"], "downgrade hook invoked"); - - const u6 = new UberUnload(); - u6.destroy(); - u6.destroy(); - u6.destroy("uninstall"); - assert.deepEqual(u6.log, ["dispose"], "dispose hook invoked"); - - const u7 = new UberUnload(); - u7.destroy("whatever"); - u7.destroy(); - u7.destroy("uninstall"); - assert.deepEqual(u7.log, ["dispose"], "dispose hook invoked"); -}; - -exports["test disposables are disposed on unload"] = function(assert) { - let loader = Loader(module); - let { Disposable } = loader.require("sdk/core/disposable"); - - let arg1 = {} - let arg2 = 2 - let disposals = 0 - - let Foo = Class({ - extends: Disposable, - setup: function setup(a, b) { - assert.equal(a, arg1, - "arguments passed to constructur is passed to setup"); - assert.equal(b, arg2, - "second argument is also passed to a setup"); - assert.ok(this instanceof Foo, - "this is an instance in the scope of the setup method"); - - this.fooed = true - }, - dispose: function dispose() { - assert.equal(this.fooed, true, "attribute was set") - this.fooed = false - disposals = disposals + 1 - } - }) - - let foo1 = Foo(arg1, arg2) - let foo2 = Foo(arg1, arg2) - - loader.unload(); - - assert.equal(disposals, 2, "both instances were disposed") -} - -exports["test destroyed windows dispose before unload"] = function(assert) { - let loader = Loader(module); - let { Disposable } = loader.require("sdk/core/disposable"); - - let arg1 = {} - let arg2 = 2 - let disposals = 0 - - let Foo = Class({ - extends: Disposable, - setup: function setup(a, b) { - assert.equal(a, arg1, - "arguments passed to constructur is passed to setup"); - assert.equal(b, arg2, - "second argument is also passed to a setup"); - assert.ok(this instanceof Foo, - "this is an instance in the scope of the setup method"); - - this.fooed = true - }, - dispose: function dispose() { - assert.equal(this.fooed, true, "attribute was set") - this.fooed = false - disposals = disposals + 1 - } - }) - - let foo1 = Foo(arg1, arg2) - let foo2 = Foo(arg1, arg2) - - foo1.destroy(); - assert.equal(disposals, 1, "destroy disposes instance") - - loader.unload(); - - assert.equal(disposals, 2, "unload disposes alive instances") -} - -exports["test disposables are GC-able"] = function(assert, done) { - let loader = Loader(module); - let { Disposable } = loader.require("sdk/core/disposable"); - let { WeakReference } = loader.require("sdk/core/reference"); - - let arg1 = {} - let arg2 = 2 - let disposals = 0 - - let Foo = Class({ - extends: Disposable, - implements: [WeakReference], - setup: function setup(a, b) { - assert.equal(a, arg1, - "arguments passed to constructur is passed to setup"); - assert.equal(b, arg2, - "second argument is also passed to a setup"); - assert.ok(this instanceof Foo, - "this is an instance in the scope of the setup method"); - - this.fooed = true - }, - dispose: function dispose() { - assert.equal(this.fooed, true, "attribute was set") - this.fooed = false - disposals = disposals + 1 - } - }); - - let foo1 = Foo(arg1, arg2) - let foo2 = Foo(arg1, arg2) - - foo1 = foo2 = null; - - Cu.schedulePreciseGC(function() { - loader.unload(); - assert.equal(disposals, 0, "GC removed dispose listeners"); - done(); - }); -} - - -exports["test loader unloads do not affect other loaders"] = function(assert) { - let loader1 = Loader(module); - let loader2 = Loader(module); - let { Disposable: Disposable1 } = loader1.require("sdk/core/disposable"); - let { Disposable: Disposable2 } = loader2.require("sdk/core/disposable"); - - let arg1 = {} - let arg2 = 2 - let disposals = 0 - - let Foo1 = Class({ - extends: Disposable1, - dispose: function dispose() { - disposals = disposals + 1; - } - }); - - let Foo2 = Class({ - extends: Disposable2, - dispose: function dispose() { - disposals = disposals + 1; - } - }); - - let foo1 = Foo1(arg1, arg2); - let foo2 = Foo2(arg1, arg2); - - assert.equal(disposals, 0, "no destroy calls"); - - loader1.unload(); - - assert.equal(disposals, 1, "1 destroy calls"); - - loader2.unload(); - - assert.equal(disposals, 2, "2 destroy calls"); -} - -exports["test disposables that throw"] = function(assert) { - let loader = Loader(module); - let { Disposable } = loader.require("sdk/core/disposable"); - - let disposals = 0 - - let Foo = Class({ - extends: Disposable, - setup: function setup(a, b) { - throw Error("Boom!") - }, - dispose: function dispose() { - disposals = disposals + 1 - } - }) - - assert.throws(function() { - let foo1 = Foo() - }, /Boom/, "disposable constructors may throw"); - - loader.unload(); - - assert.equal(disposals, 0, "no disposal if constructor threw"); -} - -exports["test multiple destroy"] = function(assert) { - let loader = Loader(module); - let { Disposable } = loader.require("sdk/core/disposable"); - - let disposals = 0 - - let Foo = Class({ - extends: Disposable, - dispose: function dispose() { - disposals = disposals + 1 - } - }) - - let foo1 = Foo(); - let foo2 = Foo(); - let foo3 = Foo(); - - assert.equal(disposals, 0, "no disposals yet"); - - foo1.destroy(); - assert.equal(disposals, 1, "disposed properly"); - foo1.destroy(); - assert.equal(disposals, 1, "didn't attempt to dispose twice"); - - foo2.destroy(); - assert.equal(disposals, 2, "other instances still dispose fine"); - foo2.destroy(); - assert.equal(disposals, 2, "but not twice"); - - loader.unload(); - - assert.equal(disposals, 3, "unload only disposed the remaining instance"); -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-dom.js b/addon-sdk/source/test/test-dom.js deleted file mode 100644 index 0b50a76395..0000000000 --- a/addon-sdk/source/test/test-dom.js +++ /dev/null @@ -1,88 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const events = require("sdk/dom/events"); -const { activeBrowserWindow: { document } } = require("sdk/deprecated/window-utils"); -const window = document.window; -/* -exports["test on / emit"] = function (assert, done) { - let element = document.createElement("div"); - events.on(element, "click", function listener(event) { - assert.equal(event.target, element, "event has correct target"); - events.removeListener(element, "click", listener); - done(); - }); - - events.emit(element, "click", { - category: "MouseEvents", - settings: [ - true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null - ] - }); -}; - -exports["test remove"] = function (assert, done) { - let element = document.createElement("span"); - let l1 = 0; - let l2 = 0; - let options = { - category: "MouseEvents", - settings: [ - true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null - ] - }; - - events.on(element, "click", function listener1(event) { - l1 ++; - assert.equal(event.target, element, "event has correct target"); - events.removeListener(element, "click", listener1); - }); - - events.on(element, "click", function listener2(event) { - l2 ++; - if (l1 < l2) { - assert.equal(l1, 1, "firs listener was called and then romeved"); - events.removeListener(element, "click", listener2); - done(); - } - events.emit(element, "click", options); - }); - - events.emit(element, "click", options); -}; - -exports["test once"] = function (assert, done) { - let element = document.createElement("h1"); - let l1 = 0; - let l2 = 0; - let options = { - category: "MouseEvents", - settings: [ - true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null - ] - }; - - - events.once(element, "click", function listener(event) { - assert.equal(event.target, element, "event target is a correct element"); - l1 ++; - }); - - events.on(element, "click", function listener(event) { - l2 ++; - if (l2 > 3) { - events.removeListener(element, "click", listener); - assert.equal(event.target, element, "event has correct target"); - assert.equal(l1, 1, "once was called only once"); - done(); - } - events.emit(element, "click", options); - }); - - events.emit(element, "click", options); -} -*/ -require("test").run(exports); diff --git a/addon-sdk/source/test/test-environment.js b/addon-sdk/source/test/test-environment.js deleted file mode 100644 index 9fec6f83dd..0000000000 --- a/addon-sdk/source/test/test-environment.js +++ /dev/null @@ -1,49 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { env } = require('sdk/system/environment'); -const { Cc, Ci } = require('chrome'); -const { get, set, exists } = Cc['@mozilla.org/process/environment;1']. - getService(Ci.nsIEnvironment); - -exports['test exists'] = function(assert) { - assert.equal('PATH' in env, exists('PATH'), - 'PATH environment variable is defined'); - assert.equal('FOO1' in env, exists('FOO1'), - 'FOO1 environment variable is not defined'); - set('FOO1', 'foo'); - assert.equal('FOO1' in env, true, - 'FOO1 environment variable was set'); - set('FOO1', null); - assert.equal('FOO1' in env, false, - 'FOO1 environment variable was unset'); -}; - -exports['test get'] = function(assert) { - assert.equal(env.PATH, get('PATH'), 'PATH env variable matches'); - assert.equal(env.BAR2, undefined, 'BAR2 env variable is not defined'); - set('BAR2', 'bar'); - assert.equal(env.BAR2, 'bar', 'BAR2 env variable was set'); - set('BAR2', null); - assert.equal(env.BAR2, undefined, 'BAR2 env variable was unset'); -}; - -exports['test set'] = function(assert) { - assert.equal(get('BAZ3'), '', 'BAZ3 env variable is not set'); - assert.equal(env.BAZ3, undefined, 'BAZ3 is not set'); - env.BAZ3 = 'baz'; - assert.equal(env.BAZ3, get('BAZ3'), 'BAZ3 env variable is set'); - assert.equal(get('BAZ3'), 'baz', 'BAZ3 env variable was set to "baz"'); -}; - -exports['test unset'] = function(assert) { - env.BLA4 = 'bla'; - assert.equal(env.BLA4, 'bla', 'BLA4 env variable is set'); - assert.equal(delete env.BLA4, true, 'BLA4 env variable is removed'); - assert.equal(env.BLA4, undefined, 'BLA4 env variable is unset'); - assert.equal('BLA4' in env, false, 'BLA4 env variable no longer exists' ); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-event-core.js b/addon-sdk/source/test/test-event-core.js deleted file mode 100644 index ca937b2596..0000000000 --- a/addon-sdk/source/test/test-event-core.js +++ /dev/null @@ -1,347 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { on, once, off, emit, count } = require('sdk/event/core'); -const { LoaderWithHookedConsole } = require("sdk/test/loader"); -const { defer } = require("sdk/core/promise"); -const { gc } = require("sdk/test/memory"); - -exports['test add a listener'] = function(assert) { - let events = [ { name: 'event#1' }, 'event#2' ]; - let target = { name: 'target' }; - - on(target, 'message', function(message) { - assert.equal(this, target, 'this is a target object'); - assert.equal(message, events.shift(), 'message is emitted event'); - }); - emit(target, 'message', events[0]); - emit(target, 'message', events[0]); -}; - -exports['test that listener is unique per type'] = function(assert) { - let actual = [] - let target = {} - function listener() { actual.push(1) } - on(target, 'message', listener); - on(target, 'message', listener); - on(target, 'message', listener); - on(target, 'foo', listener); - on(target, 'foo', listener); - - emit(target, 'message'); - assert.deepEqual([ 1 ], actual, 'only one message listener added'); - emit(target, 'foo'); - assert.deepEqual([ 1, 1 ], actual, 'same listener added for other event'); -}; - -exports['test event type matters'] = function(assert) { - let target = { name: 'target' } - on(target, 'message', function() { - assert.fail('no event is expected'); - }); - on(target, 'done', function() { - assert.pass('event is emitted'); - }); - emit(target, 'foo') - emit(target, 'done'); -}; - -exports['test all arguments are pasesd'] = function(assert) { - let foo = { name: 'foo' }, bar = 'bar'; - let target = { name: 'target' }; - on(target, 'message', function(a, b) { - assert.equal(a, foo, 'first argument passed'); - assert.equal(b, bar, 'second argument passed'); - }); - emit(target, 'message', foo, bar); -}; - -exports['test no side-effects in emit'] = function(assert) { - let target = { name: 'target' }; - on(target, 'message', function() { - assert.pass('first listener is called'); - on(target, 'message', function() { - assert.fail('second listener is called'); - }); - }); - emit(target, 'message'); -}; - -exports['test can remove next listener'] = function(assert) { - let target = { name: 'target' }; - function fail() { - return assert.fail('Listener should be removed'); - }; - - on(target, 'data', function() { - assert.pass('first litener called'); - off(target, 'data', fail); - }); - on(target, 'data', fail); - - emit(target, 'data', 'hello'); -}; - -exports['test order of propagation'] = function(assert) { - let actual = []; - let target = { name: 'target' }; - on(target, 'message', function() { actual.push(1); }); - on(target, 'message', function() { actual.push(2); }); - on(target, 'message', function() { actual.push(3); }); - emit(target, 'message'); - assert.deepEqual([ 1, 2, 3 ], actual, 'called in order they were added'); -}; - -exports['test remove a listener'] = function(assert) { - let target = { name: 'target' }; - let actual = []; - on(target, 'message', function listener() { - actual.push(1); - on(target, 'message', function() { - off(target, 'message', listener); - actual.push(2); - }) - }); - - emit(target, 'message'); - assert.deepEqual([ 1 ], actual, 'first listener called'); - emit(target, 'message'); - assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called'); - - emit(target, 'message'); - assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed'); -}; - -exports['test remove all listeners for type'] = function(assert) { - let actual = []; - let target = { name: 'target' } - on(target, 'message', function() { actual.push(1); }); - on(target, 'message', function() { actual.push(2); }); - on(target, 'message', function() { actual.push(3); }); - on(target, 'bar', function() { actual.push('b') }); - off(target, 'message'); - - emit(target, 'message'); - emit(target, 'bar'); - - assert.deepEqual([ 'b' ], actual, 'all message listeners were removed'); -}; - -exports['test remove all listeners'] = function(assert) { - let actual = []; - let target = { name: 'target' } - on(target, 'message', function() { actual.push(1); }); - on(target, 'message', function() { actual.push(2); }); - on(target, 'message', function() { actual.push(3); }); - on(target, 'bar', function() { actual.push('b') }); - off(target); - - emit(target, 'message'); - emit(target, 'bar'); - - assert.deepEqual([], actual, 'all listeners events were removed'); -}; - -exports['test falsy arguments are fine'] = function(assert) { - let type, listener, actual = []; - let target = { name: 'target' } - on(target, 'bar', function() { actual.push(0) }); - - off(target, 'bar', listener); - emit(target, 'bar'); - assert.deepEqual([ 0 ], actual, '3rd bad ard will keep listeners'); - - off(target, type); - emit(target, 'bar'); - assert.deepEqual([ 0, 0 ], actual, '2nd bad arg will keep listener'); - - off(target, type, listener); - emit(target, 'bar'); - assert.deepEqual([ 0, 0, 0 ], actual, '2nd&3rd bad args will keep listener'); -}; - -exports['test error handling'] = function(assert) { - let target = Object.create(null); - let error = Error('boom!'); - - on(target, 'message', function() { throw error; }) - on(target, 'error', function(boom) { - assert.equal(boom, error, 'thrown exception causes error event'); - }); - emit(target, 'message'); -}; - -exports['test unhandled exceptions'] = function(assert) { - let exceptions = []; - let { loader, messages } = LoaderWithHookedConsole(module); - - let { emit, on } = loader.require('sdk/event/core'); - let target = {}; - let boom = Error('Boom!'); - let drax = Error('Draax!!'); - - on(target, 'message', function() { throw boom; }); - - emit(target, 'message'); - assert.equal(messages.length, 1, 'Got the first exception'); - assert.equal(messages[0].type, 'exception', 'The console message is exception'); - assert.ok(~String(messages[0].msg).indexOf('Boom!'), - 'unhandled exception is logged'); - - on(target, 'error', function() { throw drax; }); - emit(target, 'message'); - assert.equal(messages.length, 2, 'Got the second exception'); - assert.equal(messages[1].type, 'exception', 'The console message is exception'); - assert.ok(~String(messages[1].msg).indexOf('Draax!'), - 'error in error handler is logged'); -}; - -exports['test unhandled errors'] = function(assert) { - let exceptions = []; - let { loader, messages } = LoaderWithHookedConsole(module); - - let { emit } = loader.require('sdk/event/core'); - let target = {}; - let boom = Error('Boom!'); - - emit(target, 'error', boom); - assert.equal(messages.length, 1, 'Error was logged'); - assert.equal(messages[0].type, 'exception', 'The console message is exception'); - assert.ok(~String(messages[0].msg).indexOf('Boom!'), - 'unhandled exception is logged'); -}; - -exports['test piped errors'] = function(assert) { - let exceptions = []; - let { loader, messages } = LoaderWithHookedConsole(module); - - let { emit } = loader.require('sdk/event/core'); - let { pipe } = loader.require('sdk/event/utils'); - let target = {}; - let second = {}; - - pipe(target, second); - emit(target, 'error', 'piped!'); - - assert.equal(messages.length, 1, 'error logged only once, ' + - 'considered "handled" on `target` by the catch-all pipe'); - assert.equal(messages[0].type, 'exception', 'The console message is exception'); - assert.ok(~String(messages[0].msg).indexOf('piped!'), - 'unhandled (piped) exception is logged on `second` target'); -}; - -exports['test count'] = function(assert) { - let target = {}; - - assert.equal(count(target, 'foo'), 0, 'no listeners for "foo" events'); - on(target, 'foo', function() {}); - assert.equal(count(target, 'foo'), 1, 'listener registered'); - on(target, 'foo', function() {}, 2, 'another listener registered'); - off(target) - assert.equal(count(target, 'foo'), 0, 'listeners unregistered'); -}; - -exports['test listen to all events'] = function(assert) { - let actual = []; - let target = {}; - - on(target, 'foo', message => actual.push(message)); - on(target, '*', (type, ...message) => { - actual.push([type].concat(message)); - }); - - emit(target, 'foo', 'hello'); - assert.equal(actual[0], 'hello', - 'non-wildcard listeners still work'); - assert.deepEqual(actual[1], ['foo', 'hello'], - 'wildcard listener called'); - - emit(target, 'bar', 'goodbye'); - assert.deepEqual(actual[2], ['bar', 'goodbye'], - 'wildcard listener called for unbound event name'); -}; - -exports['test once'] = function(assert, done) { - let target = {}; - let called = false; - let { resolve, promise } = defer(); - - once(target, 'foo', function(value) { - assert.ok(!called, "listener called only once"); - assert.equal(value, "bar", "correct argument was passed"); - }); - once(target, 'done', resolve); - - emit(target, 'foo', 'bar'); - emit(target, 'foo', 'baz'); - emit(target, 'done', ""); - - yield promise; -}; - -exports['test once with gc'] = function*(assert) { - let target = {}; - let called = false; - let { resolve, promise } = defer(); - - once(target, 'foo', function(value) { - assert.ok(!called, "listener called only once"); - assert.equal(value, "bar", "correct argument was passed"); - }); - once(target, 'done', resolve); - - yield gc(); - - emit(target, 'foo', 'bar'); - emit(target, 'foo', 'baz'); - emit(target, 'done', ""); - - yield promise; -}; - -exports["test removing once"] = function(assert, done) { - let target = {}; - - function test() { - assert.fail("listener was called"); - } - - once(target, "foo", test); - once(target, "done", done); - - off(target, "foo", test); - - assert.pass("emit foo"); - emit(target, "foo", "bar"); - - assert.pass("emit done"); - emit(target, "done", ""); -}; - -exports["test removing once with gc"] = function*(assert) { - let target = {}; - let { resolve, promise } = defer(); - - function test() { - assert.fail("listener was called"); - } - - once(target, "foo", test); - once(target, "done", resolve); - - yield gc(); - - off(target, "foo", test); - - assert.pass("emit foo"); - emit(target, "foo", "bar"); - - assert.pass("emit done"); - emit(target, "done", ""); - - yield promise; -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-event-dom.js b/addon-sdk/source/test/test-event-dom.js deleted file mode 100644 index fbbc6825bf..0000000000 --- a/addon-sdk/source/test/test-event-dom.js +++ /dev/null @@ -1,92 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { openWindow, closeWindow } = require('./util'); -const { Loader } = require('sdk/test/loader'); -const { getMostRecentBrowserWindow } = require('sdk/window/utils'); -const { Cc, Ci } = require('chrome'); -const els = Cc["@mozilla.org/eventlistenerservice;1"]. - getService(Ci.nsIEventListenerService); - -function countListeners(target, type) { - let listeners = els.getListenerInfoFor(target, {}); - return listeners.filter(listener => listener.type == type).length; -} - -exports['test window close clears listeners'] = function(assert) { - let window = yield openWindow(); - let loader = Loader(module); - - // Any element will do here - let gBrowser = window.gBrowser; - - // Other parts of the app may be listening for this - let windowListeners = countListeners(window, "DOMWindowClose"); - - // We can assume we're the only ones using the test events - assert.equal(countListeners(gBrowser, "TestEvent1"), 0, "Should be no listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 0, "Should be no listener for test event 2"); - - let { open } = loader.require('sdk/event/dom'); - - open(gBrowser, "TestEvent1"); - assert.equal(countListeners(window, "DOMWindowClose"), windowListeners + 1, - "Should have added a DOMWindowClose listener"); - assert.equal(countListeners(gBrowser, "TestEvent1"), 1, "Should be a listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 0, "Should be no listener for test event 2"); - - open(gBrowser, "TestEvent2"); - assert.equal(countListeners(window, "DOMWindowClose"), windowListeners + 1, - "Should not have added another DOMWindowClose listener"); - assert.equal(countListeners(gBrowser, "TestEvent1"), 1, "Should be a listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 1, "Should be a listener for test event 2"); - - window = yield closeWindow(window); - - assert.equal(countListeners(window, "DOMWindowClose"), windowListeners, - "Should have removed a DOMWindowClose listener"); - assert.equal(countListeners(gBrowser, "TestEvent1"), 0, "Should be no listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 0, "Should be no listener for test event 2"); - - loader.unload(); -}; - -exports['test unload clears listeners'] = function(assert) { - let window = getMostRecentBrowserWindow(); - let loader = Loader(module); - - // Any element will do here - let gBrowser = window.gBrowser; - - // Other parts of the app may be listening for this - let windowListeners = countListeners(window, "DOMWindowClose"); - - // We can assume we're the only ones using the test events - assert.equal(countListeners(gBrowser, "TestEvent1"), 0, "Should be no listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 0, "Should be no listener for test event 2"); - - let { open } = loader.require('sdk/event/dom'); - - open(gBrowser, "TestEvent1"); - assert.equal(countListeners(window, "DOMWindowClose"), windowListeners + 1, - "Should have added a DOMWindowClose listener"); - assert.equal(countListeners(gBrowser, "TestEvent1"), 1, "Should be a listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 0, "Should be no listener for test event 2"); - - open(gBrowser, "TestEvent2"); - assert.equal(countListeners(window, "DOMWindowClose"), windowListeners + 1, - "Should not have added another DOMWindowClose listener"); - assert.equal(countListeners(gBrowser, "TestEvent1"), 1, "Should be a listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 1, "Should be a listener for test event 2"); - - loader.unload(); - - assert.equal(countListeners(window, "DOMWindowClose"), windowListeners, - "Should have removed a DOMWindowClose listener"); - assert.equal(countListeners(gBrowser, "TestEvent1"), 0, "Should be no listener for test event 1"); - assert.equal(countListeners(gBrowser, "TestEvent2"), 0, "Should be no listener for test event 2"); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-event-target.js b/addon-sdk/source/test/test-event-target.js deleted file mode 100644 index d51314aa51..0000000000 --- a/addon-sdk/source/test/test-event-target.js +++ /dev/null @@ -1,222 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { emit } = require('sdk/event/core'); -const { EventTarget } = require('sdk/event/target'); -const { Loader } = require('sdk/test/loader'); - -exports['test add a listener'] = function(assert) { - let events = [ { name: 'event#1' }, 'event#2' ]; - let target = EventTarget(); - - target.on('message', function(message) { - assert.equal(this, target, 'this is a target object'); - assert.equal(message, events.shift(), 'message is emitted event'); - }); - - emit(target, 'message', events[0]); - emit(target, 'message', events[0]); -}; - -exports['test pass in listeners'] = function(assert) { - let actual = [ ]; - let target = EventTarget({ - onMessage: function onMessage(message) { - assert.equal(this, target, 'this is an event target'); - actual.push(1); - }, - onFoo: null, - onbla: function() { - assert.fail('`onbla` is not supposed to be called'); - } - }); - target.on('message', function(message) { - assert.equal(this, target, 'this is an event target'); - actual.push(2); - }); - - emit(target, 'message'); - emit(target, 'missing'); - - assert.deepEqual([ 1, 2 ], actual, 'all listeners trigerred in right order'); -}; - -exports['test that listener is unique per type'] = function(assert) { - let actual = [] - let target = EventTarget(); - function listener() { actual.push(1) } - target.on('message', listener); - target.on('message', listener); - target.on('message', listener); - target.on('foo', listener); - target.on('foo', listener); - - emit(target, 'message'); - assert.deepEqual([ 1 ], actual, 'only one message listener added'); - emit(target, 'foo'); - assert.deepEqual([ 1, 1 ], actual, 'same listener added for other event'); -}; - -exports['test event type matters'] = function(assert) { - let target = EventTarget(); - target.on('message', function() { - assert.fail('no event is expected'); - }); - target.on('done', function() { - assert.pass('event is emitted'); - }); - - emit(target, 'foo'); - emit(target, 'done'); -}; - -exports['test all arguments are pasesd'] = function(assert) { - let foo = { name: 'foo' }, bar = 'bar'; - let target = EventTarget(); - target.on('message', function(a, b) { - assert.equal(a, foo, 'first argument passed'); - assert.equal(b, bar, 'second argument passed'); - }); - emit(target, 'message', foo, bar); -}; - -exports['test no side-effects in emit'] = function(assert) { - let target = EventTarget(); - target.on('message', function() { - assert.pass('first listener is called'); - target.on('message', function() { - assert.fail('second listener is called'); - }); - }); - emit(target, 'message'); -}; - -exports['test order of propagation'] = function(assert) { - let actual = []; - let target = EventTarget(); - target.on('message', function() { actual.push(1); }); - target.on('message', function() { actual.push(2); }); - target.on('message', function() { actual.push(3); }); - emit(target, 'message'); - assert.deepEqual([ 1, 2, 3 ], actual, 'called in order they were added'); -}; - -exports['test remove a listener'] = function(assert) { - let target = EventTarget(); - let actual = []; - target.on('message', function listener() { - actual.push(1); - target.on('message', function() { - target.removeListener('message', listener); - actual.push(2); - }) - }); - - emit(target, 'message'); - assert.deepEqual([ 1 ], actual, 'first listener called'); - emit(target, 'message'); - assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called'); - emit(target, 'message'); - assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed'); -}; - -exports['test .off() removes all listeners'] = function(assert) { - let target = EventTarget(); - let actual = []; - target.on('message', function listener() { - actual.push(1); - target.on('message', function() { - target.removeListener('message', listener); - actual.push(2); - }) - }); - - emit(target, 'message'); - assert.deepEqual([ 1 ], actual, 'first listener called'); - emit(target, 'message'); - assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called'); - target.off(); - emit(target, 'message'); - assert.deepEqual([ 1, 1, 2 ], actual, 'target.off() removed all listeners'); -}; - -exports['test error handling'] = function(assert) { - let target = EventTarget(); - let error = Error('boom!'); - - target.on('message', function() { throw error; }) - target.on('error', function(boom) { - assert.equal(boom, error, 'thrown exception causes error event'); - }); - emit(target, 'message'); -}; - -exports['test unhandled errors'] = function(assert) { - let exceptions = []; - let loader = Loader(module); - let { emit } = loader.require('sdk/event/core'); - let { EventTarget } = loader.require('sdk/event/target'); - Object.defineProperties(loader.sandbox('sdk/event/core'), { - console: { value: { - exception: function(e) { - exceptions.push(e); - } - }} - }); - let target = EventTarget(); - let boom = Error('Boom!'); - let drax = Error('Draax!!'); - - target.on('message', function() { throw boom; }); - - emit(target, 'message'); - assert.ok(~String(exceptions[0]).indexOf('Boom!'), - 'unhandled exception is logged'); - - target.on('error', function() { throw drax; }); - emit(target, 'message'); - assert.ok(~String(exceptions[1]).indexOf('Draax!'), - 'error in error handler is logged'); -}; - -exports['test target is chainable'] = function (assert, done) { - let loader = Loader(module); - let exceptions = []; - let { EventTarget } = loader.require('sdk/event/target'); - let { emit } = loader.require('sdk/event/core'); - Object.defineProperties(loader.sandbox('sdk/event/core'), { - console: { value: { - exception: function(e) { - exceptions.push(e); - } - }} - }); - - let emitter = EventTarget(); - let boom = Error('Boom'); - let onceCalled = 0; - - emitter.once('oneTime', function () { - assert.equal(++onceCalled, 1, 'once event called only once'); - }).on('data', function (message) { - assert.equal(message, 'message', 'handles event'); - emit(emitter, 'oneTime'); - emit(emitter, 'data2', 'message2'); - }).on('phony', function () { - assert.fail('removeListener does not remove chained event'); - }).removeListener('phony') - .on('data2', function (message) { - assert.equal(message, 'message2', 'handle chained event'); - emit(emitter, 'oneTime'); - throw boom; - }).on('error', function (error) { - assert.equal(error, boom, 'error handled in chained event'); - done(); - }); - - emit(emitter, 'data', 'message'); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-event-utils.js b/addon-sdk/source/test/test-event-utils.js deleted file mode 100644 index ea69e76772..0000000000 --- a/addon-sdk/source/test/test-event-utils.js +++ /dev/null @@ -1,285 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { on, emit } = require("sdk/event/core"); -const { filter, map, merge, expand, pipe, stripListeners } = require("sdk/event/utils"); -const $ = require("./event/helpers"); - -function isEven(x) { - return !(x % 2); -} -function inc(x) { - return x + 1; -} - -exports["test filter events"] = function(assert) { - let input = {}; - let evens = filter(input, isEven); - let actual = []; - on(evens, "data", e => actual.push(e)); - - [1, 2, 3, 4, 5, 6, 7].forEach(x => emit(input, "data", x)); - - assert.deepEqual(actual, [2, 4, 6], "only even numbers passed through"); -}; - -exports["test filter emits"] = $.emits(function(input, assert) { - let output = filter(input, isEven); - assert(output, [1, 2, 3, 4, 5], [2, 4], "this is `output` & evens passed"); -});; - -exports["test filter reg once"] = $.registerOnce(function(input, assert) { - assert(filter(input, isEven), [1, 2, 3, 4, 5, 6], [2, 4, 6], - "listener can be registered only once"); -}); - -exports["test filter ignores new"] = $.ignoreNew(function(input, assert) { - assert(filter(input, isEven), [1, 2, 3], [2], - "new listener is ignored") -}); - -exports["test filter is FIFO"] = $.FIFO(function(input, assert) { - assert(filter(input, isEven), [1, 2, 3, 4], [2, 4], - "listeners are invoked in fifo order") -}); - -exports["test map events"] = function(assert) { - let input = {}; - let incs = map(input, inc); - let actual = []; - on(incs, "data", e => actual.push(e)); - - [1, 2, 3, 4].forEach(x => emit(input, "data", x)); - - assert.deepEqual(actual, [2, 3, 4, 5], "all numbers were incremented"); -}; - -exports["test map emits"] = $.emits(function(input, assert) { - let output = map(input, inc); - assert(output, [1, 2, 3], [2, 3, 4], "this is `output` & evens passed"); -}); - -exports["test map reg once"] = $.registerOnce(function(input, assert) { - assert(map(input, inc), [1, 2, 3], [2, 3, 4], - "listener can be registered only once"); -}); - -exports["test map ignores new"] = $.ignoreNew(function(input, assert) { - assert(map(input, inc), [1], [2], - "new listener is ignored") -}); - -exports["test map is FIFO"] = $.FIFO(function(input, assert) { - assert(map(input, inc), [1, 2, 3, 4], [2, 3, 4, 5], - "listeners are invoked in fifo order") -}); - -exports["test merge stream[stream]"] = function(assert) { - let a = {}, b = {}, c = {}; - let inputs = {}; - let actual = []; - - on(merge(inputs), "data", $ => actual.push($)) - - emit(inputs, "data", a); - emit(a, "data", "a1"); - emit(inputs, "data", b); - emit(b, "data", "b1"); - emit(a, "data", "a2"); - emit(inputs, "data", c); - emit(c, "data", "c1"); - emit(c, "data", "c2"); - emit(b, "data", "b2"); - emit(a, "data", "a3"); - - assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"], - "all inputs data merged into one"); -}; - -exports["test merge array[stream]"] = function(assert) { - let a = {}, b = {}, c = {}; - let inputs = {}; - let actual = []; - - on(merge([a, b, c]), "data", $ => actual.push($)) - - emit(a, "data", "a1"); - emit(b, "data", "b1"); - emit(a, "data", "a2"); - emit(c, "data", "c1"); - emit(c, "data", "c2"); - emit(b, "data", "b2"); - emit(a, "data", "a3"); - - assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"], - "all inputs data merged into one"); -}; - -exports["test merge emits"] = $.emits(function(input, assert) { - let evens = filter(input, isEven) - let output = merge([evens, input]); - assert(output, [1, 2, 3], [1, 2, 2, 3], "this is `output` & evens passed"); -}); - - -exports["test merge reg once"] = $.registerOnce(function(input, assert) { - let evens = filter(input, isEven) - let output = merge([input, evens]); - assert(output, [1, 2, 3, 4], [1, 2, 2, 3, 4, 4], - "listener can be registered only once"); -}); - -exports["test merge ignores new"] = $.ignoreNew(function(input, assert) { - let evens = filter(input, isEven) - let output = merge([input, evens]) - assert(output, [1], [1], - "new listener is ignored") -}); - -exports["test marge is FIFO"] = $.FIFO(function(input, assert) { - let evens = filter(input, isEven) - let output = merge([input, evens]) - - assert(output, [1, 2, 3, 4], [1, 2, 2, 3, 4, 4], - "listeners are invoked in fifo order") -}); - -exports["test expand"] = function(assert) { - let a = {}, b = {}, c = {}; - let inputs = {}; - let actual = []; - - on(expand(inputs, $ => $()), "data", $ => actual.push($)) - - emit(inputs, "data", () => a); - emit(a, "data", "a1"); - emit(inputs, "data", () => b); - emit(b, "data", "b1"); - emit(a, "data", "a2"); - emit(inputs, "data", () => c); - emit(c, "data", "c1"); - emit(c, "data", "c2"); - emit(b, "data", "b2"); - emit(a, "data", "a3"); - - assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"], - "all inputs data merged into one"); -}; - -exports["test pipe"] = function (assert, done) { - let src = {}; - let dest = {}; - - let aneventCount = 0, multiargsCount = 0; - let wildcardCount = {}; - - on(dest, 'an-event', arg => { - assert.equal(arg, 'my-arg', 'piped argument to event'); - ++aneventCount; - check(); - }); - on(dest, 'multiargs', (...data) => { - assert.equal(data[0], 'a', 'multiple arguments passed via pipe'); - assert.equal(data[1], 'b', 'multiple arguments passed via pipe'); - assert.equal(data[2], 'c', 'multiple arguments passed via pipe'); - ++multiargsCount; - check(); - }); - - on(dest, '*', (name, ...data) => { - wildcardCount[name] = (wildcardCount[name] || 0) + 1; - if (name === 'multiargs') { - assert.equal(data[0], 'a', 'multiple arguments passed via pipe, wildcard'); - assert.equal(data[1], 'b', 'multiple arguments passed via pipe, wildcard'); - assert.equal(data[2], 'c', 'multiple arguments passed via pipe, wildcard'); - } - if (name === 'an-event') - assert.equal(data[0], 'my-arg', 'argument passed via pipe, wildcard'); - check(); - }); - - pipe(src, dest); - - for (let i = 0; i < 3; i++) - emit(src, 'an-event', 'my-arg'); - - emit(src, 'multiargs', 'a', 'b', 'c'); - - function check () { - if (aneventCount === 3 && multiargsCount === 1 && - wildcardCount['an-event'] === 3 && - wildcardCount['multiargs'] === 1) - done(); - } -}; - -exports["test pipe multiple targets"] = function (assert) { - let src1 = {}; - let src2 = {}; - let middle = {}; - let dest = {}; - - pipe(src1, middle); - pipe(src2, middle); - pipe(middle, dest); - - let middleFired = 0; - let destFired = 0; - let src1Fired = 0; - let src2Fired = 0; - - on(src1, '*', () => src1Fired++); - on(src2, '*', () => src2Fired++); - on(middle, '*', () => middleFired++); - on(dest, '*', () => destFired++); - - emit(src1, 'ev'); - assert.equal(src1Fired, 1, 'event triggers in source in pipe chain'); - assert.equal(middleFired, 1, 'event passes through the middle of pipe chain'); - assert.equal(destFired, 1, 'event propagates to end of pipe chain'); - assert.equal(src2Fired, 0, 'event does not fire on alternative chain routes'); - - emit(src2, 'ev'); - assert.equal(src2Fired, 1, 'event triggers in source in pipe chain'); - assert.equal(middleFired, 2, - 'event passes through the middle of pipe chain from different src'); - assert.equal(destFired, 2, - 'event propagates to end of pipe chain from different src'); - assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes'); - - emit(middle, 'ev'); - assert.equal(middleFired, 3, - 'event triggers in source of pipe chain'); - assert.equal(destFired, 3, - 'event propagates to end of pipe chain from middle src'); - assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes'); - assert.equal(src2Fired, 1, 'event does not fire on alternative chain routes'); -}; - -exports['test stripListeners'] = function (assert) { - var options = { - onAnEvent: noop1, - onMessage: noop2, - verb: noop1, - value: 100 - }; - - var stripped = stripListeners(options); - assert.ok(stripped !== options, 'stripListeners should return a new object'); - assert.equal(options.onAnEvent, noop1, 'stripListeners does not affect original'); - assert.equal(options.onMessage, noop2, 'stripListeners does not affect original'); - assert.equal(options.verb, noop1, 'stripListeners does not affect original'); - assert.equal(options.value, 100, 'stripListeners does not affect original'); - - assert.ok(!stripped.onAnEvent, 'stripListeners removes `on*` values'); - assert.ok(!stripped.onMessage, 'stripListeners removes `on*` values'); - assert.equal(stripped.verb, noop1, 'stripListeners leaves not `on*` values'); - assert.equal(stripped.value, 100, 'stripListeners leaves not `on*` values'); - - function noop1 () {} - function noop2 () {} -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-file.js b/addon-sdk/source/test/test-file.js deleted file mode 100644 index 268c7f7919..0000000000 --- a/addon-sdk/source/test/test-file.js +++ /dev/null @@ -1,271 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { pathFor } = require('sdk/system'); -const file = require("sdk/io/file"); -const url = require("sdk/url"); - -const byteStreams = require("sdk/io/byte-streams"); -const textStreams = require("sdk/io/text-streams"); - -const ERRORS = { - FILE_NOT_FOUND: /^path does not exist: .+$/, - NOT_A_DIRECTORY: /^path is not a directory: .+$/, - NOT_A_FILE: /^path is not a file: .+$/, -}; - -// Use profile directory to list / read / write files. -const profilePath = pathFor('ProfD'); -const fileNameInProfile = 'compatibility.ini'; -const dirNameInProfile = 'extensions'; -const filePathInProfile = file.join(profilePath, fileNameInProfile); -const dirPathInProfile = file.join(profilePath, dirNameInProfile); - -exports.testDirName = function(assert) { - assert.equal(file.dirname(dirPathInProfile), profilePath, - "file.dirname() of dir should return parent dir"); - - assert.equal(file.dirname(filePathInProfile), profilePath, - "file.dirname() of file should return its dir"); - - let dir = profilePath; - while (dir) - dir = file.dirname(dir); - - assert.equal(dir, "", - "dirname should return empty string when dir has no parent"); -}; - -exports.testBasename = function(assert) { - // Get the top-most path -- the path with no basename. E.g., on Unix-like - // systems this will be /. We'll use it below to build up some test paths. - // We have to go to this trouble because file.join() needs a legal path as a - // base case; join("foo", "bar") doesn't work unfortunately. - let topPath = profilePath; - let parentPath = file.dirname(topPath); - while (parentPath) { - topPath = parentPath; - parentPath = file.dirname(topPath); - } - - let path = topPath; - assert.equal(file.basename(path), "", - "basename should work on paths with no components"); - - path = file.join(path, "foo"); - assert.equal(file.basename(path), "foo", - "basename should work on paths with a single component"); - - path = file.join(path, "bar"); - assert.equal(file.basename(path), "bar", - "basename should work on paths with multiple components"); -}; - -exports.testList = function(assert) { - let list = file.list(profilePath); - let found = list.filter(name => name === fileNameInProfile); - - assert.equal(found.length, 1, "file.list() should work"); - - assert.throws(function() { - file.list(filePathInProfile); - }, ERRORS.NOT_A_DIRECTORY, "file.list() on non-dir should raise error"); - - assert.throws(function() { - file.list(file.join(dirPathInProfile, "does-not-exist")); - }, ERRORS.FILE_NOT_FOUND, "file.list() on nonexistent should raise error"); -}; - -exports.testRead = function(assert) { - let contents = file.read(filePathInProfile); - assert.ok(/Compatibility/.test(contents), - "file.read() should work"); - - assert.throws(function() { - file.read(file.join(dirPathInProfile, "does-not-exists")); - }, ERRORS.FILE_NOT_FOUND, "file.read() on nonexistent file should throw"); - - assert.throws(function() { - file.read(dirPathInProfile); - }, ERRORS.NOT_A_FILE, "file.read() on dir should throw"); -}; - -exports.testJoin = function(assert) { - let baseDir = file.dirname(filePathInProfile); - - assert.equal(file.join(baseDir, fileNameInProfile), - filePathInProfile, "file.join() should work"); -}; - -exports.testOpenNonexistentForRead = function (assert) { - var filename = file.join(profilePath, 'does-not-exists'); - assert.throws(function() { - file.open(filename); - }, ERRORS.FILE_NOT_FOUND, "file.open() on nonexistent file should throw"); - - assert.throws(function() { - file.open(filename, "r"); - }, ERRORS.FILE_NOT_FOUND, "file.open('r') on nonexistent file should throw"); - - assert.throws(function() { - file.open(filename, "zz"); - }, ERRORS.FILE_NOT_FOUND, "file.open('zz') on nonexistent file should throw"); -}; - -exports.testOpenNonexistentForWrite = function (assert) { - let filename = file.join(profilePath, 'open.txt'); - - let stream = file.open(filename, "w"); - stream.close(); - - assert.ok(file.exists(filename), - "file.exists() should return true after file.open('w')"); - file.remove(filename); - assert.ok(!file.exists(filename), - "file.exists() should return false after file.remove()"); - - stream = file.open(filename, "rw"); - stream.close(); - - assert.ok(file.exists(filename), - "file.exists() should return true after file.open('rw')"); - file.remove(filename); - assert.ok(!file.exists(filename), - "file.exists() should return false after file.remove()"); -}; - -exports.testOpenDirectory = function (assert) { - let dir = dirPathInProfile; - assert.throws(function() { - file.open(dir); - }, ERRORS.NOT_A_FILE, "file.open() on directory should throw"); - - assert.throws(function() { - file.open(dir, "w"); - }, ERRORS.NOT_A_FILE, "file.open('w') on directory should throw"); -}; - -exports.testOpenTypes = function (assert) { - let filename = file.join(profilePath, 'open-types.txt'); - - - // Do the opens first to create the data file. - var stream = file.open(filename, "w"); - assert.ok(stream instanceof textStreams.TextWriter, - "open(w) should return a TextWriter"); - stream.close(); - - stream = file.open(filename, "wb"); - assert.ok(stream instanceof byteStreams.ByteWriter, - "open(wb) should return a ByteWriter"); - stream.close(); - - stream = file.open(filename); - assert.ok(stream instanceof textStreams.TextReader, - "open() should return a TextReader"); - stream.close(); - - stream = file.open(filename, "r"); - assert.ok(stream instanceof textStreams.TextReader, - "open(r) should return a TextReader"); - stream.close(); - - stream = file.open(filename, "b"); - assert.ok(stream instanceof byteStreams.ByteReader, - "open(b) should return a ByteReader"); - stream.close(); - - stream = file.open(filename, "rb"); - assert.ok(stream instanceof byteStreams.ByteReader, - "open(rb) should return a ByteReader"); - stream.close(); - - file.remove(filename); -}; - -exports.testMkpathRmdir = function (assert) { - let basePath = profilePath; - let dirs = []; - for (let i = 0; i < 3; i++) - dirs.push("test-file-dir"); - - let paths = []; - for (let i = 0; i < dirs.length; i++) { - let args = [basePath].concat(dirs.slice(0, i + 1)); - paths.unshift(file.join.apply(null, args)); - } - - for (let i = 0; i < paths.length; i++) { - assert.ok(!file.exists(paths[i]), - "Sanity check: path should not exist: " + paths[i]); - } - - file.mkpath(paths[0]); - assert.ok(file.exists(paths[0]), "mkpath should create path: " + paths[0]); - - for (let i = 0; i < paths.length; i++) { - file.rmdir(paths[i]); - assert.ok(!file.exists(paths[i]), - "rmdir should remove path: " + paths[i]); - } -}; - -exports.testMkpathTwice = function (assert) { - let dir = profilePath; - let path = file.join(dir, "test-file-dir"); - assert.ok(!file.exists(path), - "Sanity check: path should not exist: " + path); - file.mkpath(path); - assert.ok(file.exists(path), "mkpath should create path: " + path); - file.mkpath(path); - assert.ok(file.exists(path), - "After second mkpath, path should still exist: " + path); - file.rmdir(path); - assert.ok(!file.exists(path), "rmdir should remove path: " + path); -}; - -exports.testMkpathExistingNondirectory = function (assert) { - var fname = file.join(profilePath, 'conflict.txt'); - file.open(fname, "w").close(); - assert.ok(file.exists(fname), "File should exist"); - assert.throws(() => file.mkpath(fname), - /^The path already exists and is not a directory: .+$/, - "mkpath on file should raise error"); - file.remove(fname); -}; - -exports.testRmdirNondirectory = function (assert) { - var fname = file.join(profilePath, 'not-a-dir') - file.open(fname, "w").close(); - assert.ok(file.exists(fname), "File should exist"); - assert.throws(function() { - file.rmdir(fname); - }, ERRORS.NOT_A_DIRECTORY, "rmdir on file should raise error"); - file.remove(fname); - assert.ok(!file.exists(fname), "File should not exist"); - assert.throws(() => file.rmdir(fname), - ERRORS.FILE_NOT_FOUND, - "rmdir on non-existing file should raise error"); -}; - -exports.testRmdirNonempty = function (assert) { - let dir = profilePath; - let path = file.join(dir, "test-file-dir"); - assert.ok(!file.exists(path), - "Sanity check: path should not exist: " + path); - file.mkpath(path); - let filePath = file.join(path, "file"); - file.open(filePath, "w").close(); - assert.ok(file.exists(filePath), - "Sanity check: path should exist: " + filePath); - assert.throws(() => file.rmdir(path), - /^The directory is not empty: .+$/, - "rmdir on non-empty directory should raise error"); - file.remove(filePath); - file.rmdir(path); - assert.ok(!file.exists(path), "Path should not exist"); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-frame-utils.js b/addon-sdk/source/test/test-frame-utils.js deleted file mode 100644 index 501f93c87c..0000000000 --- a/addon-sdk/source/test/test-frame-utils.js +++ /dev/null @@ -1,59 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { create } = require('sdk/frame/utils'); -const { open, close } = require('sdk/window/helpers'); - -exports['test frame creation'] = function(assert, done) { - open('data:text/html;charset=utf-8,Window').then(function (window) { - let frame = create(window.document); - - assert.equal(frame.getAttribute('type'), 'content', - 'frame type is content'); - assert.ok(frame.contentWindow, 'frame has contentWindow'); - assert.equal(frame.contentWindow.location.href, 'about:blank', - 'by default "about:blank" is loaded'); - assert.equal(frame.docShell.allowAuth, false, 'auth disabled by default'); - assert.equal(frame.docShell.allowJavascript, false, 'js disabled by default'); - assert.equal(frame.docShell.allowPlugins, false, - 'plugins disabled by default'); - close(window).then(done); - }); -}; - -exports['test fram has js disabled by default'] = function(assert, done) { - open('data:text/html;charset=utf-8,window').then(function (window) { - let frame = create(window.document, { - uri: 'data:text/html;charset=utf-8,', - }); - frame.contentWindow.addEventListener('DOMContentLoaded', function ready() { - frame.contentWindow.removeEventListener('DOMContentLoaded', ready, false); - assert.ok(!~frame.contentDocument.documentElement.innerHTML.indexOf('JS'), - 'JS was executed'); - - close(window).then(done); - }, false); - }); -}; - -exports['test frame with js enabled'] = function(assert, done) { - open('data:text/html;charset=utf-8,window').then(function (window) { - let frame = create(window.document, { - uri: 'data:text/html;charset=utf-8,', - allowJavascript: true - }); - frame.contentWindow.addEventListener('DOMContentLoaded', function ready() { - frame.contentWindow.removeEventListener('DOMContentLoaded', ready, false); - assert.ok(~frame.contentDocument.documentElement.innerHTML.indexOf('JS'), - 'JS was executed'); - - close(window).then(done); - }, false); - }); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-framescript-manager.js b/addon-sdk/source/test/test-framescript-manager.js deleted file mode 100644 index 442f71edae..0000000000 --- a/addon-sdk/source/test/test-framescript-manager.js +++ /dev/null @@ -1,32 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const {loadModule} = require("framescript/manager"); -const {withTab, receiveMessage} = require("./util"); -const {getBrowserForTab} = require("sdk/tabs/utils"); - -exports.testLoadModule = withTab(function*(assert, tab) { - const {messageManager} = getBrowserForTab(tab); - - loadModule(messageManager, - require.resolve("./framescript-manager/frame-script"), - true, - "onInit"); - - const message = yield receiveMessage(messageManager, "framescript-manager/ready"); - - assert.deepEqual(message.data, {state: "ready"}, - "received ready message from the loaded module"); - - messageManager.sendAsyncMessage("framescript-manager/ping", {x: 1}); - - const pong = yield receiveMessage(messageManager, "framescript-manager/pong"); - - assert.deepEqual(pong.data, {x: 1}, - "received pong back"); -}, "data:text/html,

Message Manager

"); - - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-framescript-util.js b/addon-sdk/source/test/test-framescript-util.js deleted file mode 100644 index 0a55bcbf6c..0000000000 --- a/addon-sdk/source/test/test-framescript-util.js +++ /dev/null @@ -1,45 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const {loadModule} = require("framescript/manager"); -const {withTab, receiveMessage} = require("./util"); -const {getBrowserForTab} = require("sdk/tabs/utils"); - -exports["test windowToMessageManager"] = withTab(function*(assert, tab) { - const {messageManager} = getBrowserForTab(tab); - - loadModule(messageManager, - require.resolve("./framescript-util/frame-script"), - true, - "onInit"); - - messageManager.sendAsyncMessage("framescript-util/window/request"); - - const response = yield receiveMessage(messageManager, - "framescript-util/window/response"); - - assert.deepEqual(response.data, {window: true}, - "got response"); -}, "data:text/html,

Window to Message Manager

"); - - -exports["test nodeToMessageManager"] = withTab(function*(assert, tab) { - const {messageManager} = getBrowserForTab(tab); - - loadModule(messageManager, - require.resolve("./framescript-util/frame-script"), - true, - "onInit"); - - messageManager.sendAsyncMessage("framescript-util/node/request", "h1"); - - const response = yield receiveMessage(messageManager, - "framescript-util/node/response"); - - assert.deepEqual(response.data, {node: true}, - "got response"); -}, "data:text/html,

Node to Message Manager

"); - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-fs.js b/addon-sdk/source/test/test-fs.js deleted file mode 100644 index ed26ca3e39..0000000000 --- a/addon-sdk/source/test/test-fs.js +++ /dev/null @@ -1,621 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const { pathFor, platform } = require("sdk/system"); -const fs = require("sdk/io/fs"); -const url = require("sdk/url"); -const path = require("sdk/fs/path"); -const { defer } = require("sdk/core/promise"); -const { Buffer } = require("sdk/io/buffer"); -const { is } = require("sdk/system/xul-app"); - -// Use profile directory to list / read / write files. -const profilePath = pathFor("ProfD"); -const fileNameInProfile = "compatibility.ini"; -const dirNameInProfile = "extensions"; -const filePathInProfile = path.join(profilePath, fileNameInProfile); -const dirPathInProfile = path.join(profilePath, dirNameInProfile); -const mkdirPath = path.join(profilePath, "sdk-fixture-mkdir"); -const writePath = path.join(profilePath, "sdk-fixture-writeFile"); -const unlinkPath = path.join(profilePath, "sdk-fixture-unlink"); -const truncatePath = path.join(profilePath, "sdk-fixture-truncate"); -const renameFromPath = path.join(profilePath, "sdk-fixture-rename-from"); -const renameToPath = path.join(profilePath, "sdk-fixture-rename-to"); -const chmodPath = path.join(profilePath, "sdk-fixture-chmod"); - -const profileEntries = [ - "compatibility.ini", - "extensions", - "prefs.js" - // There are likely to be a lot more files but we can"t really - // on consistent list so we limit to this. -]; - -const isWindows = platform.indexOf('win') === 0; - -exports["test readdir"] = function(assert, end) { - var async = false; - fs.readdir(profilePath, function(error, entries) { - assert.ok(async, "readdir is async"); - assert.ok(!error, "there is no error when reading directory"); - assert.ok(profileEntries.length <= entries.length, - "got at least number of entries we expect"); - assert.ok(profileEntries.every(function(entry) { - return entries.indexOf(entry) >= 0; - }), "all profiles are present"); - end(); - }); - - async = true; -}; - -exports["test readdir error"] = function(assert, end) { - var async = false; - var path = profilePath + "-does-not-exists"; - fs.readdir(path, function(error, entries) { - assert.ok(async, "readdir is async"); - assert.equal(error.message, "ENOENT, readdir " + path); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - end(); - }); - - async = true; -}; - -exports["test readdirSync"] = function(assert) { - var async = false; - var entries = fs.readdirSync(profilePath); - assert.ok(profileEntries.length <= entries.length, - "got at least number of entries we expect"); - assert.ok(profileEntries.every(function(entry) { - return entries.indexOf(entry) >= 0; - }), "all profiles are present"); -}; - -exports["test readdirSync error"] = function(assert) { - var async = false; - var path = profilePath + "-does-not-exists"; - try { - fs.readdirSync(path); - assert.fail(Error("No error was thrown")); - } catch (error) { - assert.equal(error.message, "ENOENT, readdir " + path); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - } -}; - -exports["test readFile"] = function(assert, end) { - let async = false; - fs.readFile(filePathInProfile, function(error, content) { - assert.ok(async, "readFile is async"); - assert.ok(!error, "error is falsy"); - - assert.ok(Buffer.isBuffer(content), "readFile returns buffer"); - assert.ok(typeof(content.length) === "number", "buffer has length"); - assert.ok(content.toString().indexOf("[Compatibility]") >= 0, - "content contains expected data"); - end(); - }); - async = true; -}; - -exports["test readFile error"] = function(assert, end) { - let async = false; - let path = filePathInProfile + "-does-not-exists"; - fs.readFile(path, function(error, content) { - assert.ok(async, "readFile is async"); - assert.equal(error.message, "ENOENT, open " + path); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - - end(); - }); - async = true; -}; - -exports["test readFileSync not implemented"] = function(assert) { - let buffer = fs.readFileSync(filePathInProfile); - assert.ok(buffer.toString().indexOf("[Compatibility]") >= 0, - "read content"); -}; - -exports["test fs.stat file"] = function(assert, end) { - let async = false; - let path = filePathInProfile; - fs.stat(path, function(error, stat) { - assert.ok(async, "fs.stat is async"); - assert.ok(!error, "error is falsy"); - assert.ok(!stat.isDirectory(), "not a dir"); - assert.ok(stat.isFile(), "is a file"); - assert.ok(!stat.isSymbolicLink(), "isn't a symlink"); - assert.ok(typeof(stat.size) === "number", "size is a number"); - assert.ok(stat.exists === true, "file exists"); - assert.ok(typeof(stat.isBlockDevice()) === "boolean"); - assert.ok(typeof(stat.isCharacterDevice()) === "boolean"); - assert.ok(typeof(stat.isFIFO()) === "boolean"); - assert.ok(typeof(stat.isSocket()) === "boolean"); - assert.ok(typeof(stat.hidden) === "boolean"); - assert.ok(typeof(stat.writable) === "boolean") - assert.ok(stat.readable === true); - - end(); - }); - async = true; -}; - -exports["test fs.stat dir"] = function(assert, end) { - let async = false; - let path = dirPathInProfile; - fs.stat(path, function(error, stat) { - assert.ok(async, "fs.stat is async"); - assert.ok(!error, "error is falsy"); - assert.ok(stat.isDirectory(), "is a dir"); - assert.ok(!stat.isFile(), "not a file"); - assert.ok(!stat.isSymbolicLink(), "isn't a symlink"); - assert.ok(typeof(stat.size) === "number", "size is a number"); - assert.ok(stat.exists === true, "file exists"); - assert.ok(typeof(stat.isBlockDevice()) === "boolean"); - assert.ok(typeof(stat.isCharacterDevice()) === "boolean"); - assert.ok(typeof(stat.isFIFO()) === "boolean"); - assert.ok(typeof(stat.isSocket()) === "boolean"); - assert.ok(typeof(stat.hidden) === "boolean"); - assert.ok(typeof(stat.writable) === "boolean") - assert.ok(typeof(stat.readable) === "boolean"); - - end(); - }); - async = true; -}; - -exports["test fs.stat error"] = function(assert, end) { - let async = false; - let path = filePathInProfile + "-does-not-exists"; - fs.stat(path, function(error, stat) { - assert.ok(async, "fs.stat is async"); - assert.equal(error.message, "ENOENT, stat " + path); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - - end(); - }); - async = true; -}; - -exports["test fs.exists NO"] = function(assert, end) { - let async = false - let path = filePathInProfile + "-does-not-exists"; - fs.exists(path, function(error, value) { - assert.ok(async, "fs.exists is async"); - assert.ok(!error, "error is falsy"); - assert.ok(!value, "file does not exists"); - end(); - }); - async = true; -}; - -exports["test fs.exists YES"] = function(assert, end) { - let async = false - let path = filePathInProfile - fs.exists(path, function(error, value) { - assert.ok(async, "fs.exists is async"); - assert.ok(!error, "error is falsy"); - assert.ok(value, "file exists"); - end(); - }); - async = true; -}; - -exports["test fs.exists NO"] = function(assert, end) { - let async = false - let path = filePathInProfile + "-does-not-exists"; - fs.exists(path, function(error, value) { - assert.ok(async, "fs.exists is async"); - assert.ok(!error, "error is falsy"); - assert.ok(!value, "file does not exists"); - end(); - }); - async = true; -}; - -exports["test fs.existsSync"] = function(assert) { - let path = filePathInProfile - assert.equal(fs.existsSync(path), true, "exists"); - assert.equal(fs.existsSync(path + "-does-not-exists"), false, "exists"); -}; - -exports["test fs.mkdirSync fs.rmdirSync"] = function(assert) { - let path = mkdirPath; - - assert.equal(fs.existsSync(path), false, "does not exists"); - fs.mkdirSync(path); - assert.equal(fs.existsSync(path), true, "dir was created"); - try { - fs.mkdirSync(path); - assert.fail(Error("mkdir on existing should throw")); - } catch (error) { - assert.equal(error.message, "EEXIST, mkdir " + path); - assert.equal(error.code, "EEXIST", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 47, "error has a errno"); - } - fs.rmdirSync(path); - assert.equal(fs.existsSync(path), false, "dir was removed"); -}; - -exports["test fs.mkdir"] = function(assert, end) { - let path = mkdirPath; - - if (!fs.existsSync(path)) { - let async = false; - fs.mkdir(path, function(error) { - assert.ok(async, "mkdir is async"); - assert.ok(!error, "no error"); - assert.equal(fs.existsSync(path), true, "dir was created"); - fs.rmdirSync(path); - assert.equal(fs.existsSync(path), false, "dir was removed"); - end(); - }); - async = true; - } -}; - -exports["test fs.mkdir error"] = function(assert, end) { - let path = mkdirPath; - - if (!fs.existsSync(path)) { - fs.mkdirSync(path); - let async = false; - fs.mkdir(path, function(error) { - assert.ok(async, "mkdir is async"); - assert.equal(error.message, "EEXIST, mkdir " + path); - assert.equal(error.code, "EEXIST", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 47, "error has a errno"); - fs.rmdirSync(path); - assert.equal(fs.existsSync(path), false, "dir was removed"); - end(); - }); - async = true; - } -}; - -exports["test fs.rmdir"] = function(assert, end) { - let path = mkdirPath; - - if (!fs.existsSync(path)) { - fs.mkdirSync(path); - assert.equal(fs.existsSync(path), true, "dir exists"); - let async = false; - fs.rmdir(path, function(error) { - assert.ok(async, "mkdir is async"); - assert.ok(!error, "no error"); - assert.equal(fs.existsSync(path), false, "dir was removed"); - end(); - }); - async = true; - } -}; - - -exports["test fs.rmdir error"] = function(assert, end) { - let path = mkdirPath; - - if (!fs.existsSync(path)) { - assert.equal(fs.existsSync(path), false, "dir doesn't exists"); - let async = false; - fs.rmdir(path, function(error) { - assert.ok(async, "mkdir is async"); - assert.equal(error.message, "ENOENT, remove " + path); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - assert.equal(fs.existsSync(path), false, "dir is removed"); - end(); - }); - async = true; - } -}; - -exports["test fs.truncateSync fs.unlinkSync"] = function(assert) { - let path = truncatePath; - - assert.equal(fs.existsSync(path), false, "does not exists"); - fs.truncateSync(path); - assert.equal(fs.existsSync(path), true, "file was created"); - fs.truncateSync(path); - fs.unlinkSync(path); - assert.equal(fs.existsSync(path), false, "file was removed"); -}; - - -exports["test fs.truncate"] = function(assert, end) { - let path = truncatePath; - if (!fs.existsSync(path)) { - let async = false; - fs.truncate(path, 0, function(error) { - assert.ok(async, "truncate is async"); - assert.ok(!error, "no error"); - assert.equal(fs.existsSync(path), true, "file was created"); - fs.unlinkSync(path); - assert.equal(fs.existsSync(path), false, "file was removed"); - end(); - }) - async = true; - } -}; - -exports["test fs.unlink"] = function(assert, end) { - let path = unlinkPath; - let async = false; - assert.ok(!fs.existsSync(path), "file doesn't exists yet"); - fs.truncateSync(path, 0); - assert.ok(fs.existsSync(path), "file was created"); - fs.unlink(path, function(error) { - assert.ok(async, "fs.unlink is async"); - assert.ok(!error, "error is falsy"); - assert.ok(!fs.existsSync(path), "file was removed"); - end(); - }); - async = true; -}; - -exports["test fs.unlink error"] = function(assert, end) { - let path = unlinkPath; - let async = false; - assert.ok(!fs.existsSync(path), "file doesn't exists yet"); - fs.unlink(path, function(error) { - assert.ok(async, "fs.unlink is async"); - assert.equal(error.message, "ENOENT, remove " + path); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, path, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - end(); - }); - async = true; -}; - -exports["test fs.rename"] = function(assert, end) { - let fromPath = renameFromPath; - let toPath = renameToPath; - - fs.truncateSync(fromPath); - assert.ok(fs.existsSync(fromPath), "source file exists"); - assert.ok(!fs.existsSync(toPath), "destination doesn't exists"); - var async = false; - fs.rename(fromPath, toPath, function(error) { - assert.ok(async, "fs.rename is async"); - assert.ok(!error, "error is falsy"); - assert.ok(!fs.existsSync(fromPath), "source path no longer exists"); - assert.ok(fs.existsSync(toPath), "destination file exists"); - fs.unlinkSync(toPath); - assert.ok(!fs.existsSync(toPath), "cleaned up properly"); - end(); - }); - async = true; -}; - -exports["test fs.rename (missing source file)"] = function(assert, end) { - let fromPath = renameFromPath; - let toPath = renameToPath; - - assert.ok(!fs.existsSync(fromPath), "source file doesn't exists"); - assert.ok(!fs.existsSync(toPath), "destination doesn't exists"); - var async = false; - fs.rename(fromPath, toPath, function(error) { - assert.ok(async, "fs.rename is async"); - assert.equal(error.message, "ENOENT, rename " + fromPath); - assert.equal(error.code, "ENOENT", "error has a code"); - assert.equal(error.path, fromPath, "error has a path"); - assert.equal(error.errno, 34, "error has a errno"); - end(); - }); - async = true; -}; - -exports["test fs.rename (existing target file)"] = function(assert, end) { - let fromPath = renameFromPath; - let toPath = renameToPath; - - fs.truncateSync(fromPath); - fs.truncateSync(toPath); - assert.ok(fs.existsSync(fromPath), "source file exists"); - assert.ok(fs.existsSync(toPath), "destination file exists"); - var async = false; - fs.rename(fromPath, toPath, function(error) { - assert.ok(async, "fs.rename is async"); - assert.ok(!error, "error is falsy"); - assert.ok(!fs.existsSync(fromPath), "source path no longer exists"); - assert.ok(fs.existsSync(toPath), "destination file exists"); - fs.unlinkSync(toPath); - assert.ok(!fs.existsSync(toPath), "cleaned up properly"); - end(); - }); - async = true; -}; - -exports["test fs.writeFile"] = function(assert, end) { - let path = writePath; - let content = ["hello world", - "this is some text"].join("\n"); - - var async = false; - fs.writeFile(path, content, function(error) { - assert.ok(async, "fs write is async"); - assert.ok(!error, "error is falsy"); - assert.ok(fs.existsSync(path), "file was created"); - assert.equal(fs.readFileSync(path).toString(), - content, - "contet was written"); - fs.unlinkSync(path); - assert.ok(!fs.exists(path), "file was removed"); - - end(); - }); - async = true; -}; - -exports["test fs.writeFile (with large files)"] = function(assert, end) { - let path = writePath; - let content = ""; - - for (var i = 0; i < 100000; i++) { - content += "buffer\n"; - } - - var async = false; - fs.writeFile(path, content, function(error) { - assert.ok(async, "fs write is async"); - assert.ok(!error, "error is falsy"); - assert.ok(fs.existsSync(path), "file was created"); - assert.equal(fs.readFileSync(path).toString(), - content, - "contet was written"); - fs.unlinkSync(path); - assert.ok(!fs.exists(path), "file was removed"); - - end(); - }); - async = true; -}; - -exports["test fs.writeFile error"] = function (assert, done) { - try { - fs.writeFile({}, 'content', function (err) { - assert.fail('Error thrown from TypeError should not be caught'); - }); - } catch (e) { - assert.ok(e, - 'writeFile with a non-string error should not be caught'); - assert.equal(e.name, 'TypeError', 'error should be TypeError'); - } - fs.writeFile('not/a/valid/path', 'content', function (err) { - assert.ok(err, 'error caught and handled in callback'); - done(); - }); -}; - -exports["test fs.chmod"] = function (assert, done) { - let content = ["hej från sverige"]; - - fs.writeFile(chmodPath, content, function (err) { - testPerm("0755")() - .then(testPerm("0777")) - .then(testPerm("0666")) - .then(testPerm(0o511)) - .then(testPerm(0o200)) - .then(testPerm("0040")) - .then(testPerm("0000")) - .then(testPermSync(0o777)) - .then(testPermSync(0o666)) - .then(testPermSync("0511")) - .then(testPermSync("0200")) - .then(testPermSync("0040")) - .then(testPermSync("0000")) - .then(() => { - assert.pass("Successful chmod passes"); - }, assert.fail) - // Test invalid paths - .then(() => chmod("not-a-valid-file", 0o755)) - .then(assert.fail, (err) => { - checkPermError(err, "not-a-valid-file"); - }) - .then(() => chmod("not-a-valid-file", 0o755, "sync")) - .then(assert.fail, (err) => { - checkPermError(err, "not-a-valid-file"); - }) - // Test invalid files - .then(() => chmod("resource://not-a-real-file", 0o755)) - .then(assert.fail, (err) => { - checkPermError(err, "resource://not-a-real-file"); - }) - .then(() => chmod("resource://not-a-real-file", 0o755, 'sync')) - .then(assert.fail, (err) => { - checkPermError(err, "resource://not-a-real-file"); - }) - .then(done, assert.fail); - }); - - function checkPermError (err, path) { - assert.equal(err.message, "ENOENT, chmod " + path); - assert.equal(err.code, "ENOENT", "error has a code"); - assert.equal(err.path, path, "error has a path"); - assert.equal(err.errno, 34, "error has a errno"); - } - - function chmod (path, mode, sync) { - let { promise, resolve, reject } = defer(); - if (!sync) { - fs.chmod(path, mode, (err) => { - if (err) reject(err); - else resolve(); - }); - } else { - fs.chmodSync(path, mode); - resolve(); - } - return promise; - } - - function testPerm (mode, sync) { - return function () { - return chmod(chmodPath, mode, sync) - .then(() => getPerm(chmodPath)) - .then(perm => { - let nMode = normalizeMode(mode); - if (isWindows) - assert.equal(perm, nMode, - "mode correctly set to " + mode + " (" + nMode + " on windows)"); - else - assert.equal(perm, nMode, "mode correctly set to " + nMode); - }); - }; - } - - function testPermSync (mode) { - return testPerm(mode, true); - } - - function getPerm (path) { - let { promise, resolve, reject } = defer(); - fs.stat(path, function (err, stat) { - if (err) reject(err); - else resolve(stat.mode); - }); - return promise; - } - - /* - * Converts a unix mode `0755` into a Windows version of unix permissions - */ - function normalizeMode (mode) { - if (typeof mode === "string") - mode = parseInt(mode, 8); - - if (!isWindows) - return mode; - - var ANY_READ = 0o444; - var ANY_WRITE = 0o222; - var winMode = 0; - - // On Windows, if WRITE is true, then READ is also true - if (mode & ANY_WRITE) - winMode |= ANY_WRITE | ANY_READ; - // Minimum permissions are READ for Windows - else - winMode |= ANY_READ; - - return winMode; - } -}; - -require("test").run(exports); diff --git a/addon-sdk/source/test/test-functional.js b/addon-sdk/source/test/test-functional.js deleted file mode 100644 index 02ae15fc65..0000000000 --- a/addon-sdk/source/test/test-functional.js +++ /dev/null @@ -1,463 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { setTimeout } = require('sdk/timers'); -const utils = require('sdk/lang/functional'); -const { invoke, defer, partial, compose, memoize, once, is, isnt, - delay, wrap, curry, chainable, field, query, isInstance, debounce, throttle -} = utils; -const { LoaderWithHookedConsole } = require('sdk/test/loader'); - -exports['test forwardApply'] = function(assert) { - function sum(b, c) { return this.a + b + c; } - assert.equal(invoke(sum, [2, 3], { a: 1 }), 6, - 'passed arguments and pseoude-variable are used'); - - assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7, - 'bounded `this` pseoudo variable is used'); -}; - -exports['test deferred function'] = function(assert, done) { - let nextTurn = false; - function sum(b, c) { - assert.ok(nextTurn, 'enqueued is called in next turn of event loop'); - assert.equal(this.a + b + c, 6, - 'passed arguments an pseoude-variable are used'); - done(); - } - - let fixture = { a: 1, method: defer(sum) }; - fixture.method(2, 3); - nextTurn = true; -}; - -exports['test partial function'] = function(assert) { - function sum(b, c) { return this.a + b + c; } - - let foo = { a : 5 }; - - foo.sum7 = partial(sum, 7); - foo.sum8and4 = partial(sum, 8, 4); - - assert.equal(foo.sum7(2), 14, 'partial one arguments works'); - - assert.equal(foo.sum8and4(), 17, 'partial both arguments works'); -}; - -exports["test curry defined numeber of arguments"] = function(assert) { - var sum = curry(function(a, b, c) { - return a + b + c; - }); - - assert.equal(sum(2, 2, 1), 5, "sum(2, 2, 1) => 5"); - assert.equal(sum(2, 4)(1), 7, "sum(2, 4)(1) => 7"); - assert.equal(sum(2)(4, 2), 8, "sum(2)(4, 2) => 8"); - assert.equal(sum(2)(4)(3), 9, "sum(2)(4)(3) => 9"); -}; - -exports['test compose'] = function(assert) { - let greet = function(name) { return 'hi: ' + name; }; - let exclaim = function(sentence) { return sentence + '!'; }; - - assert.equal(compose(exclaim, greet)('moe'), 'hi: moe!', - 'can compose a function that takes another'); - - assert.equal(compose(greet, exclaim)('moe'), 'hi: moe!', - 'in this case, the functions are also commutative'); - - let target = { - name: 'Joe', - greet: compose(function exclaim(sentence) { - return sentence + '!'; - }, function(title) { - return 'hi : ' + title + ' ' + this.name; - }) - }; - - assert.equal(target.greet('Mr'), 'hi : Mr Joe!', - 'this can be passed in'); - assert.equal(target.greet.call({ name: 'Alex' }, 'Dr'), 'hi : Dr Alex!', - 'this can be applied'); - - let single = compose(function(value) { - return value + ':suffix'; - }); - - assert.equal(single('text'), 'text:suffix', 'works with single function'); - - let identity = compose(); - assert.equal(identity('bla'), 'bla', 'works with zero functions'); -}; - -exports['test wrap'] = function(assert) { - let greet = function(name) { return 'hi: ' + name; }; - let backwards = wrap(greet, function(f, name) { - return f(name) + ' ' + name.split('').reverse().join(''); - }); - - assert.equal(backwards('moe'), 'hi: moe eom', - 'wrapped the saluation function'); - - let inner = function () { return 'Hello '; }; - let target = { - name: 'Matteo', - hi: wrap(inner, function(f) { return f() + this.name; }) - }; - - assert.equal(target.hi(), 'Hello Matteo', 'works with this'); - - function noop() { } - let wrapped = wrap(noop, function(f) { - return Array.slice(arguments); - }); - - let actual = wrapped([ 'whats', 'your' ], 'vector', 'victor'); - assert.deepEqual(actual, [ noop, ['whats', 'your'], 'vector', 'victor' ], - 'works with fancy stuff'); -}; - -exports['test memoize'] = function(assert) { - const fib = n => n < 2 ? n : fib(n - 1) + fib(n - 2); - let fibnitro = memoize(fib); - - assert.equal(fib(10), 55, - 'a memoized version of fibonacci produces identical results'); - assert.equal(fibnitro(10), 55, - 'a memoized version of fibonacci produces identical results'); - - function o(key, value) { return value; } - let oo = memoize(o), v1 = {}, v2 = {}; - - - assert.equal(oo(1, v1), v1, 'returns value back'); - assert.equal(oo(1, v2), v1, 'memoized by a first argument'); - assert.equal(oo(2, v2), v2, 'returns back value if not memoized'); - assert.equal(oo(2), v2, 'memoized new value'); - assert.notEqual(oo(1), oo(2), 'values do not override'); - assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized'); - - let get = memoize(function(attribute) { return this[attribute]; }); - let target = { name: 'Bob', get: get }; - - assert.equal(target.get('name'), 'Bob', 'has correct `this`'); - assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob', - 'name is memoized'); - assert.equal(get('name'), 'Bob', 'once memoized can be called without this'); -}; - -exports['test delay'] = function(assert, done) { - let delayed = false; - delay(function() { - assert.ok(delayed, 'delayed the function'); - done(); - }, 1); - delayed = true; -}; - -exports['test delay with this'] = function(assert, done) { - let context = {}; - delay.call(context, function(name) { - assert.equal(this, context, 'this was passed in'); - assert.equal(name, 'Tom', 'argument was passed in'); - done(); - }, 10, 'Tom'); -}; - -exports['test once'] = function(assert) { - let n = 0; - let increment = once(function() { n ++; }); - - increment(); - increment(); - - assert.equal(n, 1, 'only incremented once'); - - let target = { - state: 0, - update: once(function() { - return this.state ++; - }) - }; - - target.update(); - target.update(); - - assert.equal(target.state, 1, 'this was passed in and called only once'); -}; - -exports['test once with argument'] = function(assert) { - let n = 0; - let increment = once(a => n++); - - increment(); - increment('foo'); - - assert.equal(n, 1, 'only incremented once'); - - increment(); - increment('foo'); - - assert.equal(n, 1, 'only incremented once'); -}; - -exports['test complement'] = assert => { - let { complement } = require("sdk/lang/functional"); - - let isOdd = x => Boolean(x % 2); - - assert.equal(isOdd(1), true); - assert.equal(isOdd(2), false); - - let isEven = complement(isOdd); - - assert.equal(isEven(1), false); - assert.equal(isEven(2), true); - - let foo = {}; - let isFoo = function() { return this === foo; }; - let insntFoo = complement(isFoo); - - assert.equal(insntFoo.call(foo), false); - assert.equal(insntFoo.call({}), true); -}; - -exports['test constant'] = assert => { - let { constant } = require("sdk/lang/functional"); - - let one = constant(1); - - assert.equal(one(1), 1); - assert.equal(one(2), 1); -}; - -exports['test apply'] = assert => { - let { apply } = require("sdk/lang/functional"); - - let dashify = (...args) => args.join("-"); - - assert.equal(apply(dashify, 1, [2, 3]), "1-2-3"); - assert.equal(apply(dashify, "a"), "a"); - assert.equal(apply(dashify, ["a", "b"]), "a-b"); - assert.equal(apply(dashify, ["a", "b"], "c"), "a,b-c"); - assert.equal(apply(dashify, [1, 2], [3, 4]), "1,2-3-4"); -}; - -exports['test flip'] = assert => { - let { flip } = require("sdk/lang/functional"); - - let append = (left, right) => left + " " + right; - let prepend = flip(append); - - assert.equal(append("hello", "world"), "hello world"); - assert.equal(prepend("hello", "world"), "world hello"); - - let wrap = function(left, right) { - return left + " " + this + " " + right; - }; - let invertWrap = flip(wrap); - - assert.equal(wrap.call("@", "hello", "world"), "hello @ world"); - assert.equal(invertWrap.call("@", "hello", "world"), "world @ hello"); - - let reverse = flip((...args) => args); - - assert.deepEqual(reverse(1, 2, 3, 4), [4, 3, 2, 1]); - assert.deepEqual(reverse(1), [1]); - assert.deepEqual(reverse(), []); - - // currying still works - let prependr = curry(prepend); - - assert.equal(prependr("hello", "world"), "world hello"); - assert.equal(prependr("hello")("world"), "world hello"); -}; - -exports["test when"] = assert => { - let { when } = require("sdk/lang/functional"); - - let areNums = (...xs) => xs.every(x => typeof(x) === "number"); - - let sum = when(areNums, (...xs) => xs.reduce((y, x) => x + y, 0)); - - assert.equal(sum(1, 2, 3), 6); - assert.equal(sum(1, 2, "3"), undefined); - - let multiply = when(areNums, - (...xs) => xs.reduce((y, x) => x * y, 1), - (...xs) => xs); - - assert.equal(multiply(2), 2); - assert.equal(multiply(2, 3), 6); - assert.deepEqual(multiply(2, "4"), [2, "4"]); - - function Point(x, y) { - this.x = x; - this.y = y; - } - - let isPoint = x => x instanceof Point; - - let inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1)); - - assert.equal(inc({}), undefined); - assert.deepEqual(inc(new Point(0, 0)), { x: 1, y: 1 }); - - let axis = when(isPoint, - ({ x, y }) => [x, y], - _ => [0, 0]); - - assert.deepEqual(axis(new Point(1, 4)), [1, 4]); - assert.deepEqual(axis({ foo: "bar" }), [0, 0]); -}; - -exports["test chainable"] = function(assert) { - let Player = function () { this.volume = 5; }; - Player.prototype = { - setBand: chainable(function (band) { return (this.band = band); }), - incVolume: chainable(function () { return this.volume++; }) - }; - let player = new Player(); - player - .setBand('Animals As Leaders') - .incVolume().incVolume().incVolume().incVolume().incVolume().incVolume(); - - assert.equal(player.band, 'Animals As Leaders', 'passes arguments into chained'); - assert.equal(player.volume, 11, 'accepts no arguments in chain'); -}; - -exports["test field"] = assert => { - let Num = field("constructor", 0); - assert.equal(Num.name, Number.name); - assert.ok(typeof(Num), "function"); - - let x = field("x"); - - [ - [field("foo", { foo: 1 }), 1], - [field("foo")({ foo: 1 }), 1], - [field("bar", {}), undefined], - [field("bar")({}), undefined], - [field("hey", undefined), undefined], - [field("hey")(undefined), undefined], - [field("how", null), null], - [field("how")(null), null], - [x(1), undefined], - [x(undefined), undefined], - [x(null), null], - [x({ x: 1 }), 1], - [x({ x: 2 }), 2], - ].forEach(([actual, expected]) => assert.equal(actual, expected)); -}; - -exports["test query"] = assert => { - let Num = query("constructor", 0); - assert.equal(Num.name, Number.name); - assert.ok(typeof(Num), "function"); - - let x = query("x"); - let xy = query("x.y"); - - [ - [query("foo", { foo: 1 }), 1], - [query("foo")({ foo: 1 }), 1], - [query("foo.bar", { foo: { bar: 2 } }), 2], - [query("foo.bar")({ foo: { bar: 2 } }), 2], - [query("foo.bar", { foo: 1 }), undefined], - [query("foo.bar")({ foo: 1 }), undefined], - [x(1), undefined], - [x(undefined), undefined], - [x(null), null], - [x({ x: 1 }), 1], - [x({ x: 2 }), 2], - [xy(1), undefined], - [xy(undefined), undefined], - [xy(null), null], - [xy({ x: 1 }), undefined], - [xy({ x: 2 }), undefined], - [xy({ x: { y: 1 } }), 1], - [xy({ x: { y: 2 } }), 2] - ].forEach(([actual, expected]) => assert.equal(actual, expected)); -}; - -exports["test isInstance"] = assert => { - function X() {} - function Y() {} - let isX = isInstance(X); - - [ - isInstance(X, new X()), - isInstance(X)(new X()), - !isInstance(X, new Y()), - !isInstance(X)(new Y()), - isX(new X()), - !isX(new Y()) - ].forEach(x => assert.ok(x)); -}; - -exports["test is"] = assert => { - - assert.deepEqual([ 1, 0, 1, 0, 1 ].map(is(1)), - [ true, false, true, false, true ], - "is can be partially applied"); - - assert.ok(is(1, 1)); - assert.ok(!is({}, {})); - assert.ok(is()(1)()(1), "is is curried"); - assert.ok(!is()(1)()(2)); -}; - -exports["test isnt"] = assert => { - - assert.deepEqual([ 1, 0, 1, 0, 1 ].map(isnt(0)), - [ true, false, true, false, true ], - "is can be partially applied"); - - assert.ok(!isnt(1, 1)); - assert.ok(isnt({}, {})); - assert.ok(!isnt()(1)()(1)); - assert.ok(isnt()(1)()(2)); -}; - -exports["test debounce"] = (assert, done) => { - let counter = 0; - let fn = debounce(() => counter++, 100); - - new Array(10).join(0).split("").forEach(fn); - - assert.equal(counter, 0, "debounce does not fire immediately"); - setTimeout(() => { - assert.equal(counter, 1, "function called after wait time"); - fn(); - setTimeout(() => { - assert.equal(counter, 2, "function able to be called again after wait"); - done(); - }, 150); - }, 200); -}; - -exports["test throttle"] = (assert, done) => { - let called = 0; - let attempt = 0; - let atleast100ms = false; - let throttledFn = throttle(() => { - called++; - if (called === 2) { - assert.equal(attempt, 10, "called twice, but attempted 10 times"); - fn(); - } - if (called === 3) { - assert.ok(atleast100ms, "atleast 100ms have passed"); - assert.equal(attempt, 11, "called third, waits for delay to happen"); - done(); - } - }, 200); - let fn = () => ++attempt && throttledFn(); - - setTimeout(() => atleast100ms = true, 100); - - new Array(11).join(0).split("").forEach(fn); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-globals.js b/addon-sdk/source/test/test-globals.js deleted file mode 100644 index bc33643677..0000000000 --- a/addon-sdk/source/test/test-globals.js +++ /dev/null @@ -1,30 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -Object.defineProperty(this, 'global', { value: this }); - -exports.testGlobals = function(assert) { - // the only globals in module scope should be: - // module, exports, require, dump, console - assert.equal(typeof module, 'object', 'have "module", good'); - assert.equal(typeof exports, 'object', 'have "exports", good'); - assert.equal(typeof require, 'function', 'have "require", good'); - assert.equal(typeof dump, 'function', 'have "dump", good'); - assert.equal(typeof console, 'object', 'have "console", good'); - - // in particular, these old globals should no longer be present - assert.ok(!('packaging' in global), "no 'packaging', good"); - assert.ok(!('memory' in global), "no 'memory', good"); - assert.ok(/test-globals\.js$/.test(module.uri), - 'should contain filename'); -}; - -exports.testComponent = function (assert) { - assert.throws(() => { - Components; - }, /`Components` is not available/, 'using `Components` throws'); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-heritage.js b/addon-sdk/source/test/test-heritage.js deleted file mode 100644 index e087f3e4dc..0000000000 --- a/addon-sdk/source/test/test-heritage.js +++ /dev/null @@ -1,301 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Class, extend, mix, obscure } = require('sdk/core/heritage'); - -exports['test extend'] = function(assert) { - let ancestor = { a: 1 }; - let descendant = extend(ancestor, { - b: 2, - get c() { return 3 }, - d: function() { return 4 } - }); - - assert.ok(ancestor.isPrototypeOf(descendant), - 'descendant inherits from ancestor'); - assert.ok(descendant.b, 2, 'proprety was implemented'); - assert.ok(descendant.c, 3, 'getter was implemented'); - assert.ok(descendant.d(), 4, 'method was implemented'); - - /* Will be fixed once Bug 674195 is shipped. - assert.ok(Object.isFrozen(descendant), - 'extend returns frozen objects'); - */ -}; - -exports['test mix'] = function(assert) { - let ancestor = { a: 1 } - let mixed = mix(extend(ancestor, { b: 1, c: 1 }), { c: 2 }, { d: 3 }); - - assert.deepEqual(JSON.parse(JSON.stringify(mixed)), { b: 1, c: 2, d: 3 }, - 'properties mixed as expected'); - assert.ok(ancestor.isPrototypeOf(mixed), - 'first arguments ancestor is ancestor of result'); -}; - -exports['test obscure'] = function(assert) { - let fixture = mix({ a: 1 }, obscure({ b: 2 })); - - assert.equal(fixture.a, 1, 'a property is included'); - assert.equal(fixture.b, 2, 'b proprety is included'); - assert.ok(!Object.getOwnPropertyDescriptor(fixture, 'b').enumerable, - 'obscured properties are non-enumerable'); -}; - -exports['test inheritance'] = function(assert) { - let Ancestor = Class({ - name: 'ancestor', - method: function () { - return 'hello ' + this.name; - } - }); - - assert.ok(Ancestor() instanceof Ancestor, - 'can be instantiated without new'); - assert.ok(new Ancestor() instanceof Ancestor, - 'can also be instantiated with new'); - assert.ok(Ancestor() instanceof Class, - 'if ancestor not specified than defaults to Class'); - assert.ok(Ancestor.prototype.extends, Class.prototype, - 'extends of prototype points to ancestors prototype'); - - - assert.equal(Ancestor().method(), 'hello ancestor', - 'instance inherits defined properties'); - - let Descendant = Class({ - extends: Ancestor, - name: 'descendant' - }); - - assert.ok(Descendant() instanceof Descendant, - 'instantiates correctly'); - assert.ok(Descendant() instanceof Ancestor, - 'Inherits for passed `extends`'); - assert.equal(Descendant().method(), 'hello descendant', - 'propreties inherited'); -}; - -exports['test immunity against __proto__'] = function(assert) { - let Foo = Class({ name: 'foo', hacked: false }); - - let Bar = Class({ extends: Foo, name: 'bar' }); - - assert.throws(function() { - Foo.prototype.__proto__ = { hacked: true }; - if (Foo() instanceof Base && !Foo().hacked) - throw Error('can not change prototype chain'); - }, 'prototype chain is immune to __proto__ hacks'); - - assert.throws(function() { - Foo.prototype.__proto__ = { hacked: true }; - if (Bar() instanceof Foo && !Bar().hacked) - throw Error('can not change prototype chain'); - }, 'prototype chain of decedants immune to __proto__ hacks'); -}; - -exports['test super'] = function(assert) { - var Foo = Class({ - initialize: function initialize(options) { - this.name = options.name; - } - }); - - var Bar = Class({ - extends: Foo, - initialize: function Bar(options) { - Foo.prototype.initialize.call(this, options); - this.type = 'bar'; - } - }); - - var bar = Bar({ name: 'test' }); - - assert.equal(bar.type, 'bar', 'bar initializer was called'); - assert.equal(bar.name, 'test', 'bar initializer called Foo initializer'); -}; - -exports['test initialize'] = function(assert) { - var Dog = Class({ - initialize: function initialize(name) { - this.name = name; - }, - type: 'dog', - bark: function bark() { - return 'Ruff! Ruff!' - } - }); - - var fluffy = Dog('Fluffy'); // instatiation - assert.ok(fluffy instanceof Dog, - 'instanceof works as expected'); - assert.ok(fluffy instanceof Class, - 'inherits form Class if not specified otherwise'); - assert.ok(fluffy.name, 'fluffy', - 'initialize unless specified otherwise'); -}; - -exports['test complements regular inheritace'] = function(assert) { - let Base = Class({ name: 'base' }); - - function Type() { - // ... - } - Type.prototype = Object.create(Base.prototype); - Type.prototype.run = function() { - // ... - }; - - let value = new Type(); - - assert.ok(value instanceof Type, 'creates instance of Type'); - assert.ok(value instanceof Base, 'inherits from Base'); - assert.equal(value.name, 'base', 'inherits properties from Base'); - - - let SubType = Class({ - extends: Type, - sub: 'type' - }); - - let fixture = SubType(); - - assert.ok(fixture instanceof Base, 'is instance of Base'); - assert.ok(fixture instanceof Type, 'is instance of Type'); - assert.ok(fixture instanceof SubType, 'is instance of SubType'); - - assert.equal(fixture.sub, 'type', 'proprety is defined'); - assert.equal(fixture.run, Type.prototype.run, 'proprety is inherited'); - assert.equal(fixture.name, 'base', 'inherits base properties'); -}; - -exports['test extends object'] = function(assert) { - let prototype = { constructor: function() { return this; }, name: 'me' }; - let Foo = Class({ - extends: prototype, - value: 2 - }); - let foo = new Foo(); - - assert.ok(foo instanceof Foo, 'instance of Foo'); - assert.ok(!(foo instanceof Class), 'is not instance of Class'); - assert.ok(prototype.isPrototypeOf(foo), 'inherits from given prototype'); - assert.equal(Object.getPrototypeOf(Foo.prototype), prototype, - 'contsructor prototype inherits from extends option'); - assert.equal(foo.value, 2, 'property is defined'); - assert.equal(foo.name, 'me', 'prototype proprety is inherited'); -}; - - -var HEX = Class({ - hex: function hex() { - return '#' + this.color; - } -}); - -var RGB = Class({ - red: function red() { - return parseInt(this.color.substr(0, 2), 16); - }, - green: function green() { - return parseInt(this.color.substr(2, 2), 16); - }, - blue: function blue() { - return parseInt(this.color.substr(4, 2), 16); - } -}); - -var CMYK = Class({ - black: function black() { - var color = Math.max(Math.max(this.red(), this.green()), this.blue()); - return (1 - color / 255).toFixed(4); - }, - magenta: function magenta() { - var K = this.black(); - return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); - }, - yellow: function yellow() { - var K = this.black(); - return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); - }, - cyan: function cyan() { - var K = this.black(); - return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); - } -}); - -var Color = Class({ - implements: [ HEX, RGB, CMYK ], - initialize: function initialize(color) { - this.color = color; - } -}); - -exports['test composition'] = function(assert) { - var pink = Color('FFC0CB'); - - assert.equal(pink.red(), 255, 'red() works'); - assert.equal(pink.green(), 192, 'green() works'); - assert.equal(pink.blue(), 203, 'blue() works'); - - assert.equal(pink.magenta(), 0.2471, 'magenta() works'); - assert.equal(pink.yellow(), 0.2039, 'yellow() works'); - assert.equal(pink.cyan(), 0.0000, 'cyan() works'); - - assert.ok(pink instanceof Color, 'is instance of Color'); - assert.ok(pink instanceof Class, 'is instance of Class'); -}; - -var Point = Class({ - initialize: function initialize(x, y) { - this.x = x; - this.y = y; - }, - toString: function toString() { - return this.x + ':' + this.y; - } -}) - -var Pixel = Class({ - extends: Point, - implements: [ Color ], - initialize: function initialize(x, y, color) { - Color.prototype.initialize.call(this, color); - Point.prototype.initialize.call(this, x, y); - }, - toString: function toString() { - return this.hex() + '@' + Point.prototype.toString.call(this) - } -}); - -exports['test compostion with inheritance'] = function(assert) { - var pixel = Pixel(11, 23, 'CC3399'); - - assert.equal(pixel.toString(), '#CC3399@11:23', 'stringifies correctly'); - assert.ok(pixel instanceof Pixel, 'instance of Pixel'); - assert.ok(pixel instanceof Point, 'instance of Point'); -}; - -exports['test composition with objects'] = function(assert) { - var A = { a: 1, b: 1 }; - var B = Class({ b: 2, c: 2 }); - var C = { c: 3 }; - var D = { d: 4 }; - - var ABCD = Class({ - implements: [ A, B, C, D ], - e: 5 - }); - - var f = ABCD(); - - assert.equal(f.a, 1, 'inherits A.a'); - assert.equal(f.b, 2, 'inherits B.b overrides A.b'); - assert.equal(f.c, 3, 'inherits C.c overrides B.c'); - assert.equal(f.d, 4, 'inherits D.d'); - assert.equal(f.e, 5, 'implements e'); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-hidden-frame.js b/addon-sdk/source/test/test-hidden-frame.js deleted file mode 100644 index 945c2413f4..0000000000 --- a/addon-sdk/source/test/test-hidden-frame.js +++ /dev/null @@ -1,71 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Loader } = require("sdk/test/loader"); -const hiddenFrames = require("sdk/frame/hidden-frame"); -const { HiddenFrame } = hiddenFrames; - -exports["test Frame"] = function(assert, done) { - let url = "data:text/html;charset=utf-8,"; - - let hiddenFrame = hiddenFrames.add(HiddenFrame({ - onReady: function () { - assert.equal(this.element.contentWindow.location, "about:blank", - "HiddenFrame loads about:blank by default."); - - function onDOMReady() { - hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady, - false); - assert.equal(hiddenFrame.element.contentWindow.location, url, - "HiddenFrame loads the specified content."); - done(); - } - - this.element.addEventListener("DOMContentLoaded", onDOMReady, false); - this.element.setAttribute("src", url); - } - })); -}; - -exports["test frame removed properly"] = function(assert, done) { - let url = "data:text/html;charset=utf-8,"; - - let hiddenFrame = hiddenFrames.add(HiddenFrame({ - onReady: function () { - let frame = this.element; - assert.ok(frame.parentNode, "frame has a parent node"); - hiddenFrames.remove(hiddenFrame); - assert.ok(!frame.parentNode, "frame no longer has a parent node"); - done(); - } - })); -}; - -exports["test unload detaches panels"] = function(assert, done) { - let loader = Loader(module); - let { add, remove, HiddenFrame } = loader.require("sdk/frame/hidden-frame"); - let frames = [] - - function ready() { - frames.push(this.element); - if (frames.length === 2) complete(); - } - - add(HiddenFrame({ onReady: ready })); - add(HiddenFrame({ onReady: ready })); - - function complete() { - frames.forEach(function(frame) { - assert.ok(frame.parentNode, "frame is in the document"); - }) - loader.unload(); - frames.forEach(function(frame) { - assert.ok(!frame.parentNode, "frame isn't in the document'"); - }); - done(); - } -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-host-events.js b/addon-sdk/source/test/test-host-events.js deleted file mode 100644 index 1c6664534e..0000000000 --- a/addon-sdk/source/test/test-host-events.js +++ /dev/null @@ -1,99 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { Cc, Ci } = require('chrome'); -const { defer, all } = require('sdk/core/promise'); -const { setTimeout } = require('sdk/timers'); -const { request, response } = require('sdk/addon/host'); -const { send } = require('sdk/addon/events'); -const { filter } = require('sdk/event/utils'); -const { on, emit, off } = require('sdk/event/core'); - -var stream = filter(request, (data) => /sdk-x-test/.test(data.event)); - -exports.testSend = function (assert, done) { - on(stream, 'data', handle); - send('sdk-x-test-simple', { title: 'my test data' }).then((data) => { - assert.equal(data.title, 'my response', 'response is handled'); - off(stream, 'data', handle); - done(); - }, (reason) => { - assert.fail('should not call reject'); - }); - function handle (e) { - assert.equal(e.event, 'sdk-x-test-simple', 'correct event name'); - assert.ok(e.id != null, 'message has an ID'); - assert.equal(e.data.title, 'my test data', 'serialized data passes'); - e.data.title = 'my response'; - emit(response, 'data', e); - } -}; - -exports.testSendError = function (assert, done) { - on(stream, 'data', handle); - send('sdk-x-test-error', { title: 'my test data' }).then((data) => { - assert.fail('should not call success'); - }, (reason) => { - assert.equal(reason, 'ErrorInfo', 'should reject with error/reason'); - off(stream, 'data', handle); - done(); - }); - function handle (e) { - e.error = 'ErrorInfo'; - emit(response, 'data', e); - } -}; - -exports.testMultipleSends = function (assert, done) { - let count = 0; - let ids = []; - on(stream, 'data', handle); - ['firefox', 'thunderbird', 'rust'].map(data => - send('sdk-x-test-multi', { data: data }).then(val => { - assert.ok(val === 'firefox' || val === 'rust', 'successful calls resolve correctly'); - if (++count === 3) { - off(stream, 'data', handle); - done(); - } - }, reason => { - assert.equal(reason.error, 'too blue', 'rejected calls are rejected'); - if (++count === 3) { - off(stream, 'data', handle); - done(); - } - })); - - function handle (e) { - if (e.data !== 'firefox' || e.data !== 'rust') - e.error = { data: e.data, error: 'too blue' }; - assert.ok(!~ids.indexOf(e.id), 'ID should be unique'); - assert.equal(e.event, 'sdk-x-test-multi', 'has event name'); - ids.push(e.id); - emit(response, 'data', e); - } -}; - -exports.testSerialization = function (assert, done) { - on(stream, 'data', handle); - let object = { title: 'my test data' }; - let resObject; - send('sdk-x-test-serialize', object).then(data => { - data.title = 'another title'; - assert.equal(object.title, 'my test data', 'original object not modified'); - assert.equal(resObject.title, 'new title', 'object passed by value from host'); - off(stream, 'data', handle); - done(); - }, (reason) => { - assert.fail('should not call reject'); - }); - function handle (e) { - e.data.title = 'new title'; - assert.equal(object.title, 'my test data', 'object passed by value to host'); - resObject = e.data; - emit(response, 'data', e); - } -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-hotkeys.js b/addon-sdk/source/test/test-hotkeys.js deleted file mode 100644 index ba460ee455..0000000000 --- a/addon-sdk/source/test/test-hotkeys.js +++ /dev/null @@ -1,183 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Hotkey } = require("sdk/hotkeys"); -const { keyDown } = require("sdk/dom/events/keys"); -const { Loader } = require('sdk/test/loader'); -const { getMostRecentBrowserWindow } = require("sdk/window/utils"); - -const element = getMostRecentBrowserWindow().document.documentElement; - -exports["test hotkey: function key"] = function(assert, done) { - var showHotKey = Hotkey({ - combo: "f1", - onPress: function() { - assert.pass("first callback is called"); - assert.equal(this, showHotKey, - 'Context `this` in `onPress` should be the hotkey object'); - keyDown(element, "f2"); - showHotKey.destroy(); - } - }); - - var hideHotKey = Hotkey({ - combo: "f2", - onPress: function() { - assert.pass("second callback is called"); - hideHotKey.destroy(); - done(); - } - }); - - keyDown(element, "f1"); -}; - -exports["test hotkey: accel alt shift"] = function(assert, done) { - var showHotKey = Hotkey({ - combo: "accel-shift-6", - onPress: function() { - assert.pass("first callback is called"); - keyDown(element, "accel-alt-shift-6"); - showHotKey.destroy(); - } - }); - - var hideHotKey = Hotkey({ - combo: "accel-alt-shift-6", - onPress: function() { - assert.pass("second callback is called"); - hideHotKey.destroy(); - done(); - } - }); - - keyDown(element, "accel-shift-6"); -}; - -exports["test hotkey meta & control"] = function(assert, done) { - var showHotKey = Hotkey({ - combo: "meta-3", - onPress: function() { - assert.pass("first callback is called"); - keyDown(element, "alt-control-shift-b"); - showHotKey.destroy(); - } - }); - - var hideHotKey = Hotkey({ - combo: "Ctrl-Alt-Shift-B", - onPress: function() { - assert.pass("second callback is called"); - hideHotKey.destroy(); - done(); - } - }); - - keyDown(element, "meta-3"); -}; - -exports["test hotkey: control-1 / meta--"] = function(assert, done) { - var showHotKey = Hotkey({ - combo: "control-1", - onPress: function() { - assert.pass("first callback is called"); - keyDown(element, "meta--"); - showHotKey.destroy(); - } - }); - - var hideHotKey = Hotkey({ - combo: "meta--", - onPress: function() { - assert.pass("second callback is called"); - hideHotKey.destroy(); - done(); - } - }); - - keyDown(element, "control-1"); -}; - -exports["test invalid combos"] = function(assert) { - assert.throws(function() { - Hotkey({ - combo: "d", - onPress: function() {} - }); - }, "throws if no modifier is present"); - assert.throws(function() { - Hotkey({ - combo: "alt", - onPress: function() {} - }); - }, "throws if no key is present"); - assert.throws(function() { - Hotkey({ - combo: "alt p b", - onPress: function() {} - }); - }, "throws if more then one key is present"); -}; - -exports["test no exception on unmodified keypress"] = function(assert) { - var someHotkey = Hotkey({ - combo: "control-alt-1", - onPress: () => {} - }); - keyDown(element, "a"); - assert.pass("No exception throw, unmodified keypress passed"); - someHotkey.destroy(); -}; - -exports["test hotkey: automatic destroy"] = function*(assert) { - // Hacky way to be able to create unloadable modules via makeSandboxedLoader. - let loader = Loader(module); - - var called = false; - var hotkey = loader.require("sdk/hotkeys").Hotkey({ - combo: "accel-shift-x", - onPress: () => called = true - }); - - // Unload the module so that previous hotkey is automatically destroyed - loader.unload(); - - // Ensure that the hotkey is really destroyed - keyDown(element, "accel-shift-x"); - - assert.ok(!called, "Hotkey is destroyed and not called."); - - // create a new hotkey for a different set - yield new Promise(resolve => { - let key = Hotkey({ - combo: "accel-shift-y", - onPress: () => { - key.destroy(); - assert.pass("accel-shift-y was pressed."); - resolve(); - } - }); - keyDown(element, "accel-shift-y"); - }); - - assert.ok(!called, "Hotkey is still not called, in time it would take."); - - // create a new hotkey for the same set - yield new Promise(resolve => { - let key = Hotkey({ - combo: "accel-shift-x", - onPress: () => { - key.destroy(); - assert.pass("accel-shift-x was pressed."); - resolve(); - } - }); - keyDown(element, "accel-shift-x"); - }); - - assert.ok(!called, "Hotkey is still not called, and reusing is ok."); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-httpd.js b/addon-sdk/source/test/test-httpd.js deleted file mode 100644 index 78740f1bf6..0000000000 --- a/addon-sdk/source/test/test-httpd.js +++ /dev/null @@ -1,73 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const port = 8099; -const file = require("sdk/io/file"); -const { pathFor } = require("sdk/system"); -const { Loader } = require("sdk/test/loader"); -const options = require("sdk/test/options"); - -const loader = Loader(module); -const httpd = loader.require("./lib/httpd"); -if (options.parseable || options.verbose) - loader.sandbox("./lib/httpd").DEBUG = true; - -exports.testBasicHTTPServer = function(assert, done) { - // Use the profile directory for the temporary file as that will be deleted - // when tests are complete - let basePath = pathFor("ProfD"); - let filePath = file.join(basePath, 'test-httpd.txt'); - let content = "This is the HTTPD test file.\n"; - let fileStream = file.open(filePath, 'w'); - fileStream.write(content); - fileStream.close(); - - let srv = httpd.startServerAsync(port, basePath); - - // Request this very file. - let Request = require('sdk/request').Request; - Request({ - url: "http://localhost:" + port + "/test-httpd.txt", - onComplete: function (response) { - assert.equal(response.text, content); - srv.stop(done); - } - }).get(); -}; - -exports.testDynamicServer = function (assert, done) { - let content = "This is the HTTPD test file.\n"; - - let srv = httpd.startServerAsync(port); - - // See documentation here: - //http://doxygen.db48x.net/mozilla/html/interfacensIHttpServer.html#a81fc7e7e29d82aac5ce7d56d0bedfb3a - //http://doxygen.db48x.net/mozilla/html/interfacensIHttpRequestHandler.html - srv.registerPathHandler("/test-httpd.txt", function handle(request, response) { - // Add text content type, only to avoid error in `Request` API - response.setHeader("Content-Type", "text/plain", false); - response.write(content); - }); - - // Request this very file. - let Request = require('sdk/request').Request; - Request({ - url: "http://localhost:" + port + "/test-httpd.txt", - onComplete: function (response) { - assert.equal(response.text, content); - srv.stop(done); - } - }).get(); -}; - -exports.testAutomaticPortSelection = function (assert, done) { - const srv = httpd.startServerAsync(-1); - - const port = srv.identity.primaryPort; - assert.ok(0 <= port && port <= 65535); - - srv.stop(done); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-indexed-db.js b/addon-sdk/source/test/test-indexed-db.js deleted file mode 100644 index ea53a3e725..0000000000 --- a/addon-sdk/source/test/test-indexed-db.js +++ /dev/null @@ -1,182 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { indexedDB, IDBKeyRange, DOMException - } = require("sdk/indexed-db"); - -exports["test indexedDB is frozen"] = function(assert){ - let original = indexedDB.open; - let f = function(){}; - assert.throws(function(){indexedDB.open = f}); - assert.equal(indexedDB.open,original); - assert.notEqual(indexedDB.open,f); - -}; - -exports["test db variables"] = function(assert) { - [ indexedDB, IDBKeyRange, DOMException - ].forEach(function(value) { - assert.notEqual(typeof(value), "undefined", "variable is defined"); - }); -} - -exports["test open"] = function(assert, done) { - testOpen(0, assert, done); -} - -function testOpen(step, assert, done) { - const dbName = "MyTestDatabase"; - const openParams = [ - { dbName: "MyTestDatabase", dbVersion: 10 }, - { dbName: "MyTestDatabase" }, - { dbName: "MyTestDatabase", dbOptions: { storage: "temporary" } }, - { dbName: "MyTestDatabase", dbOptions: { version: 20, storage: "default" } } - ]; - - let params = openParams[step]; - - let request; - let expectedStorage; - let expectedVersion; - let upgradeNeededCalled = false; - if ("dbVersion" in params) { - request = indexedDB.open(params.dbName, params.dbVersion); - expectedVersion = params.dbVersion; - expectedStorage = "persistent"; - } else if ("dbOptions" in params) { - request = indexedDB.open(params.dbName, params.dbOptions); - if ("version" in params.dbOptions) { - expectedVersion = params.dbOptions.version; - } else { - expectedVersion = 1; - } - if ("storage" in params.dbOptions) { - expectedStorage = params.dbOptions.storage; - } else { - expectedStorage = "persistent"; - } - } else { - request = indexedDB.open(params.dbName); - expectedVersion = 1; - expectedStorage = "persistent"; - } - request.onerror = function(event) { - assert.fail("Failed to open indexedDB") - done(); - } - request.onupgradeneeded = function(event) { - upgradeNeededCalled = true; - assert.equal(event.oldVersion, 0, "Correct old version"); - } - request.onsuccess = function(event) { - assert.pass("IndexedDB was open"); - assert.equal(upgradeNeededCalled, true, "Upgrade needed called"); - let db = request.result; - assert.equal(db.storage, expectedStorage, "Storage is correct"); - db.onversionchange = function(event) { - assert.equal(event.oldVersion, expectedVersion, "Old version is correct"); - db.close(); - } - if ("dbOptions" in params) { - request = indexedDB.deleteDatabase(params.dbName, params.dbOptions); - } else { - request = indexedDB.deleteDatabase(params.dbName); - } - request.onerror = function(event) { - assert.fail("Failed to delete indexedDB") - done(); - } - request.onsuccess = function(event) { - assert.pass("IndexedDB was deleted"); - - if (++step == openParams.length) { - done(); - } else { - testOpen(step, assert, done); - } - } - } -} - -exports["test dbname is unprefixed"] = function(assert, done) { - // verify fixes in https://bugzilla.mozilla.org/show_bug.cgi?id=786688 - let dbName = "dbname-unprefixed"; - let request = indexedDB.open(dbName); - request.onerror = function(event) { - assert.fail("Failed to open db"); - done(); - }; - request.onsuccess = function(event) { - assert.equal(request.result.name, dbName); - done(); - }; -}; - -exports["test structuring the database"] = function(assert, done) { - // This is what our customer data looks like. - let customerData = [ - { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" }, - { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" } - ]; - let dbName = "the_name"; - let request = indexedDB.open(dbName, 2); - request.onerror = function(event) { - assert.fail("Failed to open db"); - done(); - }; - request.onsuccess = function(event) { - assert.pass("transaction is complete"); - testRead(assert, done); - } - request.onupgradeneeded = function(event) { - assert.pass("data base upgrade") - - var db = event.target.result; - - // Create an objectStore to hold information about our customers. We"re - // going to use "ssn" as our key path because it"s guaranteed to be - // unique. - var objectStore = db.createObjectStore("customers", { keyPath: "ssn" }); - - // Create an index to search customers by name. We may have duplicates - // so we can"t use a unique index. - objectStore.createIndex("name", "name", { unique: false }); - - // Create an index to search customers by email. We want to ensure that - // no two customers have the same email, so use a unique index. - objectStore.createIndex("email", "email", { unique: true }); - - // Store values in the newly created objectStore. - customerData.forEach(function(data) { - objectStore.add(data); - }); - assert.pass("data added to object store"); - }; -}; - -function testRead(assert, done) { - let dbName = "the_name"; - let request = indexedDB.open(dbName, 2); - request.onsuccess = function(event) { - assert.pass("data opened") - var db = event.target.result; - let transaction = db.transaction(["customers"]); - var objectStore = transaction.objectStore("customers"); - var request = objectStore.get("444-44-4444"); - request.onerror = function(event) { - assert.fail("Failed to retrive data") - }; - request.onsuccess = function(event) { - // Do something with the request.result! - assert.equal(request.result.name, "Bill", "Name is correct"); - done(); - }; - }; - request.onerror = function() { - assert.fail("failed to open db"); - }; -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-jetpack-id.js b/addon-sdk/source/test/test-jetpack-id.js deleted file mode 100644 index 99479e32d3..0000000000 --- a/addon-sdk/source/test/test-jetpack-id.js +++ /dev/null @@ -1,64 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var getID = require("jetpack-id/index"); - -exports["test Returns GUID when `id` GUID"] = assert => { - var guid = "{8490ae4f-93bc-13af-80b3-39adf9e7b243}"; - assert.equal(getID({ id: guid }), guid); -}; - -exports["test Returns domain id when `id` domain id"] = assert => { - var id = "my-addon@jetpack"; - assert.equal(getID({ id: id }), id); -}; - -exports["test allows underscores in name"] = assert => { - var name = "my_addon"; - assert.equal(getID({ name: name }), `@${name}`); -}; - -exports["test allows underscores in id"] = assert => { - var id = "my_addon@jetpack"; - assert.equal(getID({ id: id }), id); -}; - -exports["test Returns valid name when `name` exists"] = assert => { - var id = "my-addon"; - assert.equal(getID({ name: id }), `@${id}`); -}; - - -exports["test Returns null when `id` and `name` do not exist"] = assert => { - assert.equal(getID({}), null) -} - -exports["test Returns null when no object passed in"] = assert => { - assert.equal(getID(), null) -} - -exports["test Returns null when `id` exists but not GUID/domain"] = assert => { - var id = "my-addon"; - assert.equal(getID({ id: id }), null); -} - -exports["test Returns null when `id` contains multiple @"] = assert => { - assert.equal(getID({ id: "my@addon@yeah" }), null); -}; - -exports["test Returns null when `id` or `name` specified in domain format but has invalid characters"] = assert => { - [" ", "!", "/", "$", " ", "~", "("].forEach(sym => { - assert.equal(getID({ id: "my" + sym + "addon@domain" }), null); - assert.equal(getID({ name: "my" + sym + "addon" }), null); - }); -}; - -exports["test Returns null, does not crash, when providing non-string properties for `name` and `id`"] = assert => { - assert.equal(getID({ id: 5 }), null); - assert.equal(getID({ name: 5 }), null); - assert.equal(getID({ name: {} }), null); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-keyboard-observer.js b/addon-sdk/source/test/test-keyboard-observer.js deleted file mode 100644 index 18f32eab30..0000000000 --- a/addon-sdk/source/test/test-keyboard-observer.js +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { keyPress } = require("sdk/dom/events/keys"); -const { Loader } = require("sdk/test/loader"); -const timer = require("sdk/timers"); - -exports["test unload keyboard observer"] = function(assert, done) { - let loader = Loader(module); - let element = loader.require("sdk/deprecated/window-utils"). - activeBrowserWindow.document.documentElement; - let observer = loader.require("sdk/keyboard/observer"). - observer; - let called = 0; - - observer.on("keypress", function () { called++; }); - - // dispatching "keypress" event to trigger observer listeners. - keyPress(element, "accel-%"); - - // Unload the module. - loader.unload(); - - // dispatching "keypress" even once again. - keyPress(element, "accel-%"); - - // Enqueuing asserts to make sure that assertion is not performed early. - timer.setTimeout(function () { - assert.equal(called, 1, "observer was called before unload only."); - done(); - }, 0); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-keyboard-utils.js b/addon-sdk/source/test/test-keyboard-utils.js deleted file mode 100644 index 00fc841eeb..0000000000 --- a/addon-sdk/source/test/test-keyboard-utils.js +++ /dev/null @@ -1,61 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const utils = require("sdk/keyboard/utils"); -const runtime = require("sdk/system/runtime"); - -const isMac = runtime.OS === "Darwin"; - -exports["test toString"] = function(assert) { - assert.equal(utils.toString({ - key: "B", - modifiers: [ "Shift", "Ctrl" ] - }), "Shift-Ctrl-B", "toString does not normalizes JSON"); - - assert.equal(utils.toString({ - key: "C", - modifiers: [], - }), "C", "Works with objects with empty array of modifiers"); - - assert.equal(utils.toString(Object.create((function Type() {}).prototype, { - key: { value: "d" }, - modifiers: { value: [ "alt" ] }, - method: { value: function() {} } - })), "alt-d", "Works with non-json objects"); - - assert.equal(utils.toString({ - modifiers: [ "shift", "alt" ] - }), "shift-alt-", "works with only modifiers"); -}; - -exports["test toJSON"] = function(assert) { - assert.deepEqual(utils.toJSON("Shift-Ctrl-B"), { - key: "b", - modifiers: [ "control", "shift" ] - }, "toJSON normalizes input"); - - assert.deepEqual(utils.toJSON("Meta-Alt-option-C"), { - key: "c", - modifiers: [ "alt", "meta" ] - }, "removes dublicates"); - - assert.deepEqual(utils.toJSON("AccEl+sHiFt+Z", "+"), { - key: "z", - modifiers: isMac ? [ "meta", "shift" ] : [ "control", "shift" ] - }, "normalizes OS specific keys and adjustes seperator"); -}; - -exports["test normalize"] = function assert(assert) { - assert.equal(utils.normalize("Shift Ctrl A control ctrl", " "), - "control shift a", "removes reapeted modifiers"); - assert.equal(utils.normalize("shift-ctrl-left"), "control-shift-left", - "normilizes non printed characters"); - - assert.throws(function() { - utils.normalize("shift-alt-b-z"); - }, "throws if contains more then on non-modifier key"); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-l10n-locale.js b/addon-sdk/source/test/test-l10n-locale.js deleted file mode 100644 index 564abbf1be..0000000000 --- a/addon-sdk/source/test/test-l10n-locale.js +++ /dev/null @@ -1,169 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const { getPreferedLocales, findClosestLocale } = require("sdk/l10n/locale"); -const prefs = require("sdk/preferences/service"); -const { Cc, Ci, Cu } = require("chrome"); -const { Services } = Cu.import("resource://gre/modules/Services.jsm"); -const BundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService); - -const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; -const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const PREF_ACCEPT_LANGUAGES = "intl.accept_languages"; - -function assertPrefered(assert, expected, msg) { - assert.equal(JSON.stringify(getPreferedLocales()), JSON.stringify(expected), - msg); -} - -exports.testGetPreferedLocales = function(assert) { - prefs.set(PREF_MATCH_OS_LOCALE, false); - prefs.set(PREF_SELECTED_LOCALE, ""); - prefs.set(PREF_ACCEPT_LANGUAGES, ""); - assertPrefered(assert, ["en-us"], - "When all preferences are empty, we only have en-us"); - - prefs.set(PREF_SELECTED_LOCALE, "fr"); - prefs.set(PREF_ACCEPT_LANGUAGES, "jp"); - assertPrefered(assert, ["fr", "jp", "en-us"], - "We first have useragent locale, then web one and finally en-US"); - - prefs.set(PREF_SELECTED_LOCALE, "en-US"); - prefs.set(PREF_ACCEPT_LANGUAGES, "en-US"); - assertPrefered(assert, ["en-us"], - "We do not have duplicates"); - - prefs.set(PREF_SELECTED_LOCALE, "en-US"); - prefs.set(PREF_ACCEPT_LANGUAGES, "fr"); - assertPrefered(assert, ["en-us", "fr"], - "en-US can be first if specified by higher priority preference"); - - // Reset what we changed - prefs.reset(PREF_MATCH_OS_LOCALE); - prefs.reset(PREF_SELECTED_LOCALE); - prefs.reset(PREF_ACCEPT_LANGUAGES); -} - -// In some cases, mainly on Fennec and on Linux version, -// `general.useragent.locale` is a special 'localized' value, like: -// "chrome://global/locale/intl.properties" -exports.testPreferedLocalizedLocale = function(assert) { - prefs.set(PREF_MATCH_OS_LOCALE, false); - let bundleURL = "chrome://global/locale/intl.properties"; - prefs.setLocalized(PREF_SELECTED_LOCALE, bundleURL); - let contentLocale = "ja"; - prefs.set(PREF_ACCEPT_LANGUAGES, contentLocale); - - // Read manually the expected locale value from the property file - let expectedLocale = BundleService.createBundle(bundleURL). - GetStringFromName(PREF_SELECTED_LOCALE). - toLowerCase(); - - // First add the useragent locale - let expectedLocaleList = [expectedLocale]; - - // Then the content locale - if (expectedLocaleList.indexOf(contentLocale) == -1) - expectedLocaleList.push(contentLocale); - - // Add default "en-us" fallback if the main language is not already en-us - if (expectedLocaleList.indexOf("en-us") == -1) - expectedLocaleList.push("en-us"); - - assertPrefered(assert, expectedLocaleList, "test localized pref value"); - - // Reset what we have changed - prefs.reset(PREF_MATCH_OS_LOCALE); - prefs.reset(PREF_SELECTED_LOCALE); - prefs.reset(PREF_ACCEPT_LANGUAGES); -} - -// On Linux the PREF_ACCEPT_LANGUAGES pref can be a localized pref. -exports.testPreferedContentLocale = function(assert) { - prefs.set(PREF_MATCH_OS_LOCALE, false); - let noLocale = "", - bundleURL = "chrome://global/locale/intl.properties"; - prefs.set(PREF_SELECTED_LOCALE, noLocale); - prefs.setLocalized(PREF_ACCEPT_LANGUAGES, bundleURL); - - // Read the expected locale values from the property file - let expectedLocaleList = BundleService.createBundle(bundleURL). - GetStringFromName(PREF_ACCEPT_LANGUAGES). - split(","). - map(locale => locale.trim().toLowerCase()); - - // Add default "en-us" fallback if the main language is not already en-us - if (expectedLocaleList.indexOf("en-us") == -1) - expectedLocaleList.push("en-us"); - - assertPrefered(assert, expectedLocaleList, "test localized content locale pref value"); - - // Reset what we have changed - prefs.reset(PREF_MATCH_OS_LOCALE); - prefs.reset(PREF_SELECTED_LOCALE); - prefs.reset(PREF_ACCEPT_LANGUAGES); -} - -exports.testPreferedOsLocale = function(assert) { - prefs.set(PREF_MATCH_OS_LOCALE, true); - prefs.set(PREF_SELECTED_LOCALE, ""); - prefs.set(PREF_ACCEPT_LANGUAGES, ""); - - let expectedLocale = Services.locale.getLocaleComponentForUserAgent(). - toLowerCase(); - let expectedLocaleList = [expectedLocale]; - - // Add default "en-us" fallback if the main language is not already en-us - if (expectedLocale != "en-us") - expectedLocaleList.push("en-us"); - - assertPrefered(assert, expectedLocaleList, "Ensure that we select OS locale when related preference is set"); - - // Reset what we have changed - prefs.reset(PREF_MATCH_OS_LOCALE); - prefs.reset(PREF_SELECTED_LOCALE); - prefs.reset(PREF_ACCEPT_LANGUAGES); -} - -exports.testFindClosestLocale = function(assert) { - // Second param of findClosestLocale (aMatchLocales) have to be in lowercase - assert.equal(findClosestLocale([], []), null, - "When everything is empty we get null"); - - assert.equal(findClosestLocale(["en", "en-US"], ["en"]), - "en", "We always accept exact match first 1/5"); - assert.equal(findClosestLocale(["en-US", "en"], ["en"]), - "en", "We always accept exact match first 2/5"); - assert.equal(findClosestLocale(["en", "en-US"], ["en-us"]), - "en-US", "We always accept exact match first 3/5"); - assert.equal(findClosestLocale(["ja-JP-mac", "ja", "ja-JP"], ["ja-jp"]), - "ja-JP", "We always accept exact match first 4/5"); - assert.equal(findClosestLocale(["ja-JP-mac", "ja", "ja-JP"], ["ja-jp-mac"]), - "ja-JP-mac", "We always accept exact match first 5/5"); - - assert.equal(findClosestLocale(["en", "en-GB"], ["en-us"]), - "en", "We accept more generic locale, when there is no exact match 1/2"); - assert.equal(findClosestLocale(["en-ZA", "en"], ["en-gb"]), - "en", "We accept more generic locale, when there is no exact match 2/2"); - - assert.equal(findClosestLocale(["ja-JP"], ["ja"]), - "ja-JP", "We accept more specialized locale, when there is no exact match 1/2"); - // Better to select "ja" in this case but behave same as current AddonManager - assert.equal(findClosestLocale(["ja-JP-mac", "ja"], ["ja-jp"]), - "ja-JP-mac", "We accept more specialized locale, when there is no exact match 2/2"); - - assert.equal(findClosestLocale(["en-US"], ["en-us"]), - "en-US", "We keep the original one as result 1/2"); - assert.equal(findClosestLocale(["en-us"], ["en-us"]), - "en-us", "We keep the original one as result 2/2"); - - assert.equal(findClosestLocale(["ja-JP-mac"], ["ja-jp-mac"]), - "ja-JP-mac", "We accept locale with 3 parts"); - assert.equal(findClosestLocale(["ja-JP"], ["ja-jp-mac"]), - "ja-JP", "We accept locale with 2 parts from locale with 3 parts"); - assert.equal(findClosestLocale(["ja"], ["ja-jp-mac"]), - "ja", "We accept locale with 1 part from locale with 3 parts"); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-l10n-plural-rules.js b/addon-sdk/source/test/test-l10n-plural-rules.js deleted file mode 100644 index 953d977a46..0000000000 --- a/addon-sdk/source/test/test-l10n-plural-rules.js +++ /dev/null @@ -1,85 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -const { getRulesForLocale } = require("sdk/l10n/plural-rules"); - -// For more information, please visit unicode website: -// http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - -function map(assert, f, n, form) { - assert.equal(f(n), form, n + " maps to '" + form + "'"); -} - -exports.testFrench = function(assert) { - let f = getRulesForLocale("fr"); - map(assert, f, -1, "other"); - map(assert, f, 0, "one"); - map(assert, f, 1, "one"); - map(assert, f, 1.5, "one"); - map(assert, f, 2, "other"); - map(assert, f, 100, "other"); -} - -exports.testEnglish = function(assert) { - let f = getRulesForLocale("en"); - map(assert, f, -1, "other"); - map(assert, f, 0, "other"); - map(assert, f, 1, "one"); - map(assert, f, 1.5, "other"); - map(assert, f, 2, "other"); - map(assert, f, 100, "other"); -} - -exports.testArabic = function(assert) { - let f = getRulesForLocale("ar"); - map(assert, f, -1, "other"); - map(assert, f, 0, "zero"); - map(assert, f, 0.5, "other"); - - map(assert, f, 1, "one"); - map(assert, f, 1.5, "other"); - - map(assert, f, 2, "two"); - map(assert, f, 2.5, "other"); - - map(assert, f, 3, "few"); - map(assert, f, 3.5, "few"); // I'd expect it to be 'other', but the unicode.org - // algorithm computes 'few'. - map(assert, f, 5, "few"); - map(assert, f, 10, "few"); - map(assert, f, 103, "few"); - map(assert, f, 105, "few"); - map(assert, f, 110, "few"); - map(assert, f, 203, "few"); - map(assert, f, 205, "few"); - map(assert, f, 210, "few"); - - map(assert, f, 11, "many"); - map(assert, f, 50, "many"); - map(assert, f, 99, "many"); - map(assert, f, 111, "many"); - map(assert, f, 150, "many"); - map(assert, f, 199, "many"); - - map(assert, f, 100, "other"); - map(assert, f, 101, "other"); - map(assert, f, 102, "other"); - map(assert, f, 200, "other"); - map(assert, f, 201, "other"); - map(assert, f, 202, "other"); -} - -exports.testJapanese = function(assert) { - // Japanese doesn't have plural forms. - let f = getRulesForLocale("ja"); - map(assert, f, -1, "other"); - map(assert, f, 0, "other"); - map(assert, f, 1, "other"); - map(assert, f, 1.5, "other"); - map(assert, f, 2, "other"); - map(assert, f, 100, "other"); -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-lang-type.js b/addon-sdk/source/test/test-lang-type.js deleted file mode 100644 index c0e5100768..0000000000 --- a/addon-sdk/source/test/test-lang-type.js +++ /dev/null @@ -1,166 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict" - -var utils = require("sdk/lang/type"); - -exports["test function"] = function (assert) { - assert.equal(utils.isFunction(function(){}), true, "value is a function"); - assert.equal(utils.isFunction(Object), true, "Object is a function"); - assert.equal(utils.isFunction(new Function("")), true, "Genertaed value is a function"); - assert.equal(utils.isFunction({}), false, "object is not a function"); - assert.equal(utils.isFunction(4), false, "number is not a function"); -}; - -exports["test atoms"] = function (assert) { - assert.equal(utils.isPrimitive(2), true, "number is a primitive"); - assert.equal(utils.isPrimitive(NaN), true, "`NaN` is a primitve"); - assert.equal(utils.isPrimitive(undefined), true, "`undefined` is a primitive"); - assert.equal(utils.isPrimitive(null), true, "`null` is a primitive"); - assert.equal(utils.isPrimitive(Infinity), true, "`Infinity` is a primitive"); - assert.equal(utils.isPrimitive("foo"), true, "strings are a primitive"); - assert.ok(utils.isPrimitive(true) && utils.isPrimitive(false), - "booleans are primitive"); -}; - -exports["test object"] = function (assert) { - assert.equal(utils.isObject({}), true, "`{}` is an object"); - assert.ok(!utils.isObject(null), "`null` is not an object"); - assert.ok(!utils.isObject(Object), "functions is not an object"); -}; - -exports["test generator"] = function (assert) { - assert.equal(utils.isGenerator(function*(){}), true, "`function*(){}` is a generator"); - assert.equal(utils.isGenerator(function(){}), false, "`function(){}` is not a generator"); - assert.equal(utils.isGenerator(() => {}), false, "`() => {}` is not a generator"); - assert.equal(utils.isGenerator({}), false, "`{}` is not a generator"); - assert.equal(utils.isGenerator(1), false, "`1` is not a generator"); - assert.equal(utils.isGenerator([]), false, "`[]` is not a generator"); - assert.equal(utils.isGenerator(null), false, "`null` is not a generator"); - assert.equal(utils.isGenerator(undefined), false, "`undefined` is not a generator"); -}; - -exports["test array"] = function (assert) { - assert.equal(utils.isArray([]), true, "[] is an array"); - assert.equal(utils.isArray([1]), true, "[1] is an array"); - assert.equal(utils.isArray(new Array()), true, "new Array() is an array"); - assert.equal(utils.isArray(new Array(10)), true, "new Array(10) is an array"); - assert.equal(utils.isArray(Array.prototype), true, "Array.prototype is an array"); - - assert.equal(utils.isArray(), false, "implicit undefined is not an array"); - assert.equal(utils.isArray(null), false, "null is not an array"); - assert.equal(utils.isArray(undefined), false, "undefined is not an array"); - assert.equal(utils.isArray(1), false, "1 is not an array"); - assert.equal(utils.isArray(true), false, "true is not an array"); - assert.equal(utils.isArray('foo'), false, "'foo' is not an array"); - assert.equal(utils.isArray({}), false, "{} is not an array"); - assert.equal(utils.isArray(Symbol.iterator), false, "Symbol.iterator is not an array"); -}; - -exports["test arguments"] = function (assert) { - assert.equal(utils.isArguments(arguments), true, "arguments is an arguments"); - (function() { - assert.equal(utils.isArguments(arguments), true, "arguments in nested function is an arguments"); - })(); - (function*() { - assert.equal(utils.isArguments(arguments), true, "arguments in nested generator is an arguments"); - })(); - (() => { - assert.equal(utils.isArguments(arguments), true, "arguments in arrow function is an arguments"); - })(); - - assert.equal(utils.isArguments(), false, "implicit undefined is not an arguments"); - assert.equal(utils.isArguments(null), false, "null is not an arguments"); - assert.equal(utils.isArguments(undefined), false, "undefined is not an arguments"); - assert.equal(utils.isArguments(1), false, "1 is not an arguments"); - assert.equal(utils.isArguments(true), false, "true is not an arguments"); - assert.equal(utils.isArguments('foo'), false, "'foo' is not an arguments"); - assert.equal(utils.isArguments([]), false, "[] is not an arguments"); - assert.equal(utils.isArguments({}), false, "{} is not an arguments"); - assert.equal(utils.isArguments(Symbol.iterator), false, "Symbol.iterator is not an arguments"); - (function(...args) { - assert.equal(utils.isArguments(args), false, "rest arguments is not an arguments"); - })(); -}; - -exports["test flat objects"] = function (assert) { - assert.ok(utils.isFlat({}), "`{}` is a flat object"); - assert.ok(!utils.isFlat([]), "`[]` is not a flat object"); - assert.ok(!utils.isFlat(new function() {}), "derived objects are not flat"); - assert.ok(utils.isFlat(Object.prototype), "Object.prototype is flat"); -}; - -exports["test json atoms"] = function (assert) { - assert.ok(utils.isJSON(null), "`null` is JSON"); - assert.ok(utils.isJSON(undefined), "`undefined` is JSON"); - assert.ok(utils.isJSON(NaN), "`NaN` is JSON"); - assert.ok(utils.isJSON(Infinity), "`Infinity` is JSON"); - assert.ok(utils.isJSON(true) && utils.isJSON(false), "booleans are JSON"); - assert.ok(utils.isJSON(4), utils.isJSON(0), "numbers are JSON"); - assert.ok(utils.isJSON("foo bar"), "strings are JSON"); -}; - -exports["test jsonable values"] = function (assert) { - assert.ok(utils.isJSONable(null), "`null` is JSONable"); - assert.ok(!utils.isJSONable(undefined), "`undefined` is not JSONable"); - assert.ok(utils.isJSONable(NaN), "`NaN` is JSONable"); - assert.ok(utils.isJSONable(Infinity), "`Infinity` is JSONable"); - assert.ok(utils.isJSONable(true) && utils.isJSONable(false), "booleans are JSONable"); - assert.ok(utils.isJSONable(0), "numbers are JSONable"); - assert.ok(utils.isJSONable("foo bar"), "strings are JSONable"); - assert.ok(!utils.isJSONable(function(){}), "functions are not JSONable"); - - const functionWithToJSON = function(){}; - functionWithToJSON.toJSON = function() { return "foo bar"; }; - assert.ok(utils.isJSONable(functionWithToJSON), "functions with toJSON() are JSONable"); - - assert.ok(utils.isJSONable({}), "`{}` is JSONable"); - - const foo = {}; - foo.bar = foo; - assert.ok(!utils.isJSONable(foo), "recursive objects are not JSONable"); -}; - -exports["test instanceOf"] = function (assert) { - assert.ok(utils.instanceOf(assert, Object), - "assert is object from other sandbox"); - assert.ok(utils.instanceOf(new Date(), Date), "instance of date"); - assert.ok(!utils.instanceOf(null, Object), "null is not an instance"); -}; - -exports["test json"] = function (assert) { - assert.ok(!utils.isJSON(function(){}), "functions are not json"); - assert.ok(utils.isJSON({}), "`{}` is JSON"); - assert.ok(utils.isJSON({ - a: "foo", - b: 3, - c: undefined, - d: null, - e: { - f: { - g: "bar", - p: [{}, "oueou", 56] - }, - q: { nan: NaN, infinity: Infinity }, - "non standard name": "still works" - } - }), "JSON can contain nested objects"); - - var foo = {}; - var bar = { foo: foo }; - foo.bar = bar; - assert.ok(!utils.isJSON(foo), "recursive objects are not json"); - - - assert.ok(!utils.isJSON({ get foo() { return 5 } }), - "json can not have getter"); - - assert.ok(!utils.isJSON({ foo: "bar", baz: function () {} }), - "json can not contain functions"); - - assert.ok(!utils.isJSON(Object.create({})), - "json must be direct descendant of `Object.prototype`"); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-libxul.js b/addon-sdk/source/test/test-libxul.js deleted file mode 100644 index 7a11a69cb6..0000000000 --- a/addon-sdk/source/test/test-libxul.js +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -// Test that we can link with libxul using js-ctypes - -const {Cu} = require("chrome"); -const {ctypes} = Cu.import("resource://gre/modules/ctypes.jsm", {}); -const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); - -exports.test = function(assert) { - let path = OS.Constants.Path.libxul; - assert.pass("libxul is at " + path); - let lib = ctypes.open(path); - assert.ok(lib != null, "linked to libxul successfully"); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-list.js b/addon-sdk/source/test/test-list.js deleted file mode 100644 index 9b03a95130..0000000000 --- a/addon-sdk/source/test/test-list.js +++ /dev/null @@ -1,58 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { List, addListItem, removeListItem } = require('sdk/util/list'); -const { Class } = require('sdk/core/heritage'); - -exports.testList = function(assert) { - let list = List(); - addListItem(list, 1); - - for (let key in list) { - assert.equal(key, 0, 'key is correct'); - assert.equal(list[key], 1, 'value is correct'); - } - - let count = 0; - for (let ele of list) { - assert.equal(ele, 1, 'ele is correct'); - assert.equal(++count, 1, 'count is correct'); - } - - count = 0; - for (let ele of list) { - assert.equal(ele, 1, 'ele is correct'); - assert.equal(++count, 1, 'count is correct'); - } - - removeListItem(list, 1); - assert.equal(list.length, 0, 'remove worked'); -}; - -exports.testImplementsList = function(assert) { - let List2 = Class({ - implements: [List], - initialize: function() { - List.prototype.initialize.apply(this, [0, 1, 2]); - } - }); - let list2 = List2(); - let count = 0; - - for (let ele of list2) { - assert.equal(ele, count++, 'ele is correct'); - } - - count = 0; - for (let ele of list2) { - assert.equal(ele, count++, 'ele is correct'); - } - - addListItem(list2, 3); - assert.equal(list2.length, 4, '3 was added'); - assert.equal(list2[list2.length-1], 3, '3 was added'); -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-loader.js b/addon-sdk/source/test/test-loader.js deleted file mode 100644 index 3ee3e34f0b..0000000000 --- a/addon-sdk/source/test/test-loader.js +++ /dev/null @@ -1,657 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -var { - Loader, main, unload, parseStack, resolve, join, - Require, Module -} = require('toolkit/loader'); -var { readURI } = require('sdk/net/url'); - -var root = module.uri.substr(0, module.uri.lastIndexOf('/')); - -const app = require('sdk/system/xul-app'); - -// The following adds Debugger constructor to the global namespace. -const { Cu } = require('chrome'); -const { addDebuggerToGlobal } = Cu.import('resource://gre/modules/jsdebugger.jsm', {}); -addDebuggerToGlobal(this); - -exports['test resolve'] = function (assert) { - let cuddlefish_id = 'sdk/loader/cuddlefish'; - assert.equal(resolve('../index.js', './dir/c.js'), './index.js'); - assert.equal(resolve('./index.js', './dir/c.js'), './dir/index.js'); - assert.equal(resolve('./dir/c.js', './index.js'), './dir/c.js'); - assert.equal(resolve('../utils/file.js', './dir/b.js'), './utils/file.js'); - - assert.equal(resolve('../utils/./file.js', './dir/b.js'), './utils/file.js'); - assert.equal(resolve('../utils/file.js', './'), './../utils/file.js'); - assert.equal(resolve('./utils/file.js', './'), './utils/file.js'); - assert.equal(resolve('./utils/file.js', './index.js'), './utils/file.js'); - - assert.equal(resolve('../utils/./file.js', cuddlefish_id), 'sdk/utils/file.js'); - assert.equal(resolve('../utils/file.js', cuddlefish_id), 'sdk/utils/file.js'); - assert.equal(resolve('./utils/file.js', cuddlefish_id), 'sdk/loader/utils/file.js'); - - assert.equal(resolve('..//index.js', './dir/c.js'), './index.js'); - assert.equal(resolve('../modules/XPCOMUtils.jsm', 'resource://gre/utils/file.js'), 'resource://gre/modules/XPCOMUtils.jsm'); - assert.equal(resolve('../modules/XPCOMUtils.jsm', 'chrome://gre/utils/file.js'), 'chrome://gre/modules/XPCOMUtils.jsm'); - assert.equal(resolve('../../a/b/c.json', 'file:///thing/utils/file.js'), 'file:///a/b/c.json'); - - // Does not change absolute paths - assert.equal(resolve('resource://gre/modules/file.js', './dir/b.js'), - 'resource://gre/modules/file.js'); - assert.equal(resolve('file:///gre/modules/file.js', './dir/b.js'), - 'file:///gre/modules/file.js'); - assert.equal(resolve('/root.js', './dir/b.js'), - '/root.js'); -}; - -exports['test join'] = function (assert) { - assert.equal(join('a/path', '../../../module'), '../module'); - assert.equal(join('a/path/to', '../module'), 'a/path/module'); - assert.equal(join('a/path/to', './module'), 'a/path/to/module'); - assert.equal(join('a/path/to', '././../module'), 'a/path/module'); - assert.equal(join('resource://my/path/yeah/yuh', '../whoa'), - 'resource://my/path/yeah/whoa'); - assert.equal(join('resource://my/path/yeah/yuh', './whoa'), - 'resource://my/path/yeah/yuh/whoa'); - assert.equal(join('resource:///my/path/yeah/yuh', '../whoa'), - 'resource:///my/path/yeah/whoa'); - assert.equal(join('resource:///my/path/yeah/yuh', './whoa'), - 'resource:///my/path/yeah/yuh/whoa'); - assert.equal(join('file:///my/path/yeah/yuh', '../whoa'), - 'file:///my/path/yeah/whoa'); - assert.equal(join('file:///my/path/yeah/yuh', './whoa'), - 'file:///my/path/yeah/yuh/whoa'); - assert.equal(join('a/path/to', '..//module'), 'a/path/module'); -}; - -exports['test dependency cycles'] = function(assert) { - let uri = root + '/fixtures/loader/cycles/'; - let loader = Loader({ paths: { '': uri } }); - - let program = main(loader, 'main'); - - assert.equal(program.a.b, program.b, 'module `a` gets correct `b`'); - assert.equal(program.b.a, program.a, 'module `b` gets correct `a`'); - assert.equal(program.c.main, program, 'module `c` gets correct `main`'); - - unload(loader); -} - -exports['test syntax errors'] = function(assert) { - let uri = root + '/fixtures/loader/syntax-error/'; - let loader = Loader({ paths: { '': uri } }); - - try { - let program = main(loader, 'main'); - } catch (error) { - assert.equal(error.name, "SyntaxError", "throws syntax error"); - assert.equal(error.fileName.split("/").pop(), "error.js", - "Error contains filename"); - assert.equal(error.lineNumber, 11, "error is on line 11"); - let stack = parseStack(error.stack); - - assert.equal(stack.pop().fileName, uri + "error.js", - "last frame file containing syntax error"); - assert.equal(stack.pop().fileName, uri + "main.js", - "previous frame is a requirer module"); - assert.equal(stack.pop().fileName, module.uri, - "previous to it is a test module"); - - } finally { - unload(loader); - } -} - -exports['test sandboxes are not added if error'] = function (assert) { - let uri = root + '/fixtures/loader/missing-twice/'; - let loader = Loader({ paths: { '': uri } }); - let program = main(loader, 'main'); - assert.ok(!(uri + 'not-found.js' in loader.sandboxes), 'not-found.js not in loader.sandboxes'); -} - -exports['test missing module'] = function(assert) { - let uri = root + '/fixtures/loader/missing/' - let loader = Loader({ paths: { '': uri } }); - - try { - let program = main(loader, 'main') - } catch (error) { - assert.equal(error.message, "Module `not-found` is not found at " + - uri + "not-found.js", "throws if error not found"); - - assert.equal(error.fileName.split("/").pop(), "main.js", - "Error fileName is requirer module"); - - assert.equal(error.lineNumber, 7, "error is on line 7"); - - let stack = parseStack(error.stack); - - assert.equal(stack.pop().fileName, uri + "main.js", - "loader stack is omitted"); - - assert.equal(stack.pop().fileName, module.uri, - "previous in the stack is test module"); - } finally { - unload(loader); - } -} - -exports["test invalid module not cached and throws everytime"] = function(assert) { - let uri = root + "/fixtures/loader/missing-twice/"; - let loader = Loader({ paths: { "": uri } }); - - let { firstError, secondError, invalidJSON1, invalidJSON2 } = main(loader, "main"); - assert.equal(firstError.message, "Module `not-found` is not found at " + - uri + "not-found.js", "throws on first invalid require"); - assert.equal(firstError.lineNumber, 8, "first error is on line 7"); - assert.equal(secondError.message, "Module `not-found` is not found at " + - uri + "not-found.js", "throws on second invalid require"); - assert.equal(secondError.lineNumber, 14, "second error is on line 14"); - - assert.equal(invalidJSON1.message, - "JSON.parse: unexpected character at line 1 column 1 of the JSON data", - "throws on invalid JSON"); - assert.equal(invalidJSON2.message, - "JSON.parse: unexpected character at line 1 column 1 of the JSON data", - "throws on invalid JSON second time"); -}; - -exports['test exceptions in modules'] = function(assert) { - let uri = root + '/fixtures/loader/exceptions/' - - let loader = Loader({ paths: { '': uri } }); - - try { - let program = main(loader, 'main') - } catch (error) { - assert.equal(error.message, "Boom!", "thrown errors propagate"); - - assert.equal(error.fileName.split("/").pop(), "boomer.js", - "Error comes from the module that threw it"); - - assert.equal(error.lineNumber, 8, "error is on line 8"); - - let stack = parseStack(error.stack); - - let frame = stack.pop() - assert.equal(frame.fileName, uri + "boomer.js", - "module that threw is first in the stack"); - assert.equal(frame.name, "exports.boom", - "name is in the stack"); - - frame = stack.pop() - assert.equal(frame.fileName, uri + "main.js", - "module that called it is next in the stack"); - assert.equal(frame.lineNumber, 9, "caller line is in the stack"); - - - assert.equal(stack.pop().fileName, module.uri, - "this test module is next in the stack"); - } finally { - unload(loader); - } -} - -exports['test early errors in module'] = function(assert) { - let uri = root + '/fixtures/loader/errors/'; - let loader = Loader({ paths: { '': uri } }); - - try { - let program = main(loader, 'main') - } catch (error) { - assert.equal(String(error), - "Error: opening input stream (invalid filename?)", - "thrown errors propagate"); - - assert.equal(error.fileName.split("/").pop(), "boomer.js", - "Error comes from the module that threw it"); - - assert.equal(error.lineNumber, 7, "error is on line 7"); - - let stack = parseStack(error.stack); - - let frame = stack.pop() - assert.equal(frame.fileName, uri + "boomer.js", - "module that threw is first in the stack"); - - frame = stack.pop() - assert.equal(frame.fileName, uri + "main.js", - "module that called it is next in the stack"); - assert.equal(frame.lineNumber, 7, "caller line is in the stack"); - - - assert.equal(stack.pop().fileName, module.uri, - "this test module is next in the stack"); - } finally { - unload(loader); - } -}; - -exports['test require json'] = function (assert) { - let data = require('./fixtures/loader/json/manifest.json'); - assert.equal(data.name, 'Jetpack Loader Test', 'loads json with strings'); - assert.equal(data.version, '1.0.1', 'loads json with strings'); - assert.equal(data.dependencies.async, '*', 'loads json with objects'); - assert.equal(data.dependencies.underscore, '*', 'loads json with objects'); - assert.equal(data.contributors.length, 4, 'loads json with arrays'); - assert.ok(Array.isArray(data.contributors), 'loads json with arrays'); - data.version = '2.0.0'; - let newdata = require('./fixtures/loader/json/manifest.json'); - assert.equal(newdata.version, '2.0.0', - 'JSON objects returned should be cached and the same instance'); - - try { - require('./fixtures/loader/json/invalid.json'); - assert.fail('Error not thrown when loading invalid json'); - } catch (err) { - assert.ok(err, 'error thrown when loading invalid json'); - assert.ok(/JSON\.parse/.test(err.message), - 'should thrown an error from JSON.parse, not attempt to load .json.js'); - } - - // Try again to ensure an empty module isn't loaded from cache - try { - require('./fixtures/loader/json/invalid.json'); - assert.fail('Error not thrown when loading invalid json a second time'); - } catch (err) { - assert.ok(err, - 'error thrown when loading invalid json a second time'); - assert.ok(/JSON\.parse/.test(err.message), - 'should thrown an error from JSON.parse a second time, not attempt to load .json.js'); - } -}; - -exports['test setting metadata for newly created sandboxes'] = function(assert) { - let addonID = 'random-addon-id'; - let uri = root + '/fixtures/loader/cycles/'; - let loader = Loader({ paths: { '': uri }, id: addonID }); - - let dbg = new Debugger(); - dbg.onNewGlobalObject = function(global) { - dbg.onNewGlobalObject = undefined; - - let metadata = Cu.getSandboxMetadata(global.unsafeDereference()); - assert.ok(metadata, 'this global has attached metadata'); - assert.equal(metadata.URI, uri + 'main.js', 'URI is set properly'); - assert.equal(metadata.addonID, addonID, 'addon ID is set'); - } - - let program = main(loader, 'main'); -}; - -exports['test require .json, .json.js'] = function (assert) { - let testjson = require('./fixtures/loader/json/test.json'); - assert.equal(testjson.filename, 'test.json', - 'require("./x.json") should load x.json, not x.json.js'); - - let nodotjson = require('./fixtures/loader/json/nodotjson.json'); - assert.equal(nodotjson.filename, 'nodotjson.json.js', - 'require("./x.json") should load x.json.js when x.json does not exist'); - nodotjson.data.prop = 'hydralisk'; - - // require('nodotjson.json') and require('nodotjson.json.js') - // should resolve to the same file - let nodotjsonjs = require('./fixtures/loader/json/nodotjson.json.js'); - assert.equal(nodotjsonjs.data.prop, 'hydralisk', - 'js modules are cached whether access via .json.js or .json'); -}; - -exports['test invisibleToDebugger: false'] = function (assert) { - let uri = root + '/fixtures/loader/cycles/'; - let loader = Loader({ paths: { '': uri } }); - main(loader, 'main'); - - let dbg = new Debugger(); - let sandbox = loader.sandboxes[uri + 'main.js']; - - try { - dbg.addDebuggee(sandbox); - assert.ok(true, 'debugger added visible value'); - } catch(e) { - assert.fail('debugger could not add visible value'); - } -}; - -exports['test invisibleToDebugger: true'] = function (assert) { - let uri = root + '/fixtures/loader/cycles/'; - let loader = Loader({ paths: { '': uri }, invisibleToDebugger: true }); - main(loader, 'main'); - - let dbg = new Debugger(); - let sandbox = loader.sandboxes[uri + 'main.js']; - - try { - dbg.addDebuggee(sandbox); - assert.fail('debugger added invisible value'); - } catch(e) { - assert.ok(true, 'debugger did not add invisible value'); - } -}; - -exports['test console global by default'] = function (assert) { - let uri = root + '/fixtures/loader/globals/'; - let loader = Loader({ paths: { '': uri }}); - let program = main(loader, 'main'); - - assert.ok(typeof program.console === 'object', 'global `console` exists'); - assert.ok(typeof program.console.log === 'function', 'global `console.log` exists'); - - let loader2 = Loader({ paths: { '': uri }, globals: { console: fakeConsole }}); - let program2 = main(loader2, 'main'); - - assert.equal(program2.console, fakeConsole, - 'global console can be overridden with Loader options'); - function fakeConsole () {}; -}; - -exports['test shared globals'] = function(assert) { - let uri = root + '/fixtures/loader/cycles/'; - let loader = Loader({ paths: { '': uri }, sharedGlobal: true, - sharedGlobalBlocklist: ['b'] }); - - let program = main(loader, 'main'); - - // As it is hard to verify what is the global of an object - // (due to wrappers) we check that we see the `foo` symbol - // being manually injected into the shared global object - loader.sharedGlobalSandbox.foo = true; - - let m = loader.sandboxes[uri + 'main.js']; - let a = loader.sandboxes[uri + 'a.js']; - let b = loader.sandboxes[uri + 'b.js']; - - assert.ok(Cu.getGlobalForObject(m).foo, "main is shared"); - assert.ok(Cu.getGlobalForObject(a).foo, "a is shared"); - assert.ok(!Cu.getGlobalForObject(b).foo, "b isn't shared"); - - unload(loader); -} - -exports['test deprecated shared globals exception name'] = function(assert) { - let uri = root + '/fixtures/loader/cycles/'; - let loader = Loader({ paths: { '': uri }, sharedGlobal: true, - sharedGlobalBlacklist: ['b'] }); - - let program = main(loader, 'main'); - - assert.ok(loader.sharedGlobalBlocklist.includes("b"), "b should be in the blocklist"); - assert.equal(loader.sharedGlobalBlocklist.length, loader.sharedGlobalBlacklist.length, - "both blocklists should have the same number of items."); - assert.equal(loader.sharedGlobalBlocklist.join(","), loader.sharedGlobalBlacklist.join(","), - "both blocklists should have the same items."); - - // As it is hard to verify what is the global of an object - // (due to wrappers) we check that we see the `foo` symbol - // being manually injected into the shared global object - loader.sharedGlobalSandbox.foo = true; - - let m = loader.sandboxes[uri + 'main.js']; - let a = loader.sandboxes[uri + 'a.js']; - let b = loader.sandboxes[uri + 'b.js']; - - assert.ok(Cu.getGlobalForObject(m).foo, "main is shared"); - assert.ok(Cu.getGlobalForObject(a).foo, "a is shared"); - assert.ok(!Cu.getGlobalForObject(b).foo, "b isn't shared"); - - unload(loader); -} - -exports['test prototype of global'] = function (assert) { - let uri = root + '/fixtures/loader/globals/'; - let loader = Loader({ paths: { '': uri }, sharedGlobal: true, - sandboxPrototype: { globalFoo: 5 }}); - - let program = main(loader, 'main'); - - assert.ok(program.globalFoo === 5, '`globalFoo` exists'); -}; - -exports["test require#resolve"] = function(assert) { - let foundRoot = require.resolve("sdk/tabs").replace(/sdk\/tabs.js$/, ""); - assert.ok(root, foundRoot, "correct resolution root"); - - assert.equal(foundRoot + "sdk/tabs.js", require.resolve("sdk/tabs"), "correct resolution of sdk module"); - assert.equal(foundRoot + "toolkit/loader.js", require.resolve("toolkit/loader"), "correct resolution of sdk module"); - - const localLoader = Loader({ - paths: { "foo/bar": "bizzle", - "foo/bar2/": "bizzle2", - // Just to make sure this doesn't match the first entry, - // let use resolve this module - "foo/bar-bar": "foo/bar-bar" } - }); - const localRequire = Require(localLoader, module); - assert.equal(localRequire.resolve("foo/bar"), "bizzle.js"); - assert.equal(localRequire.resolve("foo/bar/baz"), "bizzle/baz.js"); - assert.equal(localRequire.resolve("foo/bar-bar"), "foo/bar-bar.js"); - assert.equal(localRequire.resolve("foo/bar2/"), "bizzle2.js"); -}; - -const modulesURI = require.resolve("toolkit/loader").replace("toolkit/loader.js", ""); -exports["test loading a loader"] = function(assert) { - const loader = Loader({ paths: { "": modulesURI } }); - - const require = Require(loader, module); - - const requiredLoader = require("toolkit/loader"); - - assert.equal(requiredLoader.Loader, Loader, - "got the same Loader instance"); - - const jsmLoader = Cu.import(require.resolve("toolkit/loader"), {}).Loader; - - assert.equal(jsmLoader.Loader, requiredLoader.Loader, - "loading loader via jsm returns same loader"); - - unload(loader); -}; - -exports['test loader on unsupported modules with checkCompatibility true'] = function(assert) { - let loader = Loader({ - paths: { '': root + "/" }, - checkCompatibility: true - }); - let require = Require(loader, module); - - assert.throws(() => { - if (!app.is('Firefox')) { - require('fixtures/loader/unsupported/firefox'); - } - else { - require('fixtures/loader/unsupported/fennec'); - } - }, /^Unsupported Application/, "throws Unsupported Application"); - - unload(loader); -}; - -exports['test loader on unsupported modules with checkCompatibility false'] = function(assert) { - let loader = Loader({ - paths: { '': root + "/" }, - checkCompatibility: false - }); - let require = Require(loader, module); - - try { - if (!app.is('Firefox')) { - require('fixtures/loader/unsupported/firefox'); - } - else { - require('fixtures/loader/unsupported/fennec'); - } - assert.pass("loaded unsupported module without an error"); - } - catch(e) { - assert.fail(e); - } - - unload(loader); -}; - -exports['test loader on unsupported modules with checkCompatibility default'] = function(assert) { - let loader = Loader({ paths: { '': root + "/" } }); - let require = Require(loader, module); - - try { - if (!app.is('Firefox')) { - require('fixtures/loader/unsupported/firefox'); - } - else { - require('fixtures/loader/unsupported/fennec'); - } - assert.pass("loaded unsupported module without an error"); - } - catch(e) { - assert.fail(e); - } - - unload(loader); -}; - -exports["test Cu.import of toolkit/loader"] = (assert) => { - const toolkitLoaderURI = require.resolve("toolkit/loader"); - const loaderModule = Cu.import(toolkitLoaderURI).Loader; - const { Loader, Require, Main } = loaderModule; - const version = "0.1.0"; - const id = `fxos_${version.replace(".", "_")}_simulator@mozilla.org`; - const uri = `resource://${encodeURIComponent(id.replace("@", "at"))}/`; - - const loader = Loader({ - paths: { - "./": uri + "lib/", - // Can't just put `resource://gre/modules/commonjs/` as it - // won't take module overriding into account. - "": toolkitLoaderURI.replace("toolkit/loader.js", "") - }, - globals: { - console: console - }, - modules: { - "toolkit/loader": loaderModule, - addon: { - id: "simulator", - version: "0.1", - uri: uri - } - } - }); - - let require_ = Require(loader, { id: "./addon" }); - assert.equal(typeof(loaderModule), - typeof(require_("toolkit/loader")), - "module returned is whatever was mapped to it"); -}; - -exports["test Cu.import in b2g style"] = (assert) => { - const {FakeCu} = require("./loader/b2g"); - const toolkitLoaderURI = require.resolve("toolkit/loader"); - const b2g = new FakeCu(); - - const exported = {}; - const loader = b2g.import(toolkitLoaderURI, exported); - - assert.equal(typeof(exported.Loader), - "function", - "loader is a function"); - assert.equal(typeof(exported.Loader.Loader), - "function", - "Loader.Loader is a funciton"); -}; - -exports['test lazy globals'] = function (assert) { - let uri = root + '/fixtures/loader/lazy/'; - let gotFoo = false; - let foo = {}; - let modules = { - get foo() { - gotFoo = true; - return foo; - } - }; - let loader = Loader({ paths: { '': uri }, modules: modules}); - assert.ok(!gotFoo, "foo hasn't been accessed during loader instanciation"); - let program = main(loader, 'main'); - assert.ok(!gotFoo, "foo hasn't been accessed during module loading"); - assert.equal(program.useFoo(), foo, "foo mock works"); - assert.ok(gotFoo, "foo has been accessed only when we first try to use it"); -}; - -exports['test user global'] = function(assert) { - // Test case for bug 827792 - let com = {}; - let loader = require('toolkit/loader'); - let loadOptions = require('@loader/options'); - let options = loader.override(loadOptions, - {globals: loader.override(loadOptions.globals, - {com: com, - console: console, - dump: dump})}); - let subloader = loader.Loader(options); - let userRequire = loader.Require(subloader, module); - let userModule = userRequire("./loader/user-global"); - - assert.equal(userModule.getCom(), com, - "user module returns expected `com` global"); -}; - -exports['test custom require caching'] = function(assert) { - const loader = Loader({ - paths: { '': root + "/" }, - requireHook: (id, require) => { - // Just load it normally - return require(id); - } - }); - const require = Require(loader, module); - - let data = require('fixtures/loader/json/mutation.json'); - assert.equal(data.value, 1, 'has initial value'); - data.value = 2; - let newdata = require('fixtures/loader/json/mutation.json'); - assert.equal( - newdata.value, - 2, - 'JSON objects returned should be cached and the same instance' - ); -}; - -exports['test caching when proxying a loader'] = function(assert) { - const parentRequire = require; - const loader = Loader({ - paths: { '': root + "/" }, - requireHook: (id, childRequire) => { - if(id === 'gimmejson') { - return childRequire('fixtures/loader/json/mutation.json') - } - // Load it with the original (global) require - return parentRequire(id); - } - }); - const childRequire = Require(loader, module); - - let data = childRequire('./fixtures/loader/json/mutation.json'); - assert.equal(data.value, 1, 'data has initial value'); - data.value = 2; - - let newdata = childRequire('./fixtures/loader/json/mutation.json'); - assert.equal(newdata.value, 2, 'data has changed'); - - let childData = childRequire('gimmejson'); - assert.equal(childData.value, 1, 'data from child loader has initial value'); - childData.value = 3; - let newChildData = childRequire('gimmejson'); - assert.equal(newChildData.value, 3, 'data from child loader has changed'); - - data = childRequire('./fixtures/loader/json/mutation.json'); - assert.equal(data.value, 2, 'data from parent loader has not changed'); - - // Set it back to the original value just in case (this instance - // will be shared across tests) - data.value = 1; -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-match-pattern.js b/addon-sdk/source/test/test-match-pattern.js deleted file mode 100644 index 651ad31487..0000000000 --- a/addon-sdk/source/test/test-match-pattern.js +++ /dev/null @@ -1,137 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { MatchPattern } = require("sdk/util/match-pattern"); - -exports.testMatchPatternTestTrue = function(assert) { - function ok(pattern, url) { - let mp = new MatchPattern(pattern); - assert.ok(mp.test(url), pattern + " should match " + url); - } - - ok("*", "http://example.com"); - ok("*", "https://example.com"); - ok("*", "ftp://example.com"); - - ok("*.example.com", "http://example.com"); - ok("*.example.com", "http://hamburger.example.com"); - ok("*.example.com", "http://hotdog.hamburger.example.com"); - - ok("http://example.com*", "http://example.com"); - ok("http://example.com*", "http://example.com/"); - ok("http://example.com/*", "http://example.com/"); - ok("http://example.com/*", "http://example.com/potato-salad"); - ok("http://example.com/pickles/*", "http://example.com/pickles/"); - ok("http://example.com/pickles/*", "http://example.com/pickles/lemonade"); - - ok("http://example.com", "http://example.com"); - ok("http://example.com/ice-cream", "http://example.com/ice-cream"); - - ok(/.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); - ok(/.*A.*/i, "http://A.com"); - ok(/.*A.*/i, "http://a.com"); - ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); - ok('*.sample.com', 'http://ex.sample.com/foo.html'); - ok('*.amp.le.com', 'http://ex.amp.le.com'); - - ok('data:*', 'data:text/html;charset=utf-8,'); -}; - -exports.testMatchPatternTestFalse = function(assert) { - function ok(pattern, url) { - let mp = new MatchPattern(pattern); - assert.ok(!mp.test(url), pattern + " should not match " + url); - } - - ok("*", null); - ok("*", ""); - ok("*", "bogus"); - ok("*", "chrome://browser/content/browser.xul"); - ok("*", "nttp://example.com"); - - ok("*.example.com", null); - ok("*.example.com", ""); - ok("*.example.com", "bogus"); - ok("*.example.com", "http://example.net"); - ok("*.example.com", "http://foo.com"); - ok("*.example.com", "http://example.com.foo"); - ok("*.example2.com", "http://example.com"); - - ok("http://example.com/*", null); - ok("http://example.com/*", ""); - ok("http://example.com/*", "bogus"); - ok("http://example.com/*", "http://example.com"); - ok("http://example.com/*", "http://foo.com/"); - - ok("http://example.com", null); - ok("http://example.com", ""); - ok("http://example.com", "bogus"); - ok("http://example.com", "http://example.com/"); - - ok(/zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); - ok(/.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); - ok(/.*Zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=655464"); // bug 655464 - ok(/https:.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); - - // bug 856913 - ok('*.ign.com', 'http://www.design.com'); - ok('*.ign.com', 'http://design.com'); - ok('*.zilla.com', 'http://bugzilla.mozilla.com'); - ok('*.zilla.com', 'http://mo-zilla.com'); - ok('*.amp.le.com', 'http://amp-le.com'); - ok('*.amp.le.com', 'http://examp.le.com'); -}; - -exports.testMatchPatternErrors = function(assert) { - assert.throws( - () => new MatchPattern("*.google.com/*"), - /There can be at most one/, - "MatchPattern throws when supplied multiple '*'" - ); - - assert.throws( - () => new MatchPattern("google.com"), - /expected to be either an exact URL/, - "MatchPattern throws when the wildcard doesn't use '*' and doesn't " + - "look like a URL" - ); - - assert.throws( - () => new MatchPattern("http://google*.com"), - /expected to be the first or the last/, - "MatchPattern throws when a '*' is in the middle of the wildcard" - ); - - assert.throws( - () => new MatchPattern(/ /g), - /^A RegExp match pattern cannot be set to `global` \(i\.e\. \/\/g\)\.$/, - "MatchPattern throws on a RegExp set to `global` (i.e. //g)." - ); - - assert.throws( - () => new MatchPattern( / /m ), - /^A RegExp match pattern cannot be set to `multiline` \(i\.e\. \/\/m\)\.$/, - "MatchPattern throws on a RegExp set to `multiline` (i.e. //m)." - ); -}; - -exports.testMatchPatternInternals = function(assert) { - assert.equal( - new MatchPattern("http://google.com/test").exactURL, - "http://google.com/test" - ); - - assert.equal( - new MatchPattern("http://google.com/test/*").urlPrefix, - "http://google.com/test/" - ); - - assert.equal( - new MatchPattern("*.example.com").domain, - "example.com" - ); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-method.js b/addon-sdk/source/test/test-method.js deleted file mode 100644 index 0478593c1f..0000000000 --- a/addon-sdk/source/test/test-method.js +++ /dev/null @@ -1,7 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -module.exports = require("method/test/common"); diff --git a/addon-sdk/source/test/test-module.js b/addon-sdk/source/test/test-module.js deleted file mode 100644 index 1f9979e4b6..0000000000 --- a/addon-sdk/source/test/test-module.js +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -/** Disabled because of Bug 672199 -exports["test module exports are frozen"] = function(assert) { - assert.ok(Object.isFrozen(require("sdk/hotkeys")), - "module exports are frozen"); -}; - -exports["test redefine exported property"] = function(assert) { - let hotkeys = require("sdk/hotkeys"); - let { Hotkey } = hotkeys; - try { Object.defineProperty(hotkeys, 'Hotkey', { value: {} }); } catch(e) {} - assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be redefined"); -}; -*/ - -exports["test can't delete exported property"] = function(assert) { - let hotkeys = require("sdk/hotkeys"); - let { Hotkey } = hotkeys; - - try { delete hotkeys.Hotkey; } catch(e) {} - assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be deleted"); -}; - -exports["test can't override exported property"] = function(assert) { - let hotkeys = require("sdk/hotkeys"); - let { Hotkey } = hotkeys; - - try { hotkeys.Hotkey = Object } catch(e) {} - assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be overriden"); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-modules.js b/addon-sdk/source/test/test-modules.js deleted file mode 100644 index ee9d3d9b54..0000000000 --- a/addon-sdk/source/test/test-modules.js +++ /dev/null @@ -1,150 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -exports.testDefine = function(assert) { - let tiger = require('./modules/tiger'); - assert.equal(tiger.name, 'tiger', 'name proprety was exported properly'); - assert.equal(tiger.type, 'cat', 'property form other module exported'); -}; - -exports.testDefineInoresNonFactory = function(assert) { - let mod = require('./modules/async2'); - assert.equal(mod.name, 'async2', 'name proprety was exported properly'); - assert.ok(mod.traditional2Name !== 'traditional2', '1st is ignored'); -}; -/* Disable test that require AMD specific functionality: - -// define() that exports a function as the module value, -// specifying a module name. -exports.testDefExport = function(assert) { - var add = require('modules/add'); - assert.equal(add(1, 1), 2, 'Named define() exporting a function'); -}; - -// define() that exports function as a value, but is anonymous -exports.testAnonDefExport = function (assert) { - var subtract = require('modules/subtract'); - assert.equal(subtract(4, 2), 2, - 'Anonymous define() exporting a function'); -} - -// using require([], function () {}) to load modules. -exports.testSimpleRequire = function (assert) { - require(['modules/blue', 'modules/orange'], function (blue, orange) { - assert.equal(blue.name, 'blue', 'Simple require for blue'); - assert.equal(orange.name, 'orange', 'Simple require for orange'); - assert.equal(orange.parentType, 'color', - 'Simple require dependency check for orange'); - }); -} - -// using nested require([]) calls. -exports.testSimpleRequireNested = function (assert) { - require(['modules/blue', 'modules/orange', 'modules/green'], - function (blue, orange, green) { - - require(['modules/orange', 'modules/red'], function (orange, red) { - assert.equal(red.name, 'red', 'Simple require for red'); - assert.equal(red.parentType, 'color', - 'Simple require dependency check for red'); - assert.equal(blue.name, 'blue', 'Simple require for blue'); - assert.equal(orange.name, 'orange', 'Simple require for orange'); - assert.equal(orange.parentType, 'color', - 'Simple require dependency check for orange'); - assert.equal(green.name, 'green', 'Simple require for green'); - assert.equal(green.parentType, 'color', - 'Simple require dependency check for green'); - }); - - }); -} - -// requiring a traditional module, that uses async, that use traditional and -// async, with a circular reference -exports.testMixedCircular = function (assert) { - var t = require('modules/traditional1'); - assert.equal(t.name, 'traditional1', 'Testing name'); - assert.equal(t.traditional2Name, 'traditional2', - 'Testing dependent name'); - assert.equal(t.traditional1Name, 'traditional1', 'Testing circular name'); - assert.equal(t.async2Name, 'async2', 'Testing async2 name'); - assert.equal(t.async2Traditional2Name, 'traditional2', - 'Testing nested traditional2 name'); -} - -// Testing define()(function(require) {}) with some that use exports, -// some that use return. -exports.testAnonExportsReturn = function (assert) { - var lion = require('modules/lion'); - require(['modules/tiger', 'modules/cheetah'], function (tiger, cheetah) { - assert.equal('lion', lion, 'Check lion name'); - assert.equal('tiger', tiger.name, 'Check tiger name'); - assert.equal('cat', tiger.type, 'Check tiger type'); - assert.equal('cheetah', cheetah(), 'Check cheetah name'); - }); -} - -// circular dependency -exports.testCircular = function (assert) { - var pollux = require('modules/pollux'), - castor = require('modules/castor'); - - assert.equal(pollux.name, 'pollux', 'Pollux\'s name'); - assert.equal(pollux.getCastorName(), - 'castor', 'Castor\'s name from Pollux.'); - assert.equal(castor.name, 'castor', 'Castor\'s name'); - assert.equal(castor.getPolluxName(), 'pollux', - 'Pollux\'s name from Castor.'); -} - -// test a bad module that asks for exports but also does a define() return -exports.testBadExportAndReturn = function (assert) { - var passed = false; - try { - var bad = require('modules/badExportAndReturn'); - } catch(e) { - passed = /cannot use exports and also return/.test(e.toString()); - } - assert.equal(passed, true, 'Make sure exports and return fail'); -} - -// test a bad circular dependency, where an exported value is needed, but -// the return value happens too late, a module already asked for the exported -// value. -exports.testBadExportAndReturnCircular = function (assert) { - var passed = false; - try { - var bad = require('modules/badFirst'); - } catch(e) { - passed = /after another module has referenced its exported value/ - .test(e.toString()); - } - assert.equal(passed, true, 'Make sure return after an exported ' + - 'value is grabbed by another module fails.'); -} - -// only allow one define call per file. -exports.testOneDefine = function (assert) { - var passed = false; - try { - var dupe = require('modules/dupe'); - } catch(e) { - passed = /Only one call to define/.test(e.toString()); - } - assert.equal(passed, true, 'Only allow one define call per module'); -} - -// only allow one define call per file, testing a bad nested define call. -exports.testOneDefineNested = function (assert) { - var passed = false; - try { - var dupe = require('modules/dupeNested'); - } catch(e) { - passed = /Only one call to define/.test(e.toString()); - } - assert.equal(passed, true, 'Only allow one define call per module'); -} -*/ - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-mozilla-toolkit-versioning.js b/addon-sdk/source/test/test-mozilla-toolkit-versioning.js deleted file mode 100644 index 1489195959..0000000000 --- a/addon-sdk/source/test/test-mozilla-toolkit-versioning.js +++ /dev/null @@ -1,59 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const {parse, increment} = require("mozilla-toolkit-versioning/index") - -const TestParse = assert => (version, min, max) => { - const actual = parse(version); - assert.equal(actual.min, min); - assert.equal(actual.max, max); -} - -const TestInc = assert => (version, expected) => { - assert.equal(increment(version), expected, - `increment: ${version} should be equal ${expected}`) -} - - - -exports['test parse(version) single value'] = assert => { - const testParse = TestParse(assert) - testParse('1.2.3', '1.2.3', '1.2.3'); - testParse('>=1.2.3', '1.2.3', undefined); - testParse('<=1.2.3', undefined, '1.2.3'); - testParse('>1.2.3', '1.2.3.1', undefined); - testParse('<1.2.3', undefined, '1.2.3.-1'); - testParse('*', undefined, undefined); -}; - -exports['test parse(version) range'] = assert => { - const testParse = TestParse(assert); - testParse('>=1.2.3 <=2.3.4', '1.2.3', '2.3.4'); - testParse('>1.2.3 <=2.3.4', '1.2.3.1', '2.3.4'); - testParse('>=1.2.3 <2.3.4', '1.2.3', '2.3.4.-1'); - testParse('>1.2.3 <2.3.4', '1.2.3.1', '2.3.4.-1'); - - testParse('<=2.3.4 >=1.2.3', '1.2.3', '2.3.4'); - testParse('<=2.3.4 >1.2.3', '1.2.3.1', '2.3.4'); - testParse('<2.3.4 >=1.2.3', '1.2.3', '2.3.4.-1'); - testParse('<2.3.4 >1.2.3', '1.2.3.1', '2.3.4.-1'); - - testParse('1.2.3pre1 - 2.3.4', '1.2.3pre1', '2.3.4'); -}; - -exports['test increment(version)'] = assert => { - const testInc = TestInc(assert); - - testInc('1.2.3', '1.2.3.1'); - testInc('1.2.3a', '1.2.3a1'); - testInc('1.2.3pre', '1.2.3pre1'); - testInc('1.2.3pre1', '1.2.3pre2'); - testInc('1.2', '1.2.1'); - testInc('1.2pre1a', '1.2pre1b'); - testInc('1.2pre1pre', '1.2pre1prf'); -}; - - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-mpl2-license-header.js b/addon-sdk/source/test/test-mpl2-license-header.js deleted file mode 100644 index 22a2cf0eab..0000000000 --- a/addon-sdk/source/test/test-mpl2-license-header.js +++ /dev/null @@ -1,105 +0,0 @@ -// Note: This line is here intentionally, to break MPL2_LICENSE_TEST -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cc, Ci, Cu } = require("chrome"); -const options = require('@loader/options'); -const { id } = require("sdk/self"); -const { getAddonByID } = require("sdk/addon/manager"); -const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence"); -const { readURISync } = require('sdk/net/url'); -const { Request } = require('sdk/request'); -const { defer } = require("sdk/core/promise"); - -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); - -const MIT_LICENSE_HEADER = []; - -const MPL2_LICENSE_TEST = new RegExp([ - "^\\/\\* This Source Code Form is subject to the terms of the Mozilla Public", - " \\* License, v\\. 2\\.0\\. If a copy of the MPL was not distributed with this", - " \\* file, You can obtain one at http:\\/\\/mozilla\\.org\\/MPL\\/2\\.0\\/\\. \\*\\/" -].join("\n")); - -// Note: Using regular expressions because the paths a different for cfx vs jpm -const IGNORES = [ - /lib[\/\\](diffpatcher|method)[\/\\].+$/, // MIT - /lib[\/\\]sdk[\/\\]fs[\/\\]path\.js$/, // MIT - /lib[\/\\]sdk[\/\\]system[\/\\]child_process[\/\\].*/, - /tests?[\/\\]buffers[\/\\].+$/, // MIT - /tests?[\/\\]path[\/\\]test-path\.js$/, - /tests?[\/\\]querystring[\/\\]test-querystring\.js$/, -]; - -const ignoreFile = file => !!IGNORES.find(regex => regex.test(file)); - -const baseURI = "resource://test-sdk-addon/"; - -const uri = (path="") => baseURI + path; - -const toFile = x => x.QueryInterface(Ci.nsIFile); -const isTestFile = ({ path, leafName }) => { - return !ignoreFile(path) && /\.jsm?$/.test(leafName) -}; -const getFileURI = x => ios.newFileURI(x).spec; - -const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries)); - -const isDirectory = x => x.isDirectory(); -const getEntries = directory => mapcat(entry => { - if (isDirectory(entry)) { - return getEntries(entry); - } - else if (isTestFile(entry)) { - return [ entry ]; - } - return []; -}, filter(() => true, getDirectoryEntries(directory))); - -function readURL(url) { - let { promise, resolve } = defer(); - - Request({ - url: url, - overrideMimeType: "text/plain", - onComplete: (response) => resolve(response.text) - }).get(); - - return promise; -} - -exports["test MPL2 license header"] = function*(assert) { - let addon = yield getAddonByID(id); - let xpiURI = addon.getResourceURI(); - let rootURL = xpiURI.spec; - assert.ok(rootURL, rootURL); - let files = [...getEntries(xpiURI.QueryInterface(Ci.nsIFileURL).file)]; - - assert.ok(files.length > 1, files.length + " files found."); - let failures = []; - let success = 0; - - for (let i = 0, len = files.length; i < len; i++) { - let file = files[i]; - assert.ok(file.path, "Trying " + file.path); - - const URI = ios.newFileURI(file); - - let leafName = URI.spec.replace(rootURL, ""); - - let contents = yield readURL(URI.spec); - if (!MPL2_LICENSE_TEST.test(contents)) { - failures.push(leafName); - } - } - - assert.equal(1, failures.length, "we expect one failure"); - assert.ok(/test-mpl2-license-header\.js$/.test(failures[0]), "the only failure is this file"); - failures.shift(); - assert.equal("", failures.join(",\n"), failures.length + " files found missing the required mpl 2 header"); -} - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-namespace.js b/addon-sdk/source/test/test-namespace.js deleted file mode 100644 index 636682e9e4..0000000000 --- a/addon-sdk/source/test/test-namespace.js +++ /dev/null @@ -1,120 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { ns } = require("sdk/core/namespace"); -const { Cc, Ci, Cu } = require("chrome"); -const { setTimeout } = require("sdk/timers") - -exports["test post GC references"] = function (assert, done) { - var target = {}, local = ns() - local(target).there = true - - assert.equal(local(target).there, true, "namespaced preserved"); - - Cu.schedulePreciseGC(function() { - assert.equal(local(target).there, true, "namespace is preserved post GC"); - done(); - }); -}; - -exports["test namsepace basics"] = function(assert) { - var privates = ns(); - var object = { foo: function foo() { return "hello foo"; } }; - - assert.notEqual(privates(object), object, - "namespaced object is not the same"); - assert.ok(!('foo' in privates(object)), - "public properties are not in the namespace"); - - assert.equal(privates(object), privates(object), - "same namespaced object is returned on each call"); -}; - -exports["test namespace overlays"] = function(assert) { - var _ = ns(); - var object = { foo: 'foo' }; - - _(object).foo = 'bar'; - - assert.equal(_(object).foo, "bar", - "namespaced property `foo` changed value"); - - assert.equal(object.foo, "foo", - "public property `foo` has original value"); - - object.foo = "baz"; - assert.equal(_(object).foo, "bar", - "property changes do not affect namespaced properties"); - - object.bar = "foo"; - assert.ok(!("bar" in _(object)), - "new public properties are not reflected in namespace"); -}; - -exports["test shared namespaces"] = function(assert) { - var _ = ns(); - - var f1 = { hello: 1 }; - var f2 = { foo: 'foo', hello: 2 }; - _(f1).foo = _(f2).foo = 'bar'; - - assert.equal(_(f1).hello, _(f2).hello, "namespace can be shared"); - assert.notEqual(f1.hello, _(f1).hello, "shared namespace can overlay"); - assert.notEqual(f2.hello, _(f2).hello, "target is not affected"); - - _(f1).hello = 3; - - assert.notEqual(_(f1).hello, _(f2).hello, - "namespaced property can be overided"); - assert.equal(_(f2).hello, _({}).hello, "namespace does not change"); -}; - -exports["test multi namespace"] = function(assert) { - var n1 = ns(); - var n2 = ns(); - var object = { baz: 1 }; - n1(object).foo = 1; - n2(object).foo = 2; - n1(object).bar = n2(object).bar = 3; - - assert.notEqual(n1(object).foo, n2(object).foo, - "object can have multiple namespaces"); - assert.equal(n1(object).bar, n2(object).bar, - "object can have matching props in diff namespaces"); -}; - -exports["test ns alias"] = function(assert) { - assert.strictEqual(ns, require('sdk/core/namespace').Namespace, - "ns is an alias of Namespace"); -}; - -exports["test ns inheritance"] = function(assert) { - let _ = ns(); - - let prototype = { level: 1 }; - let object = Object.create(prototype); - let delegee = Object.create(object); - - _(prototype).foo = {}; - - assert.ok(!Object.prototype.hasOwnProperty.call(_(delegee), "foo"), - "namespaced property is not copied to descendants"); - assert.equal(_(delegee).foo, _(prototype).foo, - "namespaced properties are inherited by descendants"); - - _(object).foo = {}; - assert.notEqual(_(object).foo, _(prototype).foo, - "namespaced properties may be shadowed"); - assert.equal(_(object).foo, _(delegee).foo, - "shadwed properties are inherited by descendants"); - - _(object).bar = {}; - assert.ok(!("bar" in _(prototype)), - "descendants properties are not copied to ancestors"); - assert.ok(_(object).bar, _(delegee).bar, - "descendants properties are inherited"); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-native-loader.js b/addon-sdk/source/test/test-native-loader.js deleted file mode 100644 index cc71855224..0000000000 --- a/addon-sdk/source/test/test-native-loader.js +++ /dev/null @@ -1,423 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -var { - Loader, main, unload, parseStack, resolve, nodeResolve -} = require('toolkit/loader'); -var { readURI } = require('sdk/net/url'); -var { all } = require('sdk/core/promise'); -var { before, after } = require('sdk/test/utils'); -var testOptions = require('@test/options'); - -var root = module.uri.substr(0, module.uri.lastIndexOf('/')) -// The following adds Debugger constructor to the global namespace. -const { Cc, Ci, Cu } = require('chrome'); -const { addDebuggerToGlobal } = Cu.import('resource://gre/modules/jsdebugger.jsm', {}); -addDebuggerToGlobal(this); - -const { NetUtil } = Cu.import('resource://gre/modules/NetUtil.jsm', {}); - -const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"] - .getService(Ci.nsIResProtocolHandler); - -const fileRoot = resProto.resolveURI(NetUtil.newURI(root)); - -let variants = [ - { - description: "unpacked resource:", - getRootURI(fixture) { - return `${root}/fixtures/${fixture}/`; - }, - }, - { - description: "unpacked file:", - getRootURI(fixture) { - return `${fileRoot}/fixtures/${fixture}/`; - }, - }, - { - description: "packed resource:", - getRootURI(fixture) { - return `resource://${fixture}/`; - }, - }, - { - description: "packed jar:", - getRootURI(fixture) { - return `jar:${fileRoot}/fixtures/${fixture}.xpi!/`; - }, - }, -]; - -let fixtures = [ - 'native-addon-test', - 'native-overrides-test', -]; - -for (let variant of variants) { - exports[`test nodeResolve (${variant.description})`] = function (assert) { - let rootURI = variant.getRootURI('native-addon-test'); - let manifest = {}; - manifest.dependencies = {}; - - // Handles extensions - resolveTest('../package.json', './dir/c.js', './package.json'); - resolveTest('../dir/b.js', './dir/c.js', './dir/b.js'); - - resolveTest('./dir/b', './index.js', './dir/b.js'); - resolveTest('../index', './dir/b.js', './index.js'); - resolveTest('../', './dir/b.js', './index.js'); - resolveTest('./dir/a', './index.js', './dir/a.js', 'Precedence dir/a.js over dir/a/'); - resolveTest('../utils', './dir/a.js', './utils/index.js', 'Requiring a directory defaults to dir/index.js'); - resolveTest('../newmodule', './dir/c.js', './newmodule/lib/file.js', 'Uses package.json main in dir to load appropriate "main"'); - resolveTest('test-math', './utils/index.js', './node_modules/test-math/index.js', - 'Dependencies default to their index.js'); - resolveTest('test-custom-main', './utils/index.js', './node_modules/test-custom-main/lib/custom-entry.js', - 'Dependencies use "main" entry'); - resolveTest('test-math/lib/sqrt', './utils/index.js', './node_modules/test-math/lib/sqrt.js', - 'Dependencies\' files can be consumed via "/"'); - - resolveTest('sdk/tabs/utils', './index.js', undefined, - 'correctly ignores SDK references in paths'); - resolveTest('fs', './index.js', undefined, - 'correctly ignores built in node modules in paths'); - - resolveTest('test-add', './node_modules/test-math/index.js', - './node_modules/test-math/node_modules/test-add/index.js', - 'Dependencies\' dependencies can be found'); - - resolveTest('resource://gre/modules/commonjs/sdk/tabs.js', './index.js', undefined, - 'correctly ignores absolute URIs.'); - - resolveTest('../tabs', 'resource://gre/modules/commonjs/sdk/addon/bootstrap.js', undefined, - 'correctly ignores attempts to resolve from a module at an absolute URI.'); - - resolveTest('sdk/tabs', 'resource://gre/modules/commonjs/sdk/addon/bootstrap.js', undefined, - 'correctly ignores attempts to resolve from a module at an absolute URI.'); - - function resolveTest (id, requirer, expected, msg) { - let result = nodeResolve(id, requirer, { manifest: manifest, rootURI: rootURI }); - assert.equal(result, expected, 'nodeResolve ' + id + ' from ' + requirer + ' ' +msg); - } - } - - /* - // TODO not working in current env - exports[`test bundle (${variant.description`] = function (assert, done) { - loadAddon('/native-addons/native-addon-test/') - }; - */ - - exports[`test native Loader with mappings (${variant.description})`] = function (assert, done) { - all([ - getJSON('/fixtures/native-addon-test/expectedmap.json'), - getJSON('/fixtures/native-addon-test/package.json') - ]).then(([expectedMap, manifest]) => { - - // Override dummy module and point it to `test-math` to see if the - // require is pulling from the mapping - expectedMap['./index.js']['./dir/dummy'] = './dir/a.js'; - - let rootURI = variant.getRootURI('native-addon-test'); - let loader = Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - requireMap: expectedMap, - isNative: true - }); - - let program = main(loader); - assert.equal(program.dummyModule, 'dir/a', - 'The lookup uses the information given in the mapping'); - - testLoader(program, assert); - unload(loader); - done(); - }).then(null, (reason) => console.error(reason)); - }; - - exports[`test native Loader overrides (${variant.description})`] = function*(assert) { - const expectedKeys = Object.keys(require("sdk/io/fs")).join(", "); - const manifest = yield getJSON('/fixtures/native-overrides-test/package.json'); - const rootURI = variant.getRootURI('native-overrides-test'); - - let loader = Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - metadata: manifest, - isNative: true - }); - - let program = main(loader); - let fooKeys = Object.keys(program.foo).join(", "); - let barKeys = Object.keys(program.foo).join(", "); - let fsKeys = Object.keys(program.fs).join(", "); - let overloadKeys = Object.keys(program.overload.fs).join(", "); - let overloadLibKeys = Object.keys(program.overloadLib.fs).join(", "); - - assert.equal(fooKeys, expectedKeys, "foo exports sdk/io/fs"); - assert.equal(barKeys, expectedKeys, "bar exports sdk/io/fs"); - assert.equal(fsKeys, expectedKeys, "sdk/io/fs exports sdk/io/fs"); - assert.equal(overloadKeys, expectedKeys, "overload exports foo which exports sdk/io/fs"); - assert.equal(overloadLibKeys, expectedKeys, "overload/lib/foo exports foo/lib/foo"); - assert.equal(program.internal, "test", "internal exports ./lib/internal"); - assert.equal(program.extra, true, "fs-extra was exported properly"); - - assert.equal(program.Tabs, "no tabs exist", "sdk/tabs exports ./lib/tabs from the add-on"); - assert.equal(program.CoolTabs, "no tabs exist", "sdk/tabs exports ./lib/tabs from the node_modules"); - assert.equal(program.CoolTabsLib, "a cool tabs implementation", "./lib/tabs true relative path from the node_modules"); - - assert.equal(program.ignore, "do not ignore this export", "../ignore override was ignored."); - - unload(loader); - }; - - exports[`test invalid native Loader overrides cause no errors (${variant.description})`] = function*(assert) { - const manifest = yield getJSON('/fixtures/native-overrides-test/package.json'); - const rootURI = variant.getRootURI('native-overrides-test'); - const EXPECTED = JSON.stringify({}); - - let makeLoader = (rootURI, manifest) => Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - metadata: manifest, - isNative: true - }); - - manifest.jetpack.overrides = "string"; - let loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to a string caused no errors making the loader"); - unload(loader); - - manifest.jetpack.overrides = true; - loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to a boolean caused no errors making the loader"); - unload(loader); - - manifest.jetpack.overrides = 5; - loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to a number caused no errors making the loader"); - unload(loader); - - manifest.jetpack.overrides = null; - loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to null caused no errors making the loader"); - unload(loader); - }; - - exports[`test invalid native Loader jetpack key cause no errors (${variant.description})`] = function*(assert) { - const manifest = yield getJSON('/fixtures/native-overrides-test/package.json'); - const rootURI = variant.getRootURI('native-overrides-test'); - const EXPECTED = JSON.stringify({}); - - let makeLoader = (rootURI, manifest) => Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - metadata: manifest, - isNative: true - }); - - manifest.jetpack = "string"; - let loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to a string caused no errors making the loader"); - unload(loader); - - manifest.jetpack = true; - loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to a boolean caused no errors making the loader"); - unload(loader); - - manifest.jetpack = 5; - loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to a number caused no errors making the loader"); - unload(loader); - - manifest.jetpack = null; - loader = makeLoader(rootURI, manifest); - assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED, - "setting jetpack.overrides to null caused no errors making the loader"); - unload(loader); - }; - - exports[`test native Loader without mappings (${variant.description})`] = function (assert, done) { - getJSON('/fixtures/native-addon-test/package.json').then(manifest => { - let rootURI = variant.getRootURI('native-addon-test'); - let loader = Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - isNative: true - }); - - let program = main(loader); - testLoader(program, assert); - unload(loader); - done(); - }).then(null, (reason) => console.error(reason)); - }; - - exports[`test require#resolve with relative, dependencies (${variant.description})`] = function(assert, done) { - getJSON('/fixtures/native-addon-test/package.json').then(manifest => { - let rootURI = variant.getRootURI('native-addon-test'); - let loader = Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - isNative: true - }); - - let program = main(loader); - let fixtureRoot = program.require.resolve("./").replace(/index\.js$/, ""); - - assert.equal(variant.getRootURI("native-addon-test"), fixtureRoot, "correct resolution root"); - assert.equal(program.require.resolve("test-math"), fixtureRoot + "node_modules/test-math/index.js", "works with node_modules"); - assert.equal(program.require.resolve("./newmodule"), fixtureRoot + "newmodule/lib/file.js", "works with directory mains"); - assert.equal(program.require.resolve("./dir/a"), fixtureRoot + "dir/a.js", "works with normal relative module lookups"); - assert.equal(program.require.resolve("modules/Promise.jsm"), "resource://gre/modules/Promise.jsm", "works with path lookups"); - - // TODO bug 1050422, handle loading non JS/JSM file paths - // assert.equal(program.require.resolve("test-assets/styles.css"), fixtureRoot + "node_modules/test-assets/styles.css", - // "works with different file extension lookups in dependencies"); - - unload(loader); - done(); - }).then(null, (reason) => console.error(reason)); - }; -} - -before(exports, () => { - for (let fixture of fixtures) { - let url = `jar:${root}/fixtures/${fixture}.xpi!/`; - - resProto.setSubstitution(fixture, NetUtil.newURI(url)); - } -}); - -after(exports, () => { - for (let fixture of fixtures) - resProto.setSubstitution(fixture, null); -}); - -exports['test JSM loading'] = function (assert, done) { - getJSON('/fixtures/jsm-package/package.json').then(manifest => { - let rootURI = root + '/fixtures/jsm-package/'; - let loader = Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - isNative: true - }); - - let program = main(loader); - assert.ok(program.localJSMCached, 'local relative JSMs are cached'); - assert.ok(program.isCachedJSAbsolute , 'absolute resource:// js are cached'); - assert.ok(program.isCachedPath, 'JSMs resolved in paths are cached'); - assert.ok(program.isCachedAbsolute, 'absolute resource:// JSMs are cached'); - - assert.ok(program.localJSM, 'able to load local relative JSMs'); - all([ - program.isLoadedPath(10), - program.isLoadedAbsolute(20), - program.isLoadedJSAbsolute(30) - ]).then(([path, absolute, jsabsolute]) => { - assert.equal(path, 10, 'JSM files resolved from path work'); - assert.equal(absolute, 20, 'JSM files resolved from full resource:// work'); - assert.equal(jsabsolute, 30, 'JS files resolved from full resource:// work'); - }).then(done, console.error); - - }).then(null, console.error); -}; - -function testLoader (program, assert) { - // Test 'main' entries - // no relative custom main `lib/index.js` - assert.equal(program.customMainModule, 'custom entry file', - 'a node_module dependency correctly uses its `main` entry in manifest'); - // relative custom main `./lib/index.js` - assert.equal(program.customMainModuleRelative, 'custom entry file relative', - 'a node_module dependency correctly uses its `main` entry in manifest with relative ./'); - // implicit './index.js' - assert.equal(program.defaultMain, 'default main', - 'a node_module dependency correctly defautls to index.js for main'); - - // Test directory exports - assert.equal(program.directoryDefaults, 'utils', - '`require`ing a directory defaults to dir/index.js'); - assert.equal(program.directoryMain, 'main from new module', - '`require`ing a directory correctly loads the `main` entry and not index.js'); - assert.equal(program.resolvesJSoverDir, 'dir/a', - '`require`ing "a" resolves "a.js" over "a/index.js"'); - - // Test dependency's dependencies - assert.ok(program.math.add, - 'correctly defaults to index.js of a module'); - assert.equal(program.math.add(10, 5), 15, - 'node dependencies correctly include their own dependencies'); - assert.equal(program.math.subtract(10, 5), 5, - 'node dependencies correctly include their own dependencies'); - assert.equal(program.mathInRelative.subtract(10, 5), 5, - 'relative modules can also include node dependencies'); - - // Test SDK natives - assert.ok(program.promise.defer, 'main entry can include SDK modules with no deps'); - assert.ok(program.promise.resolve, 'main entry can include SDK modules with no deps'); - assert.ok(program.eventCore.on, 'main entry can include SDK modules that have dependencies'); - assert.ok(program.eventCore.off, 'main entry can include SDK modules that have dependencies'); - - // Test JSMs - assert.ok(program.promisejsm.defer, 'can require JSM files in path'); - assert.equal(program.localJSM.test, 'this is a jsm', - 'can require relative JSM files'); - - // Other tests - assert.equal(program.areModulesCached, true, - 'modules are correctly cached'); - assert.equal(program.testJSON.dependencies['test-math'], '*', - 'correctly requires JSON files'); -} - -function getJSON (uri) { - return readURI(root + uri).then(manifest => JSON.parse(manifest)); -} - -function makePaths (uri) { - // Uses development SDK modules if overloaded in loader - let sdkPaths = testOptions.paths ? testOptions.paths[''] : 'resource://gre/modules/commonjs/'; - return { - './': uri, - 'sdk/': sdkPaths + 'sdk/', - 'toolkit/': sdkPaths + 'toolkit/', - 'modules/': 'resource://gre/modules/' - }; -} - -function loadAddon (uri, map) { - let rootURI = root + uri; - getJSON(uri + '/package.json').then(manifest => { - let loader = Loader({ - paths: makePaths(rootURI), - rootURI: rootURI, - manifest: manifest, - isNative: true, - modules: { - '@test/options': testOptions - } - }); - let program = main(loader); - }).then(null, console.error); -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-native-options.js b/addon-sdk/source/test/test-native-options.js deleted file mode 100644 index 84212757a4..0000000000 --- a/addon-sdk/source/test/test-native-options.js +++ /dev/null @@ -1,183 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { setDefaults, injectOptions: inject, validate } = require('sdk/preferences/native-options'); -const { activeBrowserWindow: { document } } = require("sdk/deprecated/window-utils"); -const { preferencesBranch, id } = require('sdk/self'); -const { get } = require('sdk/preferences/service'); -const { setTimeout } = require('sdk/timers'); -const simple = require('sdk/simple-prefs'); -const fixtures = require('./fixtures'); -const { Cc, Ci } = require('chrome'); - -const prefsrv = Cc['@mozilla.org/preferences-service;1']. - getService(Ci.nsIPrefService); - -function injectOptions(preferences, preferencesBranch, document, parent) { - inject({ - id: id, - preferences: preferences, - preferencesBranch: preferencesBranch, - document: document, - parent: parent - }); -} - -exports.testValidate = function(assert) { - let { preferences } = packageJSON('simple-prefs'); - - let block = () => validate(preferences); - - delete preferences[3].options[0].value; - assert.throws(block, /option requires both a value/, "option missing value error"); - - delete preferences[2].options; - assert.throws(block, /'test3' pref requires options/, "menulist missing options error"); - - preferences[1].type = 'control'; - assert.throws(block, /'test2' control requires a label/, "control missing label error"); - - preferences[1].type = 'nonvalid'; - assert.throws(block, /'test2' pref must be of valid type/, "invalid pref type error"); - - delete preferences[0].title; - assert.throws(block, /'test' pref requires a title/, "pref missing title error"); -} - -exports.testNoPrefs = function(assert, done) { - let { preferences } = packageJSON('no-prefs'); - - let parent = document.createDocumentFragment(); - injectOptions(preferences || [], preferencesBranch, document, parent); - assert.equal(parent.children.length, 0, "No setting elements injected"); - - // must test with events because we can't reset default prefs - function onPrefChange(name) { - assert.fail("No preferences should be defined"); - } - - simple.on('', onPrefChange); - setDefaults(preferences || [], preferencesBranch); - setTimeout(function() { - assert.pass("No preferences were defined"); - simple.off('', onPrefChange); - done(); - }, 100); -} - -exports.testCurlyID = function(assert) { - let { preferences, id } = packageJSON('curly-id'); - let branch = prefsrv.getDefaultBranch('extensions.' + id); - - let parent = document.createDocumentFragment(); - injectOptions(preferences, id, document, parent); - assert.equal(parent.children.length, 1, "One setting elements injected"); - assert.equal(parent.firstElementChild.attributes.pref.value, - "extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13", - "Setting pref attribute is set properly"); - - setDefaults(preferences, id); - assert.equal(get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13'), - 26, "test13 is 26"); - - branch.deleteBranch(''); - assert.equal(get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13'), - undefined, "test13 is undefined"); -} - -exports.testPreferencesBranch = function(assert) { - let { preferences, 'preferences-branch': prefsBranch } = packageJSON('preferences-branch'); - let branch = prefsrv.getDefaultBranch('extensions.' + prefsBranch); - - let parent = document.createDocumentFragment(); - injectOptions(preferences, prefsBranch, document, parent); - assert.equal(parent.children.length, 1, "One setting elements injected"); - assert.equal(parent.firstElementChild.attributes.pref.value, - "extensions.human-readable.test42", - "Setting pref attribute is set properly"); - - setDefaults(preferences, prefsBranch); - assert.equal(get('extensions.human-readable.test42'), true, "test42 is true"); - - branch.deleteBranch(''); - assert.equal(get('extensions.human-readable.test42'), undefined, "test42 is undefined"); -} - -exports.testSimplePrefs = function(assert) { - let { preferences } = packageJSON('simple-prefs'); - let branch = prefsrv.getDefaultBranch('extensions.' + preferencesBranch); - - function assertPref(setting, name, type, title, description = null) { - assert.equal(setting.getAttribute('data-jetpack-id'), id, - "setting 'data-jetpack-id' attribute correct"); - assert.equal(setting.getAttribute('pref'), 'extensions.' + id + '.' + name, - "setting 'pref' attribute correct"); - assert.equal(setting.getAttribute('pref-name'), name, - "setting 'pref-name' attribute correct"); - assert.equal(setting.getAttribute('type'), type, - "setting 'type' attribute correct"); - assert.equal(setting.getAttribute('title'), title, - "setting 'title' attribute correct"); - if (description) { - assert.equal(setting.getAttribute('desc'), description, - "setting 'desc' attribute correct"); - } - else { - assert.ok(!setting.hasAttribute('desc'), - "setting 'desc' attribute is not present"); - } - } - - function assertOption(option, value, label) { - assert.equal(option.getAttribute('value'), value, "value attribute correct"); - assert.equal(option.getAttribute('label'), label, "label attribute correct"); - } - - let parent = document.createDocumentFragment(); - injectOptions(preferences, preferencesBranch, document, parent); - assert.equal(parent.children.length, 8, "Eight setting elements injected"); - - assertPref(parent.children[0], 'test', 'bool', 't\u00EBst', 'descr\u00EFpti\u00F6n'); - assertPref(parent.children[1], 'test2', 'string', 't\u00EBst'); - assertPref(parent.children[2], 'test3', 'menulist', '">menuitem'); - let radios = parent.children[3].querySelectorAll('radiogroup>radio'); - - assertOption(menuItems[0], '0', 'label1'); - assertOption(menuItems[1], '1', 'label2'); - assertOption(radios[0], 'red', 'rouge'); - assertOption(radios[1], 'blue', 'bleu'); - - setDefaults(preferences, preferencesBranch); - assert.strictEqual(simple.prefs.test, false, "test is false"); - assert.strictEqual(simple.prefs.test2, "\u00FCnic\u00F8d\u00E9", "test2 is unicode"); - assert.strictEqual(simple.prefs.test3, "1", "test3 is '1'"); - assert.strictEqual(simple.prefs.test4, "red", "test4 is 'red'"); - - // Only delete the test preferences to avoid unsetting any test harness - // preferences. - for (let setting of parent.children) { - let name = setting.getAttribute('pref-name'); - branch.deleteBranch("." + name); - } - - assert.strictEqual(simple.prefs.test, undefined, "test is undefined"); - assert.strictEqual(simple.prefs.test2, undefined, "test2 is undefined"); - assert.strictEqual(simple.prefs.test3, undefined, "test3 is undefined"); - assert.strictEqual(simple.prefs.test4, undefined, "test4 is undefined"); -} - -function packageJSON(dir) { - return require(fixtures.url('preferences/' + dir + '/package.json')); -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-net-url.js b/addon-sdk/source/test/test-net-url.js deleted file mode 100644 index 9e463b798c..0000000000 --- a/addon-sdk/source/test/test-net-url.js +++ /dev/null @@ -1,137 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { readURI, readURISync } = require("sdk/net/url"); -const data = require("./fixtures"); - -const utf8text = "Hello, ゼロ!"; -const latin1text = "Hello, ゼロ!"; - -const dataURIutf8 = "data:text/plain;charset=utf-8," + encodeURIComponent(utf8text); -const dataURIlatin1 = "data:text/plain;charset=ISO-8859-1," + escape(latin1text); -const chromeURI = "chrome://global-platform/locale/accessible.properties"; - -exports["test async readURI"] = function(assert, done) { - let content = ""; - - readURI(data.url("test-net-url.txt")).then(function(data) { - content = data; - assert.equal(content, utf8text, "The URL content is loaded properly"); - done(); - }, function() { - assert.fail("should not reject"); - done(); - }) - - assert.equal(content, "", "The URL content is not load yet"); -} - -exports["test readURISync"] = function(assert) { - let content = readURISync(data.url("test-net-url.txt")); - - assert.equal(content, utf8text, "The URL content is loaded properly"); -} - -exports["test async readURI with ISO-8859-1 charset"] = function(assert, done) { - let content = ""; - - readURI(data.url("test-net-url.txt"), { charset : "ISO-8859-1"}).then(function(data) { - content = data; - assert.equal(content, latin1text, "The URL content is loaded properly"); - done(); - }, function() { - assert.fail("should not reject"); - done(); - }) - - assert.equal(content, "", "The URL content is not load yet"); -} - -exports["test readURISync with ISO-8859-1 charset"] = function(assert) { - let content = readURISync(data.url("test-net-url.txt"), "ISO-8859-1"); - - assert.equal(content, latin1text, "The URL content is loaded properly"); -} - -exports["test async readURI with not existing file"] = function(assert, done) { - readURI(data.url("test-net-url-fake.txt")).then(function(data) { - assert.fail("should not resolve"); - done(); - }, function(reason) { - assert.ok(reason.indexOf("Failed to read:") === 0); - done(); - }) -} - -exports["test readURISync with not existing file"] = function(assert) { - assert.throws(function() { - readURISync(data.url("test-net-url-fake.txt")); - }, /NS_ERROR_FILE_NOT_FOUND/); -} - -exports["test async readURI with data URI"] = function(assert, done) { - let content = ""; - - readURI(dataURIutf8).then(function(data) { - content = data; - assert.equal(content, utf8text, "The URL content is loaded properly"); - done(); - }, function() { - assert.fail("should not reject"); - done(); - }) - - assert.equal(content, "", "The URL content is not load yet"); -} - -exports["test readURISync with data URI"] = function(assert) { - let content = readURISync(dataURIutf8); - - assert.equal(content, utf8text, "The URL content is loaded properly"); -} - -exports["test async readURI with data URI and ISO-8859-1 charset"] = function(assert, done) { - let content = ""; - - readURI(dataURIlatin1, { charset : "ISO-8859-1"}).then(function(data) { - content = unescape(data); - assert.equal(content, latin1text, "The URL content is loaded properly"); - done(); - }, function() { - assert.fail("should not reject"); - done(); - }) - - assert.equal(content, "", "The URL content is not load yet"); -} - -exports["test readURISync with data URI and ISO-8859-1 charset"] = function(assert) { - let content = unescape(readURISync(dataURIlatin1, "ISO-8859-1")); - - assert.equal(content, latin1text, "The URL content is loaded properly"); -} - -exports["test readURISync with chrome URI"] = function(assert) { - let content = readURISync(chromeURI); - - assert.ok(content, "The URL content is loaded properly"); -} - -exports["test async readURI with chrome URI"] = function(assert, done) { - let content = ""; - - readURI(chromeURI).then(function(data) { - content = data; - assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly"); - done(); - }, function() { - assert.fail("should not reject"); - done(); - }) - - assert.equal(content, "", "The URL content is not load yet"); -} - -require("sdk/test").run(exports) diff --git a/addon-sdk/source/test/test-node-os.js b/addon-sdk/source/test/test-node-os.js deleted file mode 100644 index e8316d7d96..0000000000 --- a/addon-sdk/source/test/test-node-os.js +++ /dev/null @@ -1,33 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var os = require("node/os"); -var system = require("sdk/system"); - -exports["test os"] = function (assert) { - assert.equal(os.tmpdir(), system.pathFor("TmpD"), "os.tmpdir() matches temp dir"); - assert.ok(os.endianness() === "BE" || os.endianness() === "LE", "os.endianness is BE or LE"); - - assert.ok(os.arch().length > 0, "os.arch() returns a value"); - assert.equal(typeof os.arch(), "string", "os.arch() returns a string"); - assert.ok(os.type().length > 0, "os.type() returns a value"); - assert.equal(typeof os.type(), "string", "os.type() returns a string"); - assert.ok(os.platform().length > 0, "os.platform() returns a value"); - assert.equal(typeof os.platform(), "string", "os.platform() returns a string"); - - assert.ok(os.release().length > 0, "os.release() returns a value"); - assert.equal(typeof os.release(), "string", "os.release() returns a string"); - assert.ok(os.hostname().length > 0, "os.hostname() returns a value"); - assert.equal(typeof os.hostname(), "string", "os.hostname() returns a string"); - assert.ok(os.EOL === "\n" || os.EOL === "\r\n", "os.EOL returns a correct EOL char"); - - assert.deepEqual(os.loadavg(), [0, 0, 0], "os.loadavg() returns an array of 0s"); - - ["uptime", "totalmem", "freemem", "cpus"].forEach(method => { - assert.throws(() => os[method](), "os." + method + " throws"); - }); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-notifications.js b/addon-sdk/source/test/test-notifications.js deleted file mode 100644 index 9a4d6cea06..0000000000 --- a/addon-sdk/source/test/test-notifications.js +++ /dev/null @@ -1,94 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - "use strict"; - -const { Loader } = require('sdk/test/loader'); - -exports["test onClick"] = function(assert) { - let [loader, mockAlertServ] = makeLoader(module); - let notifs = loader.require("sdk/notifications"); - let data = "test data"; - let opts = { - onClick: function (clickedData) { - assert.equal(this, notifs, "|this| should be notifications module"); - assert.equal(clickedData, data, - "data passed to onClick should be correct"); - }, - data: data, - title: "test title", - text: "test text", - iconURL: "test icon URL" - }; - notifs.notify(opts); - mockAlertServ.click(); - loader.unload(); -}; - -exports['test numbers and URLs in options'] = function(assert) { - let [loader] = makeLoader(module); - let notifs = loader.require('sdk/notifications'); - let opts = { - title: 123, - text: 45678, - // must use in-loader `sdk/url` module for the validation type check to work - iconURL: loader.require('sdk/url').URL('data:image/png,blah') - }; - try { - notifs.notify(opts); - assert.pass('using numbers and URLs in options works'); - } catch (e) { - assert.fail('using numbers and URLs in options must not throw'); - } - loader.unload(); -} - -exports['test new tag, dir and lang options'] = function(assert) { - let [loader] = makeLoader(module); - let notifs = loader.require('sdk/notifications'); - let opts = { - title: 'best', - tag: 'tagging', - lang: 'en' - }; - - try { - opts.dir = 'ttb'; - notifs.notify(opts); - assert.fail('`dir` option must not accept TopToBottom direction.'); - } catch (e) { - assert.equal(e.message, - '`dir` option must be one of: "auto", "ltr" or "rtl".'); - } - - try { - opts.dir = 'rtl'; - notifs.notify(opts); - assert.pass('`dir` option accepts "rtl" direction.'); - } catch (e) { - assert.fail('`dir` option must accept "rtl" direction.'); - } - - loader.unload(); -} - -// Returns [loader, mockAlertService]. -function makeLoader(module) { - let loader = Loader(module); - let mockAlertServ = { - showAlertNotification: function (imageUrl, title, text, textClickable, - cookie, alertListener, name) { - this._cookie = cookie; - this._alertListener = alertListener; - }, - click: function () { - this._alertListener.observe(null, "alertclickcallback", this._cookie); - } - }; - loader.require("sdk/notifications"); - let scope = loader.sandbox("sdk/notifications"); - scope.notify = mockAlertServ.showAlertNotification.bind(mockAlertServ); - return [loader, mockAlertServ]; -} - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-object.js b/addon-sdk/source/test/test-object.js deleted file mode 100644 index a380fac8a3..0000000000 --- a/addon-sdk/source/test/test-object.js +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -'use strict'; - -const { merge, extend, has, each } = require('sdk/util/object'); - -var o = { - 'paper': 0, - 'rock': 1, - 'scissors': 2 -}; - -//exports.testMerge = function(assert) {} -//exports.testExtend = function(assert) {} - -exports.testHas = function(assert) { - assert.equal(has(o, 'paper'), true, 'has correctly finds key'); - assert.equal(has(o, 'rock'), true, 'has correctly finds key'); - assert.equal(has(o, 'scissors'), true, 'has correctly finds key'); - assert.equal(has(o, 'nope'), false, 'has correctly does not find key'); - assert.equal(has(o, '__proto__'), false, 'has correctly does not find key'); - assert.equal(has(o, 'isPrototypeOf'), false, 'has correctly does not find key'); -}; - -exports.testEach = function(assert) { - var keys = new Set(); - each(o, function (value, key, object) { - keys.add(key); - assert.equal(o[key], value, 'Key and value pairs passed in'); - assert.equal(o, object, 'Object passed in'); - }); - assert.equal(keys.size, 3, 'All keys have been iterated upon'); -}; - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-observers.js b/addon-sdk/source/test/test-observers.js deleted file mode 100644 index 4f15a87f12..0000000000 --- a/addon-sdk/source/test/test-observers.js +++ /dev/null @@ -1,183 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Loader } = require("sdk/test/loader"); -const { isWeak, WeakReference } = require("sdk/core/reference"); -const { subscribe, unsubscribe, - observe, Observer } = require("sdk/core/observer"); -const { Class } = require("sdk/core/heritage"); - -const { Cc, Ci, Cu } = require("chrome"); -const { notifyObservers } = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); -const { defer } = require("sdk/core/promise"); - -const message = x => ({wrappedJSObject: x}); - -exports["test subscribe unsubscribe"] = assert => { - const topic = Date.now().toString(32); - const Subscriber = Class({ - extends: Observer, - initialize: function(observe) { - this.observe = observe; - } - }); - observe.define(Subscriber, (x, subject, _, data) => - x.observe(subject.wrappedJSObject.x)); - - let xs = []; - const x = Subscriber((...rest) => xs.push(...rest)); - - let ys = []; - const y = Subscriber((...rest) => ys.push(...rest)); - - const publish = (topic, data) => - notifyObservers(message(data), topic, null); - - publish({x:0}); - - subscribe(x, topic); - - publish(topic, {x:1}); - - subscribe(y, topic); - - publish(topic, {x:2}); - publish(topic + "!", {x: 2.5}); - - unsubscribe(x, topic); - - publish(topic, {x:3}); - - subscribe(y, topic); - - publish(topic, {x:4}); - - subscribe(x, topic); - - publish(topic, {x:5}); - - unsubscribe(x, topic); - unsubscribe(y, topic); - - publish(topic, {x:6}); - - assert.deepEqual(xs, [1, 2, 5]); - assert.deepEqual(ys, [2, 3, 4, 5]); -} - -exports["test weak observers are GC-ed on unload"] = (assert, end) => { - const topic = Date.now().toString(32); - const loader = Loader(module); - const { Observer, observe, - subscribe, unsubscribe } = loader.require("sdk/core/observer"); - const { isWeak, WeakReference } = loader.require("sdk/core/reference"); - - const MyObserver = Class({ - extends: Observer, - initialize: function(observe) { - this.observe = observe; - } - }); - observe.define(MyObserver, (x, ...rest) => x.observe(...rest)); - - const MyWeakObserver = Class({ - extends: MyObserver, - implements: [WeakReference] - }); - - let xs = []; - let ys = []; - let x = new MyObserver((subject, topic, data) => { - xs.push(subject.wrappedJSObject, topic, data); - }); - let y = new MyWeakObserver((subject, topic, data) => { - ys.push(subject.wrappedJSObject, topic, data); - }); - - subscribe(x, topic); - subscribe(y, topic); - - - notifyObservers(message({ foo: 1 }), topic, null); - x = null; - y = null; - loader.unload(); - - Cu.schedulePreciseGC(() => { - - notifyObservers(message({ bar: 2 }), topic, ":)"); - - assert.deepEqual(xs, [{ foo: 1 }, topic, null, - { bar: 2 }, topic, ":)"], - "non week observer is kept"); - - assert.deepEqual(ys, [{ foo: 1 }, topic, null], - "week observer was GC-ed"); - - end(); - }); -}; - -exports["test weak observer unsubscribe"] = function*(assert) { - const loader = Loader(module); - const { Observer, observe, subscribe, unsubscribe } = loader.require("sdk/core/observer"); - const { WeakReference } = loader.require("sdk/core/reference"); - - let sawNotification = false; - let firstWait = defer(); - let secondWait = defer(); - - const WeakObserver = Class({ - extends: Observer, - implements: [WeakReference], - observe: function() { - sawNotification = true; - firstWait.resolve(); - } - }); - - const StrongObserver = Class({ - extends: Observer, - observe: function() { - secondWait.resolve(); - } - }); - - observe.define(Observer, (x, ...rest) => x.observe(...rest)); - - let weakObserver = new WeakObserver; - let strongObserver = new StrongObserver(); - subscribe(weakObserver, "test-topic"); - subscribe(strongObserver, "test-wait"); - - notifyObservers(null, "test-topic", null); - yield firstWait.promise; - - assert.ok(sawNotification, "Should have seen notification before GC"); - sawNotification = false; - - yield loader.require("sdk/test/memory").gc(); - - notifyObservers(null, "test-topic", null); - notifyObservers(null, "test-wait", null); - yield secondWait.promise; - - assert.ok(sawNotification, "Should have seen notification after GC"); - sawNotification = false; - - try { - unsubscribe(weakObserver, "test-topic"); - unsubscribe(strongObserver, "test-wait"); - assert.pass("Should not have seen an exception"); - } - catch (e) { - assert.fail("Should not have seen an exception"); - } - - loader.unload(); -}; - -require("sdk/test").run(exports); diff --git a/addon-sdk/source/test/test-page-mod-debug.js b/addon-sdk/source/test/test-page-mod-debug.js deleted file mode 100644 index 86f4911492..0000000000 --- a/addon-sdk/source/test/test-page-mod-debug.js +++ /dev/null @@ -1,66 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cc, Ci, Cu } = require("chrome"); -const { PageMod } = require("sdk/page-mod"); -const { testPageMod, handleReadyState, openNewTab, - contentScriptWhenServer, createLoader } = require("./page-mod/helpers"); -const { cleanUI, after } = require("sdk/test/utils"); -const { open, getFrames, getMostRecentBrowserWindow, getInnerId } = require("sdk/window/utils"); - -const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {}); -const { require: devtoolsRequire } = devtools; -const contentGlobals = devtoolsRequire("devtools/server/content-globals"); - -// The following adds Debugger constructor to the global namespace. -const { addDebuggerToGlobal } = require('resource://gre/modules/jsdebugger.jsm'); -addDebuggerToGlobal(this); - -exports.testDebugMetadata = function(assert, done) { - let dbg = new Debugger; - let globalDebuggees = []; - dbg.onNewGlobalObject = function(global) { - globalDebuggees.push(global); - } - - let mods = testPageMod(assert, done, "about:", [{ - include: "about:", - contentScriptWhen: "start", - contentScript: "null;", - }], function(win, done) { - assert.ok(globalDebuggees.some(function(global) { - try { - let metadata = Cu.getSandboxMetadata(global.unsafeDereference()); - return metadata && metadata.addonID && metadata.SDKContentScript && - metadata['inner-window-id'] == getInnerId(win); - } catch(e) { - // Some of the globals might not be Sandbox instances and thus - // will cause getSandboxMetadata to fail. - return false; - } - }), "one of the globals is a content script"); - done(); - } - ); -}; - -exports.testDevToolsExtensionsGetContentGlobals = function(assert, done) { - let mods = testPageMod(assert, done, "about:", [{ - include: "about:", - contentScriptWhen: "start", - contentScript: "null;", - }], function(win, done) { - assert.equal(contentGlobals.getContentGlobals({ 'inner-window-id': getInnerId(win) }).length, 1); - done(); - } - ); -}; - -after(exports, function*(name, assert) { - assert.pass("cleaning ui."); - yield cleanUI(); -}); - -require('sdk/test').run(exports); diff --git a/addon-sdk/source/test/test-page-mod.js b/addon-sdk/source/test/test-page-mod.js deleted file mode 100644 index d03463d2db..0000000000 --- a/addon-sdk/source/test/test-page-mod.js +++ /dev/null @@ -1,2214 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const { Cc, Ci, Cu } = require("chrome"); -const { PageMod } = require("sdk/page-mod"); -const { testPageMod, handleReadyState, openNewTab, - contentScriptWhenServer, createLoader } = require("./page-mod/helpers"); -const { Loader } = require("sdk/test/loader"); -const tabs = require("sdk/tabs"); -const { setTimeout } = require("sdk/timers"); -const system = require("sdk/system/events"); -const { open, getFrames, getMostRecentBrowserWindow, getInnerId } = require("sdk/window/utils"); -const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab, - getBrowserForTab } = require("sdk/tabs/utils"); -const xulApp = require("sdk/system/xul-app"); -const { isPrivateBrowsingSupported } = require("sdk/self"); -const { isPrivate } = require("sdk/private-browsing"); -const { openWebpage } = require("./private-browsing/helper"); -const { isTabPBSupported, isWindowPBSupported } = require("sdk/private-browsing/utils"); -const promise = require("sdk/core/promise"); -const { pb } = require("./private-browsing/helper"); -const { URL } = require("sdk/url"); -const { defer, all, resolve } = require("sdk/core/promise"); -const { waitUntil } = require("sdk/test/utils"); -const data = require("./fixtures"); -const { cleanUI, after } = require("sdk/test/utils"); - -const testPageURI = data.url("test.html"); - -function Isolate(worker) { - return "(" + worker + ")()"; -} - -/* Tests for the PageMod APIs */ - -exports.testPageMod1 = function*(assert) { - let modAttached = defer(); - let mod = PageMod({ - include: /about:/, - contentScriptWhen: "end", - contentScript: "new " + function WorkerScope() { - window.document.body.setAttribute("JEP-107", "worked"); - - self.port.once("done", () => { - self.port.emit("results", window.document.body.getAttribute("JEP-107")) - }); - }, - onAttach: function(worker) { - assert.equal(this, mod, "The 'this' object is the page mod."); - mod.port.once("results", modAttached.resolve) - mod.port.emit("done"); - } - }); - - let tab = yield new Promise(resolve => { - tabs.open({ - url: "about:", - inBackground: true, - onReady: resolve - }) - }); - assert.pass("test tab was opened."); - - let worked = yield modAttached.promise; - assert.pass("test mod was attached."); - - mod.destroy(); - assert.pass("test mod was destroyed."); - - assert.equal(worked, "worked", "PageMod.onReady test"); -}; - -exports.testPageMod2 = function*(assert) { - let modAttached = defer(); - let mod = PageMod({ - include: testPageURI, - contentScriptWhen: "end", - contentScript: [ - 'new ' + function contentScript() { - window.AUQLUE = function() { return 42; } - try { - window.AUQLUE() - } - catch(e) { - throw new Error("PageMod scripts executed in order"); - } - document.documentElement.setAttribute("first", "true"); - }, - 'new ' + function contentScript() { - document.documentElement.setAttribute("second", "true"); - - self.port.once("done", () => { - self.port.emit("results", { - "first": window.document.documentElement.getAttribute("first"), - "second": window.document.documentElement.getAttribute("second"), - "AUQLUE": unsafeWindow.getAUQLUE() - }); - }); - } - ], - onAttach: modAttached.resolve - }); - - let tab = yield new Promise(resolve => { - tabs.open({ - url: testPageURI, - inBackground: true, - onReady: resolve - }) - }); - assert.pass("test tab was opened."); - - let worker = yield modAttached.promise; - assert.pass("test mod was attached."); - - let results = yield new Promise(resolve => { - worker.port.once("results", resolve) - worker.port.emit("done"); - }); - - mod.destroy(); - assert.pass("test mod was destroyed."); - - assert.equal(results["first"], - "true", - "PageMod test #2: first script has run"); - assert.equal(results["second"], - "true", - "PageMod test #2: second script has run"); - assert.equal(results["AUQLUE"], false, - "PageMod test #2: scripts get a wrapped window"); -}; - -exports.testPageModIncludes = function*(assert) { - var modsAttached = []; - var modNumber = 0; - var modAttached = defer(); - let includes = [ - "*", - "*.google.com", - "resource:*", - "resource:", - testPageURI - ]; - let expected = [ - false, - false, - true, - false, - true - ] - - let mod = PageMod({ - include: testPageURI, - contentScript: 'new ' + function() { - self.port.on("get-local-storage", () => { - let result = {}; - self.options.forEach(include => { - result[include] = !!window.localStorage[include] - }); - - self.port.emit("got-local-storage", result); - - window.localStorage.clear(); - }); - }, - contentScriptOptions: includes, - onAttach: modAttached.resolve - }); - - function createPageModTest(include, expectedMatch) { - var modIndex = modNumber++; - - let attached = defer(); - modsAttached.push(expectedMatch ? attached.promise : resolve()); - - // ...and corresponding PageMod options - return PageMod({ - include: include, - contentScript: 'new ' + function() { - self.on("message", function(msg) { - window.localStorage[msg] = true - self.port.emit('done'); - }); - }, - // The testPageMod callback with test assertions is called on 'end', - // and we want this page mod to be attached before it gets called, - // so we attach it on 'start'. - contentScriptWhen: 'start', - onAttach: function(worker) { - assert.pass("mod " + modIndex + " was attached"); - - worker.port.once("done", () => { - assert.pass("mod " + modIndex + " is done"); - attached.resolve(worker); - }); - worker.postMessage(this.include[0]); - } - }); - } - - let mods = [ - createPageModTest("*", false), - createPageModTest("*.google.com", false), - createPageModTest("resource:*", true), - createPageModTest("resource:", false), - createPageModTest(testPageURI, true) - ]; - - let tab = yield new Promise(resolve => { - tabs.open({ - url: testPageURI, - inBackground: true, - onReady: resolve - }); - }); - assert.pass("tab was opened"); - - yield all(modsAttached); - assert.pass("all mods were attached."); - - mods.forEach(mod => mod.destroy()); - assert.pass("all mods were destroyed."); - - yield modAttached.promise; - assert.pass("final test mod was attached."); - - yield new Promise(resolve => { - mod.port.on("got-local-storage", (storage) => { - includes.forEach((include, i) => { - assert.equal(storage[include], expected[i], "localStorage is correct for " + include); - }); - resolve(); - }); - mod.port.emit("get-local-storage"); - }); - assert.pass("final test of localStorage is complete."); - - mod.destroy(); - assert.pass("final test mod was destroyed."); -}; - -exports.testPageModExcludes = function(assert, done) { - var asserts = []; - function createPageModTest(include, exclude, expectedMatch) { - // Create an 'onload' test function... - asserts.push(function(test, win) { - var matches = JSON.stringify([include, exclude]) in win.localStorage; - assert.ok(expectedMatch ? matches : !matches, - "[include, exclude] = [" + include + ", " + exclude + - "] match test, expected: " + expectedMatch); - }); - // ...and corresponding PageMod options - return { - include: include, - exclude: exclude, - contentScript: 'new ' + function() { - self.on("message", function(msg) { - // The key in localStorage is "[, ]". - window.localStorage[JSON.stringify(msg)] = true; - }); - }, - // The testPageMod callback with test assertions is called on 'end', - // and we want this page mod to be attached before it gets called, - // so we attach it on 'start'. - contentScriptWhen: 'start', - onAttach: function(worker) { - worker.postMessage([this.include[0], this.exclude[0]]); - } - }; - } - - testPageMod(assert, done, testPageURI, [ - createPageModTest("*", testPageURI, false), - createPageModTest(testPageURI, testPageURI, false), - createPageModTest(testPageURI, "resource://*", false), - createPageModTest(testPageURI, "*.google.com", true) - ], - function (win, done) { - waitUntil(() => win.localStorage[JSON.stringify([testPageURI, "*.google.com"])], - testPageURI + " page-mod to be executed") - .then(() => { - asserts.forEach(fn => fn(assert, win)); - win.localStorage.clear(); - done(); - }); - }); -}; - -exports.testPageModValidationAttachTo = function(assert) { - [{ val: 'top', type: 'string "top"' }, - { val: 'frame', type: 'string "frame"' }, - { val: ['top', 'existing'], type: 'array with "top" and "existing"' }, - { val: ['frame', 'existing'], type: 'array with "frame" and "existing"' }, - { val: ['top'], type: 'array with "top"' }, - { val: ['frame'], type: 'array with "frame"' }, - { val: undefined, type: 'undefined' }].forEach((attachTo) => { - new PageMod({ attachTo: attachTo.val, include: '*.validation111' }); - assert.pass("PageMod() does not throw when attachTo is " + attachTo.type); - }); - - [{ val: 'existing', type: 'string "existing"' }, - { val: ['existing'], type: 'array with "existing"' }, - { val: 'not-legit', type: 'string with "not-legit"' }, - { val: ['not-legit'], type: 'array with "not-legit"' }, - { val: {}, type: 'object' }].forEach((attachTo) => { - assert.throws(() => - new PageMod({ attachTo: attachTo.val, include: '*.validation111' }), - /The `attachTo` option/, - "PageMod() throws when 'attachTo' option is " + attachTo.type + "."); - }); -}; - -exports.testPageModValidationInclude = function(assert) { - [{ val: undefined, type: 'undefined' }, - { val: {}, type: 'object' }, - { val: [], type: 'empty array'}, - { val: [/regexp/, 1], type: 'array with non string/regexp' }, - { val: 1, type: 'number' }].forEach((include) => { - assert.throws(() => new PageMod({ include: include.val }), - /The `include` option must always contain atleast one rule/, - "PageMod() throws when 'include' option is " + include.type + "."); - }); - - [{ val: '*.validation111', type: 'string' }, - { val: /validation111/, type: 'regexp' }, - { val: ['*.validation111'], type: 'array with length > 0'}].forEach((include) => { - new PageMod({ include: include.val }); - assert.pass("PageMod() does not throw when include option is " + include.type); - }); -}; - -exports.testPageModValidationExclude = function(assert) { - let includeVal = '*.validation111'; - - [{ val: {}, type: 'object' }, - { val: [], type: 'empty array'}, - { val: [/regexp/, 1], type: 'array with non string/regexp' }, - { val: 1, type: 'number' }].forEach((exclude) => { - assert.throws(() => new PageMod({ include: includeVal, exclude: exclude.val }), - /If set, the `exclude` option must always contain at least one rule as a string, regular expression, or an array of strings and regular expressions./, - "PageMod() throws when 'exclude' option is " + exclude.type + "."); - }); - - [{ val: undefined, type: 'undefined' }, - { val: '*.validation111', type: 'string' }, - { val: /validation111/, type: 'regexp' }, - { val: ['*.validation111'], type: 'array with length > 0'}].forEach((exclude) => { - new PageMod({ include: includeVal, exclude: exclude.val }); - assert.pass("PageMod() does not throw when exclude option is " + exclude.type); - }); -}; - -/* Tests for internal functions. */ -exports.testCommunication1 = function*(assert) { - let workerDone = defer(); - - let mod = PageMod({ - include: "about:*", - contentScriptWhen: "end", - contentScript: 'new ' + function WorkerScope() { - self.on('message', function(msg) { - document.body.setAttribute('JEP-107', 'worked'); - self.postMessage(document.body.getAttribute('JEP-107')); - }); - self.port.on('get-jep-107', () => { - self.port.emit('got-jep-107', document.body.getAttribute('JEP-107')); - }); - }, - onAttach: function(worker) { - worker.on('error', function(e) { - assert.fail('Errors where reported'); - }); - worker.on('message', function(value) { - assert.equal( - "worked", - value, - "test comunication" - ); - workerDone.resolve(); - }); - worker.postMessage("do it!") - } - }); - - let tab = yield new Promise(resolve => { - tabs.open({ - url: "about:", - onReady: resolve - }); - }); - assert.pass("opened tab"); - - yield workerDone.promise; - assert.pass("the worker has made a change"); - - let value = yield new Promise(resolve => { - mod.port.once("got-jep-107", resolve); - mod.port.emit("get-jep-107"); - }); - - assert.equal("worked", value, "attribute should be modified"); - - mod.destroy(); - assert.pass("the worker was destroyed"); -}; - -exports.testCommunication2 = function*(assert) { - let workerDone = defer(); - let url = data.url("test.html"); - - let mod = PageMod({ - include: url, - contentScriptWhen: 'start', - contentScript: 'new ' + function WorkerScope() { - document.documentElement.setAttribute('AUQLUE', 42); - - window.addEventListener('load', function listener() { - self.postMessage({ - msg: 'onload', - AUQLUE: document.documentElement.getAttribute('AUQLUE') - }); - }, false); - - self.on("message", function(msg) { - if (msg == "get window.test") { - unsafeWindow.changesInWindow(); - } - - self.postMessage({ - msg: document.documentElement.getAttribute("test") - }); - }); - }, - onAttach: function(worker) { - worker.on('error', function(e) { - assert.fail('Errors where reported'); - }); - worker.on('message', function({ msg, AUQLUE }) { - if ('onload' == msg) { - assert.equal('42', AUQLUE, 'PageMod scripts executed in order'); - worker.postMessage('get window.test'); - } - else { - assert.equal('changes in window', msg, 'PageMod test #2: second script has run'); - workerDone.resolve(); - } - }); - } - }); - - let tab = yield new Promise(resolve => { - tabs.open({ - url: url, - inBackground: true, - onReady: resolve - }); - }); - assert.pass("opened tab"); - - yield workerDone.promise; - - mod.destroy(); - assert.pass("the worker was destroyed"); -}; - -exports.testEventEmitter = function(assert, done) { - let workerDone = false, - callbackDone = null; - - testPageMod(assert, done, "about:", [{ - include: "about:*", - contentScript: 'new ' + function WorkerScope() { - self.port.on('addon-to-content', function(data) { - self.port.emit('content-to-addon', data); - }); - }, - onAttach: function(worker) { - worker.on('error', function(e) { - assert.fail('Errors were reported : '+e); - }); - worker.port.on('content-to-addon', function(value) { - assert.equal( - "worked", - value, - "EventEmitter API works!" - ); - if (callbackDone) - callbackDone(); - else - workerDone = true; - }); - worker.port.emit('addon-to-content', 'worked'); - } - }], - function(win, done) { - if (workerDone) - done(); - else - callbackDone = done; - } - ); -}; - -// Execute two concurrent page mods on same document to ensure that their -// JS contexts are different -exports.testMixedContext = function(assert, done) { - let doneCallback = null; - let messages = 0; - let modObject = { - include: "data:text/html;charset=utf-8,", - contentScript: 'new ' + function WorkerScope() { - // Both scripts will execute this, - // context is shared if one script see the other one modification. - let isContextShared = "sharedAttribute" in document; - self.postMessage(isContextShared); - document.sharedAttribute = true; - }, - onAttach: function(w) { - w.on("message", function (isContextShared) { - if (isContextShared) { - assert.fail("Page mod contexts are mixed."); - doneCallback(); - } - else if (++messages == 2) { - assert.pass("Page mod contexts are different."); - doneCallback(); - } - }); - } - }; - testPageMod(assert, done, "data:text/html;charset=utf-8,", [modObject, modObject], - function(win, done) { - doneCallback = done; - } - ); -}; - -exports.testHistory = function(assert, done) { - // We need a valid url in order to have a working History API. - // (i.e do not work on data: or about: pages) - // Test bug 679054. - let url = data.url("test-page-mod.html"); - let callbackDone = null; - testPageMod(assert, done, url, [{ - include: url, - contentScriptWhen: 'end', - contentScript: 'new ' + function WorkerScope() { - history.pushState({}, "", "#"); - history.replaceState({foo: "bar"}, "", "#"); - self.postMessage(history.state); - }, - onAttach: function(worker) { - worker.on('message', function (data) { - assert.equal(JSON.stringify(data), JSON.stringify({foo: "bar"}), - "History API works!"); - callbackDone(); - }); - } - }], - function(win, done) { - callbackDone = done; - } - ); -}; - -exports.testRelatedTab = function(assert, done) { - let tab; - let pageMod = new PageMod({ - include: "about:*", - onAttach: function(worker) { - assert.ok(!!worker.tab, "Worker.tab exists"); - assert.equal(tab, worker.tab, "Worker.tab is valid"); - pageMod.destroy(); - tab.close(done); - } - }); - - tabs.open({ - url: "about:", - onOpen: function onOpen(t) { - tab = t; - } - }); -}; - -// related to bug #989288 -// https://bugzilla.mozilla.org/show_bug.cgi?id=989288 -exports.testRelatedTabNewWindow = function(assert, done) { - let url = "about:logo" - let pageMod = new PageMod({ - include: url, - onAttach: function(worker) { - assert.equal(worker.tab.url, url, "Worker.tab.url is valid"); - worker.tab.close(done); - } - }); - - tabs.activeTab.attach({ - contentScript: "window.open('about:logo', '', " + - "'width=800,height=600,resizable=no,status=no,location=no');" - }); - -}; - -exports.testRelatedTabNoRequireTab = function(assert, done) { - let loader = Loader(module); - let tab; - let url = "data:text/html;charset=utf-8," + encodeURI("Test related worker tab 2"); - let { PageMod } = loader.require("sdk/page-mod"); - let pageMod = new PageMod({ - include: url, - onAttach: function(worker) { - assert.equal(worker.tab.url, url, "Worker.tab.url is valid"); - worker.tab.close(function() { - pageMod.destroy(); - loader.unload(); - done(); - }); - } - }); - - tabs.open(url); -}; - -exports.testRelatedTabNoOtherReqs = function(assert, done) { - let loader = Loader(module); - let { PageMod } = loader.require("sdk/page-mod"); - let pageMod = new PageMod({ - include: "about:blank?testRelatedTabNoOtherReqs", - onAttach: function(worker) { - assert.ok(!!worker.tab, "Worker.tab exists"); - pageMod.destroy(); - worker.tab.close(function() { - worker.destroy(); - loader.unload(); - done(); - }); - } - }); - - tabs.open({ - url: "about:blank?testRelatedTabNoOtherReqs" - }); -}; - -exports.testWorksWithExistingTabs = function(assert, done) { - let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document"); - let { PageMod } = require("sdk/page-mod"); - tabs.open({ - url: url, - onReady: function onReady(tab) { - let pageModOnExisting = new PageMod({ - include: url, - attachTo: ["existing", "top", "frame"], - onAttach: function(worker) { - assert.ok(!!worker.tab, "Worker.tab exists"); - assert.equal(tab, worker.tab, "A worker has been created on this existing tab"); - - worker.on('pageshow', () => { - assert.fail("Should not have seen pageshow for an already loaded page"); - }); - - setTimeout(function() { - pageModOnExisting.destroy(); - pageModOffExisting.destroy(); - tab.close(done); - }, 0); - } - }); - - let pageModOffExisting = new PageMod({ - include: url, - onAttach: function(worker) { - assert.fail("pageModOffExisting page-mod should not have attached to anything"); - } - }); - } - }); -}; - -exports.testExistingFrameDoesntMatchInclude = function(assert, done) { - let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42'; - let iframe = '