Move Add-on SDK source to toolkit/jetpack

This commit is contained in:
NTD
2018-02-09 06:46:43 -05:00
committed by Roy Tam
parent 0a03067ee3
commit 23b67db438
993 changed files with 484 additions and 100663 deletions
-29
View File
@@ -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
-107
View File
@@ -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)
-542
View File
@@ -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',
]
-20
View File
@@ -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',
]
-5
View File
@@ -1,5 +0,0 @@
.gitignore export-ignore
.hgignore export-ignore
.hgtags export-ignore
.gitattributes export-ignore
python-lib/cuddlefish/_version.py export-subst
-36
View File
@@ -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
-18
View File
@@ -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
-26
View File
@@ -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
-54
View File
@@ -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
-30
View File
@@ -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.
-34
View File
@@ -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)
-84
View File
@@ -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()"
-134
View File
@@ -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 <NO NAME>\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
-66
View File
@@ -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()"
-99
View File
@@ -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()"
-33
View File
@@ -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()
-6
View File
@@ -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" %*
-23
View File
@@ -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
-7
View File
@@ -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
@@ -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
@@ -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)
-34
View File
@@ -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);
});
});
}
@@ -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;
@@ -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());
}
@@ -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();
});
});
});
});
@@ -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());
}
@@ -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();
});
})
});
});
@@ -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();
});
});
});
});
});
@@ -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);
});
});
@@ -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;
-104
View File
@@ -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;
@@ -1,11 +0,0 @@
addon-sdk
github
stackoverflow
bugzilla
irc
jsantell
mossop
gozala
zer0
autonome
0c0w3
@@ -1,3 +0,0 @@
# Actor REPL
Simple REPL for a Firefox debugging protocol.
File diff suppressed because one or more lines are too long
@@ -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;
}
}
@@ -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/. -->
<html>
<head>
<link rel="stylesheet" href="./codemirror.css">
<link rel="stylesheet" href="./main.css">
<script src="./codemirror-compressed.js"></script>
<views>
<section class="task cm-s-default">
<pre class="request "></pre>
<pre class="response"><span class="one"></span><span class="two"></span><span class="three"></span></pre>
</section>
</views>
</head>
<body>
<pre class="input"></pre>
</body>
<script>
function debounce(fn, ms) {
var id;
return function(...args) {
clearTimeout(id);
id = setTimeout(fn, ms, ...args);
};
}
function Try(fn) {
return function(...args) {
try { return fn(...args); }
catch (error) { return null; }
};
}
var parse = Try(JSON.parse);
var CommandHistory = {
init: function() {
this._state = {};
this._state.els = document.querySelectorAll("body > section.task > .request");
this._state.idx = this._state.els.length;
},
get prev() {
if (!!this._state.els && this._state.idx > 0) {
this._state.idx--;
return this._state.els[this._state.idx].textContent;
}
return "";
},
get next() {
if (!!this._state.els && this._state.idx < this._state.els.length-1) {
this._state.idx++;
return this._state.els[this._state.idx].textContent;
}
return "";
}
}
function cmdHistory(fn, editor) {
editor.setValue(fn());
document.querySelector(".input").scrollIntoView();
}
var cmdHistoryNext = cmdHistory.bind(null, () => CommandHistory.next);
var cmdHistoryBack = cmdHistory.bind(null, () => CommandHistory.prev);
function send(editor) {
var input = editor.getWrapperElement().parentNode;
var code = editor.getValue().trim();
var packet = parse(code);
if (packet) {
var task = document.querySelector("views .task").cloneNode(true);
var request = task.querySelector(".request");
var response = task.querySelector(".response");
input.parentNode.insertBefore(task, input);
CodeMirror.runMode(JSON.stringify(packet, 2, 2),
"application/json",
request);
response.classList.add("pending");
editor.setValue("");
document.querySelector(".input").scrollIntoView();
port.postMessage(packet);
}
}
var editor = CodeMirror(document.querySelector(".input"), {
autofocus: true,
mode: "application/json",
matchBrackets: true,
value: '{"to": "root", "type": "requestTypes"}',
extraKeys: {"Cmd-Enter": send,
"Ctrl-Enter": send,
"Cmd-Down": cmdHistoryNext,
"Ctrl-Down": cmdHistoryNext,
"Cmd-Up": cmdHistoryBack,
"Ctrl-Up": cmdHistoryBack}
});
editor.on("change", debounce(function(editor) {
var input = editor.getWrapperElement().parentNode;
if (parse(editor.getValue().trim())) {
input.classList.remove("invalid");
} else {
input.classList.add("invalid");
}
}, 800));
</script>
<script>
window.addEventListener("message", event => {
window.port = event.ports[0];
port.onmessage = onMessage;
});
var onMessage = (event) => {
var packet = event.data;
var code = JSON.stringify(packet, 2, 2);
var input = document.querySelector(".input");
var response = document.querySelector(".task .response.pending");
if (!response) {
message = document.querySelector("views .task").cloneNode(true);
response = message.querySelector(".response");
response.classList.add("message");
input.parentNode.insertBefore(message, input);
}
if (packet.error) {
response.classList.add("error");
}
CodeMirror.runMode(code, "application/json", response);
response.classList.remove("pending");
document.querySelector(".input").scrollIntoView();
CommandHistory.init();
};
</script>
</html>
@@ -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);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

@@ -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 }
});
@@ -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"
}
@@ -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);
@@ -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("<EventTarget/>", "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);
@@ -1,50 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
<head>
<script src="resource://sdk/dev/volcan.js"></script>
<script src="./task.js"></script>
</head>
<body>
</body>
<script>
const wait = (target, type, capture) => new Promise((resolve, reject) => {
const listener = event => {
target.removeEventListener(type, listener, capture);
resolve(event);
};
target.addEventListener(type, listener, capture);
});
const display = message =>
document.body.innerHTML += message + "<br/>";
Task.spawn(function*() {
var event = yield wait(window, "message");
var port = event.ports[0];
display("Port received");
var root = yield volcan.connect(port);
display("Connected to a debugger");
var message = yield root.echo("hello")
display("Received echo for: " + message);
var list = yield root.listTabs();
display("You have " + list.tabs.length + " open tabs");
var activeTab = list.tabs[list.selected];
display("Your active tab url is: " + activeTab.url);
var sheets = yield activeTab.styleSheetsActor.getStyleSheets();
display("Page in active tab has " + sheets.length + " stylesheets");
});
</script>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

@@ -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 = {});
@@ -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 }
});
@@ -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"
}
@@ -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);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

@@ -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/. -->
<html><body>
<h1>Hello World</h1>
</body></html>
@@ -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 });
}
@@ -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"
}
@@ -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");
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -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/. -->
<html>
<head>
</head>
<body>
</body>
</html>
@@ -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;
}
@@ -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 }
});
@@ -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"
}
@@ -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);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@@ -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/. -->
<html>
<body>
<button id=post>post!</button>
</body>
<script>
window.addEventListener("message", event => {
console.log("Document message", event, event.data, event.source !== window && event.source === window.parent);
});
window.addEventListener("click", event => {
if (event.target.id === "post") {
console.log("click!")
window.parent.postMessage("ping!", "*");
}
});
console.log(window.parent === window)
</script>
</html>
@@ -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");
}
});
@@ -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"
}
}
@@ -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);
@@ -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;
@@ -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"
}
@@ -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);
-44
View File
@@ -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);
});
-71
View File
@@ -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"
}
-39
View File
@@ -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"
}
}
@@ -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)
@@ -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
@@ -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)
@@ -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"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)"
# detect the define idiom of the form:
# define("module name", ["dep1", "dep2", "dep3"], function() {})
# by capturing the contents of the list in a group.
DEF_RE = re.compile(r"(require|define)\s*\(\s*([\'\"][^\'\"]+[\'\"]\s*,)?\s*\[([^\]]+)\]")
# Out of the async dependencies, do not allow quotes in them.
DEF_RE_ALLOWED = re.compile(r"^[\'\"][^\'\"]+[\'\"]$")
def scan_requirements_with_grep(fn, lines):
requires = {}
first_location = {}
for (lineno0, line) in enumerate(lines):
for clause in line.split(";"):
clause = clause.strip()
iscomment = False
for commentprefix in COMMENT_PREFIXES:
if clause.startswith(commentprefix):
iscomment = True
if iscomment:
continue
mo = re.finditer(REQUIRE_RE, clause)
if mo:
for mod in mo:
modname = mod.group(1)
requires[modname] = {}
if modname not in first_location:
first_location[modname] = lineno0 + 1
# define() can happen across multiple lines, so join everyone up.
wholeshebang = "\n".join(lines)
for match in DEF_RE.finditer(wholeshebang):
# this should net us a list of string literals separated by commas
for strbit in match.group(3).split(","):
strbit = strbit.strip()
# There could be a trailing comma netting us just whitespace, so
# filter that out. Make sure that only string values with
# quotes around them are allowed, and no quotes are inside
# the quoted value.
if strbit and DEF_RE_ALLOWED.match(strbit):
modname = strbit[1:-1]
if modname not in ["exports"]:
requires[modname] = {}
# joining all the lines means we lose line numbers, so we
# can't fill first_location[]
return requires, first_location
CHROME_ALIASES = [
(re.compile(r"Components\.classes"), "Cc"),
(re.compile(r"Components\.interfaces"), "Ci"),
(re.compile(r"Components\.utils"), "Cu"),
(re.compile(r"Components\.results"), "Cr"),
(re.compile(r"Components\.manager"), "Cm"),
]
OTHER_CHROME = re.compile(r"Components\.[a-zA-Z]")
def scan_for_bad_chrome(fn, lines, stderr):
problems = False
old_chrome = set() # i.e. "Cc" when we see "Components.classes"
old_chrome_lines = [] # list of (lineno, line.strip()) tuples
for lineno,line in enumerate(lines):
# note: this scanner is not obligated to spot all possible forms of
# chrome access. The scanner is detecting voluntary requests for
# chrome. Runtime tools will enforce allowance or denial of access.
line = line.strip()
iscomment = False
for commentprefix in COMMENT_PREFIXES:
if line.startswith(commentprefix):
iscomment = True
break
if iscomment:
continue
old_chrome_in_this_line = set()
for (regexp,alias) in CHROME_ALIASES:
if regexp.search(line):
old_chrome_in_this_line.add(alias)
if not old_chrome_in_this_line:
if OTHER_CHROME.search(line):
old_chrome_in_this_line.add("components")
old_chrome.update(old_chrome_in_this_line)
if old_chrome_in_this_line:
old_chrome_lines.append( (lineno+1, line) )
if old_chrome:
print >>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
@@ -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() {}
@@ -1,39 +0,0 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>mobile-utils@mozilla.com</em:id>
<em:version>1.0</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<!-- Fennec-XUL -->
<em:targetApplication>
<Description>
<em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Fennec-NativeUI -->
<em:targetApplication>
<Description>
<em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Mobile Addon-SDK utility addon</em:name>
<em:description>Allow better integration with cfx tool.</em:description>
<em:creator>Mozilla Corporation</em:creator>
</Description>
</RDF>
@@ -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()
@@ -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\<username>\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
@@ -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
}
@@ -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]
@@ -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,<placeholder/>")
# 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!"
@@ -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 <https://developer.mozilla.org/en/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
@@ -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"
}
'''
@@ -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()
@@ -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/. */
@@ -1,5 +0,0 @@
{
"loader": "lib/main.js",
"icon": "explicit-icon.png",
"icon64": "explicit-icon64.png"
}
@@ -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/. */
@@ -1,3 +0,0 @@
{
"loader": "lib/main.js"
}
@@ -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/. */
@@ -1,3 +0,0 @@
{
"loader": "lib/main.js"
}
@@ -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/. */
@@ -1,3 +0,0 @@
{
"loader": "lib/bar-loader.js"
}
@@ -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/. */
@@ -1,4 +0,0 @@
{
"loader": "lib/foo-loader.js",
"dependencies": ["bar"]
}
@@ -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/. -->
minimal docs
@@ -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();
};

Some files were not shown because too many files have changed in this diff Show More