Backed out 22 changesets (bug 1350646) for talos damp failures

Backed out changeset c3108aebee35 (bug 1350646)
Backed out changeset ee8f7fa9d410 (bug 1350646)
Backed out changeset 79e77c06ff44 (bug 1350646)
Backed out changeset 5584fdcd2ee0 (bug 1350646)
Backed out changeset 7571b064a77e (bug 1350646)
Backed out changeset 7f65323c56f9 (bug 1350646)
Backed out changeset 10bbf7c53afe (bug 1350646)
Backed out changeset e40544ead983 (bug 1350646)
Backed out changeset 5eb77e6de0a3 (bug 1350646)
Backed out changeset 6d9244a8ac40 (bug 1350646)
Backed out changeset 13110c98b0fd (bug 1350646)
Backed out changeset 096ff315b48b (bug 1350646)
Backed out changeset 57500d9ea832 (bug 1350646)
Backed out changeset 5a45d9e25a00 (bug 1350646)
Backed out changeset e931e4ff5e11 (bug 1350646)
Backed out changeset d914c050c965 (bug 1350646)
Backed out changeset 35c4d4cd77c7 (bug 1350646)
Backed out changeset 7687db575011 (bug 1350646)
Backed out changeset c68367bb0eb3 (bug 1350646)
Backed out changeset 3ca040743aeb (bug 1350646)
Backed out changeset 8d4c2f001354 (bug 1350646)
Backed out changeset 67c251e2c9db (bug 1350646)

MozReview-Commit-ID: GLM80BkVx85
This commit is contained in:
Phil Ringnalda 2017-08-09 20:03:17 -07:00
Родитель 276b8889d3
Коммит ca2c01b0ad
195 изменённых файлов: 27519 добавлений и 109 удалений

107
addon-sdk/mach_commands.py Normal file
Просмотреть файл

@ -0,0 +1,107 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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)

Просмотреть файл

@ -20,103 +20,472 @@ EXTRA_JS_MODULES.sdk.system += [
'source/modules/system/Startup.js',
]
modules = [
'index.js',
'jetpack-id/index.js',
'method/core.js',
'mozilla-toolkit-versioning/index.js',
'mozilla-toolkit-versioning/lib/utils.js',
'node/os.js',
'sdk/addon/installer.js',
'sdk/addon/window.js',
'sdk/base64.js',
'sdk/clipboard.js',
'sdk/console/plain-text.js',
'sdk/console/traceback.js',
'sdk/core/disposable.js',
'sdk/core/heritage.js',
'sdk/core/namespace.js',
'sdk/core/observer.js',
'sdk/core/promise.js',
'sdk/core/reference.js',
'sdk/deprecated/unit-test-finder.js',
'sdk/deprecated/unit-test.js',
'sdk/deprecated/window-utils.js',
'sdk/event/chrome.js',
'sdk/event/core.js',
'sdk/event/dom.js',
'sdk/event/target.js',
'sdk/event/utils.js',
'sdk/frame/utils.js',
'sdk/io/file.js',
'sdk/lang/functional.js',
'sdk/lang/functional/concurrent.js',
'sdk/lang/functional/core.js',
'sdk/lang/functional/helpers.js',
'sdk/lang/type.js',
'sdk/lang/weak-set.js',
'sdk/net/url.js',
'sdk/platform/xpcom.js',
'sdk/preferences/service.js',
'sdk/preferences/utils.js',
'sdk/private-browsing.js',
'sdk/private-browsing/utils.js',
'sdk/querystring.js',
'sdk/self.js',
'sdk/system.js',
'sdk/system/environment.js',
'sdk/system/events.js',
'sdk/system/globals.js',
'sdk/system/process.js',
'sdk/system/runtime.js',
'sdk/system/unload.js',
'sdk/system/xul-app.js',
'sdk/system/xul-app.jsm',
'sdk/test.js',
'sdk/test/assert.js',
'sdk/test/harness.js',
'sdk/test/loader.js',
'sdk/test/options.js',
'sdk/test/utils.js',
'sdk/timers.js',
'sdk/uri/resource.js',
'sdk/url.js',
'sdk/url/utils.js',
'sdk/util/array.js',
'sdk/util/collection.js',
'sdk/util/deprecate.js',
'sdk/util/dispatcher.js',
'sdk/util/list.js',
'sdk/util/object.js',
'sdk/util/sequence.js',
'sdk/util/uuid.js',
'sdk/window/utils.js',
'sdk/zip/utils.js',
'test.js',
'toolkit/loader.js',
'toolkit/require.js',
EXTRA_JS_MODULES.commonjs.method.test += [
'source/lib/method/test/browser.js',
'source/lib/method/test/common.js',
]
commonjs = EXTRA_JS_MODULES.commonjs
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',
]
sources = {}
def get_sources(path):
key = '/'.join(path)
if key in sources:
return sources[key]
EXTRA_JS_MODULES.commonjs.sdk.frame += [
'source/lib/sdk/frame/hidden-frame.js',
'source/lib/sdk/frame/utils.js',
]
source_dir = commonjs
for dir_ in path:
source_dir = source_dir[dir_]
EXTRA_JS_MODULES.commonjs.sdk.panel += [
'source/lib/sdk/panel/events.js',
'source/lib/sdk/panel/utils.js',
]
sources[key] = source_dir
return source_dir
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',
]
for module in modules:
path = module.split('/')[:-1]
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',
]
source_dir = get_sources(path)
source_dir += ['source/lib/%s' % module]
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',
]
with Files("**"):
BUG_COMPONENT = ("Add-on SDK", "General")

Просмотреть файл

@ -0,0 +1,20 @@
# -*- 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',
]

Просмотреть файл

@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cu } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { MessagePort, MessageChannel } = require("../sdk/messaging");
const { DevToolsShim } = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const outputs = new WeakMap();
const inputs = new WeakMap();
const targets = new WeakMap();
const transports = new WeakMap();
const inputFor = port => inputs.get(port);
const outputFor = port => outputs.get(port);
const transportFor = port => transports.get(port);
const fromTarget = target => {
const debuggee = new Debuggee();
const { port1, port2 } = new MessageChannel();
inputs.set(debuggee, port1);
outputs.set(debuggee, port2);
targets.set(debuggee, target);
return debuggee;
};
exports.fromTarget = fromTarget;
const Debuggee = Class({
extends: MessagePort.prototype,
close: function() {
const server = transportFor(this);
if (server) {
transports.delete(this);
server.close();
}
outputFor(this).close();
},
start: function() {
const target = targets.get(this);
if (target.isLocalTab) {
// Since a remote protocol connection will be made, let's start the
// DebuggerServer here, once and for all tools.
let transport = DevToolsShim.connectDebuggerServer();
transports.set(this, transport);
}
// TODO: Implement support for remote connections (See Bug 980421)
else {
throw Error("Remote targets are not yet supported");
}
// pipe messages send to the debuggee to an actual
// server via remote debugging protocol transport.
inputFor(this).addEventListener("message", ({data}) =>
transportFor(this).send(data));
// pipe messages received from the remote debugging
// server transport onto the this debuggee.
transportFor(this).hooks = {
onPacket: packet => inputFor(this).postMessage(packet),
onClosed: () => inputFor(this).close()
};
inputFor(this).start();
outputFor(this).start();
},
postMessage: function(data) {
return outputFor(this).postMessage(data);
},
get onmessage() {
return outputFor(this).onmessage;
},
set onmessage(onmessage) {
outputFor(this).onmessage = onmessage;
},
addEventListener: function(...args) {
return outputFor(this).addEventListener(...args);
},
removeEventListener: function(...args) {
return outputFor(this).removeEventListener(...args);
}
});
exports.Debuggee = Debuggee;

Просмотреть файл

@ -0,0 +1,120 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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";
(function({content, sendSyncMessage, addMessageListener, sendAsyncMessage}) {
const Cc = Components.classes;
const Ci = Components.interfaces;
const observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
const channels = new Map();
const handles = new WeakMap();
// Takes remote port handle and creates a local one.
// also set's up a messaging channel between them.
// This is temporary workaround until Bug 914974 is fixed
// and port can be transfered through message manager.
const demarshal = (handle) => {
if (handle.type === "MessagePort") {
if (!channels.has(handle.id)) {
const channel = new content.MessageChannel();
channels.set(handle.id, channel);
handles.set(channel.port1, handle);
channel.port1.onmessage = onOutPort;
}
return channels.get(handle.id).port2;
}
return null;
};
const onOutPort = event => {
const handle = handles.get(event.target);
sendAsyncMessage("sdk/port/message", {
port: handle,
message: event.data
});
};
const onInPort = ({data}) => {
const channel = channels.get(data.port.id);
if (channel)
channel.port1.postMessage(data.message);
};
const onOutEvent = event =>
sendSyncMessage("sdk/event/" + event.type,
{ type: event.type,
data: event.data });
const onInMessage = (message) => {
const {type, data, origin, bubbles, cancelable, ports} = message.data;
const event = new content.MessageEvent(type, {
bubbles: bubbles,
cancelable: cancelable,
data: data,
origin: origin,
target: content,
source: content,
ports: ports.map(demarshal)
});
content.dispatchEvent(event);
};
const onReady = event => {
channels.clear();
};
addMessageListener("sdk/event/message", onInMessage);
addMessageListener("sdk/port/message", onInPort);
const observer = {
handleEvent: ({target, type}) => {
observer.observe(target, type);
},
observe: (document, topic, data) => {
// When frame associated with message manager is removed from document `docShell`
// is set to `null` but observer is still kept alive. At this point accesing
// `content.document` throws "can't access dead object" exceptions. In order to
// avoid leaking observer and logged errors observer is going to be removed when
// `docShell` is set to `null`.
if (!docShell) {
observerService.removeObserver(observer, topic);
}
else if (document === content.document) {
if (topic.endsWith("-document-interactive")) {
sendAsyncMessage("sdk/event/ready", {
type: "ready",
readyState: document.readyState,
uri: document.documentURI
});
}
if (topic.endsWith("-document-loaded")) {
sendAsyncMessage("sdk/event/load", {
type: "load",
readyState: document.readyState,
uri: document.documentURI
});
}
if (topic === "unload") {
channels.clear();
sendAsyncMessage("sdk/event/unload", {
type: "unload",
readyState: "uninitialized",
uri: document.documentURI
});
}
}
}
};
observerService.addObserver(observer, "content-document-interactive");
observerService.addObserver(observer, "content-document-loaded");
observerService.addObserver(observer, "chrome-document-interactive");
observerService.addObserver(observer, "chrome-document-loaded");
addEventListener("unload", observer, false);
})(this);

Просмотреть файл

@ -0,0 +1,259 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cu } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { curry } = require("../sdk/lang/functional");
const { EventTarget } = require("../sdk/event/target");
const { Disposable, setup, dispose } = require("../sdk/core/disposable");
const { emit, off, setListeners } = require("../sdk/event/core");
const { when } = require("../sdk/event/utils");
const { getFrameElement } = require("../sdk/window/utils");
const { contract, validate } = require("../sdk/util/contract");
const { data: { url: resolve }} = require("../sdk/self");
const { identify } = require("../sdk/ui/id");
const { isLocalURL, URL } = require("../sdk/url");
const { encode } = require("../sdk/base64");
const { marshal, demarshal } = require("./ports");
const { fromTarget } = require("./debuggee");
const { removed } = require("../sdk/dom/events");
const { id: addonID } = require("../sdk/self");
const { viewFor } = require("../sdk/view/core");
const { createView } = require("./panel/view");
const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
const FRAME_SCRIPT = module.uri.replace("/panel.js", "/frame-script.js");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const makeID = name =>
("dev-panel-" + addonID + "-" + name).
split("/").join("-").
split(".").join("-").
split(" ").join("-").
replace(/[^A-Za-z0-9_\-]/g, "");
// Weak mapping between `Panel` instances and their frame's
// `nsIMessageManager`.
const managers = new WeakMap();
// Return `nsIMessageManager` for the given `Panel` instance.
const managerFor = x => managers.get(x);
// Weak mappinging between iframe's and their owner
// `Panel` instances.
const panels = new WeakMap();
const panelFor = frame => panels.get(frame);
// Weak mapping between panels and debugees they're targeting.
const debuggees = new WeakMap();
const debuggeeFor = panel => debuggees.get(panel);
const frames = new WeakMap();
const frameFor = panel => frames.get(panel);
const setAttributes = (node, attributes) => {
for (var key in attributes)
node.setAttribute(key, attributes[key]);
};
const onStateChange = ({target, data}) => {
const panel = panelFor(target);
panel.readyState = data.readyState;
emit(panel, data.type, { target: panel, type: data.type });
};
// port event listener on the message manager that demarshalls
// and forwards to the actual receiver. This is a workaround
// until Bug 914974 is fixed.
const onPortMessage = ({data, target}) => {
const port = demarshal(target, data.port);
if (port)
port.postMessage(data.message);
};
// When frame is removed from the toolbox destroy panel
// associated with it to release all the resources.
const onFrameRemove = frame => {
panelFor(frame).destroy();
};
const onFrameInited = frame => {
frame.style.visibility = "visible";
}
const inited = frame => new Promise(resolve => {
const { messageManager } = frame.frameLoader;
const listener = message => {
messageManager.removeMessageListener("sdk/event/ready", listener);
resolve(frame);
};
messageManager.addMessageListener("sdk/event/ready", listener);
});
const getTarget = ({target}) => target;
const Panel = Class({
extends: Disposable,
implements: [EventTarget],
get id() {
return makeID(this.name || this.label);
},
readyState: "uninitialized",
ready: function() {
const { readyState } = this;
const isReady = readyState === "complete" ||
readyState === "interactive";
return isReady ? Promise.resolve(this) :
when(this, "ready").then(getTarget);
},
loaded: function() {
const { readyState } = this;
const isLoaded = readyState === "complete";
return isLoaded ? Promise.resolve(this) :
when(this, "load").then(getTarget);
},
unloaded: function() {
const { readyState } = this;
const isUninitialized = readyState === "uninitialized";
return isUninitialized ? Promise.resolve(this) :
when(this, "unload").then(getTarget);
},
postMessage: function(data, ports=[]) {
const manager = managerFor(this);
manager.sendAsyncMessage("sdk/event/message", {
type: "message",
bubbles: false,
cancelable: false,
data: data,
origin: this.url,
ports: ports.map(marshal(manager))
});
}
});
exports.Panel = Panel;
validate.define(Panel, contract({
label: {
is: ["string"],
msg: "The `option.label` must be a provided"
},
tooltip: {
is: ["string", "undefined"],
msg: "The `option.tooltip` must be a string"
},
icon: {
is: ["string"],
map: x => x && resolve(x),
ok: x => isLocalURL(x),
msg: "The `options.icon` must be a valid local URI."
},
url: {
map: x => resolve(x.toString()),
is: ["string"],
ok: x => isLocalURL(x),
msg: "The `options.url` must be a valid local URI."
},
invertIconForLightTheme: {
is: ["boolean", "undefined"],
msg: "The `options.invertIconForLightTheme` must be a boolean."
},
invertIconForDarkTheme: {
is: ["boolean", "undefined"],
msg: "The `options.invertIconForDarkTheme` must be a boolean."
}
}));
setup.define(Panel, (panel, {window, toolbox, url}) => {
// Hack: Given that iframe created by devtools API is no good for us,
// we obtain original iframe and replace it with the one that has
// desired configuration.
const original = getFrameElement(window);
const container = original.parentNode;
original.remove();
const frame = createView(panel, container.ownerDocument);
// Following modifications are a temporary workaround until Bug 1049188
// is fixed.
// Enforce certain iframe customizations regardless of users request.
setAttributes(frame, {
"id": original.id,
"src": url,
"flex": 1,
"forceOwnRefreshDriver": "",
"tooltip": "aHTMLTooltip"
});
frame.style.visibility = "hidden";
frame.classList.add("toolbox-panel-iframe");
// Inject iframe into designated node until add-on author decides
// to inject it elsewhere instead.
if (!frame.parentNode)
container.appendChild(frame);
// associate view with a panel
frames.set(panel, frame);
// associate panel model with a frame view.
panels.set(frame, panel);
const debuggee = fromTarget(toolbox.target);
// associate debuggee with a panel.
debuggees.set(panel, debuggee);
// Setup listeners for the frame message manager.
const { messageManager } = frame.frameLoader;
messageManager.addMessageListener("sdk/event/ready", onStateChange);
messageManager.addMessageListener("sdk/event/load", onStateChange);
messageManager.addMessageListener("sdk/event/unload", onStateChange);
messageManager.addMessageListener("sdk/port/message", onPortMessage);
messageManager.loadFrameScript(FRAME_SCRIPT, false);
managers.set(panel, messageManager);
// destroy panel if frame is removed.
removed(frame).then(onFrameRemove);
// show frame when it is initialized.
inited(frame).then(onFrameInited);
// set listeners if there are ones defined on the prototype.
setListeners(panel, Object.getPrototypeOf(panel));
panel.setup({ debuggee: debuggee });
});
createView.define(Panel, (panel, document) => {
const frame = document.createElement("iframe");
setAttributes(frame, {
"sandbox": "allow-scripts",
// We end up using chrome iframe with forced message manager
// as fixing a swapFrameLoader seemed like a giant task (see
// Bug 1075490).
"type": "chrome",
"forcemessagemanager": true,
"transparent": true,
"seamless": "seamless",
});
return frame;
});
dispose.define(Panel, function(panel) {
debuggeeFor(panel).close();
debuggees.delete(panel);
managers.delete(panel);
frames.delete(panel);
panel.readyState = "destroyed";
panel.dispose();
});
viewFor.define(Panel, frameFor);

Просмотреть файл

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { method } = require("method/core");
const createView = method("dev/panel/view#createView");
exports.createView = createView;

Просмотреть файл

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
// This module provides `marshal` and `demarshal` functions
// that can be used to send MessagePort's over `nsIFrameMessageManager`
// until Bug 914974 is fixed.
const { add, iterator } = require("../sdk/lang/weak-set");
const { curry } = require("../sdk/lang/functional");
var id = 0;
const ports = new WeakMap();
// Takes `nsIFrameMessageManager` and `MessagePort` instances
// and returns a handle representing given `port`. Messages
// received on given `port` will be forwarded to a message
// manager under `sdk/port/message` and messages like:
// { port: { type: "MessagePort", id: 2}, data: data }
// Where id is an identifier associated with a given `port`
// and `data` is an `event.data` received on port.
const marshal = curry((manager, port) => {
if (!ports.has(port)) {
id = id + 1;
const handle = {type: "MessagePort", id: id};
// Bind id to the given port
ports.set(port, handle);
// Obtain a weak reference to a port.
add(exports, port);
port.onmessage = event => {
manager.sendAsyncMessage("sdk/port/message", {
port: handle,
message: event.data
});
};
return handle;
}
return ports.get(port);
});
exports.marshal = marshal;
// Takes `nsIFrameMessageManager` instance and a handle returned
// `marshal(manager, port)` returning a `port` that was passed
// to it. Note that `port` may be GC-ed in which case returned
// value will be `null`.
const demarshal = curry((manager, {type, id}) => {
if (type === "MessagePort") {
for (let port of iterator(exports)) {
if (id === ports.get(port).id)
return port;
}
}
return null;
});
exports.demarshal = demarshal;

Просмотреть файл

@ -0,0 +1,135 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Class } = require("../sdk/core/heritage");
const { EventTarget } = require("../sdk/event/target");
const { Disposable, setup, dispose } = require("../sdk/core/disposable");
const { contract, validate } = require("../sdk/util/contract");
const { id: addonID } = require("../sdk/self");
const { onEnable, onDisable } = require("dev/theme/hooks");
const { isString, instanceOf, isFunction } = require("sdk/lang/type");
const { add } = require("sdk/util/array");
const { data } = require("../sdk/self");
const { isLocalURL } = require("../sdk/url");
const makeID = name =>
("dev-theme-" + addonID + (name ? "-" + name : "")).
split(/[ . /]/).join("-").
replace(/[^A-Za-z0-9_\-]/g, "");
const Theme = Class({
extends: Disposable,
implements: [EventTarget],
initialize: function(options) {
this.name = options.name;
this.label = options.label;
this.styles = options.styles;
// Event handlers
this.onEnable = options.onEnable;
this.onDisable = options.onDisable;
},
get id() {
return makeID(this.name || this.label);
},
setup: function() {
// Any initialization steps done at the registration time.
},
getStyles: function() {
if (!this.styles) {
return [];
}
if (isString(this.styles)) {
if (isLocalURL(this.styles)) {
return [data.url(this.styles)];
}
}
let result = [];
for (let style of this.styles) {
if (isString(style)) {
if (isLocalURL(style)) {
style = data.url(style);
}
add(result, style);
} else if (instanceOf(style, Theme)) {
result = result.concat(style.getStyles());
}
}
return result;
},
getClassList: function() {
let result = [];
for (let style of this.styles) {
if (instanceOf(style, Theme)) {
result = result.concat(style.getClassList());
}
}
if (this.name) {
add(result, this.name);
}
return result;
}
});
exports.Theme = Theme;
// Initialization & dispose
setup.define(Theme, (theme) => {
theme.classList = [];
theme.setup();
});
dispose.define(Theme, function(theme) {
theme.dispose();
});
// Validation
validate.define(Theme, contract({
label: {
is: ["string"],
msg: "The `option.label` must be a provided"
},
}));
// Support theme events: apply and unapply the theme.
onEnable.define(Theme, (theme, {window, oldTheme}) => {
if (isFunction(theme.onEnable)) {
theme.onEnable(window, oldTheme);
}
});
onDisable.define(Theme, (theme, {window, newTheme}) => {
if (isFunction(theme.onDisable)) {
theme.onDisable(window, newTheme);
}
});
// Support for built-in themes
const LightTheme = Theme({
name: "theme-light",
styles: "chrome://devtools/skin/light-theme.css",
});
const DarkTheme = Theme({
name: "theme-dark",
styles: "chrome://devtools/skin/dark-theme.css",
});
exports.LightTheme = LightTheme;
exports.DarkTheme = DarkTheme;

Просмотреть файл

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { method } = require("method/core");
const onEnable = method("dev/theme/hooks#onEnable");
const onDisable = method("dev/theme/hooks#onDisable");
exports.onEnable = onEnable;
exports.onDisable = onDisable;

Просмотреть файл

@ -0,0 +1,107 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cu, Cc, Ci } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { Disposable, setup } = require("../sdk/core/disposable");
const { contract, validate } = require("../sdk/util/contract");
const { each, pairs, values } = require("../sdk/util/sequence");
const { onEnable, onDisable } = require("../dev/theme/hooks");
const { DevToolsShim } = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
// This is temporary workaround to allow loading of the developer tools client - volcan
// into a toolbox panel, this hack won't be necessary as soon as devtools patch will be
// shipped in nightly, after which it can be removed. Bug 1038517
const registerSDKURI = () => {
const ioService = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
const resourceHandler = ioService.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
const uri = module.uri.replace("dev/toolbox.js", "");
resourceHandler.setSubstitution("sdk", ioService.newURI(uri));
};
registerSDKURI();
const Tool = Class({
extends: Disposable,
setup: function(params={}) {
const { panels } = validate(this, params);
const { themes } = validate(this, params);
this.panels = panels;
this.themes = themes;
each(([key, Panel]) => {
const { url, label, tooltip, icon, invertIconForLightTheme,
invertIconForDarkTheme } = validate(Panel.prototype);
const { id } = Panel.prototype;
DevToolsShim.registerTool({
id: id,
url: "about:blank",
label: label,
tooltip: tooltip,
icon: icon,
invertIconForLightTheme: invertIconForLightTheme,
invertIconForDarkTheme: invertIconForDarkTheme,
isTargetSupported: target => target.isLocalTab,
build: (window, toolbox) => {
const panel = new Panel();
setup(panel, { window: window,
toolbox: toolbox,
url: url });
return panel.ready();
}
});
}, pairs(panels));
each(([key, theme]) => {
validate(theme);
setup(theme);
DevToolsShim.registerTheme({
id: theme.id,
label: theme.label,
stylesheets: theme.getStyles(),
classList: theme.getClassList(),
onApply: (window, oldTheme) => {
onEnable(theme, { window: window,
oldTheme: oldTheme });
},
onUnapply: (window, newTheme) => {
onDisable(theme, { window: window,
newTheme: newTheme });
}
});
}, pairs(themes));
},
dispose: function() {
each(Panel => DevToolsShim.unregisterTool(Panel.prototype.id),
values(this.panels));
each(Theme => DevToolsShim.unregisterTheme(Theme.prototype.id),
values(this.themes));
}
});
validate.define(Tool, contract({
panels: {
is: ["object", "undefined"]
},
themes: {
is: ["object", "undefined"]
}
}));
exports.Tool = Tool;

Просмотреть файл

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cu } = require("chrome");
const { DevToolsShim } = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
const { getActiveTab } = require("../sdk/tabs/utils");
const { getMostRecentBrowserWindow } = require("../sdk/window/utils");
const targetFor = target => {
target = target || getActiveTab(getMostRecentBrowserWindow());
return DevToolsShim.getTargetForTab(target);
};
const getId = id => ((id.prototype && id.prototype.id) || id.id || id);
const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
exports.getCurrentPanel = getCurrentPanel;
const openToolbox = (id, tab) => {
id = getId(id);
return DevToolsShim.showToolbox(targetFor(tab), id);
};
exports.openToolbox = openToolbox;
const closeToolbox = tab => DevToolsShim.closeToolbox(targetFor(tab));
exports.closeToolbox = closeToolbox;
const getToolbox = tab => DevToolsShim.getToolbox(targetFor(tab));
exports.getToolbox = getToolbox;
const openToolboxPanel = (id, tab) => {
id = getId(id);
return DevToolsShim.showToolbox(targetFor(tab), id).then(getCurrentPanel);
};
exports.openToolboxPanel = openToolboxPanel;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,45 @@
"use strict";
var method = require("../method/core")
// Method is designed to work with data structures representing application
// state. Calling it with a state should return object representing `delta`
// that has being applied to a previous state to get to a current state.
//
// Example
//
// diff(state) // => { "item-id-1": { title: "some title" } "item-id-2": null }
var diff = method("diff@diffpatcher")
// diff between `null` / `undefined` to any hash is a hash itself.
diff.define(null, function(from, to) { return to })
diff.define(undefined, function(from, to) { return to })
diff.define(Object, function(from, to) {
return calculate(from, to || {}) || {}
})
function calculate(from, to) {
var diff = {}
var changes = 0
Object.keys(from).forEach(function(key) {
changes = changes + 1
if (!(key in to) && from[key] != null) diff[key] = null
else changes = changes - 1
})
Object.keys(to).forEach(function(key) {
changes = changes + 1
var previous = from[key]
var current = to[key]
if (previous === current) return (changes = changes - 1)
if (typeof(current) !== "object") return diff[key] = current
if (typeof(previous) !== "object") return diff[key] = current
var delta = calculate(previous, current)
if (delta) diff[key] = delta
else changes = changes - 1
})
return changes ? diff : null
}
diff.calculate = calculate
module.exports = diff

Просмотреть файл

@ -0,0 +1,5 @@
"use strict";
exports.diff = require("./diff")
exports.patch = require("./patch")
exports.rebase = require("./rebase")

Просмотреть файл

@ -0,0 +1,21 @@
"use strict";
var method = require("../method/core")
var rebase = require("./rebase")
// Method is designed to work with data structures representing application
// state. Calling it with a state and delta should return object representing
// new state, with changes in `delta` being applied to previous.
//
// ## Example
//
// patch(state, {
// "item-id-1": { completed: false }, // update
// "item-id-2": null // delete
// })
var patch = method("patch@diffpatcher")
patch.define(Object, function patch(hash, delta) {
return rebase({}, hash, delta)
})
module.exports = patch

Просмотреть файл

@ -0,0 +1,36 @@
"use strict";
var nil = {}
var owns = ({}).hasOwnProperty
function rebase(result, parent, delta) {
var key, current, previous, update
for (key in parent) {
if (owns.call(parent, key)) {
previous = parent[key]
update = owns.call(delta, key) ? delta[key] : nil
if (previous === null) continue
else if (previous === void(0)) continue
else if (update === null) continue
else if (update === void(0)) continue
else result[key] = previous
}
}
for (key in delta) {
if (owns.call(delta, key)) {
update = delta[key]
current = owns.call(result, key) ? result[key] : nil
if (current === update) continue
else if (update === null) continue
else if (update === void(0)) continue
else if (current === nil) result[key] = update
else if (typeof(update) !== "object") result[key] = update
else if (typeof(current) !== "object") result[key] = update
else result[key]= rebase({}, current, update)
}
}
return result
}
module.exports = rebase

Просмотреть файл

@ -0,0 +1,3 @@
"use strict";
require("test").run(require("./index"))

Просмотреть файл

@ -0,0 +1,59 @@
"use strict";
var diff = require("../diff")
exports["test diff from null"] = function(assert) {
var to = { a: 1, b: 2 }
assert.equal(diff(null, to), to, "diff null to x returns x")
assert.equal(diff(void(0), to), to, "diff undefined to x returns x")
}
exports["test diff to null"] = function(assert) {
var from = { a: 1, b: 2 }
assert.deepEqual(diff({ a: 1, b: 2 }, null),
{ a: null, b: null },
"diff x null returns x with all properties nullified")
}
exports["test diff identical"] = function(assert) {
assert.deepEqual(diff({}, {}), {}, "diff on empty objects is {}")
assert.deepEqual(diff({ a: 1, b: 2 }, { a: 1, b: 2 }), {},
"if properties match diff is {}")
assert.deepEqual(diff({ a: 1, b: { c: { d: 3, e: 4 } } },
{ a: 1, b: { c: { d: 3, e: 4 } } }), {},
"diff between identical nested hashes is {}")
}
exports["test diff delete"] = function(assert) {
assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2 }), { a: null },
"missing property is deleted")
assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2 }), { a: 2, b: null },
"missing property is deleted another updated")
assert.deepEqual(diff({ a: 1, b: 2 }, {}), { a: null, b: null },
"missing propertes are deleted")
assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, {}),
{ a: null, b: null },
"missing deep propertes are deleted")
assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { b: { c: {} } }),
{ a: null, b: { c: { d: null } } },
"missing nested propertes are deleted")
}
exports["test add update"] = function(assert) {
assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2, c: 3 }), { a: null, c: 3 },
"delete and add")
assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2, c: 3 }), { a: 2, b: null, c: 3 },
"delete and adds")
assert.deepEqual(diff({}, { a: 1, b: 2 }), { a: 1, b: 2 },
"diff on empty objcet returns equivalen of to")
assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { d: 3 }),
{ a: null, b: null, d: 3 },
"missing deep propertes are deleted")
assert.deepEqual(diff({ b: { c: {} }, d: null }, { a: 1, b: { c: { d: 2 } } }),
{ a: 1, b: { c: { d: 2 } } },
"missing nested propertes are deleted")
}

Просмотреть файл

@ -0,0 +1,14 @@
"use strict";
var diff = require("../diff")
var patch = require("../patch")
exports["test diff"] = require("./diff")
exports["test patch"] = require("./patch")
exports["test patch(a, diff(a, b)) => b"] = function(assert) {
var a = { a: { b: 1 }, c: { d: 2 } }
var b = { a: { e: 3 }, c: { d: 4 } }
assert.deepEqual(patch(a, diff(a, b)), b, "patch(a, diff(a, b)) => b")
}

Просмотреть файл

@ -0,0 +1,83 @@
"use strict";
var patch = require("../patch")
exports["test patch delete"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, { a: null }), { b: 2 }, "null removes property")
}
exports["test patch delete with void"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
"void(0) removes property")
}
exports["test patch delete missing"] = function(assert) {
assert.deepEqual(patch({ a: 1, b: 2 }, { c: null }),
{ a: 1, b: 2 },
"null removes property if exists");
assert.deepEqual(patch({ a: 1, b: 2 }, { c: void(0) }),
{ a: 1, b: 2 },
"void removes property if exists");
}
exports["test delete deleted"] = function(assert) {
assert.deepEqual(patch({ a: null, b: 2, c: 3, d: void(0)},
{ a: void(0), b: null, d: null }),
{c: 3},
"removed all existing and non existing");
}
exports["test update deleted"] = function(assert) {
assert.deepEqual(patch({ a: null, b: void(0), c: 3},
{ a: { b: 2 } }),
{ a: { b: 2 }, c: 3 },
"replace deleted");
}
exports["test patch delete with void"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
"void(0) removes property")
}
exports["test patch addition"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
"new properties are added")
}
exports["test patch addition"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
"new properties are added")
}
exports["test hash on itself"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, hash), hash,
"applying hash to itself returns hash itself")
}
exports["test patch with empty delta"] = function(assert) {
var hash = { a: 1, b: 2 }
assert.deepEqual(patch(hash, {}), hash,
"applying empty delta results in no changes")
}
exports["test patch nested data"] = function(assert) {
assert.deepEqual(patch({ a: { b: 1 }, c: { d: 2 } },
{ a: { b: null, e: 3 }, c: { d: 4 } }),
{ a: { e: 3 }, c: { d: 4 } },
"nested structures can also be patched")
}

Просмотреть файл

@ -0,0 +1,3 @@
"use strict";
require("retape")(require("./index"))

Просмотреть файл

@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 globalMM = Components.classes["@mozilla.org/globalmessagemanager;1"].
getService(Components.interfaces.nsIMessageListenerManager);
// Load frame scripts from the same dir as this module.
// Since this JSM will be loaded using require(), PATH will be
// overridden while running tests, just like any other module.
const PATH = __URI__.replace('framescript/FrameScriptManager.jsm', '');
// Builds a unique loader ID for this runtime. We prefix with the SDK path so
// overriden versions of the SDK don't conflict
var LOADER_ID = 0;
this.getNewLoaderID = () => {
return PATH + ":" + LOADER_ID++;
}
const frame_script = function(contentFrame, PATH) {
let { registerContentFrame } = Components.utils.import(PATH + 'framescript/content.jsm', {});
registerContentFrame(contentFrame);
}
globalMM.loadFrameScript("data:,(" + frame_script.toString() + ")(this, " + JSON.stringify(PATH) + ");", true);
this.EXPORTED_SYMBOLS = ['getNewLoaderID'];

Просмотреть файл

@ -0,0 +1,94 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import('resource://gre/modules/Services.jsm');
const cpmm = Cc['@mozilla.org/childprocessmessagemanager;1'].
getService(Ci.nsISyncMessageSender);
this.EXPORTED_SYMBOLS = ["registerContentFrame"];
// This may be an overriden version of the SDK so use the PATH as a key for the
// initial messages before we have a loaderID.
const PATH = __URI__.replace('framescript/content.jsm', '');
const { Loader } = Cu.import(PATH + 'toolkit/loader.js', {});
// one Loader instance per addon (per @loader/options to be precise)
var addons = new Map();
// Tell the parent that a new process is ready
cpmm.sendAsyncMessage('sdk/remote/process/start', {
modulePath: PATH
});
// Load a child process module loader with the given loader options
cpmm.addMessageListener('sdk/remote/process/load', ({ data: { modulePath, loaderID, options, reason } }) => {
if (modulePath != PATH)
return;
// During startup races can mean we get a second load message
if (addons.has(loaderID))
return;
options.waiveInterposition = true;
let loader = Loader.Loader(options);
let addon = {
loader,
require: Loader.Require(loader, { id: 'LoaderHelper' }),
}
addons.set(loaderID, addon);
cpmm.sendAsyncMessage('sdk/remote/process/attach', {
loaderID,
processID: Services.appinfo.processID,
isRemote: Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
});
addon.child = addon.require('sdk/remote/child');
for (let contentFrame of frames.values())
addon.child.registerContentFrame(contentFrame);
});
// Unload a child process loader
cpmm.addMessageListener('sdk/remote/process/unload', ({ data: { loaderID, reason } }) => {
if (!addons.has(loaderID))
return;
let addon = addons.get(loaderID);
Loader.unload(addon.loader, reason);
// We want to drop the reference to the loader but never allow creating a new
// loader with the same ID
addons.set(loaderID, {});
})
var frames = new Set();
this.registerContentFrame = contentFrame => {
contentFrame.addEventListener("unload", () => {
unregisterContentFrame(contentFrame);
});
frames.add(contentFrame);
for (let addon of addons.values()) {
if ("child" in addon)
addon.child.registerContentFrame(contentFrame);
}
};
function unregisterContentFrame(contentFrame) {
frames.delete(contentFrame);
for (let addon of addons.values()) {
if ("child" in addon)
addon.child.unregisterContentFrame(contentFrame);
}
}

Просмотреть файл

@ -0,0 +1,215 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { query, constant, cache } = require("sdk/lang/functional");
const { pairs, each, map, object } = require("sdk/util/sequence");
const { nodeToMessageManager } = require("./util");
// Decorator function that takes `f` function and returns one that attempts
// to run `f` with given arguments. In case of exception error is logged
// and `fallback` is returned instead.
const Try = (fn, fallback=null) => (...args) => {
try {
return fn(...args);
} catch(error) {
console.error(error);
return fallback;
}
};
// Decorator funciton that takes `f` function and returns one that returns
// JSON cloned result of whatever `f` returns for given arguments.
const JSONReturn = f => (...args) => JSON.parse(JSON.stringify(f(...args)));
const Null = constant(null);
// Table of readers mapped to field names they're going to be reading.
const readers = Object.create(null);
// Read function takes "contextmenu" event target `node` and returns table of
// read field names mapped to appropriate values. Read uses above defined read
// table to read data for all registered readers.
const read = node =>
object(...map(([id, read]) => [id, read(node, id)], pairs(readers)));
// Table of built-in readers, each takes a descriptor and returns a reader:
// descriptor -> node -> JSON
const parsers = Object.create(null)
// Function takes a descriptor of the remotely defined reader and parsese it
// to construct a local reader that's going to read out data from context menu
// target.
const parse = descriptor => {
const parser = parsers[descriptor.category];
if (!parser) {
console.error("Unknown reader descriptor was received", descriptor, `"${descriptor.category}"`);
return Null
}
return Try(parser(descriptor));
}
// TODO: Test how chrome's mediaType behaves to try and match it's behavior.
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const SVG_NS = "http://www.w3.org/2000/svg";
// Firefox always creates a HTMLVideoElement when loading an ogg file
// directly. If the media is actually audio, be smarter and provide a
// context menu with audio operations.
// Source: https://github.com/mozilla/gecko-dev/blob/28c2fca3753c5371643843fc2f2f205146b083b7/browser/base/content/nsContextMenu.js#L632-L637
const isVideoLoadingAudio = node =>
node.readyState >= node.HAVE_METADATA &&
(node.videoWidth == 0 || node.videoHeight == 0)
const isVideo = node =>
node instanceof node.ownerGlobal.HTMLVideoElement &&
!isVideoLoadingAudio(node);
const isAudio = node => {
const {HTMLVideoElement, HTMLAudioElement} = node.ownerGlobal;
return node instanceof HTMLAudioElement ? true :
node instanceof HTMLVideoElement ? isVideoLoadingAudio(node) :
false;
};
const isImage = ({namespaceURI, localName}) =>
namespaceURI === HTML_NS && localName === "img" ? true :
namespaceURI === XUL_NS && localName === "image" ? true :
namespaceURI === SVG_NS && localName === "image" ? true :
false;
parsers["reader/MediaType()"] = constant(node =>
isImage(node) ? "image" :
isAudio(node) ? "audio" :
isVideo(node) ? "video" :
null);
const readLink = node =>
node.namespaceURI === HTML_NS && node.localName === "a" ? node.href :
readLink(node.parentNode);
parsers["reader/LinkURL()"] = constant(node =>
node.matches("a, a *") ? readLink(node) : null);
// Reader that reads out `true` if "contextmenu" `event.target` matches
// `descriptor.selector` and `false` if it does not.
parsers["reader/SelectorMatch()"] = ({selector}) =>
node => node.matches(selector);
// Accessing `selectionStart` and `selectionEnd` properties on non
// editable input nodes throw exceptions, there for we need this util
// function to guard us against them.
const getInputSelection = node => {
try {
if ("selectionStart" in node && "selectionEnd" in node) {
const {selectionStart, selectionEnd} = node;
return {selectionStart, selectionEnd}
}
}
catch(_) {}
return null;
}
// Selection reader does not really cares about descriptor so it is
// a constant function returning selection reader. Selection reader
// returns string of the selected text or `null` if there is no selection.
parsers["reader/Selection()"] = constant(node => {
const selection = node.ownerDocument.getSelection();
if (!selection.isCollapsed) {
return selection.toString();
}
// If target node is editable (text, input, textarea, etc..) document does
// not really handles selections there. There for we fallback to checking
// `selectionStart` `selectionEnd` properties and if they are present we
// extract selections manually from the `node.value`.
else {
const selection = getInputSelection(node);
const isSelected = selection &&
Number.isInteger(selection.selectionStart) &&
Number.isInteger(selection.selectionEnd) &&
selection.selectionStart !== selection.selectionEnd;
return isSelected ? node.value.substring(selection.selectionStart,
selection.selectionEnd) :
null;
}
});
// Query reader just reads out properties from the node, so we just use `query`
// utility function.
parsers["reader/Query()"] = ({path}) => JSONReturn(query(path));
// Attribute reader just reads attribute of the event target node.
parsers["reader/Attribute()"] = ({name}) => node => node.getAttribute(name);
// Extractor reader defines generates a reader out of serialized function, who's
// return value is JSON cloned. Note: We do know source will evaluate to function
// as that's what we serialized on the other end, it's also ok if generated function
// is going to throw as registered readers are wrapped in try catch to avoid breakting
// unrelated readers.
parsers["reader/Extractor()"] = ({source}) =>
JSONReturn(new Function("return (" + source + ")")());
// If the context-menu target node or any of its ancestors is one of these,
// Firefox uses a tailored context menu, and so the page context doesn't apply.
// There for `reader/isPage()` will read `false` in that case otherwise it's going
// to read `true`.
const nonPageElements = ["a", "applet", "area", "button", "canvas", "object",
"embed", "img", "input", "map", "video", "audio", "menu",
"option", "select", "textarea", "[contenteditable=true]"];
const nonPageSelector = nonPageElements.
concat(nonPageElements.map(tag => `${tag} *`)).
join(", ");
// Note: isPageContext implementation could have actually used SelectorMatch reader,
// but old implementation was also checked for collapsed selection there for to keep
// the behavior same we end up implementing a new reader.
parsers["reader/isPage()"] = constant(node =>
node.ownerGlobal.getSelection().isCollapsed &&
!node.matches(nonPageSelector));
// Reads `true` if node is in an iframe otherwise returns true.
parsers["reader/isFrame()"] = constant(node =>
!!node.ownerGlobal.frameElement);
parsers["reader/isEditable()"] = constant(node => {
const selection = getInputSelection(node);
return selection ? !node.readOnly && !node.disabled : node.isContentEditable;
});
// TODO: Add some reader to read out tab id.
const onReadersUpdate = message => {
each(([id, descriptor]) => {
if (descriptor) {
readers[id] = parse(descriptor);
}
else {
delete readers[id];
}
}, pairs(message.data));
};
exports.onReadersUpdate = onReadersUpdate;
const onContextMenu = event => {
if (!event.defaultPrevented) {
const manager = nodeToMessageManager(event.target);
manager.sendSyncMessage("sdk/context-menu/read", read(event.target), readers);
}
};
exports.onContextMenu = onContextMenu;
const onContentFrame = (frame) => {
// Listen for contextmenu events in on this frame.
frame.addEventListener("contextmenu", onContextMenu);
// Listen to registered reader changes and update registry.
frame.addMessageListener("sdk/context-menu/readers", onReadersUpdate);
// Request table of readers (if this is loaded in a new process some table
// changes may be missed, this is way to sync up).
frame.sendAsyncMessage("sdk/context-menu/readers?");
};
exports.onContentFrame = onContentFrame;

Просмотреть файл

@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const mime = "application/javascript";
const requireURI = module.uri.replace("framescript/manager.js",
"toolkit/require.js");
const requireLoadURI = `data:${mime},this["Components"].utils.import("${requireURI}")`
// Loads module with given `id` into given `messageManager` via shared module loader. If `init`
// string is passed, will call module export with that name and pass frame script environment
// of the `messageManager` into it. Since module will load only once per process (which is
// once for chrome proces & second for content process) it is useful to have an init function
// to setup event listeners on each content frame.
const loadModule = (messageManager, id, allowDelayed, init) => {
const moduleLoadURI = `${requireLoadURI}.require("${id}")`
const uri = init ? `${moduleLoadURI}.${init}(this)` : moduleLoadURI;
messageManager.loadFrameScript(uri, allowDelayed);
};
exports.loadModule = loadModule;

Просмотреть файл

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Ci } = require("chrome");
const windowToMessageManager = window =>
window.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDocShell).
sameTypeRootTreeItem.
QueryInterface(Ci.nsIDocShell).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIContentFrameMessageManager);
exports.windowToMessageManager = windowToMessageManager;
const nodeToMessageManager = node =>
windowToMessageManager(node.ownerGlobal);
exports.nodeToMessageManager = nodeToMessageManager;

Просмотреть файл

@ -0,0 +1,20 @@
"use strict";
exports["test common"] = require("./common")
var Method = require("../core")
exports["test host objects"] = function(assert) {
var isElement = Method("is-element")
isElement.define(function() { return false })
isElement.define(Element, function() { return true })
assert.notDeepEqual(typeof(Element.prototype[isElement]), "number",
"Host object's prototype is extended with a number value")
assert.ok(!isElement({}), "object is not an Element")
assert.ok(document.createElement("div"), "Element is an element")
}
require("test").run(exports)

Просмотреть файл

@ -0,0 +1,272 @@
"use strict";
var Method = require("../core")
function type(value) {
return Object.prototype.toString.call(value).
split(" ").
pop().
split("]").
shift().
toLowerCase()
}
var values = [
null, // 0
undefined, // 1
Infinity, // 2
NaN, // 3
5, // 4
{}, // 5
Object.create({}), // 6
Object.create(null), // 7
[], // 8
/foo/, // 9
new Date(), // 10
Function, // 11
function() {}, // 12
true, // 13
false, // 14
"string" // 15
]
function True() { return true }
function False() { return false }
var trues = values.map(True)
var falses = values.map(False)
exports["test throws if not implemented"] = function(assert) {
var method = Method("nope")
assert.throws(function() {
method({})
}, /not implement/i, "method throws if not implemented")
assert.throws(function() {
method(null)
}, /not implement/i, "method throws on null")
}
exports["test all types inherit from default"] = function(assert) {
var isImplemented = Method("isImplemented")
isImplemented.define(function() { return true })
values.forEach(function(value) {
assert.ok(isImplemented(value),
type(value) + " inherits deafult implementation")
})
}
exports["test default can be implemented later"] = function(assert) {
var isImplemented = Method("isImplemented")
isImplemented.define(function() {
return true
})
values.forEach(function(value) {
assert.ok(isImplemented(value),
type(value) + " inherits deafult implementation")
})
}
exports["test dispatch not-implemented"] = function(assert) {
var isDefault = Method("isDefault")
values.forEach(function(value) {
assert.throws(function() {
isDefault(value)
}, /not implement/, type(value) + " throws if not implemented")
})
}
exports["test dispatch default"] = function(assert) {
var isDefault = Method("isDefault")
// Implement default
isDefault.define(True)
assert.deepEqual(values.map(isDefault), trues,
"all implementation inherit from default")
}
exports["test dispatch null"] = function(assert) {
var isNull = Method("isNull")
// Implement default
isNull.define(False)
isNull.define(null, True)
assert.deepEqual(values.map(isNull),
[ true ].
concat(falses.slice(1)),
"only null gets methods defined for null")
}
exports["test dispatch undefined"] = function(assert) {
var isUndefined = Method("isUndefined")
// Implement default
isUndefined.define(False)
isUndefined.define(undefined, True)
assert.deepEqual(values.map(isUndefined),
[ false, true ].
concat(falses.slice(2)),
"only undefined gets methods defined for undefined")
}
exports["test dispatch object"] = function(assert) {
var isObject = Method("isObject")
// Implement default
isObject.define(False)
isObject.define(Object, True)
assert.deepEqual(values.map(isObject),
[ false, false, false, false, false ].
concat(trues.slice(5, 13)).
concat([false, false, false]),
"all values except primitives inherit Object methods")
}
exports["test dispatch number"] = function(assert) {
var isNumber = Method("isNumber")
isNumber.define(False)
isNumber.define(Number, True)
assert.deepEqual(values.map(isNumber),
falses.slice(0, 2).
concat(true, true, true).
concat(falses.slice(5)),
"all numbers inherit from Number method")
}
exports["test dispatch string"] = function(assert) {
var isString = Method("isString")
isString.define(False)
isString.define(String, True)
assert.deepEqual(values.map(isString),
falses.slice(0, 15).
concat(true),
"all strings inherit from String method")
}
exports["test dispatch function"] = function(assert) {
var isFunction = Method("isFunction")
isFunction.define(False)
isFunction.define(Function, True)
assert.deepEqual(values.map(isFunction),
falses.slice(0, 11).
concat(true, true).
concat(falses.slice(13)),
"all functions inherit from Function method")
}
exports["test dispatch date"] = function(assert) {
var isDate = Method("isDate")
isDate.define(False)
isDate.define(Date, True)
assert.deepEqual(values.map(isDate),
falses.slice(0, 10).
concat(true).
concat(falses.slice(11)),
"all dates inherit from Date method")
}
exports["test dispatch RegExp"] = function(assert) {
var isRegExp = Method("isRegExp")
isRegExp.define(False)
isRegExp.define(RegExp, True)
assert.deepEqual(values.map(isRegExp),
falses.slice(0, 9).
concat(true).
concat(falses.slice(10)),
"all regexps inherit from RegExp method")
}
exports["test redefine for descendant"] = function(assert) {
var isFoo = Method("isFoo")
var ancestor = {}
isFoo.implement(ancestor, function() { return true })
var descendant = Object.create(ancestor)
isFoo.implement(descendant, function() { return false })
assert.ok(isFoo(ancestor), "defined on ancestor")
assert.ok(!isFoo(descendant), "overrided for descendant")
}
exports["test on custom types"] = function(assert) {
function Bar() {}
var isBar = Method("isBar")
isBar.define(function() { return false })
isBar.define(Bar, function() { return true })
assert.ok(!isBar({}), "object is get's default implementation")
assert.ok(isBar(new Bar()), "Foo type objects get own implementation")
var isObject = Method("isObject")
isObject.define(function() { return false })
isObject.define(Object, function() { return true })
assert.ok(isObject(new Bar()), "foo inherits implementation from object")
isObject.define(Bar, function() { return false })
assert.ok(!isObject(new Bar()),
"implementation inherited form object can be overrided")
}
exports["test error types"] = function(assert) {
var isError = Method("isError")
isError.define(function() { return false })
isError.define(Error, function() { return true })
assert.ok(isError(Error("boom")), "error is error")
assert.ok(isError(TypeError("boom")), "type error is an error")
assert.ok(isError(EvalError("boom")), "eval error is an error")
assert.ok(isError(RangeError("boom")), "range error is an error")
assert.ok(isError(ReferenceError("boom")), "reference error is an error")
assert.ok(isError(SyntaxError("boom")), "syntax error is an error")
assert.ok(isError(URIError("boom")), "URI error is an error")
}
exports["test override define polymorphic method"] = function(assert) {
var define = Method.define
var implement = Method.implement
var fn = Method("fn")
var methods = {}
implement(define, fn, function(method, label, implementation) {
methods[label] = implementation
})
function foo() {}
define(fn, "foo-case", foo)
assert.equal(methods["foo-case"], foo, "define set property")
}
exports["test override define via method API"] = function(assert) {
var define = Method.define
var implement = Method.implement
var fn = Method("fn")
var methods = {}
define.implement(fn, function(method, label, implementation) {
methods[label] = implementation
})
function foo() {}
define(fn, "foo-case", foo)
assert.equal(methods["foo-case"], foo, "define set property")
}
require("test").run(exports)

183
addon-sdk/source/lib/sdk/addon/bootstrap.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,183 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cu } = require("chrome");
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
const { readURI } = require("sdk/net/url");
const { mount, unmount } = require("sdk/uri/resource");
const { setTimeout } = require("sdk/timers");
const { Loader, Require, Module, main, unload } = require("toolkit/loader");
const prefs = require("sdk/preferences/service");
// load below now, so that it can be used by sdk/addon/runner
// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});
const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
"install", "uninstall", "upgrade", "downgrade" ];
const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
// Takes add-on ID and normalizes it to a domain name so that add-on
// can be mapped to resource://domain/
const readDomain = id =>
// If only `@` character is the first one, than just substract it,
// otherwise fallback to legacy normalization code path. Note: `.`
// is valid character for resource substitutaiton & we intend to
// make add-on URIs intuitive, so it's best to just stick to an
// add-on author typed input.
id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
id.toLowerCase().
replace(/@/g, "-at-").
replace(/\./g, "-dot-").
replace(UUID_PATTERN, "$1");
const readPaths = id => {
const base = `extensions.modules.${id}.path.`;
const domain = readDomain(id);
return prefs.keys(base).reduce((paths, key) => {
const value = prefs.get(key);
const name = key.replace(base, "");
const path = name.split(".").join("/");
const prefix = path.length ? `${path}/` : path;
const uri = value.endsWith("/") ? value : `${value}/`;
const root = `extensions.modules.${domain}.commonjs.path.${name}`;
mount(root, uri);
paths[prefix] = `resource://${root}/`;
return paths;
}, {});
};
const Bootstrap = function(mountURI) {
this.mountURI = mountURI;
this.install = this.install.bind(this);
this.uninstall = this.uninstall.bind(this);
this.startup = this.startup.bind(this);
this.shutdown = this.shutdown.bind(this);
};
Bootstrap.prototype = {
constructor: Bootstrap,
mount(domain, rootURI) {
mount(domain, rootURI);
this.domain = domain;
},
unmount() {
if (this.domain) {
unmount(this.domain);
this.domain = null;
}
},
install(addon, reason) {
return new Promise(resolve => resolve());
},
uninstall(addon, reason) {
return new Promise(resolve => {
const {id} = addon;
prefs.reset(`extensions.${id}.sdk.domain`);
prefs.reset(`extensions.${id}.sdk.version`);
prefs.reset(`extensions.${id}.sdk.rootURI`);
prefs.reset(`extensions.${id}.sdk.baseURI`);
prefs.reset(`extensions.${id}.sdk.load.reason`);
resolve();
});
},
startup(addon, reasonCode) {
const { id, version, resourceURI: { spec: addonURI } } = addon;
const rootURI = this.mountURI || addonURI;
const reason = REASON[reasonCode];
const self = this;
return spawn(function*() {
const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
const domain = readDomain(id);
const baseURI = `resource://${domain}/`;
this.mount(domain, rootURI);
prefs.set(`extensions.${id}.sdk.domain`, domain);
prefs.set(`extensions.${id}.sdk.version`, version);
prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
prefs.set(`extensions.${id}.sdk.load.reason`, reason);
const command = prefs.get(`extensions.${id}.sdk.load.command`);
const loader = Loader({
id,
isNative: true,
checkCompatibility: true,
prefixURI: baseURI,
rootURI: baseURI,
name: metadata.name,
paths: Object.assign({
"": "resource://gre/modules/commonjs/",
"devtools/": "resource://devtools/",
"./": baseURI
}, readPaths(id)),
manifest: metadata,
metadata: metadata,
modules: {
"@test/options": {},
},
noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
});
self.loader = loader;
const module = Module("package.json", `${baseURI}package.json`);
const require = Require(loader, module);
const main = command === "test" ? "sdk/test/runner" : null;
const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
// Init the 'sdk/webextension' module from the bootstrap addon parameter.
if (addon.webExtension)
require("sdk/webextension").initFromBootstrapAddonParam(addon);
const { startup } = require("sdk/addon/runner");
startup(reason, {loader, main, prefsURI});
}.bind(this)).catch(error => {
console.error(`Failed to start ${id} addon`, error);
throw error;
});
},
shutdown(addon, code) {
this.unmount();
return this.unload(REASON[code]);
},
unload(reason) {
return new Promise(resolve => {
const { loader } = this;
if (loader) {
this.loader = null;
unload(loader, reason);
setTimeout(() => {
for (let uri of Object.keys(loader.sandboxes)) {
let sandbox = loader.sandboxes[uri];
if (Cu.getClassName(sandbox, true) == "Sandbox")
Cu.nukeSandbox(sandbox);
delete loader.sandboxes[uri];
delete loader.modules[uri];
}
try {
Cu.nukeSandbox(loader.sharedGlobalSandbox);
} catch (e) {
Cu.reportError(e);
}
resolve();
}, 1000);
}
else {
resolve();
}
});
}
};
exports.Bootstrap = Bootstrap;

Просмотреть файл

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
'stability': 'experimental'
};
var { request: hostReq, response: hostRes } = require('./host');
var { defer: async } = require('../lang/functional');
var { defer } = require('../core/promise');
var { emit: emitSync, on, off } = require('../event/core');
var { uuid } = require('../util/uuid');
var emit = async(emitSync);
// Map of IDs to deferreds
var requests = new Map();
// May not be necessary to wrap this in `async`
// once promises are async via bug 881047
var receive = async(function ({data, id, error}) {
let request = requests.get(id);
if (request) {
if (error) request.reject(error);
else request.resolve(clone(data));
requests.delete(id);
}
});
on(hostRes, 'data', receive);
/*
* Send is a helper to be used in client APIs to send
* a request to host
*/
function send (eventName, data) {
let id = uuid();
let deferred = defer();
requests.set(id, deferred);
emit(hostReq, 'data', {
id: id,
data: clone(data),
event: eventName
});
return deferred.promise;
}
exports.send = send;
/*
* Implement internal structured cloning algorithm in the future?
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#internal-structured-cloning-algorithm
*/
function clone (obj) {
return JSON.parse(JSON.stringify(obj || {}));
}

Просмотреть файл

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
exports.request = {};
exports.response = {};

Просмотреть файл

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
const { defer } = require("../core/promise");
function getAddonByID(id) {
let { promise, resolve } = defer();
AddonManager.getAddonByID(id, resolve);
return promise;
}
exports.getAddonByID = getAddonByID;

Просмотреть файл

@ -0,0 +1,176 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci, Cu } = require('chrome');
const { rootURI, metadata, isNative } = require('@loader/options');
const { id, loadReason } = require('../self');
const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
const { exit, env, staticArgs } = require('../system');
const { when: unload } = require('../system/unload');
const globals = require('../system/globals');
const { get } = require('../preferences/service');
const { preferences } = metadata;
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "DevToolsShim", function () {
return Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {}).
DevToolsShim;
});
// Initializes default preferences
function setDefaultPrefs(prefsURI) {
const prefs = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService).
QueryInterface(Ci.nsIPrefBranch);
const branch = prefs.getDefaultBranch('');
const sandbox = Sandbox({
name: prefsURI,
prototype: {
pref: function(key, val) {
switch (typeof val) {
case 'boolean':
branch.setBoolPref(key, val);
break;
case 'number':
if (val % 1 == 0) // number must be a integer, otherwise ignore it
branch.setIntPref(key, val);
break;
case 'string':
branch.setCharPref(key, val);
break;
}
}
}
});
// load preferences.
evaluate(sandbox, prefsURI);
}
function definePseudo(loader, id, exports) {
let uri = resolveURI(id, loader.mapping);
loader.modules[uri] = { exports: exports };
}
function startup(reason, options) {
return Startup.onceInitialized.then(() => {
// Inject globals ASAP in order to have console API working ASAP
Object.defineProperties(options.loader.globals, descriptor(globals));
// NOTE: Module is intentionally required only now because it relies
// on existence of hidden window, which does not exists until startup.
let { ready } = require('../addon/window');
// Load localization manifest and .properties files.
// Run the addon even in case of error (best effort approach)
require('../l10n/loader').
load(rootURI).
catch(function failure(error) {
if (!isNative)
console.info("Error while loading localization: " + error.message);
}).
then(function onLocalizationReady(data) {
// Exports data to a pseudo module so that api-utils/l10n/core
// can get access to it
definePseudo(options.loader, '@l10n/data', data ? data : null);
return ready;
}).then(function() {
run(options);
}).catch(console.exception);
return void 0; // otherwise we raise a warning, see bug 910304
});
}
function run(options) {
try {
// Try initializing HTML localization before running main module. Just print
// an exception in case of error, instead of preventing addon to be run.
try {
// Do not enable HTML localization while running test as it is hard to
// disable. Because unit tests are evaluated in a another Loader who
// doesn't have access to this current loader.
if (options.main !== 'sdk/test/runner') {
require('../l10n/html').enable();
}
}
catch(error) {
console.exception(error);
}
// native-options does stuff directly with preferences key from package.json
if (preferences && preferences.length > 0) {
try {
require('../preferences/native-options').
enable({ preferences: preferences, id: id }).
catch(console.exception);
}
catch (error) {
console.exception(error);
}
}
else {
// keeping support for addons packaged with older SDK versions,
// when cfx didn't include the 'preferences' key in @loader/options
// Initialize inline options localization, without preventing addon to be
// run in case of error
try {
require('../l10n/prefs').enable();
}
catch(error) {
console.exception(error);
}
// TODO: When bug 564675 is implemented this will no longer be needed
// Always set the default prefs, because they disappear on restart
if (options.prefsURI) {
// Only set if `prefsURI` specified
try {
setDefaultPrefs(options.prefsURI);
}
catch (err) {
// cfx bootstrap always passes prefsURI, even in addons without prefs
}
}
}
// this is where the addon's main.js finally run.
let program = main(options.loader, options.main);
if (typeof(program.onUnload) === 'function')
unload(program.onUnload);
if (typeof(program.main) === 'function') {
program.main({
loadReason: loadReason,
staticArgs: staticArgs
}, {
print: function print(_) { dump(_ + '\n') },
quit: exit
});
}
if (get("extensions." + id + ".sdk.debug.show", false)) {
DevToolsShim.initBrowserToolboxProcessForAddon(id);
}
} catch (error) {
console.exception(error);
throw error;
}
}
exports.startup = startup;
// If add-on is lunched via `cfx run` we need to use `system.exit` to let
// cfx know we're done (`cfx test` will take care of exit so we don't do
// anything here).
if (env.CFX_COMMAND === 'run') {
unload(function(reason) {
if (reason === 'shutdown')
exit(0);
});
}

Просмотреть файл

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { events } = require("../window/events");
const { filter } = require("../event/utils");
const { isBrowser } = require("../window/utils");
// TODO: `isBrowser` detects weather window is a browser by checking
// `windowtype` attribute, which means that all 'open' events will be
// filtered out since document is not loaded yet. Maybe we can find a better
// implementation for `isBrowser`. Either way it's not really needed yet
// neither window tracker provides this event.
exports.events = filter(events, ({target}) => isBrowser(target));

Просмотреть файл

@ -16,6 +16,7 @@ module.metadata = {
const { Cc, Ci } = require("chrome");
const { DataURL } = require("./url");
const apiUtils = require("./deprecated/api-utils");
/*
While these data flavors resemble Internet media types, they do
no directly map to them.
@ -89,6 +90,15 @@ exports.set = function(aData, aDataType) {
}
}
options = apiUtils.validateOptions(options, {
data: {
is: ["string"]
},
datatype: {
is: ["string"]
}
});
let flavor = fromJetpackFlavor(options.datatype);
if (!flavor)
@ -198,6 +208,12 @@ exports.get = function(aDataType) {
options.datatype = "text";
}
options = apiUtils.validateOptions(options, {
datatype: {
is: ["string"]
}
});
var xferable = Cc["@mozilla.org/widget/transferable;1"].
createInstance(Ci.nsITransferable);
if (!xferable)

Просмотреть файл

@ -0,0 +1,305 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Object.freeze({
// TODO: Bug 727854 Use same implementation than common JS modules,
// i.e. EventEmitter module
/**
* Create an EventEmitter instance.
*/
createEventEmitter: function createEventEmitter(emit) {
let listeners = Object.create(null);
let eventEmitter = Object.freeze({
emit: emit,
on: function on(name, callback) {
if (typeof callback !== "function")
return this;
if (!(name in listeners))
listeners[name] = [];
listeners[name].push(callback);
return this;
},
once: function once(name, callback) {
eventEmitter.on(name, function onceCallback() {
eventEmitter.removeListener(name, onceCallback);
callback.apply(callback, arguments);
});
},
removeListener: function removeListener(name, callback) {
if (!(name in listeners))
return;
let index = listeners[name].indexOf(callback);
if (index == -1)
return;
listeners[name].splice(index, 1);
}
});
function onEvent(name) {
if (!(name in listeners))
return [];
let args = Array.slice(arguments, 1);
let results = [];
for (let callback of listeners[name]) {
results.push(callback.apply(null, args));
}
return results;
}
return {
eventEmitter: eventEmitter,
emit: onEvent
};
},
/**
* Create an EventEmitter instance to communicate with chrome module
* by passing only strings between compartments.
* This function expects `emitToChrome` function, that allows to send
* events to the chrome module. It returns the EventEmitter as `pipe`
* attribute, and, `onChromeEvent` a function that allows chrome module
* to send event into the EventEmitter.
*
* pipe.emit --> emitToChrome
* onChromeEvent --> callback registered through pipe.on
*/
createPipe: function createPipe(emitToChrome) {
let ContentWorker = this;
function onEvent(type, ...args) {
// JSON.stringify is buggy with cross-sandbox values,
// it may return "{}" on functions. Use a replacer to match them correctly.
let replacer = (k, v) =>
typeof(v) === "function"
? (type === "console" ? Function.toString.call(v) : void(0))
: v;
let str = JSON.stringify([type, ...args], replacer);
emitToChrome(str);
}
let { eventEmitter, emit } =
ContentWorker.createEventEmitter(onEvent);
return {
pipe: eventEmitter,
onChromeEvent: function onChromeEvent(array) {
// We either receive a stringified array, or a real array.
// We still allow to pass an array of objects, in WorkerSandbox.emitSync
// in order to allow sending DOM node reference between content script
// and modules (only used for context-menu API)
let args = typeof array == "string" ? JSON.parse(array) : array;
return emit.apply(null, args);
}
};
},
injectConsole: function injectConsole(exports, pipe) {
exports.console = Object.freeze({
log: pipe.emit.bind(null, "console", "log"),
info: pipe.emit.bind(null, "console", "info"),
warn: pipe.emit.bind(null, "console", "warn"),
error: pipe.emit.bind(null, "console", "error"),
debug: pipe.emit.bind(null, "console", "debug"),
exception: pipe.emit.bind(null, "console", "exception"),
trace: pipe.emit.bind(null, "console", "trace"),
time: pipe.emit.bind(null, "console", "time"),
timeEnd: pipe.emit.bind(null, "console", "timeEnd")
});
},
injectTimers: function injectTimers(exports, chromeAPI, pipe, console) {
// wrapped functions from `'timer'` module.
// Wrapper adds `try catch` blocks to the callbacks in order to
// emit `error` event if exception is thrown in
// the Worker global scope.
// @see http://www.w3.org/TR/workers/#workerutils
// List of all living timeouts/intervals
let _timers = Object.create(null);
// Keep a reference to original timeout functions
let {
setTimeout: chromeSetTimeout,
setInterval: chromeSetInterval,
clearTimeout: chromeClearTimeout,
clearInterval: chromeClearInterval
} = chromeAPI.timers;
function registerTimer(timer) {
let registerMethod = null;
if (timer.kind == "timeout")
registerMethod = chromeSetTimeout;
else if (timer.kind == "interval")
registerMethod = chromeSetInterval;
else
throw new Error("Unknown timer kind: " + timer.kind);
if (typeof timer.fun == 'string') {
let code = timer.fun;
timer.fun = () => chromeAPI.sandbox.evaluate(exports, code);
} else if (typeof timer.fun != 'function') {
throw new Error('Unsupported callback type' + typeof timer.fun);
}
let id = registerMethod(onFire, timer.delay);
function onFire() {
try {
if (timer.kind == "timeout")
delete _timers[id];
timer.fun.apply(null, timer.args);
} catch(e) {
console.exception(e);
let wrapper = {
instanceOfError: instanceOf(e, Error),
value: e,
};
if (wrapper.instanceOfError) {
wrapper.value = {
message: e.message,
fileName: e.fileName,
lineNumber: e.lineNumber,
stack: e.stack,
name: e.name,
};
}
pipe.emit('error', wrapper);
}
}
_timers[id] = timer;
return id;
}
// copied from sdk/lang/type.js since modules are not available here
function instanceOf(value, Type) {
var isConstructorNameSame;
var isConstructorSourceSame;
// If `instanceof` returned `true` we know result right away.
var isInstanceOf = value instanceof Type;
// If `instanceof` returned `false` we do ducktype check since `Type` may be
// from a different sandbox. If a constructor of the `value` or a constructor
// of the value's prototype has same name and source we assume that it's an
// instance of the Type.
if (!isInstanceOf && value) {
isConstructorNameSame = value.constructor.name === Type.name;
isConstructorSourceSame = String(value.constructor) == String(Type);
isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
instanceOf(Object.getPrototypeOf(value), Type);
}
return isInstanceOf;
}
function unregisterTimer(id) {
if (!(id in _timers))
return;
let { kind } = _timers[id];
delete _timers[id];
if (kind == "timeout")
chromeClearTimeout(id);
else if (kind == "interval")
chromeClearInterval(id);
else
throw new Error("Unknown timer kind: " + kind);
}
function disableAllTimers() {
Object.keys(_timers).forEach(unregisterTimer);
}
exports.setTimeout = function ContentScriptSetTimeout(callback, delay) {
return registerTimer({
kind: "timeout",
fun: callback,
delay: delay,
args: Array.slice(arguments, 2)
});
};
exports.clearTimeout = function ContentScriptClearTimeout(id) {
unregisterTimer(id);
};
exports.setInterval = function ContentScriptSetInterval(callback, delay) {
return registerTimer({
kind: "interval",
fun: callback,
delay: delay,
args: Array.slice(arguments, 2)
});
};
exports.clearInterval = function ContentScriptClearInterval(id) {
unregisterTimer(id);
};
// On page-hide, save a list of all existing timers before disabling them,
// in order to be able to restore them on page-show.
// These events are fired when the page goes in/out of bfcache.
// https://developer.mozilla.org/En/Working_with_BFCache
let frozenTimers = [];
pipe.on("pageshow", function onPageShow() {
frozenTimers.forEach(registerTimer);
});
pipe.on("pagehide", function onPageHide() {
frozenTimers = [];
for (let id in _timers)
frozenTimers.push(_timers[id]);
disableAllTimers();
// Some other pagehide listeners may register some timers that won't be
// frozen as this particular pagehide listener is called first.
// So freeze these timers on next cycle.
chromeSetTimeout(function () {
for (let id in _timers)
frozenTimers.push(_timers[id]);
disableAllTimers();
}, 0);
});
// Unregister all timers when the page is destroyed
// (i.e. when it is removed from bfcache)
pipe.on("detach", function clearTimeouts() {
disableAllTimers();
_timers = {};
frozenTimers = [];
});
},
injectMessageAPI: function injectMessageAPI(exports, pipe, console) {
let ContentWorker = this;
let { eventEmitter: port, emit : portEmit } =
ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
pipe.on("event", portEmit);
let self = {
port: port,
postMessage: pipe.emit.bind(null, "message"),
on: pipe.on.bind(null),
once: pipe.once.bind(null),
removeListener: pipe.removeListener.bind(null),
};
Object.defineProperty(exports, "self", {
value: self
});
},
injectOptions: function (exports, options) {
Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
},
inject: function (exports, chromeAPI, emitToChrome, options) {
let ContentWorker = this;
let { pipe, onChromeEvent } =
ContentWorker.createPipe(emitToChrome);
ContentWorker.injectConsole(exports, pipe);
ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
ContentWorker.injectMessageAPI(exports, pipe, exports.console);
if ( options !== undefined ) {
ContentWorker.injectOptions(exports, options);
}
Object.freeze( exports.self );
return onChromeEvent;
}
});

Просмотреть файл

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "deprecated"
};
const { deprecateUsage } = require('../util/deprecate');
Object.defineProperty(exports, "Worker", {
get: function() {
deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.');
return require('./worker').Worker;
}
});

Просмотреть файл

@ -0,0 +1,407 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Class } = require("../core/heritage");
const self = require("../self");
const { WorkerChild } = require("./worker-child");
const { getInnerId } = require("../window/utils");
const { Ci } = require("chrome");
const { Services } = require("resource://gre/modules/Services.jsm");
const system = require('../system/events');
const { process } = require('../remote/child');
// These functions are roughly copied from sdk/selection which doesn't work
// in the content process
function getElementWithSelection(window) {
let element = Services.focus.getFocusedElementForWindow(window, false, {});
if (!element)
return null;
try {
// Accessing selectionStart and selectionEnd on e.g. a button
// results in an exception thrown as per the HTML5 spec. See
// http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
let { value, selectionStart, selectionEnd } = element;
let hasSelection = typeof value === "string" &&
!isNaN(selectionStart) &&
!isNaN(selectionEnd) &&
selectionStart !== selectionEnd;
return hasSelection ? element : null;
}
catch (err) {
console.exception(err);
return null;
}
}
function safeGetRange(selection, rangeNumber) {
try {
let { rangeCount } = selection;
let range = null;
for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) {
range = selection.getRangeAt(rangeNumber);
if (range && range.toString())
break;
range = null;
}
return range;
}
catch (e) {
return null;
}
}
function getSelection(window) {
let selection = window.getSelection();
let range = safeGetRange(selection);
if (range)
return range.toString();
let node = getElementWithSelection(window);
if (!node)
return null;
return node.value.substring(node.selectionStart, node.selectionEnd);
}
//These are used by PageContext.isCurrent below. If the popupNode or any of
//its ancestors is one of these, Firefox uses a tailored context menu, and so
//the page context doesn't apply.
const NON_PAGE_CONTEXT_ELTS = [
Ci.nsIDOMHTMLAnchorElement,
Ci.nsIDOMHTMLAreaElement,
Ci.nsIDOMHTMLButtonElement,
Ci.nsIDOMHTMLCanvasElement,
Ci.nsIDOMHTMLEmbedElement,
Ci.nsIDOMHTMLImageElement,
Ci.nsIDOMHTMLInputElement,
Ci.nsIDOMHTMLMapElement,
Ci.nsIDOMHTMLMediaElement,
Ci.nsIDOMHTMLMenuElement,
Ci.nsIDOMHTMLObjectElement,
Ci.nsIDOMHTMLOptionElement,
Ci.nsIDOMHTMLSelectElement,
Ci.nsIDOMHTMLTextAreaElement,
];
// List all editable types of inputs. Or is it better to have a list
// of non-editable inputs?
var editableInputs = {
email: true,
number: true,
password: true,
search: true,
tel: true,
text: true,
textarea: true,
url: true
};
var CONTEXTS = {};
var Context = Class({
initialize: function(id) {
this.id = id;
},
adjustPopupNode: function adjustPopupNode(popupNode) {
return popupNode;
},
// Gets state to pass through to the parent process for the node the user
// clicked on
getState: function(popupNode) {
return false;
}
});
// Matches when the context-clicked node doesn't have any of
// NON_PAGE_CONTEXT_ELTS in its ancestors
CONTEXTS.PageContext = Class({
extends: Context,
getState: function(popupNode) {
// If there is a selection in the window then this context does not match
if (!popupNode.ownerGlobal.getSelection().isCollapsed)
return false;
// If the clicked node or any of its ancestors is one of the blocked
// NON_PAGE_CONTEXT_ELTS then this context does not match
while (!(popupNode instanceof Ci.nsIDOMDocument)) {
if (NON_PAGE_CONTEXT_ELTS.some(type => popupNode instanceof type))
return false;
popupNode = popupNode.parentNode;
}
return true;
}
});
// Matches when there is an active selection in the window
CONTEXTS.SelectionContext = Class({
extends: Context,
getState: function(popupNode) {
if (!popupNode.ownerGlobal.getSelection().isCollapsed)
return true;
try {
// The node may be a text box which has selectionStart and selectionEnd
// properties. If not this will throw.
let { selectionStart, selectionEnd } = popupNode;
return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
selectionStart !== selectionEnd;
}
catch (e) {
return false;
}
}
});
// Matches when the context-clicked node or any of its ancestors matches the
// selector given
CONTEXTS.SelectorContext = Class({
extends: Context,
initialize: function initialize(id, selector) {
Context.prototype.initialize.call(this, id);
this.selector = selector;
},
adjustPopupNode: function adjustPopupNode(popupNode) {
let selector = this.selector;
while (!(popupNode instanceof Ci.nsIDOMDocument)) {
if (popupNode.matches(selector))
return popupNode;
popupNode = popupNode.parentNode;
}
return null;
},
getState: function(popupNode) {
return !!this.adjustPopupNode(popupNode);
}
});
// Matches when the page url matches any of the patterns given
CONTEXTS.URLContext = Class({
extends: Context,
getState: function(popupNode) {
return popupNode.ownerDocument.URL;
}
});
// Matches when the user-supplied predicate returns true
CONTEXTS.PredicateContext = Class({
extends: Context,
getState: function(node) {
let window = node.ownerGlobal;
let data = {};
data.documentType = node.ownerDocument.contentType;
data.documentURL = node.ownerDocument.location.href;
data.targetName = node.nodeName.toLowerCase();
data.targetID = node.id || null ;
if ((data.targetName === 'input' && editableInputs[node.type]) ||
data.targetName === 'textarea') {
data.isEditable = !node.readOnly && !node.disabled;
}
else {
data.isEditable = node.isContentEditable;
}
data.selectionText = getSelection(window, "TEXT");
data.srcURL = node.src || null;
data.value = node.value || null;
while (!data.linkURL && node) {
data.linkURL = node.href || null;
node = node.parentNode;
}
return data;
},
});
function instantiateContext({ id, type, args }) {
if (!(type in CONTEXTS)) {
console.error("Attempt to use unknown context " + type);
return;
}
return new CONTEXTS[type](id, ...args);
}
var ContextWorker = Class({
implements: [ WorkerChild ],
// Calls the context workers context listeners and returns the first result
// that is either a string or a value that evaluates to true. If all of the
// listeners returned false then returns false. If there are no listeners,
// returns true (show the menu item by default).
getMatchedContext: function getCurrentContexts(popupNode) {
let results = this.sandbox.emitSync("context", popupNode);
if (!results.length)
return true;
return results.reduce((val, result) => val || result);
},
// Emits a click event in the worker's port. popupNode is the node that was
// context-clicked, and clickedItemData is the data of the item that was
// clicked.
fireClick: function fireClick(popupNode, clickedItemData) {
this.sandbox.emitSync("click", popupNode, clickedItemData);
}
});
// Gets the item's content script worker for a window, creating one if necessary
// Once created it will be automatically destroyed when the window unloads.
// If there is not content scripts for the item then null will be returned.
function getItemWorkerForWindow(item, window) {
if (!item.contentScript && !item.contentScriptFile)
return null;
let id = getInnerId(window);
let worker = item.workerMap.get(id);
if (worker)
return worker;
worker = ContextWorker({
id: item.id,
window,
manager: item.manager,
contentScript: item.contentScript,
contentScriptFile: item.contentScriptFile,
onDetach: function() {
item.workerMap.delete(id);
}
});
item.workerMap.set(id, worker);
return worker;
}
// A very simple remote proxy for every item. It's job is to provide data for
// the main process to use to determine visibility state and to call into
// content scripts when clicked.
var RemoteItem = Class({
initialize: function(options, manager) {
this.id = options.id;
this.contexts = options.contexts.map(instantiateContext);
this.contentScript = options.contentScript;
this.contentScriptFile = options.contentScriptFile;
this.manager = manager;
this.workerMap = new Map();
keepAlive.set(this.id, this);
},
destroy: function() {
for (let worker of this.workerMap.values()) {
worker.destroy();
}
keepAlive.delete(this.id);
},
activate: function(popupNode, data) {
let worker = getItemWorkerForWindow(this, popupNode.ownerGlobal);
if (!worker)
return;
for (let context of this.contexts)
popupNode = context.adjustPopupNode(popupNode);
worker.fireClick(popupNode, data);
},
// Fills addonInfo with state data to send through to the main process
getContextState: function(popupNode, addonInfo) {
if (!(self.id in addonInfo)) {
addonInfo[self.id] = {
processID: process.id,
items: {}
};
}
let worker = getItemWorkerForWindow(this, popupNode.ownerGlobal);
let contextStates = {};
for (let context of this.contexts)
contextStates[context.id] = context.getState(popupNode);
addonInfo[self.id].items[this.id] = {
// It isn't ideal to create a PageContext for every item but there isn't
// a good shared place to do it.
pageContext: (new CONTEXTS.PageContext()).getState(popupNode),
contextStates,
hasWorker: !!worker,
workerContext: worker ? worker.getMatchedContext(popupNode) : true
}
}
});
exports.RemoteItem = RemoteItem;
// Holds remote items for this frame.
var keepAlive = new Map();
// Called to create remote proxies for items. If they already exist we destroy
// and recreate. This can happen if the item changes in some way or in odd
// timing cases where the frame script is create around the same time as the
// item is created in the main process
process.port.on('sdk/contextmenu/createitems', (process, items) => {
for (let itemoptions of items) {
let oldItem = keepAlive.get(itemoptions.id);
if (oldItem) {
oldItem.destroy();
}
let item = new RemoteItem(itemoptions, this);
}
});
process.port.on('sdk/contextmenu/destroyitems', (process, items) => {
for (let id of items) {
let item = keepAlive.get(id);
item.destroy();
}
});
var lastPopupNode = null;
system.on('content-contextmenu', ({ subject }) => {
let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject;
lastPopupNode = popupNode;
for (let item of keepAlive.values()) {
item.getContextState(popupNode, addonInfo);
}
}, true);
process.port.on('sdk/contextmenu/activateitems', (process, items, data) => {
for (let id of items) {
let item = keepAlive.get(id);
if (!item)
continue;
item.activate(lastPopupNode, data);
}
});

Просмотреть файл

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Ci } = require("chrome");
lazyRequire(this, "../event/dom", "open");
const { observe } = require("../event/chrome");
const { filter, merge, map, expand } = require("../event/utils");
const { windows } = require("../window/utils");
const { events: windowEvents } = require("sdk/window/events");
// Note: Please note that even though pagehide event is included
// it's not observable reliably since it's not always triggered
// when closing tabs. Implementation can be imrpoved once that
// event will be necessary.
var TYPES = ["DOMContentLoaded", "load", "pageshow", "pagehide"];
var insert = observe("document-element-inserted");
var windowCreate = merge([
observe("content-document-global-created"),
observe("chrome-document-global-created")
]);
var create = map(windowCreate, function({target, data, type}) {
return { target: target.document, type: type, data: data }
});
function streamEventsFrom({document}) {
// Map supported event types to a streams of those events on the given
// `window` for the inserted document and than merge these streams into
// single form stream off all window state change events.
let stateChanges = TYPES.map(function(type) {
return open(document, type, { capture: true });
});
// Since load events on document occur for every loded resource
return filter(merge(stateChanges), function({target}) {
return target instanceof Ci.nsIDOMDocument
})
}
exports.streamEventsFrom = streamEventsFrom;
var opened = windows(null, { includePrivate: true });
var state = merge(opened.map(streamEventsFrom));
var futureReady = filter(windowEvents, ({type}) =>
type === "DOMContentLoaded");
var futureWindows = map(futureReady, ({target}) => target);
var futureState = expand(futureWindows, streamEventsFrom);
exports.events = merge([insert, create, state, futureState]);

Просмотреть файл

@ -0,0 +1,132 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Ci, Cc, Cu } = require("chrome");
lazyRequireModule(this, "../l10n/core", "core");
lazyRequire(this, "../stylesheet/utils", "loadSheet", "removeSheet");
const { process, frames } = require("../remote/child");
var observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
const assetsURI = require('../self').data.url();
const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}";
function translateElementAttributes(element) {
// Translateable attributes
const attrList = ['title', 'accesskey', 'alt', 'label', 'placeholder'];
const ariaAttrMap = {
'ariaLabel': 'aria-label',
'ariaValueText': 'aria-valuetext',
'ariaMozHint': 'aria-moz-hint'
};
const attrSeparator = '.';
// Try to translate each of the attributes
for (let attribute of attrList) {
const data = core.get(element.dataset.l10nId + attrSeparator + attribute);
if (data)
element.setAttribute(attribute, data);
}
// Look for the aria attribute translations that match fxOS's aliases
for (let attrAlias in ariaAttrMap) {
const data = core.get(element.dataset.l10nId + attrSeparator + attrAlias);
if (data)
element.setAttribute(ariaAttrMap[attrAlias], data);
}
}
// Taken from Gaia:
// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470
function translateElement(element) {
element = element || document;
// check all translatable children (= w/ a `data-l10n-id' attribute)
var children = element.querySelectorAll('*[data-l10n-id]');
var elementCount = children.length;
for (var i = 0; i < elementCount; i++) {
var child = children[i];
// translate the child
var key = child.dataset.l10nId;
var data = core.get(key);
if (data)
child.textContent = data;
translateElementAttributes(child);
}
}
exports.translateElement = translateElement;
function onDocumentReady2Translate(event) {
let document = event.target;
document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate);
translateElement(document);
try {
// Finally display document when we finished replacing all text content
if (document.defaultView)
removeSheet(document.defaultView, hideSheetUri, 'user');
}
catch(e) {
console.exception(e);
}
}
function onContentWindow(document) {
// Accept only HTML documents
if (!(document instanceof Ci.nsIDOMHTMLDocument))
return;
// Bug 769483: data:URI documents instanciated with nsIDOMParser
// have a null `location` attribute at this time
if (!document.location)
return;
// Accept only document from this addon
if (document.location.href.indexOf(assetsURI) !== 0)
return;
try {
// First hide content of the document in order to have content blinking
// between untranslated and translated states
loadSheet(document.defaultView, hideSheetUri, 'user');
}
catch(e) {
console.exception(e);
}
// Wait for DOM tree to be built before applying localization
document.addEventListener("DOMContentLoaded", onDocumentReady2Translate);
}
// Listen to creation of content documents in order to translate them as soon
// as possible in their loading process
const ON_CONTENT = "document-element-inserted";
let enabled = false;
function enable() {
if (enabled)
return;
addObserver(onContentWindow, ON_CONTENT, false);
enabled = true;
}
process.port.on("sdk/l10n/html/enable", enable);
function disable() {
if (!enabled)
return;
removeObserver(onContentWindow, ON_CONTENT);
enabled = false;
}
process.port.on("sdk/l10n/html/disable", disable);

Просмотреть файл

@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { isValidURI, isLocalURL, URL } = require('../url');
const { contract } = require('../util/contract');
const { isString, isNil, instanceOf, isJSONable } = require('../lang/type');
const { validateOptions,
string, array, object, either, required } = require('../deprecated/api-utils');
const isValidScriptFile = (value) =>
(isString(value) || instanceOf(value, URL)) && isLocalURL(value);
// map of property validations
const valid = {
contentURL: {
is: either(string, object),
ok: url => isNil(url) || isLocalURL(url) || isValidURI(url),
msg: 'The `contentURL` option must be a valid URL.'
},
contentScriptFile: {
is: either(string, object, array),
ok: value => isNil(value) || [].concat(value).every(isValidScriptFile),
msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
},
contentScript: {
is: either(string, array),
ok: value => isNil(value) || [].concat(value).every(isString),
msg: 'The `contentScript` option must be a string or an array of strings.'
},
contentScriptWhen: {
is: required(string),
map: value => value || 'end',
ok: value => ~['start', 'ready', 'end'].indexOf(value),
msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
},
contentScriptOptions: {
ok: value => isNil(value) || isJSONable(value),
msg: 'The contentScriptOptions should be a jsonable value.'
}
};
exports.validationAttributes = valid;
/**
* Shortcut function to validate property with validation.
* @param {Object|Number|String} suspect
* value to validate
* @param {Object} validation
* validation rule passed to `api-utils`
*/
function validate(suspect, validation) {
return validateOptions(
{ $: suspect },
{ $: validation }
).$;
}
function Allow(script) {
return {
get script() {
return script;
},
set script(value) {
script = !!value;
}
};
}
exports.contract = contract(valid);

Просмотреть файл

@ -0,0 +1,230 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
lazyRequire(this, '../content/utils', 'getAttachEventType');
const { Class } = require('../core/heritage');
const { Disposable } = require('../core/disposable');
lazyRequire(this, './worker-child', 'WorkerChild');
const { EventTarget } = require('../event/target');
const { on, emit, once, setListeners } = require('../event/core');
lazyRequire(this, '../dom/events',{'on': 'domOn', 'removeListener': 'domOff'});
lazyRequire(this, '../util/object', "merge");
lazyRequire(this, '../window/utils', "getFrames");
lazyRequire(this, '../private-browsing/utils', "ignoreWindow");
lazyRequire(this, '../stylesheet/style', 'Style');
lazyRequire(this, '../content/mod', 'attach', 'detach');
lazyRequire(this, '../util/rules', 'Rules');
lazyRequire(this, '../util/uuid', 'uuid');
const { frames, process } = require('../remote/child');
const pagemods = new Map();
const styles = new WeakMap();
var styleFor = (mod) => styles.get(mod);
// Helper functions
var modMatchesURI = (mod, uri) => mod.include.matchesAny(uri) && !mod.exclude.matchesAny(uri);
/**
* PageMod constructor (exported below).
* @constructor
*/
const ChildPageMod = Class({
implements: [
EventTarget,
Disposable,
],
setup: function PageMod(model) {
merge(this, model);
// Set listeners on {PageMod} itself, not the underlying worker,
// like `onMessage`, as it'll get piped.
setListeners(this, model);
function* deserializeRules(rules) {
for (let rule of rules) {
yield rule.type == "string" ? rule.value
: new RegExp(rule.pattern, rule.flags);
}
}
let include = [...deserializeRules(this.include)];
this.include = Rules();
this.include.add.apply(this.include, include);
let exclude = [...deserializeRules(this.exclude)];
this.exclude = Rules();
this.exclude.add.apply(this.exclude, exclude);
if (this.contentStyle || this.contentStyleFile) {
styles.set(this, Style({
uri: this.contentStyleFile,
source: this.contentStyle
}));
}
pagemods.set(this.id, this);
this.seenDocuments = new WeakMap();
// `applyOnExistingDocuments` has to be called after `pagemods.add()`
// otherwise its calls to `onContent` method won't do anything.
if (this.attachTo.includes('existing'))
applyOnExistingDocuments(this);
},
dispose: function() {
let style = styleFor(this);
if (style)
detach(style);
for (let i in this.include)
this.include.remove(this.include[i]);
pagemods.delete(this.id);
}
});
function onContentWindow({ target: document }) {
// Return if we have no pagemods
if (pagemods.size === 0)
return;
let window = document.defaultView;
// XML documents don't have windows, and we don't yet support them.
if (!window)
return;
// Frame event listeners are bound to the frame the event came from by default
let frame = this;
// We apply only on documents in tabs of Firefox
if (!frame.isTab)
return;
// When the tab is private, only addons with 'private-browsing' flag in
// their package.json can apply content script to private documents
if (ignoreWindow(window))
return;
for (let pagemod of pagemods.values()) {
if (modMatchesURI(pagemod, window.location.href))
onContent(pagemod, window);
}
}
frames.addEventListener("DOMDocElementInserted", onContentWindow, true);
function applyOnExistingDocuments (mod) {
for (let frame of frames) {
// Fake a newly created document
let window = frame.content;
// on startup with e10s, contentWindow might not exist yet,
// in which case we will get notified by "document-element-inserted".
if (!window || !window.frames)
return;
let uri = window.location.href;
if (mod.attachTo.includes("top") && modMatchesURI(mod, uri))
onContent(mod, window);
if (mod.attachTo.includes("frame"))
getFrames(window).
filter(iframe => modMatchesURI(mod, iframe.location.href)).
forEach(frame => onContent(mod, frame));
}
}
function createWorker(mod, window) {
let workerId = String(uuid());
// Instruct the parent to connect to this worker. Do this first so the parent
// side is connected before the worker attempts to send any messages there
let frame = frames.getFrameForWindow(window.top);
frame.port.emit('sdk/page-mod/worker-create', mod.id, {
id: workerId,
url: window.location.href
});
// Create a child worker and notify the parent
let worker = WorkerChild({
id: workerId,
window: window,
contentScript: mod.contentScript,
contentScriptFile: mod.contentScriptFile,
contentScriptOptions: mod.contentScriptOptions
});
once(worker, 'detach', () => worker.destroy());
}
function onContent (mod, window) {
let isTopDocument = window.top === window;
// Is a top level document and `top` is not set, ignore
if (isTopDocument && !mod.attachTo.includes("top"))
return;
// Is a frame document and `frame` is not set, ignore
if (!isTopDocument && !mod.attachTo.includes("frame"))
return;
// ensure we attach only once per document
let seen = mod.seenDocuments;
if (seen.has(window.document))
return;
seen.set(window.document, true);
let style = styleFor(mod);
if (style)
attach(style, window);
// Immediately evaluate content script if the document state is already
// matching contentScriptWhen expectations
if (isMatchingAttachState(mod, window)) {
createWorker(mod, window);
return;
}
let eventName = getAttachEventType(mod) || 'load';
domOn(window, eventName, function onReady (e) {
if (e.target.defaultView !== window)
return;
domOff(window, eventName, onReady, true);
createWorker(mod, window);
// Attaching is asynchronous so if the document is already loaded we will
// miss the pageshow event so send a synthetic one.
if (window.document.readyState == "complete") {
mod.on('attach', worker => {
try {
worker.send('pageshow');
emit(worker, 'pageshow');
}
catch (e) {
// This can fail if an earlier attach listener destroyed the worker
}
});
}
}, true);
}
function isMatchingAttachState (mod, window) {
let state = window.document.readyState;
return 'start' === mod.contentScriptWhen ||
// Is `load` event already dispatched?
'complete' === state ||
// Is DOMContentLoaded already dispatched and waiting for it?
('ready' === mod.contentScriptWhen && state === 'interactive')
}
process.port.on('sdk/page-mod/create', (process, model) => {
if (pagemods.has(model.id))
return;
new ChildPageMod(model);
});
process.port.on('sdk/page-mod/destroy', (process, id) => {
let mod = pagemods.get(id);
if (mod)
mod.destroy();
});

Просмотреть файл

@ -0,0 +1,157 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { frames } = require("../remote/child");
const { Class } = require("../core/heritage");
const { Disposable } = require('../core/disposable');
lazyRequire(this, "../self", "data");
lazyRequire(this, "../dom/events", "once");
lazyRequire(this, "./utils", "getAttachEventType");
lazyRequire(this, '../util/rules', "Rules");
lazyRequire(this, '../util/uuid', "uuid");
lazyRequire(this, "./worker-child", "WorkerChild");
const { Cc, Ci, Cu } = require("chrome");
const { on: onSystemEvent } = require("../system/events");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, 'appShell',
"@mozilla.org/appshell/appShellService;1",
"nsIAppShellService");
const pages = new Map();
const DOC_INSERTED = "document-element-inserted";
function isValidURL(page, url) {
return !page.rules || page.rules.matchesAny(url);
}
const ChildPage = Class({
implements: [ Disposable ],
setup: function(frame, id, options) {
this.id = id;
this.frame = frame;
this.options = options;
this.webNav = appShell.createWindowlessBrowser(false);
this.docShell.allowJavascript = this.options.allow.script;
// Accessing the browser's window forces the initial about:blank document to
// be created before we start listening for notifications
this.contentWindow;
this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
pages.set(this.id, this);
this.contentURL = options.contentURL;
if (options.include) {
this.rules = Rules();
this.rules.add.apply(this.rules, [].concat(options.include));
}
},
dispose: function() {
pages.delete(this.id);
this.webProgress.removeProgressListener(this);
this.webNav.close();
this.webNav = null;
},
attachWorker: function() {
if (!isValidURL(this, this.contentWindow.location.href))
return;
this.options.id = uuid().toString();
this.options.window = this.contentWindow;
this.frame.port.emit("sdk/frame/connect", this.id, {
id: this.options.id,
url: this.contentWindow.document.documentURIObject.spec
});
new WorkerChild(this.options);
},
get docShell() {
return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
},
get webProgress() {
return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
},
get contentWindow() {
return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
},
get contentURL() {
return this.options.contentURL;
},
set contentURL(url) {
this.options.contentURL = url;
url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank";
this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
},
onLocationChange: function(progress, request, location, flags) {
// Ignore inner-frame events
if (progress != this.webProgress)
return;
// Ignore events that don't change the document
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
return;
let event = getAttachEventType(this.options);
// Attaching at the start of the load is handled by the
// document-element-inserted listener.
if (event == DOC_INSERTED)
return;
once(this.contentWindow, event, () => {
this.attachWorker();
}, false);
},
QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"])
});
onSystemEvent(DOC_INSERTED, ({type, subject, data}) => {
let page = Array.from(pages.values()).find(p => p.contentWindow.document === subject);
if (!page)
return;
if (getAttachEventType(page.options) == DOC_INSERTED)
page.attachWorker();
}, true);
frames.port.on("sdk/frame/create", (frame, id, options) => {
new ChildPage(frame, id, options);
});
frames.port.on("sdk/frame/set", (frame, id, params) => {
let page = pages.get(id);
if (!page)
return;
if ("allowScript" in params)
page.docShell.allowJavascript = params.allowScript;
if ("contentURL" in params)
page.contentURL = params.contentURL;
});
frames.port.on("sdk/frame/destroy", (frame, id) => {
let page = pages.get(id);
if (!page)
return;
page.destroy();
});

Просмотреть файл

@ -0,0 +1,426 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
'stability': 'unstable'
};
const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
lazyRequire(this, '../event/core', "on", "off", "emit");
lazyRequire(this, './sandbox/events', "events");
lazyRequire(this, './utils', "requiresAddonGlobal");
lazyRequire(this, '../lang/functional', {"delay": "async"});
const { Ci, Cu, Cc } = require('chrome');
lazyRequireModule(this, "../timers", "timer");
lazyRequire(this, '../url', "URL");
lazyRequire(this, '../loader/sandbox', "sandbox", "evaluate", "load");
lazyRequire(this, '../util/object', "merge");
lazyRequire(this, '../tabs/utils', "getTabForContentWindowNoShim");
lazyRequire(this, '../window/utils', "getInnerId");
lazyRequire(this, '../console/plain-text', "PlainTextConsole");
lazyRequire(this, '../self', "data");
lazyRequire(this, '../remote/core', "isChildLoader");
// WeakMap of sandboxes so we can access private values
const sandboxes = new WeakMap();
/* Trick the linker in order to ensure shipping these files in the XPI.
require('./content-worker.js');
Then, retrieve URL of these files in the XPI:
*/
var prefix = module.uri.split('sandbox.js')[0];
const CONTENT_WORKER_URL = prefix + 'content-worker.js';
const metadata = require('@loader/options').metadata;
// Fetch additional list of domains to authorize access to for each content
// script. It is stored in manifest `metadata` field which contains
// package.json data. This list is originaly defined by authors in
// `permissions` attribute of their package.json addon file.
const permissions = (metadata && metadata['permissions']) || {};
const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || [];
const waiveSecurityMembrane = !!permissions['unsafe-content-script'];
const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager;
const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
getService(Ci.nsIScriptSecurityManager);
const JS_VERSION = '1.8';
// Tests whether this window is loaded in a tab
function isWindowInTab(window) {
if (isChildLoader) {
let { frames } = require('../remote/child');
let frame = frames.getFrameForWindow(window.top);
return frame && frame.isTab;
}
else {
// The deprecated sync worker API still does everything in the main process
return getTabForContentWindowNoShim(window);
}
}
const WorkerSandbox = Class({
implements: [ EventTarget ],
/**
* Emit a message to the worker content sandbox
*/
emit: function emit(type, ...args) {
// JSON.stringify is buggy with cross-sandbox values,
// it may return "{}" on functions. Use a replacer to match them correctly.
let replacer = (k, v) =>
typeof(v) === "function"
? (type === "console" ? Function.toString.call(v) : void(0))
: v;
// Ensure having an asynchronous behavior
async(() =>
emitToContent(this, JSON.stringify([type, ...args], replacer))
);
},
/**
* Synchronous version of `emit`.
* /!\ Should only be used when it is strictly mandatory /!\
* Doesn't ensure passing only JSON values.
* Mainly used by context-menu in order to avoid breaking it.
*/
emitSync: function emitSync(...args) {
// because the arguments could be also non JSONable values,
// we need to ensure the array instance is created from
// the content's sandbox
return emitToContent(this, new modelFor(this).sandbox.Array(...args));
},
/**
* Configures sandbox and loads content scripts into it.
* @param {Worker} worker
* content worker
*/
initialize: function WorkerSandbox(worker, window) {
let model = {};
sandboxes.set(this, model);
model.worker = worker;
// We receive a wrapped window, that may be an xraywrapper if it's content
let proto = window;
// TODO necessary?
// Ensure that `emit` has always the right `this`
this.emit = this.emit.bind(this);
this.emitSync = this.emitSync.bind(this);
// Use expanded principal for content-script if the content is a
// regular web content for better isolation.
// (This behavior can be turned off for now with the unsafe-content-script
// flag to give addon developers time for making the necessary changes)
// But prevent it when the Worker isn't used for a content script but for
// injecting `addon` object into a Panel scope, for example.
// That's because:
// 1/ It is useless to use multiple domains as the worker is only used
// to communicate with the addon,
// 2/ By using it it would prevent the document to have access to any JS
// value of the worker. As JS values coming from multiple domain principals
// can't be accessed by 'mono-principals' (principal with only one domain).
// Even if this principal is for a domain that is specified in the multiple
// domain principal.
let principals = window;
let wantGlobalProperties = [];
let isSystemPrincipal = secMan.isSystemPrincipal(
window.document.nodePrincipal);
if (!isSystemPrincipal && !requiresAddonGlobal(worker)) {
if (EXPANDED_PRINCIPALS.length > 0) {
// We have to replace XHR constructor of the content document
// with a custom cross origin one, automagically added by platform code:
delete proto.XMLHttpRequest;
wantGlobalProperties.push('XMLHttpRequest');
}
if (!waiveSecurityMembrane)
principals = EXPANDED_PRINCIPALS.concat(window);
}
// Create the sandbox and bind it to window in order for content scripts to
// have access to all standard globals (window, document, ...)
let content = sandbox(principals, {
sandboxPrototype: proto,
wantXrays: !requiresAddonGlobal(worker),
wantGlobalProperties: wantGlobalProperties,
wantExportHelpers: true,
sameZoneAs: window,
metadata: {
SDKContentScript: true,
'inner-window-id': getInnerId(window)
}
});
model.sandbox = content;
// We have to ensure that window.top and window.parent are the exact same
// object than window object, i.e. the sandbox global object. But not
// always, in case of iframes, top and parent are another window object.
let top = window.top === window ? content : content.top;
let parent = window.parent === window ? content : content.parent;
merge(content, {
// We need 'this === window === top' to be true in toplevel scope:
get window() {
return content;
},
get top() {
return top;
},
get parent() {
return parent;
}
});
// Use the Greasemonkey naming convention to provide access to the
// unwrapped window object so the content script can access document
// JavaScript values.
// NOTE: this functionality is experimental and may change or go away
// at any time!
//
// Note that because waivers aren't propagated between origins, we
// need the unsafeWindow getter to live in the sandbox.
var unsafeWindowGetter =
new content.Function('return window.wrappedJSObject || window;');
Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter});
// Load trusted code that will inject content script API.
let ContentWorker = load(content, CONTENT_WORKER_URL);
// prepare a clean `self.options`
let options = 'contentScriptOptions' in worker ?
JSON.stringify(worker.contentScriptOptions) :
undefined;
// Then call `inject` method and communicate with this script
// by trading two methods that allow to send events to the other side:
// - `onEvent` called by content script
// - `result.emitToContent` called by addon script
let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker);
let chromeAPI = createChromeAPI(ContentWorker);
let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);
// Merge `emitToContent` into our private model of the
// WorkerSandbox so we can communicate with content script
model.emitToContent = result;
let console = new PlainTextConsole(null, getInnerId(window));
// Handle messages send by this script:
setListeners(this, console);
// Inject `addon` global into target document if document is trusted,
// `addon` in document is equivalent to `self` in content script.
if (requiresAddonGlobal(worker)) {
Object.defineProperty(getUnsafeWindow(window), 'addon', {
value: content.self,
configurable: true
}
);
}
// Inject our `console` into target document if worker doesn't have a tab
// (e.g Panel, PageWorker).
// `worker.tab` can't be used because bug 804935.
if (!isWindowInTab(window)) {
let win = getUnsafeWindow(window);
// export our chrome console to content window, as described here:
// https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
let con = Cu.createObjectIn(win);
let genPropDesc = function genPropDesc(fun) {
return { enumerable: true, configurable: true, writable: true,
value: console[fun] };
}
const properties = {
log: genPropDesc('log'),
info: genPropDesc('info'),
warn: genPropDesc('warn'),
error: genPropDesc('error'),
debug: genPropDesc('debug'),
trace: genPropDesc('trace'),
dir: genPropDesc('dir'),
group: genPropDesc('group'),
groupCollapsed: genPropDesc('groupCollapsed'),
groupEnd: genPropDesc('groupEnd'),
time: genPropDesc('time'),
timeEnd: genPropDesc('timeEnd'),
profile: genPropDesc('profile'),
profileEnd: genPropDesc('profileEnd'),
exception: genPropDesc('exception'),
assert: genPropDesc('assert'),
count: genPropDesc('count'),
table: genPropDesc('table'),
clear: genPropDesc('clear'),
dirxml: genPropDesc('dirxml'),
timeStamp: genPropDesc('timeStamp'),
};
Object.defineProperties(con, properties);
Cu.makeObjectPropsNormal(con);
win.console = con;
};
emit(events, "content-script-before-inserted", {
window: window,
worker: worker
});
// The order of `contentScriptFile` and `contentScript` evaluation is
// intentional, so programs can load libraries like jQuery from script URLs
// and use them in scripts.
let contentScriptFile = ('contentScriptFile' in worker)
? worker.contentScriptFile
: null,
contentScript = ('contentScript' in worker)
? worker.contentScript
: null;
if (contentScriptFile)
importScripts.apply(null, [this].concat(contentScriptFile));
if (contentScript) {
evaluateIn(
this,
Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
);
}
},
destroy: function destroy(reason) {
if (typeof reason != 'string')
reason = '';
this.emitSync('event', 'detach', reason);
let model = modelFor(this);
model.sandbox = null
model.worker = null;
},
});
exports.WorkerSandbox = WorkerSandbox;
/**
* Imports scripts to the sandbox by reading files under urls and
* evaluating its source. If exception occurs during evaluation
* `'error'` event is emitted on the worker.
* This is actually an analog to the `importScript` method in web
* workers but in our case it's not exposed even though content
* scripts may be able to do it synchronously since IO operation
* takes place in the UI process.
*/
function importScripts (workerSandbox, ...urls) {
let { worker, sandbox } = modelFor(workerSandbox);
for (let i in urls) {
let contentScriptFile = data.url(urls[i]);
try {
let uri = URL(contentScriptFile);
if (uri.scheme === 'resource')
load(sandbox, String(uri));
else
throw Error('Unsupported `contentScriptFile` url: ' + String(uri));
}
catch(e) {
emit(worker, 'error', e);
}
}
}
function setListeners (workerSandbox, console) {
let { worker } = modelFor(workerSandbox);
// console.xxx calls
workerSandbox.on('console', function consoleListener (kind, ...args) {
console[kind].apply(console, args);
});
// self.postMessage calls
workerSandbox.on('message', function postMessage(data) {
// destroyed?
if (worker)
emit(worker, 'message', data);
});
// self.port.emit calls
workerSandbox.on('event', function portEmit (...eventArgs) {
// If not destroyed, emit event information to worker
// `eventArgs` has the event name as first element,
// and remaining elements are additional arguments to pass
if (worker)
emit.apply(null, [worker.port].concat(eventArgs));
});
// unwrap, recreate and propagate async Errors thrown from content-script
workerSandbox.on('error', function onError({instanceOfError, value}) {
if (worker) {
let error = value;
if (instanceOfError) {
error = new Error(value.message, value.fileName, value.lineNumber);
error.stack = value.stack;
error.name = value.name;
}
emit(worker, 'error', error);
}
});
}
/**
* Evaluates code in the sandbox.
* @param {String} code
* JavaScript source to evaluate.
* @param {String} [filename='javascript:' + code]
* Name of the file
*/
function evaluateIn (workerSandbox, code, filename) {
let { worker, sandbox } = modelFor(workerSandbox);
try {
evaluate(sandbox, code, filename || 'javascript:' + code);
}
catch(e) {
emit(worker, 'error', e);
}
}
/**
* Method called by the worker sandbox when it needs to send a message
*/
function onContentEvent (workerSandbox, args) {
// As `emit`, we ensure having an asynchronous behavior
async(function () {
// We emit event to chrome/addon listeners
emit.apply(null, [workerSandbox].concat(JSON.parse(args)));
});
}
function modelFor (workerSandbox) {
return sandboxes.get(workerSandbox);
}
function getUnsafeWindow (win) {
return win.wrappedJSObject || win;
}
function emitToContent (workerSandbox, args) {
return modelFor(workerSandbox).emitToContent(args);
}
function createChromeAPI (scope) {
return Cu.cloneInto({
timers: {
setTimeout: timer.setTimeout.bind(timer),
setInterval: timer.setInterval.bind(timer),
clearTimeout: timer.clearTimeout.bind(timer),
clearInterval: timer.clearInterval.bind(timer),
},
sandbox: {
evaluate: evaluate,
},
}, scope, {cloneFunctions: true});
}

Просмотреть файл

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const events = {};
exports.events = events;

Просмотреть файл

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci } = require('chrome');
const system = require('sdk/system/events');
const { frames } = require('sdk/remote/child');
const { WorkerChild } = require('sdk/content/worker-child');
// map observer topics to tab event names
const EVENTS = {
'content-document-global-created': 'create',
'chrome-document-global-created': 'create',
'content-document-interactive': 'ready',
'chrome-document-interactive': 'ready',
'content-document-loaded': 'load',
'chrome-document-loaded': 'load',
// 'content-page-shown': 'pageshow', // bug 1024105
}
function topicListener({ subject, type }) {
// NOTE detect the window from the subject:
// - on *-global-created the subject is the window
// - in the other cases it is the document object
let window = subject instanceof Ci.nsIDOMWindow ? subject : subject.defaultView;
if (!window){
return;
}
let frame = frames.getFrameForWindow(window);
if (frame) {
let readyState = frame.content.document.readyState;
frame.port.emit('sdk/tab/event', EVENTS[type], { readyState });
}
}
for (let topic in EVENTS)
system.on(topic, topicListener, true);
// bug 1024105 - content-page-shown notification doesn't pass persisted param
function eventListener({target, type, persisted}) {
let frame = this;
if (target === frame.content.document) {
frame.port.emit('sdk/tab/event', type, persisted);
}
}
frames.addEventListener('pageshow', eventListener, true);
frames.port.on('sdk/tab/attach', (frame, options) => {
options.window = frame.content;
new WorkerChild(options);
});
// Forward the existent frames's readyState.
for (let frame of frames) {
let readyState = frame.content.document.readyState;
frame.port.emit('sdk/tab/event', 'init', { readyState });
}

Просмотреть файл

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
'stability': 'unstable'
};
const { Cc, Ci, Cu } = require('chrome');
const AppShellService = Cc['@mozilla.org/appshell/appShellService;1'].
getService(Ci.nsIAppShellService);
const NS = 'http://www.w3.org/1999/xhtml';
const COLOR = 'rgb(255,255,255)';
/**
* Creates canvas element with a thumbnail of the passed window.
* @param {Window} window
* @returns {Element}
*/
function getThumbnailCanvasForWindow(window) {
let aspectRatio = 0.5625; // 16:9
let thumbnail = AppShellService.hiddenDOMWindow.document
.createElementNS(NS, 'canvas');
thumbnail.mozOpaque = true;
thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
thumbnail.height = Math.round(thumbnail.width * aspectRatio);
let ctx = thumbnail.getContext('2d');
let snippetWidth = window.innerWidth * .6;
let scale = thumbnail.width / snippetWidth;
ctx.scale(scale, scale);
ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
snippetWidth * aspectRatio, COLOR);
return thumbnail;
}
exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
/**
* Creates Base64 encoded data URI of the thumbnail for the passed window.
* @param {Window} window
* @returns {String}
*/
exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
return getThumbnailCanvasForWindow(window).toDataURL()
};
// default 80x45 blank when not available
exports.BLANK = 'data:image/png;base64,' +
'iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQ'+
'EAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC';

Просмотреть файл

@ -0,0 +1,158 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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';
lazyRequire(this, '../util/object', 'merge');
const { Class } = require('../core/heritage');
lazyRequire(this, '../event/core', 'emit');
const { EventTarget } = require('../event/target');
lazyRequire(this, '../window/utils', 'getInnerId');
lazyRequire(this, '../lang/type', 'instanceOf', 'isObject');
lazyRequireModule(this, '../system/events', 'system');
const { when } = require('../system/unload');
lazyRequire(this, './sandbox', 'WorkerSandbox');
const { Ci } = require('chrome');
const { process, frames } = require('../remote/child');
const EVENTS = {
'chrome-page-shown': 'pageshow',
'content-page-shown': 'pageshow',
'chrome-page-hidden': 'pagehide',
'content-page-hidden': 'pagehide',
'inner-window-destroyed': 'detach',
}
// The parent Worker must have been created (or an async message sent to spawn
// its creation) before creating the WorkerChild or messages from the content
// script to the parent will get lost.
const WorkerChild = Class({
implements: [EventTarget],
initialize(options) {
merge(this, options);
keepAlive.set(this.id, this);
this.windowId = getInnerId(this.window);
if (this.contentScriptOptions)
this.contentScriptOptions = JSON.parse(this.contentScriptOptions);
this.port = EventTarget();
this.port.on('*', this.send.bind(this, 'event'));
this.on('*', this.send.bind(this));
this.observe = this.observe.bind(this);
for (let topic in EVENTS)
system.on(topic, this.observe);
this.receive = this.receive.bind(this);
process.port.on('sdk/worker/message', this.receive);
this.sandbox = WorkerSandbox(this, this.window);
// If the document has an unexpected readyState, its worker-child instance is initialized
// as frozen until one of the known readyState is reached.
let initialDocumentReadyState = this.window.document.readyState;
this.frozen = [
"loading", "interactive", "complete"
].includes(initialDocumentReadyState) ? false : true;
if (this.frozen) {
console.warn("SDK worker-child started as frozen on unexpected initial document.readyState", {
initialDocumentReadyState, windowLocation: this.window.location.href,
});
}
this.frozenMessages = [];
this.on('pageshow', () => {
this.frozen = false;
this.frozenMessages.forEach(args => this.sandbox.emit(...args));
this.frozenMessages = [];
});
this.on('pagehide', () => {
this.frozen = true;
});
},
// messages
receive(process, id, args) {
if (id !== this.id)
return;
args = JSON.parse(args);
if (this.frozen)
this.frozenMessages.push(args);
else
this.sandbox.emit(...args);
if (args[0] === 'detach')
this.destroy(args[1]);
},
send(...args) {
process.port.emit('sdk/worker/event', this.id, JSON.stringify(args, exceptions));
},
// notifications
observe({ type, subject }) {
if (!this.sandbox)
return;
if (subject.defaultView && getInnerId(subject.defaultView) === this.windowId) {
this.sandbox.emitSync(EVENTS[type]);
emit(this, EVENTS[type]);
}
if (type === 'inner-window-destroyed' &&
subject.QueryInterface(Ci.nsISupportsPRUint64).data === this.windowId) {
this.destroy();
}
},
get frame() {
return frames.getFrameForWindow(this.window.top);
},
// detach/destroy: unload and release the sandbox
destroy(reason) {
if (!this.sandbox)
return;
for (let topic in EVENTS)
system.off(topic, this.observe);
process.port.off('sdk/worker/message', this.receive);
this.sandbox.destroy(reason);
this.sandbox = null;
keepAlive.delete(this.id);
this.send('detach');
}
})
exports.WorkerChild = WorkerChild;
// Error instances JSON poorly
function exceptions(key, value) {
if (!isObject(value) || !instanceOf(value, Error))
return value;
let _errorType = value.constructor.name;
let { message, fileName, lineNumber, stack, name } = value;
return { _errorType, message, fileName, lineNumber, stack, name };
}
// workers for windows in this tab
var keepAlive = new Map();
process.port.on('sdk/worker/create', (process, options, cpows) => {
options.window = cpows.window;
let worker = new WorkerChild(options);
let frame = frames.getFrameForWindow(options.window.top);
frame.port.emit('sdk/worker/connect', options.id, options.window.location.href);
});
when(reason => {
for (let worker of keepAlive.values())
worker.destroy(reason);
});

Просмотреть файл

@ -0,0 +1,180 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
lazyRequire(this, '../event/core', "emit");
const { omit, merge } = require('../util/object');
const { Class } = require('../core/heritage');
const { method } = require('../lang/functional');
lazyRequire(this, '../window/utils', "getInnerId");
const { EventTarget } = require('../event/target');
lazyRequire(this, '../private-browsing/utils', "isPrivate");
lazyRequire(this, '../tabs/utils', "getTabForBrowser", "getTabForContentWindowNoShim", "getBrowserForTab");
lazyRequire(this, './utils', "attach", "connect", "detach", "destroy", "makeChildOptions");
const { ensure } = require('../system/unload');
lazyRequire(this, '../system/events', {"on": "observe"});
const { Ci, Cu } = require('chrome');
lazyRequire(this, 'sdk/model/core', {"modelFor": "tabFor"});
const { remoteRequire, processes, frames } = require('../remote/parent');
remoteRequire('sdk/content/worker-child');
const workers = new WeakMap();
var modelFor = (worker) => workers.get(worker);
const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
"The script may not be initialized yet, or may already have been unloaded.";
// a handle for communication between content script and addon code
const Worker = Class({
implements: [EventTarget],
initialize(options = {}) {
ensure(this, 'detach');
let model = {
attached: false,
destroyed: false,
earlyEvents: [], // fired before worker was attached
frozen: true, // document is not yet active
options,
};
workers.set(this, model);
this.on('detach', this.detach);
EventTarget.prototype.initialize.call(this, options);
this.receive = this.receive.bind(this);
this.port = EventTarget();
this.port.emit = this.send.bind(this, 'event');
this.postMessage = this.send.bind(this, 'message');
if ('window' in options) {
let window = options.window;
delete options.window;
attach(this, window);
}
},
// messages
receive(process, id, args) {
let model = modelFor(this);
if (id !== model.id || !model.attached)
return;
args = JSON.parse(args);
if (model.destroyed && args[0] != 'detach')
return;
if (args[0] === 'event')
emit(this.port, ...args.slice(1))
else
emit(this, ...args);
},
send(...args) {
let model = modelFor(this);
if (model.destroyed && args[0] !== 'detach')
throw new Error(ERR_DESTROYED);
if (!model.attached) {
model.earlyEvents.push(args);
return;
}
processes.port.emit('sdk/worker/message', model.id, JSON.stringify(args));
},
// properties
get url() {
let { url } = modelFor(this);
return url;
},
get contentURL() {
return this.url;
},
get tab() {
require('sdk/tabs');
let { frame } = modelFor(this);
if (!frame)
return null;
let rawTab = getTabForBrowser(frame.frameElement);
return rawTab && tabFor(rawTab);
},
toString: () => '[object Worker]',
detach: method(detach),
destroy: method(destroy),
})
exports.Worker = Worker;
attach.define(Worker, function(worker, window) {
let model = modelFor(worker);
if (model.attached)
detach(worker);
let childOptions = makeChildOptions(model.options);
processes.port.emitCPOW('sdk/worker/create', [childOptions], { window });
let listener = (frame, id, url) => {
if (id != childOptions.id)
return;
frames.port.off('sdk/worker/connect', listener);
connect(worker, frame, { id, url });
};
frames.port.on('sdk/worker/connect', listener);
});
connect.define(Worker, function(worker, frame, { id, url }) {
let model = modelFor(worker);
if (model.attached)
detach(worker);
model.id = id;
model.frame = frame;
model.url = url;
// Messages from content -> chrome come through the process message manager
// since that lives longer than the frame message manager
processes.port.on('sdk/worker/event', worker.receive);
model.attached = true;
model.destroyed = false;
model.frozen = false;
model.earlyEvents.forEach(args => worker.send(...args));
model.earlyEvents = [];
emit(worker, 'attach');
});
// unload and release the child worker, release window reference
detach.define(Worker, function(worker) {
let model = modelFor(worker);
if (!model.attached)
return;
processes.port.off('sdk/worker/event', worker.receive);
model.attached = false;
model.destroyed = true;
emit(worker, 'detach');
});
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
// Something in the parent side has destroyed the worker, tell the child to
// detach, the child will respond when it has detached
destroy.define(Worker, function(worker, reason) {
let model = modelFor(worker);
model.destroyed = true;
if (!model.attached)
return;
worker.send('detach', reason);
});

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,146 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Class } = require("../core/heritage");
lazyRequire(this, "../util/match-pattern", "MatchPattern");
const readers = require("./readers");
// Context class is required to implement a single `isCurrent(target)` method
// that must return boolean value indicating weather given target matches a
// context or not. Most context implementations below will have an associated
// reader that way context implementation can setup a reader to extract necessary
// information to make decision if target is matching a context.
const Context = Class({
isRequired: false,
isCurrent(target) {
throw Error("Context class must implement isCurrent(target) method");
},
get required() {
Object.defineProperty(this, "required", {
value: Object.assign(Object.create(Object.getPrototypeOf(this)),
this,
{isRequired: true})
});
return this.required;
}
});
Context.required = function(...params) {
return Object.assign(new this(...params), {isRequired: true});
};
exports.Context = Context;
// Next few context implementations use an associated reader to extract info
// from the context target and story it to a private symbol associtaed with
// a context implementation. That way name collisions are avoided while required
// information is still carried along.
const isPage = Symbol("context/page?")
const PageContext = Class({
extends: Context,
read: {[isPage]: new readers.isPage()},
isCurrent: target => target[isPage]
});
exports.Page = PageContext;
const isFrame = Symbol("context/frame?");
const FrameContext = Class({
extends: Context,
read: {[isFrame]: new readers.isFrame()},
isCurrent: target => target[isFrame]
});
exports.Frame = FrameContext;
const selection = Symbol("context/selection")
const SelectionContext = Class({
read: {[selection]: new readers.Selection()},
isCurrent: target => !!target[selection]
});
exports.Selection = SelectionContext;
const link = Symbol("context/link");
const LinkContext = Class({
extends: Context,
read: {[link]: new readers.LinkURL()},
isCurrent: target => !!target[link]
});
exports.Link = LinkContext;
const isEditable = Symbol("context/editable?")
const EditableContext = Class({
extends: Context,
read: {[isEditable]: new readers.isEditable()},
isCurrent: target => target[isEditable]
});
exports.Editable = EditableContext;
const mediaType = Symbol("context/mediaType")
const ImageContext = Class({
extends: Context,
read: {[mediaType]: new readers.MediaType()},
isCurrent: target => target[mediaType] === "image"
});
exports.Image = ImageContext;
const VideoContext = Class({
extends: Context,
read: {[mediaType]: new readers.MediaType()},
isCurrent: target => target[mediaType] === "video"
});
exports.Video = VideoContext;
const AudioContext = Class({
extends: Context,
read: {[mediaType]: new readers.MediaType()},
isCurrent: target => target[mediaType] === "audio"
});
exports.Audio = AudioContext;
const isSelectorMatch = Symbol("context/selector/mathches?")
const SelectorContext = Class({
extends: Context,
initialize(selector) {
this.selector = selector;
// Each instance of selector context will need to store read
// data into different field, so that case with multilpe selector
// contexts won't cause a conflicts.
this[isSelectorMatch] = Symbol(selector);
this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
},
isCurrent(target) {
return target[this[isSelectorMatch]];
}
});
exports.Selector = SelectorContext;
const url = Symbol("context/url");
const URLContext = Class({
extends: Context,
initialize(pattern) {
this.pattern = new MatchPattern(pattern);
},
read: {[url]: new readers.PageURL()},
isCurrent(target) {
return this.pattern.test(target[url]);
}
});
exports.URL = URLContext;
var PredicateContext = Class({
extends: Context,
initialize(isMatch) {
if (typeof(isMatch) !== "function") {
throw TypeError("Predicate context mus be passed a function");
}
this.isMatch = isMatch
},
isCurrent(target) {
return this.isMatch(target);
}
});
exports.Predicate = PredicateContext;

Просмотреть файл

@ -0,0 +1,384 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 Contexts = require("./context");
const Readers = require("./readers");
const Component = require("../ui/component");
const { Class } = require("../core/heritage");
const { map, filter, object, reduce, keys, symbols,
pairs, values, each, some, isEvery, count } = require("../util/sequence");
const { loadModule } = require("framescript/manager");
const { Cu, Cc, Ci } = require("chrome");
const prefs = require("sdk/preferences/service");
const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
const preferencesService = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).
getBranch(null);
const readTable = Symbol("context-menu/read-table");
const nameTable = Symbol("context-menu/name-table");
const onContext = Symbol("context-menu/on-context");
const isMatching = Symbol("context-menu/matching-handler?");
exports.onContext = onContext;
exports.readTable = readTable;
exports.nameTable = nameTable;
const propagateOnContext = (item, data) =>
each(child => child[onContext](data), item.state.children);
const isContextMatch = item => !item[isMatching] || item[isMatching]();
// For whatever reason addWeakMessageListener does not seems to work as our
// instance seems to dropped even though it's alive. This is simple workaround
// to avoid dead object excetptions.
const WeakMessageListener = function(receiver, handler="receiveMessage") {
this.receiver = receiver
this.handler = handler
};
WeakMessageListener.prototype = {
constructor: WeakMessageListener,
receiveMessage(message) {
if (Cu.isDeadWrapper(this.receiver)) {
message.target.messageManager.removeMessageListener(message.name, this);
}
else {
this.receiver[this.handler](message);
}
}
};
const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold";
const onMessage = Symbol("context-menu/message-listener");
const onPreferceChange = Symbol("context-menu/preference-change");
const ContextMenuExtension = Class({
extends: Component,
initialize: Component,
setup() {
const messageListener = new WeakMessageListener(this, onMessage);
loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame");
globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener);
globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener);
preferencesService.addObserver(OVERFLOW_THRESH, this);
},
observe(_, __, name) {
if (name === OVERFLOW_THRESH) {
const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
this[Component.patch]({overflowThreshold});
}
},
[onMessage]({name, data, target}) {
if (name === "sdk/context-menu/read")
this[onContext]({target, data});
if (name === "sdk/context-menu/readers?")
target.messageManager.sendAsyncMessage("sdk/context-menu/readers",
JSON.parse(JSON.stringify(this.state.readers)));
},
[Component.initial](options={}, children) {
const element = options.element || null;
const target = options.target || null;
const readers = Object.create(null);
const users = Object.create(null);
const registry = new WeakSet();
const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
return { target, children: [], readers, users, element,
registry, overflowThreshold };
},
[Component.isUpdated](before, after) {
// Update only if target changed, since there is no point in re-rendering
// when children are. Also new items added won't be in sync with a latest
// context target so we should really just render before drawing context
// menu.
return before.target !== after.target;
},
[Component.render]({element, children, overflowThreshold}) {
if (!element) return null;
const items = children.filter(isContextMatch);
const body = items.length === 0 ? items :
items.length < overflowThreshold ? [new Separator(),
...items] :
[{tagName: "menu",
className: "sdk-context-menu-overflow-menu",
label: "Add-ons",
accesskey: "A",
children: [{tagName: "menupopup",
children: items}]}];
return {
element: element,
tagName: "menugroup",
style: "-moz-box-orient: vertical;",
className: "sdk-context-menu-extension",
children: body
}
},
// Adds / remove child to it's own list.
add(item) {
this[Component.patch]({children: this.state.children.concat(item)});
},
remove(item) {
this[Component.patch]({
children: this.state.children.filter(x => x !== item)
});
},
register(item) {
const { users, registry } = this.state;
if (registry.has(item)) return;
registry.add(item);
// Each (ContextHandler) item has a readTable that is a
// map of keys to readers extracting them from the content.
// During the registraction we update intrnal record of unique
// readers and users per reader. Most context will have a reader
// shared across all instances there for map of users per reader
// is stored separately from the reader so that removing reader
// will occur only when no users remain.
const table = item[readTable];
// Context readers store data in private symbols so we need to
// collect both table keys and private symbols.
const names = [...keys(table), ...symbols(table)];
const readers = map(name => table[name], names);
// Create delta for registered readers that will be merged into
// internal readers table.
const added = filter(x => !users[x.id], readers);
const delta = object(...map(x => [x.id, x], added));
const update = reduce((update, reader) => {
const n = update[reader.id] || 0;
update[reader.id] = n + 1;
return update;
}, Object.assign({}, users), readers);
// Patch current state with a changes that registered item caused.
this[Component.patch]({users: update,
readers: Object.assign(this.state.readers, delta)});
if (count(added)) {
globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
JSON.parse(JSON.stringify(delta)));
}
},
unregister(item) {
const { users, registry } = this.state;
if (!registry.has(item)) return;
registry.delete(item);
const table = item[readTable];
const names = [...keys(table), ...symbols(table)];
const readers = map(name => table[name], names);
const update = reduce((update, reader) => {
update[reader.id] = update[reader.id] - 1;
return update;
}, Object.assign({}, users), readers);
const removed = filter(id => !update[id], keys(update));
const delta = object(...map(x => [x, null], removed));
this[Component.patch]({users: update,
readers: Object.assign(this.state.readers, delta)});
if (count(removed)) {
globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
JSON.parse(JSON.stringify(delta)));
}
},
[onContext]({data, target}) {
propagateOnContext(this, data);
const document = target.ownerDocument;
const element = document.getElementById("contentAreaContextMenu");
this[Component.patch]({target: data, element: element});
}
});this,
exports.ContextMenuExtension = ContextMenuExtension;
// Takes an item options and
const makeReadTable = ({context, read}) => {
// Result of this function is a tuple of all readers &
// name, reader id pairs.
// Filter down to contexts that have a reader associated.
const contexts = filter(context => context.read, context);
// Merge all contexts read maps to a single hash, note that there should be
// no name collisions as context implementations expect to use private
// symbols for storing it's read data.
return Object.assign({}, ...map(({read}) => read, contexts), read);
}
const readTarget = (nameTable, data) =>
object(...map(([name, id]) => [name, data[id]], nameTable))
const ContextHandler = Class({
extends: Component,
initialize: Component,
get context() {
return this.state.options.context;
},
get read() {
return this.state.options.read;
},
[Component.initial](options) {
return {
table: makeReadTable(options),
requiredContext: filter(context => context.isRequired, options.context),
optionalContext: filter(context => !context.isRequired, options.context)
}
},
[isMatching]() {
const {target, requiredContext, optionalContext} = this.state;
return isEvery(context => context.isCurrent(target), requiredContext) &&
(count(optionalContext) === 0 ||
some(context => context.isCurrent(target), optionalContext));
},
setup() {
const table = makeReadTable(this.state.options);
this[readTable] = table;
this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)),
...map(name => [name, table[name].id], keys(table))];
contextMenu.register(this);
each(child => contextMenu.remove(child), this.state.children);
contextMenu.add(this);
},
dispose() {
contextMenu.remove(this);
each(child => contextMenu.unregister(child), this.state.children);
contextMenu.unregister(this);
},
// Internal `Symbol("onContext")` method is invoked when "contextmenu" event
// occurs in content process. Context handles with children delegate to each
// child and patch it's internal state to reflect new contextmenu target.
[onContext](data) {
propagateOnContext(this, data);
this[Component.patch]({target: readTarget(this[nameTable], data)});
}
});
const isContextHandler = item => item instanceof ContextHandler;
exports.ContextHandler = ContextHandler;
const Menu = Class({
extends: ContextHandler,
[isMatching]() {
return ContextHandler.prototype[isMatching].call(this) &&
this.state.children.filter(isContextHandler)
.some(isContextMatch);
},
[Component.render]({children, options}) {
const items = children.filter(isContextMatch);
return {tagName: "menu",
className: "sdk-context-menu menu-iconic",
label: options.label,
accesskey: options.accesskey,
image: options.icon,
children: [{tagName: "menupopup",
children: items}]};
}
});
exports.Menu = Menu;
const onCommand = Symbol("context-menu/item/onCommand");
const Item = Class({
extends: ContextHandler,
get onClick() {
return this.state.options.onClick;
},
[Component.render]({options}) {
const {label, icon, accesskey} = options;
return {tagName: "menuitem",
className: "sdk-context-menu-item menuitem-iconic",
label,
accesskey,
image: icon,
oncommand: this};
},
handleEvent(event) {
if (this.onClick)
this.onClick(this.state.target);
}
});
exports.Item = Item;
var Separator = Class({
extends: Component,
initialize: Component,
[Component.render]() {
return {tagName: "menuseparator",
className: "sdk-context-menu-separator"}
},
[onContext]() {
}
});
exports.Separator = Separator;
exports.Contexts = Contexts;
exports.Readers = Readers;
const createElement = (vnode, {document}) => {
const node = vnode.namespace ?
document.createElementNS(vnode.namespace, vnode.tagName) :
document.createElement(vnode.tagName);
node.setAttribute("data-component-path", vnode[Component.path]);
each(([key, value]) => {
if (key === "tagName") {
return;
}
if (key === "children") {
return;
}
if (key.startsWith("on")) {
node.addEventListener(key.substr(2), value)
return;
}
if (typeof(value) !== "object" &&
typeof(value) !== "function" &&
value !== void(0) &&
value !== null)
{
if (key === "className") {
node[key] = value;
}
else {
node.setAttribute(key, value);
}
return;
}
}, pairs(vnode));
each(child => node.appendChild(createElement(child, {document})), vnode.children);
return node;
};
const htmlWriter = tree => {
if (tree !== null) {
const root = tree.element;
const node = createElement(tree, {document: root.ownerDocument});
const before = root.querySelector("[data-component-path='/']");
if (before) {
root.replaceChild(node, before);
} else {
root.appendChild(node);
}
}
};
const contextMenu = ContextMenuExtension();
exports.contextMenu = contextMenu;
Component.mount(contextMenu, htmlWriter);

Просмотреть файл

@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Class } = require("../core/heritage");
const { extend } = require("../util/object");
const { memoize, method, identity } = require("../lang/functional");
const serializeCategory = ({type}) => ({ category: `reader/${type}()` });
const Reader = Class({
initialize() {
this.id = `reader/${this.type}()`
},
toJSON() {
return serializeCategory(this);
}
});
const MediaTypeReader = Class({ extends: Reader, type: "MediaType" });
exports.MediaType = MediaTypeReader;
const LinkURLReader = Class({ extends: Reader, type: "LinkURL" });
exports.LinkURL = LinkURLReader;
const SelectionReader = Class({ extends: Reader, type: "Selection" });
exports.Selection = SelectionReader;
const isPageReader = Class({ extends: Reader, type: "isPage" });
exports.isPage = isPageReader;
const isFrameReader = Class({ extends: Reader, type: "isFrame" });
exports.isFrame = isFrameReader;
const isEditable = Class({ extends: Reader, type: "isEditable"});
exports.isEditable = isEditable;
const ParameterizedReader = Class({
extends: Reader,
readParameter: function(value) {
return value;
},
toJSON: function() {
var json = serializeCategory(this);
json[this.parameter] = this[this.parameter];
return json;
},
initialize(...params) {
if (params.length) {
this[this.parameter] = this.readParameter(...params);
}
this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`;
}
});
exports.ParameterizedReader = ParameterizedReader;
const QueryReader = Class({
extends: ParameterizedReader,
type: "Query",
parameter: "path"
});
exports.Query = QueryReader;
const AttributeReader = Class({
extends: ParameterizedReader,
type: "Attribute",
parameter: "name"
});
exports.Attribute = AttributeReader;
const SrcURLReader = Class({
extends: AttributeReader,
name: "src",
});
exports.SrcURL = SrcURLReader;
const PageURLReader = Class({
extends: QueryReader,
path: "ownerDocument.URL",
});
exports.PageURL = PageURLReader;
const SelectorMatchReader = Class({
extends: ParameterizedReader,
type: "SelectorMatch",
parameter: "selector"
});
exports.SelectorMatch = SelectorMatchReader;
const extractors = new WeakMap();
extractors.id = 0;
var Extractor = Class({
extends: ParameterizedReader,
type: "Extractor",
parameter: "source",
initialize: function(f) {
this[this.parameter] = String(f);
if (!extractors.has(f)) {
extractors.id = extractors.id + 1;
extractors.set(f, extractors.id);
}
this.id = `reader/${this.type}.for(${extractors.get(f)})`
}
});
exports.Extractor = Extractor;

Просмотреть файл

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 shared = require("toolkit/require");
const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core");
const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable")
const { Class } = require("sdk/core/heritage")
const makeDisposable = Type => Class({
extends: Type,
implements: [Disposable],
initialize: Type.prototype.initialize,
setup(...params) {
Type.prototype.setup.call(this, ...params);
setupDisposable(this);
},
dispose(...params) {
disposeDisposable(this);
Type.prototype.dispose.call(this, ...params);
}
});
exports.Separator = Separator;
exports.Contexts = Contexts;
exports.Readers = Readers;
// Subclass Item & Menu shared classes so their items
// will be unloaded when add-on is unloaded.
exports.Item = makeDisposable(Item);
exports.Menu = makeDisposable(Menu);

Просмотреть файл

@ -0,0 +1,197 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "deprecated"
};
const { merge } = require("../util/object");
const { union } = require("../util/array");
const { isNil, isRegExp } = require("../lang/type");
// The possible return values of getTypeOf.
const VALID_TYPES = [
"array",
"boolean",
"function",
"null",
"number",
"object",
"string",
"undefined",
"regexp"
];
const { isArray } = Array;
/**
* Returns a validated options dictionary given some requirements. If any of
* the requirements are not met, an exception is thrown.
*
* @param options
* An object, the options dictionary to validate. It's not modified.
* If it's null or otherwise falsey, an empty object is assumed.
* @param requirements
* An object whose keys are the expected keys in options. Any key in
* options that is not present in requirements is ignored. Each value
* in requirements is itself an object describing the requirements of
* its key. There are four optional keys in this object:
* map: A function that's passed the value of the key in options.
* map's return value is taken as the key's value in the final
* validated options, is, and ok. If map throws an exception
* it's caught and discarded, and the key's value is its value in
* options.
* is: An array containing any number of the typeof type names. If
* the key's value is none of these types, it fails validation.
* Arrays, null and regexps are identified by the special type names
* "array", "null", "regexp"; "object" will not match either. No type
* coercion is done.
* ok: A function that's passed the key's value. If it returns
* false, the value fails validation.
* msg: If the key's value fails validation, an exception is thrown.
* This string will be used as its message. If undefined, a
* generic message is used, unless is is defined, in which case
* the message will state that the value needs to be one of the
* given types.
* @return An object whose keys are those keys in requirements that are also in
* options and whose values are the corresponding return values of map
* or the corresponding values in options. Note that any keys not
* shared by both requirements and options are not in the returned
* object.
*/
exports.validateOptions = function validateOptions(options, requirements) {
options = options || {};
let validatedOptions = {};
for (let key in requirements) {
let isOptional = false;
let mapThrew = false;
let req = requirements[key];
let [optsVal, keyInOpts] = (key in options) ?
[options[key], true] :
[undefined, false];
if (req.map) {
try {
optsVal = req.map(optsVal);
}
catch (err) {
if (err instanceof RequirementError)
throw err;
mapThrew = true;
}
}
if (req.is) {
let types = req.is;
if (!isArray(types) && isArray(types.is))
types = types.is;
if (isArray(types)) {
isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v));
// Sanity check the caller's type names.
types.forEach(function (typ) {
if (VALID_TYPES.indexOf(typ) < 0) {
let msg = 'Internal error: invalid requirement type "' + typ + '".';
throw new Error(msg);
}
});
if (types.indexOf(getTypeOf(optsVal)) < 0)
throw new RequirementError(key, req);
}
}
if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal)))
throw new RequirementError(key, req);
if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined))
validatedOptions[key] = optsVal;
}
return validatedOptions;
};
exports.addIterator = function addIterator(obj, keysValsGenerator) {
obj.__iterator__ = function(keysOnly, keysVals) {
let keysValsIterator = keysValsGenerator.call(this);
// "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
// and "for (.. in Iterator(..))" gets [key, value] pairs.
let index = keysOnly ? 0 : 1;
while (true)
yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
};
};
// Similar to typeof, except arrays, null and regexps are identified by "array" and
// "null" and "regexp", not "object".
var getTypeOf = exports.getTypeOf = function getTypeOf(val) {
let typ = typeof(val);
if (typ === "object") {
if (!val)
return "null";
if (isArray(val))
return "array";
if (isRegExp(val))
return "regexp";
}
return typ;
}
function RequirementError(key, requirement) {
Error.call(this);
this.name = "RequirementError";
let msg = requirement.msg;
if (!msg) {
msg = 'The option "' + key + '" ';
msg += requirement.is ?
"must be one of the following types: " + requirement.is.join(", ") :
"is invalid.";
}
this.message = msg;
}
RequirementError.prototype = Object.create(Error.prototype);
var string = { is: ['string', 'undefined', 'null'] };
exports.string = string;
var number = { is: ['number', 'undefined', 'null'] };
exports.number = number;
var boolean = { is: ['boolean', 'undefined', 'null'] };
exports.boolean = boolean;
var object = { is: ['object', 'undefined', 'null'] };
exports.object = object;
var array = { is: ['array', 'undefined', 'null'] };
exports.array = array;
var isTruthyType = type => !(type === 'undefined' || type === 'null');
var findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v };
function required(req) {
let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType);
return merge({}, req, {is: types});
}
exports.required = required;
function optional(req) {
req = merge({is: []}, req);
req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null');
return req;
}
exports.optional = optional;
function either(...types) {
return union.apply(null, types.map(findTypes));
}
exports.either = either;

Просмотреть файл

@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Class } = require("../../core/heritage");
const { removeListener, on } = require("../../dom/events");
/**
* Event targets
* can be added / removed by calling `observe / ignore` methods. Composer should
* provide array of event types it wishes to handle as property
* `supportedEventsTypes` and function for handling all those events as
* `handleEvent` property.
*/
exports.DOMEventAssembler = Class({
/**
* Function that is supposed to handle all the supported events (that are
* present in the `supportedEventsTypes`) from all the observed
* `eventTargets`.
* @param {Event} event
* Event being dispatched.
*/
handleEvent() {
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
},
/**
* Array of supported event names.
* @type {String[]}
*/
get supportedEventsTypes() {
throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
},
/**
* Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
* supported events will be registered on the given `eventTarget`.
* @param {EventTarget} eventTarget
*/
observe: function observe(eventTarget) {
this.supportedEventsTypes.forEach(function(eventType) {
on(eventTarget, eventType, this);
}, this);
},
/**
* Removes `eventTarget` from the list of observed `eventTarget`s. Listeners
* for all supported events will be unregistered from the given `eventTarget`.
* @param {EventTarget} eventTarget
*/
ignore: function ignore(eventTarget) {
this.supportedEventsTypes.forEach(function(eventType) {
removeListener(eventTarget, eventType, this);
}, this);
}
});

Просмотреть файл

@ -0,0 +1,288 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
*
* `deprecated/sync-worker` was previously `content/worker`, that was
* incompatible with e10s. we are in the process of switching to the new
* asynchronous `Worker`, which behaves slightly differently in some edge
* cases, so we are keeping this one around for a short period.
* try to switch to the new one as soon as possible..
*
*/
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
const { on, off, emit, setListeners } = require('../event/core');
const {
attach, detach, destroy
} = require('../content/utils');
const { method } = require('../lang/functional');
const { Ci, Cu, Cc } = require('chrome');
const unload = require('../system/unload');
const events = require('../system/events');
const { getInnerId } = require("../window/utils");
const { WorkerSandbox } = require('../content/sandbox');
const { isPrivate } = require('../private-browsing/utils');
// A weak map of workers to hold private attributes that
// should not be exposed
const workers = new WeakMap();
var modelFor = (worker) => workers.get(worker);
const ERR_DESTROYED =
"Couldn't find the worker to receive this message. " +
"The script may not be initialized yet, or may already have been unloaded.";
const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
"until it is visible again.";
/**
* Message-passing facility for communication between code running
* in the content and add-on process.
* @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
*/
const Worker = Class({
implements: [EventTarget],
initialize: function WorkerConstructor (options) {
// Save model in weak map to not expose properties
let model = createModel();
workers.set(this, model);
options = options || {};
if ('contentScriptFile' in options)
this.contentScriptFile = options.contentScriptFile;
if ('contentScriptOptions' in options)
this.contentScriptOptions = options.contentScriptOptions;
if ('contentScript' in options)
this.contentScript = options.contentScript;
if ('injectInDocument' in options)
this.injectInDocument = !!options.injectInDocument;
setListeners(this, options);
unload.ensure(this, "destroy");
// Ensure that worker.port is initialized for contentWorker to be able
// to send events during worker initialization.
this.port = createPort(this);
model.documentUnload = documentUnload.bind(this);
model.pageShow = pageShow.bind(this);
model.pageHide = pageHide.bind(this);
if ('window' in options)
attach(this, options.window);
},
/**
* Sends a message to the worker's global scope. Method takes single
* argument, which represents data to be sent to the worker. The data may
* be any primitive type value or `JSON`. Call of this method asynchronously
* emits `message` event with data value in the global scope of this
* worker.
*
* `message` event listeners can be set either by calling
* `self.on` with a first argument string `"message"` or by
* implementing `onMessage` function in the global scope of this worker.
* @param {Number|String|JSON} data
*/
postMessage: function (...data) {
let model = modelFor(this);
let args = ['message'].concat(data);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
processMessage.apply(null, [this].concat(args));
},
get url () {
let model = modelFor(this);
// model.window will be null after detach
return model.window ? model.window.document.location.href : null;
},
get contentURL () {
let model = modelFor(this);
return model.window ? model.window.document.URL : null;
},
// Implemented to provide some of the previous features of exposing sandbox
// so that Worker can be extended
getSandbox: function () {
return modelFor(this).contentWorker;
},
toString: function () { return '[object Worker]'; },
attach: method(attach),
detach: method(detach),
destroy: method(destroy)
});
exports.Worker = Worker;
attach.define(Worker, function (worker, window) {
let model = modelFor(worker);
model.window = window;
// Track document unload to destroy this worker.
// We can't watch for unload event on page's window object as it
// prevents bfcache from working:
// https://developer.mozilla.org/En/Working_with_BFCache
model.windowID = getInnerId(model.window);
events.on("inner-window-destroyed", model.documentUnload);
// will set model.contentWorker pointing to the private API:
model.contentWorker = WorkerSandbox(worker, model.window);
// Listen to pagehide event in order to freeze the content script
// while the document is frozen in bfcache:
model.window.addEventListener("pageshow", model.pageShow, true);
model.window.addEventListener("pagehide", model.pageHide, true);
// Mainly enable worker.port.emit to send event to the content worker
model.inited = true;
model.frozen = false;
// Fire off `attach` event
emit(worker, 'attach', window);
// Process all events and messages that were fired before the
// worker was initialized.
model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
});
/**
* Remove all internal references to the attached document
* Tells _port to unload itself and removes all the references from itself.
*/
detach.define(Worker, function (worker, reason) {
let model = modelFor(worker);
// maybe unloaded before content side is created
if (model.contentWorker) {
model.contentWorker.destroy(reason);
}
model.contentWorker = null;
if (model.window) {
model.window.removeEventListener("pageshow", model.pageShow, true);
model.window.removeEventListener("pagehide", model.pageHide, true);
}
model.window = null;
// This method may be called multiple times,
// avoid dispatching `detach` event more than once
if (model.windowID) {
model.windowID = null;
events.off("inner-window-destroyed", model.documentUnload);
model.earlyEvents.length = 0;
emit(worker, 'detach');
}
model.inited = false;
});
isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
/**
* Tells content worker to unload itself and
* removes all the references from itself.
*/
destroy.define(Worker, function (worker, reason) {
detach(worker, reason);
modelFor(worker).inited = true;
// Specifying no type or listener removes all listeners
// from target
off(worker);
off(worker.port);
});
/**
* Events fired by workers
*/
function documentUnload ({ subject, data }) {
let model = modelFor(this);
let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (innerWinID != model.windowID) return false;
detach(this);
return true;
}
function pageShow () {
let model = modelFor(this);
model.contentWorker.emitSync('pageshow');
emit(this, 'pageshow');
model.frozen = false;
}
function pageHide () {
let model = modelFor(this);
model.contentWorker.emitSync('pagehide');
emit(this, 'pagehide');
model.frozen = true;
}
/**
* Fired from postMessage and emitEventToContent, or from the earlyMessage
* queue when fired before the content is loaded. Sends arguments to
* contentWorker if able
*/
function processMessage (worker, ...args) {
let model = modelFor(worker) || {};
if (!model.contentWorker)
throw new Error(ERR_DESTROYED);
if (model.frozen)
throw new Error(ERR_FROZEN);
model.contentWorker.emit.apply(null, args);
}
function createModel () {
return {
// List of messages fired before worker is initialized
earlyEvents: [],
// Is worker connected to the content worker sandbox ?
inited: false,
// Is worker being frozen? i.e related document is frozen in bfcache.
// Content script should not be reachable if frozen.
frozen: true,
/**
* Reference to the content side of the worker.
* @type {WorkerGlobalScope}
*/
contentWorker: null,
/**
* Reference to the window that is accessible from
* the content scripts.
* @type {Object}
*/
window: null
};
}
function createPort (worker) {
let port = EventTarget();
port.emit = emitEventToContent.bind(null, worker);
return port;
}
/**
* Emit a custom event to the content script,
* i.e. emit this event on `self.port`
*/
function emitEventToContent (worker, ...eventArgs) {
let model = modelFor(worker);
let args = ['event'].concat(eventArgs);
if (!model.inited) {
model.earlyEvents.push(args);
return;
}
processMessage.apply(null, [worker].concat(args));
}

Просмотреть файл

@ -34,6 +34,7 @@ const findAndRunTests = function findAndRunTests(options) {
exports.findAndRunTests = findAndRunTests;
var runnerWindows = new WeakMap();
var runnerTabs = new WeakMap();
const TestRunner = function TestRunner(options) {
options = options || {};
@ -41,6 +42,7 @@ const TestRunner = function TestRunner(options) {
// remember the id's for the open window and tab
let window = getMostRecentBrowserWindow();
runnerWindows.set(this, getInnerId(window));
runnerTabs.set(this, getTabId(getSelectedTab(window)));
this.fs = options.fs;
this.console = options.console || console;
@ -328,18 +330,31 @@ TestRunner.prototype = {
return all(winPromises).then(() => {
let browserWins = wins.filter(isBrowser);
let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
let newTabID = getTabId(getSelectedTab(wins[0]));
let oldTabID = runnerTabs.get(this);
let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
let failure = false;
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
failure = true;
this.fail("Should not be any unexpected windows open");
}
else if (hasMoreTabsOpen) {
failure = true;
this.fail("Should not be any unexpected tabs open");
}
else if (oldTabID != newTabID) {
failure = true;
runnerTabs.set(this, newTabID);
this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID);
}
if (failure) {
console.log("Windows open:");
for (let win of wins) {
if (isBrowser(win)) {
tabs = [];
tabs = getTabs(win);
console.log(win.location + " - " + tabs.map(getURI).join(", "));
}
else {

Просмотреть файл

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
'stability': 'unstable'
};
const events = require('./events.js');
exports.emit = (element, type, obj) => events.emit(element, type, obj, true);
exports.on = (element, type, listener, capture) => events.on(element, type, listener, capture, true);
exports.once = (element, type, listener, capture) => events.once(element, type, listener, capture, true);
exports.removeListener = (element, type, listener, capture) => events.removeListener(element, type, listener, capture, true);
exports.removed = events.removed;
exports.when = (element, eventName, capture) => events.when(element, eventName, capture ? capture : false, true);

Просмотреть файл

@ -0,0 +1,192 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Cu } = require("chrome");
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
// Utility function that returns copy of the given `text` with last character
// removed if it is `"s"`.
function singularify(text) {
return text[text.length - 1] === "s" ? text.substr(0, text.length - 1) : text;
}
// Utility function that takes event type, argument is passed to
// `document.createEvent` and returns name of the initializer method of the
// given event. Please note that there are some event types whose initializer
// methods can't be guessed by this function. For more details see following
// link: https://developer.mozilla.org/En/DOM/Document.createEvent
function getInitializerName(category) {
return "init" + singularify(category);
}
/**
* Registers an event `listener` on a given `element`, that will be called
* when events of specified `type` is dispatched on the `element`.
* @param {Element} element
* Dom element to register listener on.
* @param {String} type
* A string representing the
* [event type](https://developer.mozilla.org/en/DOM/event.type) to
* listen for.
* @param {Function} listener
* Function that is called whenever an event of the specified `type`
* occurs.
* @param {Boolean} capture
* If true, indicates that the user wishes to initiate capture. After
* initiating capture, all events of the specified type will be dispatched
* to the registered listener before being dispatched to any `EventTarget`s
* beneath it in the DOM tree. Events which are bubbling upward through
* the tree will not trigger a listener designated to use capture.
* See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
* for a detailed explanation.
*/
function on(element, type, listener, capture, shimmed = false) {
// `capture` defaults to `false`.
capture = capture || false;
if (shimmed) {
element.addEventListener(type, listener, capture);
} else {
ShimWaiver.getProperty(element, "addEventListener")(type, listener, capture);
}
}
exports.on = on;
/**
* Registers an event `listener` on a given `element`, that will be called
* only once, next time event of specified `type` is dispatched on the
* `element`.
* @param {Element} element
* Dom element to register listener on.
* @param {String} type
* A string representing the
* [event type](https://developer.mozilla.org/en/DOM/event.type) to
* listen for.
* @param {Function} listener
* Function that is called whenever an event of the specified `type`
* occurs.
* @param {Boolean} capture
* If true, indicates that the user wishes to initiate capture. After
* initiating capture, all events of the specified type will be dispatched
* to the registered listener before being dispatched to any `EventTarget`s
* beneath it in the DOM tree. Events which are bubbling upward through
* the tree will not trigger a listener designated to use capture.
* See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
* for a detailed explanation.
*/
function once(element, type, listener, capture, shimmed = false) {
on(element, type, function selfRemovableListener(event) {
removeListener(element, type, selfRemovableListener, capture, shimmed);
listener.apply(this, arguments);
}, capture, shimmed);
}
exports.once = once;
/**
* Unregisters an event `listener` on a given `element` for the events of the
* specified `type`.
*
* @param {Element} element
* Dom element to unregister listener from.
* @param {String} type
* A string representing the
* [event type](https://developer.mozilla.org/en/DOM/event.type) to
* listen for.
* @param {Function} listener
* Function that is called whenever an event of the specified `type`
* occurs.
* @param {Boolean} capture
* If true, indicates that the user wishes to initiate capture. After
* initiating capture, all events of the specified type will be dispatched
* to the registered listener before being dispatched to any `EventTarget`s
* beneath it in the DOM tree. Events which are bubbling upward through
* the tree will not trigger a listener designated to use capture.
* See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
* for a detailed explanation.
*/
function removeListener(element, type, listener, capture, shimmed = false) {
if (shimmed) {
element.removeEventListener(type, listener, capture);
} else {
ShimWaiver.getProperty(element, "removeEventListener")(type, listener, capture);
}
}
exports.removeListener = removeListener;
/**
* Emits event of the specified `type` and `category` on the given `element`.
* Specified `settings` are used to initialize event before dispatching it.
* @param {Element} element
* Dom element to dispatch event on.
* @param {String} type
* A string representing the
* [event type](https://developer.mozilla.org/en/DOM/event.type).
* @param {Object} options
* Options object containing following properties:
* - `category`: String passed to the `document.createEvent`. Option is
* optional and defaults to "UIEvents".
* - `initializer`: If passed it will be used as name of the method used
* to initialize event. If omitted name will be generated from the
* `category` field by prefixing it with `"init"` and removing last
* character if it matches `"s"`.
* - `settings`: Array of settings that are forwarded to the event
* initializer after firs `type` argument.
* @see https://developer.mozilla.org/En/DOM/Document.createEvent
*/
function emit(element, type, { category, initializer, settings }, shimmed = false) {
category = category || "UIEvents";
initializer = initializer || getInitializerName(category);
let document = element.ownerDocument;
let event = document.createEvent(category);
event[initializer].apply(event, [type].concat(settings));
if (shimmed) {
element.dispatchEvent(event);
} else {
ShimWaiver.getProperty(element, "dispatchEvent")(event);
}
};
exports.emit = emit;
// Takes DOM `element` and returns promise which is resolved
// when given element is removed from it's parent node.
const removed = element => {
return new Promise(resolve => {
const { MutationObserver } = element.ownerGlobal;
const observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
for (let node of mutation.removedNodes || []) {
if (node === element) {
observer.disconnect();
resolve(element);
}
}
}
});
observer.observe(element.parentNode, {childList: true});
});
};
exports.removed = removed;
const when = (element, eventName, capture=false, shimmed=false) => new Promise(resolve => {
const listener = event => {
if (shimmed) {
element.removeEventListener(eventName, listener, capture);
} else {
ShimWaiver.getProperty(element, "removeEventListener")(eventName, listener, capture);
}
resolve(event);
};
if (shimmed) {
element.addEventListener(eventName, listener, capture);
} else {
ShimWaiver.getProperty(element, "addEventListener")(eventName, listener, capture);
}
});
exports.when = when;

Просмотреть файл

@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { emit } = require("../events");
const { getCodeForKey, toJSON } = require("../../keyboard/utils");
const { has } = require("../../util/array");
const { isString } = require("../../lang/type");
const INITIALIZER = "initKeyEvent";
const CATEGORY = "KeyboardEvent";
function Options(options) {
if (!isString(options))
return options;
var { key, modifiers } = toJSON(options);
return {
key: key,
control: has(modifiers, "control"),
alt: has(modifiers, "alt"),
shift: has(modifiers, "shift"),
meta: has(modifiers, "meta")
};
}
var keyEvent = exports.keyEvent = function keyEvent(element, type, options) {
emit(element, type, {
initializer: INITIALIZER,
category: CATEGORY,
settings: [
!("bubbles" in options) || options.bubbles !== false,
!("cancelable" in options) || options.cancelable !== false,
"window" in options && options.window ? options.window : null,
"control" in options && !!options.control,
"alt" in options && !!options.alt,
"shift" in options && !!options.shift,
"meta" in options && !!options.meta,
getCodeForKey(options.key) || 0,
options.key.length === 1 ? options.key.charCodeAt(0) : 0
]
});
}
exports.keyDown = function keyDown(element, options) {
keyEvent(element, "keydown", Options(options));
};
exports.keyUp = function keyUp(element, options) {
keyEvent(element, "keyup", Options(options));
};
exports.keyPress = function keyPress(element, options) {
keyEvent(element, "keypress", Options(options));
};

Просмотреть файл

@ -0,0 +1,115 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci } = require("chrome");
const { Class } = require("../core/heritage");
const { List, addListItem, removeListItem } = require("../util/list");
const { EventTarget } = require("../event/target");
lazyRequire(this, "../event/core", "emit");
lazyRequire(this, "./utils", { "create": "makeFrame" });
lazyRequire(this, "../core/promise", "defer");
const { when: unload } = require("../system/unload");
lazyRequire(this, "../deprecated/api-utils", "validateOptions", "getTypeOf");
lazyRequire(this, "../addon/window", "window");
lazyRequire(this, "../util/array", "fromIterator");
// This cache is used to access friend properties between functions
// without exposing them on the public API.
var cache = new Set();
var elements = new WeakMap();
function contentLoaded(target) {
var deferred = defer();
target.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
// "DOMContentLoaded" events from nested frames propagate up to target,
// ignore events unless it's DOMContentLoaded for the given target.
if (event.target === target || event.target === target.contentDocument) {
target.removeEventListener("DOMContentLoaded", DOMContentLoaded);
deferred.resolve(target);
}
});
return deferred.promise;
}
function FrameOptions(options) {
options = options || {}
return validateOptions(options, FrameOptions.validator);
}
FrameOptions.validator = {
onReady: {
is: ["undefined", "function", "array"],
ok: function(v) {
if (getTypeOf(v) === "array") {
// make sure every item is a function
return v.every(item => typeof(item) === "function")
}
return true;
}
},
onUnload: {
is: ["undefined", "function"]
}
};
var HiddenFrame = Class({
extends: EventTarget,
initialize: function initialize(options) {
options = FrameOptions(options);
EventTarget.prototype.initialize.call(this, options);
},
get element() {
return elements.get(this);
},
toString: function toString() {
return "[object Frame]"
}
});
exports.HiddenFrame = HiddenFrame
function addHidenFrame(frame) {
if (!(frame instanceof HiddenFrame))
throw Error("The object to be added must be a HiddenFrame.");
// This instance was already added.
if (cache.has(frame)) return frame;
else cache.add(frame);
let element = makeFrame(window.document, {
nodeName: "iframe",
type: "content",
allowJavascript: true,
allowPlugins: true,
allowAuth: true,
});
elements.set(frame, element);
contentLoaded(element).then(function onFrameReady(element) {
emit(frame, "ready");
}, console.exception);
return frame;
}
exports.add = addHidenFrame
function removeHiddenFrame(frame) {
if (!(frame instanceof HiddenFrame))
throw Error("The object to be removed must be a HiddenFrame.");
if (!cache.has(frame)) return;
// Remove from cache before calling in order to avoid loop
cache.delete(frame);
emit(frame, "unload")
let element = frame.element
if (element) element.remove()
}
exports.remove = removeHiddenFrame;
unload(() => fromIterator(cache).forEach(removeHiddenFrame));

Просмотреть файл

@ -0,0 +1,500 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// Adapted version of:
// https://github.com/joyent/node/blob/v0.11.3/lib/path.js
// Shim process global from node.
var process = Object.create(require('../system'));
process.cwd = process.pathFor.bind(process, 'CurProcD');
// Update original check in node `process.platform === 'win32'` since in SDK it's `winnt`.
var isWindows = process.platform.indexOf('win') === 0;
// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
// if the path tries to go above the root, `up` ends up > 0
var up = 0;
for (var i = parts.length - 1; i >= 0; i--) {
var last = parts[i];
if (last === '.') {
parts.splice(i, 1);
} else if (last === '..') {
parts.splice(i, 1);
up++;
} else if (up) {
parts.splice(i, 1);
up--;
}
}
// if the path is allowed to go above the root, restore leading ..s
if (allowAboveRoot) {
for (; up--; up) {
parts.unshift('..');
}
}
return parts;
}
if (isWindows) {
// Regex to split a windows path into three parts: [*, device, slash,
// tail] windows-only
var splitDeviceRe =
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
// Regex to split the tail part of the above into [*, dir, basename, ext]
var splitTailRe =
/^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;
// Function to split a filename into [root, dir, basename, ext]
// windows version
var splitPath = function(filename) {
// Separate device+slash from tail
var result = splitDeviceRe.exec(filename),
device = (result[1] || '') + (result[2] || ''),
tail = result[3] || '';
// Split the tail into dir, basename and extension
var result2 = splitTailRe.exec(tail),
dir = result2[1],
basename = result2[2],
ext = result2[3];
return [device, dir, basename, ext];
};
var normalizeUNCRoot = function(device) {
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
};
// path.resolve([from ...], to)
// windows version
exports.resolve = function() {
var resolvedDevice = '',
resolvedTail = '',
resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1; i--) {
var path;
if (i >= 0) {
path = arguments[i];
} else if (!resolvedDevice) {
path = process.cwd();
} else {
// Windows has the concept of drive-specific current working
// directories. If we've resolved a drive letter but not yet an
// absolute path, get cwd for that drive. We're sure the device is not
// an unc path at this points, because unc paths are always absolute.
path = process.env['=' + resolvedDevice];
// Verify that a drive-local cwd was found and that it actually points
// to our drive. If not, default to the drive's root.
if (!path || path.substr(0, 3).toLowerCase() !==
resolvedDevice.toLowerCase() + '\\') {
path = resolvedDevice + '\\';
}
}
// Skip empty and invalid entries
if (typeof path !== 'string') {
throw new TypeError('Arguments to path.resolve must be strings');
} else if (!path) {
continue;
}
var result = splitDeviceRe.exec(path),
device = result[1] || '',
isUnc = device && device.charAt(1) !== ':',
isAbsolute = exports.isAbsolute(path),
tail = result[3];
if (device &&
resolvedDevice &&
device.toLowerCase() !== resolvedDevice.toLowerCase()) {
// This path points to another device so it is not applicable
continue;
}
if (!resolvedDevice) {
resolvedDevice = device;
}
if (!resolvedAbsolute) {
resolvedTail = tail + '\\' + resolvedTail;
resolvedAbsolute = isAbsolute;
}
if (resolvedDevice && resolvedAbsolute) {
break;
}
}
// Convert slashes to backslashes when `resolvedDevice` points to an UNC
// root. Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
resolvedDevice = normalizeUNCRoot(resolvedDevice);
}
// At this point the path should be resolved to a full absolute path,
// but handle relative paths to be safe (might happen when process.cwd()
// fails)
// Normalize the tail path
function f(p) {
return !!p;
}
resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
!resolvedAbsolute).join('\\');
return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
'.';
};
// windows version
exports.normalize = function(path) {
var result = splitDeviceRe.exec(path),
device = result[1] || '',
isUnc = device && device.charAt(1) !== ':',
isAbsolute = exports.isAbsolute(path),
tail = result[3],
trailingSlash = /[\\\/]$/.test(tail);
// If device is a drive letter, we'll normalize to lower case.
if (device && device.charAt(1) === ':') {
device = device[0].toLowerCase() + device.substr(1);
}
// Normalize the tail path
tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
return !!p;
}), !isAbsolute).join('\\');
if (!tail && !isAbsolute) {
tail = '.';
}
if (tail && trailingSlash) {
tail += '\\';
}
// Convert slashes to backslashes when `device` points to an UNC root.
// Also squash multiple slashes into a single one where appropriate.
if (isUnc) {
device = normalizeUNCRoot(device);
}
return device + (isAbsolute ? '\\' : '') + tail;
};
// windows version
exports.isAbsolute = function(path) {
var result = splitDeviceRe.exec(path),
device = result[1] || '',
isUnc = device && device.charAt(1) !== ':';
// UNC paths are always absolute
return !!result[2] || isUnc;
};
// windows version
exports.join = function() {
function f(p) {
if (typeof p !== 'string') {
throw new TypeError('Arguments to path.join must be strings');
}
return p;
}
var paths = Array.prototype.filter.call(arguments, f);
var joined = paths.join('\\');
// Make sure that the joined path doesn't start with two slashes, because
// normalize() will mistake it for an UNC path then.
//
// This step is skipped when it is very clear that the user actually
// intended to point at an UNC path. This is assumed when the first
// non-empty string arguments starts with exactly two slashes followed by
// at least one more non-slash character.
//
// Note that for normalize() to treat a path as an UNC path it needs to
// have at least 2 components, so we don't filter for that here.
// This means that the user can use join to construct UNC paths from
// a server name and a share name; for example:
// path.join('//server', 'share') -> '\\\\server\\share\')
if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
joined = joined.replace(/^[\\\/]{2,}/, '\\');
}
return exports.normalize(joined);
};
// path.relative(from, to)
// it will solve the relative path from 'from' to 'to', for instance:
// from = 'C:\\orandea\\test\\aaa'
// to = 'C:\\orandea\\impl\\bbb'
// The output of the function should be: '..\\..\\impl\\bbb'
// windows version
exports.relative = function(from, to) {
from = exports.resolve(from);
to = exports.resolve(to);
// windows is not case sensitive
var lowerFrom = from.toLowerCase();
var lowerTo = to.toLowerCase();
function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}
var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}
if (start > end) return [];
return arr.slice(start, end - start + 1);
}
var toParts = trim(to.split('\\'));
var lowerFromParts = trim(lowerFrom.split('\\'));
var lowerToParts = trim(lowerTo.split('\\'));
var length = Math.min(lowerFromParts.length, lowerToParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (lowerFromParts[i] !== lowerToParts[i]) {
samePartsLength = i;
break;
}
}
if (samePartsLength == 0) {
return to;
}
var outputParts = [];
for (var i = samePartsLength; i < lowerFromParts.length; i++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
return outputParts.join('\\');
};
exports.sep = '\\';
exports.delimiter = ';';
} else /* posix */ {
// Split a filename into [root, dir, basename, ext], unix version
// 'root' is just a slash, or nothing.
var splitPathRe =
/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
var splitPath = function(filename) {
return splitPathRe.exec(filename).slice(1);
};
// path.resolve([from ...], to)
// posix version
exports.resolve = function() {
var resolvedPath = '',
resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
var path = (i >= 0) ? arguments[i] : process.cwd();
// Skip empty and invalid entries
if (typeof path !== 'string') {
throw new TypeError('Arguments to path.resolve must be strings');
} else if (!path) {
continue;
}
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path.charAt(0) === '/';
}
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when process.cwd() fails)
// Normalize the path
resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
return !!p;
}), !resolvedAbsolute).join('/');
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
};
// path.normalize(path)
// posix version
exports.normalize = function(path) {
var isAbsolute = exports.isAbsolute(path),
trailingSlash = path.substr(-1) === '/';
// Normalize the path
path = normalizeArray(path.split('/').filter(function(p) {
return !!p;
}), !isAbsolute).join('/');
if (!path && !isAbsolute) {
path = '.';
}
if (path && trailingSlash) {
path += '/';
}
return (isAbsolute ? '/' : '') + path;
};
// posix version
exports.isAbsolute = function(path) {
return path.charAt(0) === '/';
};
// posix version
exports.join = function() {
var paths = Array.prototype.slice.call(arguments, 0);
return exports.normalize(paths.filter(function(p, index) {
if (typeof p !== 'string') {
throw new TypeError('Arguments to path.join must be strings');
}
return p;
}).join('/'));
};
// path.relative(from, to)
// posix version
exports.relative = function(from, to) {
from = exports.resolve(from).substr(1);
to = exports.resolve(to).substr(1);
function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}
var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}
if (start > end) return [];
return arr.slice(start, end - start + 1);
}
var fromParts = trim(from.split('/'));
var toParts = trim(to.split('/'));
var length = Math.min(fromParts.length, toParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (fromParts[i] !== toParts[i]) {
samePartsLength = i;
break;
}
}
var outputParts = [];
for (var i = samePartsLength; i < fromParts.length; i++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
return outputParts.join('/');
};
exports.sep = '/';
exports.delimiter = ':';
}
exports.dirname = function(path) {
var result = splitPath(path),
root = result[0],
dir = result[1];
if (!root && !dir) {
// No dirname whatsoever
return '.';
}
if (dir) {
// It has a dirname, strip trailing slash
dir = dir.substr(0, dir.length - 1);
}
return root + dir;
};
exports.basename = function(path, ext) {
var f = splitPath(path)[2];
// TODO: make this comparison case-insensitive on windows?
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
return f;
};
exports.extname = function(path) {
return splitPath(path)[3];
};
if (isWindows) {
exports._makeLong = function(path) {
// Note: this will *probably* throw somewhere.
if (typeof path !== 'string')
return path;
if (!path) {
return '';
}
var resolvedPath = exports.resolve(path);
if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
// path is local filesystem path, which needs to be converted
// to long UNC path.
return '\\\\?\\' + resolvedPath;
} else if (/^\\\\[^?.]/.test(resolvedPath)) {
// path is network UNC path, which needs to be converted
// to long UNC path.
return '\\\\?\\UNC\\' + resolvedPath.substring(2);
}
return path;
};
} else {
exports._makeLong = function(path) {
return path;
};
}

Просмотреть файл

@ -0,0 +1,40 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
const INVALID_HOTKEY = "Hotkey must have at least one modifier.";
const { toJSON: jsonify, toString: stringify,
isFunctionKey } = require("./keyboard/utils");
const { register, unregister } = require("./keyboard/hotkeys");
const Hotkey = exports.Hotkey = function Hotkey(options) {
if (!(this instanceof Hotkey))
return new Hotkey(options);
// Parsing key combination string.
let hotkey = jsonify(options.combo);
if (!isFunctionKey(hotkey.key) && !hotkey.modifiers.length) {
throw new TypeError(INVALID_HOTKEY);
}
this.onPress = options.onPress && options.onPress.bind(this);
this.toString = stringify.bind(null, hotkey);
// Registering listener on keyboard combination enclosed by this hotkey.
// Please note that `this.toString()` is a normalized version of
// `options.combination` where order of modifiers is sorted and `accel` is
// replaced with platform specific key.
register(this.toString(), this.onPress);
// We freeze instance before returning it in order to make it's properties
// read-only.
return Object.freeze(this);
};
Hotkey.prototype.destroy = function destroy() {
unregister(this.toString(), this.onPress);
};

Просмотреть файл

@ -0,0 +1,78 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci } = require("chrome");
const { id } = require("./self");
// placeholder, copied from bootstrap.js
var sanitizeId = function(id){
let uuidRe =
/^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
let domain = id.
toLowerCase().
replace(/@/g, "-at-").
replace(/\./g, "-dot-").
replace(uuidRe, "$1");
return domain
};
const PSEUDOURI = "indexeddb://" + sanitizeId(id) // https://bugzilla.mozilla.org/show_bug.cgi?id=779197
// Use XPCOM because `require("./url").URL` doesn't expose the raw uri object.
var principaluri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService).newURI(PSEUDOURI);
var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
var principal = ssm.createCodebasePrincipal(principaluri, {});
function toArray(args) {
return Array.prototype.slice.call(args);
}
function openInternal(args, forPrincipal, deleting) {
if (forPrincipal) {
args = toArray(args);
} else {
args = [principal].concat(toArray(args));
}
if (args.length == 2) {
args.push({ storage: "persistent" });
} else if (!deleting && args.length >= 3 && typeof args[2] === "number") {
args[2] = { version: args[2], storage: "persistent" };
}
if (deleting) {
return indexedDB.deleteForPrincipal.apply(indexedDB, args);
}
return indexedDB.openForPrincipal.apply(indexedDB, args);
}
exports.indexedDB = Object.freeze({
open: function () {
return openInternal(arguments, false, false);
},
deleteDatabase: function () {
return openInternal(arguments, false, true);
},
openForPrincipal: function () {
return openInternal(arguments, true, false);
},
deleteForPrincipal: function () {
return openInternal(arguments, true, true);
},
cmp: indexedDB.cmp.bind(indexedDB)
});
exports.IDBKeyRange = IDBKeyRange;
exports.DOMException = Ci.nsIDOMDOMException;

Просмотреть файл

@ -0,0 +1,73 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { windows, isBrowser, isInteractive, isDocumentLoaded,
getOuterId } = require("../window/utils");
const { InputPort } = require("./system");
const { lift, merges, foldp, keepIf, start, Input } = require("../event/utils");
const { patch } = require("diffpatcher/index");
const { Sequence, seq, filter, object, pairs } = require("../util/sequence");
// Create lazy iterators from the regular arrays, although
// once https://github.com/mozilla/addon-sdk/pull/1314 lands
// `windows` will be transforme to lazy iterators.
// When iterated over belowe sequences items will represent
// state of windows at the time of iteration.
const opened = seq(function*() {
const items = windows("navigator:browser", {includePrivate: true});
for (let item of items) {
yield [getOuterId(item), item];
}
});
const interactive = filter(([_, window]) => isInteractive(window), opened);
const loaded = filter(([_, window]) => isDocumentLoaded(window), opened);
// Helper function that converts given argument to a delta.
const Update = window => window && object([getOuterId(window), window]);
const Delete = window => window && object([getOuterId(window), null]);
// Signal represents delta for last top level window close.
const LastClosed = lift(Delete,
keepIf(isBrowser, null,
new InputPort({topic: "domwindowclosed"})));
exports.LastClosed = LastClosed;
const windowFor = document => document && document.defaultView;
// Signal represent delta for last top level window document becoming interactive.
const InteractiveDoc = new InputPort({topic: "chrome-document-interactive"});
const InteractiveWin = lift(windowFor, InteractiveDoc);
const LastInteractive = lift(Update, keepIf(isBrowser, null, InteractiveWin));
exports.LastInteractive = LastInteractive;
// Signal represent delta for last top level window loaded.
const LoadedDoc = new InputPort({topic: "chrome-document-loaded"});
const LoadedWin = lift(windowFor, LoadedDoc);
const LastLoaded = lift(Update, keepIf(isBrowser, null, LoadedWin));
exports.LastLoaded = LastLoaded;
const initialize = input => {
if (!input.initialized) {
input.value = object(...input.value);
Input.start(input);
input.initialized = true;
}
};
// Signal represents set of top level interactive windows, updated any
// time new window becomes interactive or one get's closed.
const Interactive = foldp(patch, interactive, merges([LastInteractive,
LastClosed]));
Interactive[start] = initialize;
exports.Interactive = Interactive;
// Signal represents set of top level loaded window, updated any time
// new window becomes interactive or one get's closed.
const Loaded = foldp(patch, loaded, merges([LastLoaded, LastClosed]));
Loaded[start] = initialize;
exports.Loaded = Loaded;

Просмотреть файл

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cu } = require("chrome");
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { receive } = require("../event/utils");
const { InputPort } = require("./system");
const { object} = require("../util/sequence");
const { getOuterId } = require("../window/utils");
const Input = function() {};
Input.prototype = Object.create(InputPort.prototype);
Input.prototype.onCustomizeStart = function (window) {
receive(this, object([getOuterId(window), true]));
}
Input.prototype.onCustomizeEnd = function (window) {
receive(this, object([getOuterId(window), null]));
}
Input.prototype.addListener = input => CustomizableUI.addListener(input);
Input.prototype.removeListener = input => CustomizableUI.removeListener(input);
exports.CustomizationInput = Input;

Просмотреть файл

@ -0,0 +1,85 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci } = require("chrome");
const { InputPort } = require("./system");
const { getFrameElement, getOuterId,
getOwnerBrowserWindow } = require("../window/utils");
const { isnt } = require("../lang/functional");
const { foldp, lift, merges, keepIf } = require("../event/utils");
const { object } = require("../util/sequence");
const { compose } = require("../lang/functional");
const { LastClosed } = require("./browser");
const { patch } = require("diffpatcher/index");
const Document = Ci.nsIDOMDocument;
const isntNull = isnt(null);
const frameID = frame => frame.id;
const browserID = compose(getOuterId, getOwnerBrowserWindow);
const isInnerFrame = frame =>
frame && frame.hasAttribute("data-is-sdk-inner-frame");
// Utility function that given content window loaded in our frame views returns
// an actual frame. This basically takes care of fact that actual frame document
// is loaded in the nested iframe. If content window is not loaded in the nested
// frame of the frame view it returs null.
const getFrame = document =>
document && document.defaultView && getFrameElement(document.defaultView);
const FrameInput = function(options) {
const input = keepIf(isInnerFrame, null,
lift(getFrame, new InputPort(options)));
return lift(frame => {
if (!frame) return frame;
const [id, owner] = [frameID(frame), browserID(frame)];
return object([id, {owners: object([owner, options.update])}]);
}, input);
};
const LastLoading = new FrameInput({topic: "document-element-inserted",
update: {readyState: "loading"}});
exports.LastLoading = LastLoading;
const LastInteractive = new FrameInput({topic: "content-document-interactive",
update: {readyState: "interactive"}});
exports.LastInteractive = LastInteractive;
const LastLoaded = new FrameInput({topic: "content-document-loaded",
update: {readyState: "complete"}});
exports.LastLoaded = LastLoaded;
const LastUnloaded = new FrameInput({topic: "content-page-hidden",
update: null});
exports.LastUnloaded = LastUnloaded;
// Represents state of SDK frames in form of data structure:
// {"frame#1": {"id": "frame#1",
// "inbox": {"data": "ping",
// "target": {"id": "frame#1", "owner": "outerWindowID#2"},
// "source": {"id": "frame#1"}}
// "url": "resource://addon-1/data/index.html",
// "owners": {"outerWindowID#1": {"readyState": "loading"},
// "outerWindowID#2": {"readyState": "complete"}}
//
//
// frame#2: {"id": "frame#2",
// "url": "resource://addon-1/data/main.html",
// "outbox": {"data": "pong",
// "source": {"id": "frame#2", "owner": "outerWindowID#1"}
// "target": {"id": "frame#2"}}
// "owners": {outerWindowID#1: {readyState: "interacitve"}}}}
const Frames = foldp(patch, {}, merges([
LastLoading,
LastInteractive,
LastLoaded,
LastUnloaded,
new InputPort({ id: "frame-mailbox" }),
new InputPort({ id: "frame-change" }),
new InputPort({ id: "frame-changed" })
]));
exports.Frames = Frames;

Просмотреть файл

@ -0,0 +1,113 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cr, Cu } = require("chrome");
const { Input, start, stop, end, receive, outputs } = require("../event/utils");
const { once, off } = require("../event/core");
const { id: addonID } = require("../self");
const unloadMessage = require("@loader/unload");
const observerService = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
const addonUnloadTopic = "sdk:loader:destroy";
const isXrayWrapper = Cu.isXrayWrapper;
// In the past SDK used to double-wrap notifications dispatched, which
// made them awkward to use outside of SDK. At present they no longer
// do that, although we still supported for legacy reasons.
const isLegacyWrapper = x =>
x && x.wrappedJSObject &&
"observersModuleSubjectWrapper" in x.wrappedJSObject;
const unwrapLegacy = x => x.wrappedJSObject.object;
// `InputPort` provides a way to create a signal out of the observer
// notification subject's for the given `topic`. If `options.initial`
// is provided it is used as initial value otherwise `null` is used.
// Constructor can be given `options.id` that will be used to create
// a `topic` which is namespaced to an add-on (this avoids conflicts
// when multiple add-on are used, although in a future host probably
// should just be shared across add-ons). It is also possible to
// specify a specific `topic` via `options.topic` which is used as
// without namespacing. Created signal ends whenever add-on is
// unloaded.
const InputPort = function InputPort({id, topic, initial}) {
this.id = id || topic;
this.topic = topic || "sdk:" + addonID + ":" + id;
this.value = initial === void(0) ? null : initial;
this.observing = false;
this[outputs] = [];
};
// InputPort type implements `Input` signal interface.
InputPort.prototype = new Input();
InputPort.prototype.constructor = InputPort;
// When port is started (which is when it's subgraph get's
// first subscriber) actual observer is registered.
InputPort.start = input => {
input.addListener(input);
// Also register add-on unload observer to end this signal
// when that happens.
addObserver(input, addonUnloadTopic, false);
};
InputPort.prototype[start] = InputPort.start;
InputPort.addListener = input => addObserver(input, input.topic, false);
InputPort.prototype.addListener = InputPort.addListener;
// When port is stopped (which is when it's subgraph has no
// no subcribers left) an actual observer unregistered.
// Note that port stopped once it ends as well (which is when
// add-on is unloaded).
InputPort.stop = input => {
input.removeListener(input);
removeObserver(input, addonUnloadTopic);
};
InputPort.prototype[stop] = InputPort.stop;
InputPort.removeListener = input => removeObserver(input, input.topic);
InputPort.prototype.removeListener = InputPort.removeListener;
// `InputPort` also implements `nsIObserver` interface and
// `nsISupportsWeakReference` interfaces as it's going to be used as such.
InputPort.prototype.QueryInterface = function(iid) {
if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference))
throw Cr.NS_ERROR_NO_INTERFACE;
return this;
};
// `InputPort` instances implement `observe` method, which is invoked when
// observer notifications are dispatched. The `subject` of that notification
// are received on this signal.
InputPort.prototype.observe = function(subject, topic, data) {
// Unwrap message from the subject. SDK used to have it's own version of
// wrappedJSObjects which take precedence, if subject has `wrappedJSObject`
// and it's not an XrayWrapper use it as message. Otherwise use subject as
// is.
const message = subject === null ? null :
isLegacyWrapper(subject) ? unwrapLegacy(subject) :
isXrayWrapper(subject) ? subject :
subject.wrappedJSObject ? subject.wrappedJSObject :
subject;
// If observer topic matches topic of the input port receive a message.
if (topic === this.topic) {
receive(this, message);
}
// If observe topic is add-on unload topic we create an end message.
if (topic === addonUnloadTopic && message === unloadMessage) {
end(this);
}
};
exports.InputPort = InputPort;

Просмотреть файл

@ -0,0 +1,351 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
'stability': 'experimental'
};
/*
* Encodings supported by TextEncoder/Decoder:
* utf-8, utf-16le, utf-16be
* http://encoding.spec.whatwg.org/#interface-textencoder
*
* Node however supports the following encodings:
* ascii, utf-8, utf-16le, usc2, base64, hex
*/
const { Cu } = require('chrome');
lazyRequire(this, 'sdk/lang/type', "isNumber");
Cu.importGlobalProperties(["TextEncoder", "TextDecoder"]);
exports.TextEncoder = TextEncoder;
exports.TextDecoder = TextDecoder;
/**
* Use WeakMaps to work around Bug 929146, which prevents us from adding
* getters or values to typed arrays
* https://bugzilla.mozilla.org/show_bug.cgi?id=929146
*/
const parents = new WeakMap();
const views = new WeakMap();
function Buffer(subject, encoding /*, bufferLength */) {
// Allow invocation without `new` constructor
if (!(this instanceof Buffer))
return new Buffer(subject, encoding, arguments[2]);
var type = typeof(subject);
switch (type) {
case 'number':
// Create typed array of the given size if number.
try {
let buffer = new Uint8Array(subject > 0 ? Math.floor(subject) : 0);
return buffer;
} catch (e) {
if (/invalid array length/.test(e.message) ||
/invalid arguments/.test(e.message))
throw new RangeError('Could not instantiate buffer: size of buffer may be too large');
else
throw new Error('Could not instantiate buffer');
}
break;
case 'string':
// If string encode it and use buffer for the returned Uint8Array
// to create a local patched version that acts like node buffer.
encoding = encoding || 'utf8';
return new Uint8Array(new TextEncoder(encoding).encode(subject).buffer);
case 'object':
// This form of the constructor uses the form of
// new Uint8Array(buffer, offset, length);
// So we can instantiate a typed array within the constructor
// to inherit the appropriate properties, where both the
// `subject` and newly instantiated buffer share the same underlying
// data structure.
if (arguments.length === 3)
return new Uint8Array(subject, encoding, arguments[2]);
// If array or alike just make a copy with a local patched prototype.
else
return new Uint8Array(subject);
default:
throw new TypeError('must start with number, buffer, array or string');
}
}
exports.Buffer = Buffer;
// Tests if `value` is a Buffer.
Buffer.isBuffer = value => value instanceof Buffer
// Returns true if the encoding is a valid encoding argument & false otherwise
Buffer.isEncoding = function (encoding) {
if (!encoding) return false;
try {
new TextDecoder(encoding);
} catch(e) {
return false;
}
return true;
}
// Gives the actual byte length of a string. encoding defaults to 'utf8'.
// This is not the same as String.prototype.length since that returns the
// number of characters in a string.
Buffer.byteLength = (value, encoding = 'utf8') =>
new TextEncoder(encoding).encode(value).byteLength
// Direct copy of the nodejs's buffer implementation:
// https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177
Buffer.concat = function(list, length) {
if (!Array.isArray(list))
throw new TypeError('Usage: Buffer.concat(list[, length])');
if (typeof length === 'undefined') {
length = 0;
for (var i = 0; i < list.length; i++)
length += list[i].length;
} else {
length = ~~length;
}
if (length < 0)
length = 0;
if (list.length === 0)
return new Buffer(0);
else if (list.length === 1)
return list[0];
if (length < 0)
throw new RangeError('length is not a positive number');
var buffer = new Buffer(length);
var pos = 0;
for (var i = 0; i < list.length; i++) {
var buf = list[i];
buf.copy(buffer, pos);
pos += buf.length;
}
return buffer;
};
// Node buffer is very much like Uint8Array although it has bunch of methods
// that typically can be used in combination with `DataView` while preserving
// access by index. Since in SDK each module has it's own set of bult-ins it
// ok to patch ours to make it nodejs Buffer compatible.
const Uint8ArraySet = Uint8Array.prototype.set
Buffer.prototype = Uint8Array.prototype;
Object.defineProperties(Buffer.prototype, {
parent: {
get: function() { return parents.get(this, undefined); }
},
view: {
get: function () {
let view = views.get(this, undefined);
if (view) return view;
view = new DataView(this.buffer);
views.set(this, view);
return view;
}
},
toString: {
value: function(encoding, start, end) {
encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8';
start = Math.max(0, ~~start);
end = Math.min(this.length, end === void(0) ? this.length : ~~end);
return new TextDecoder(encoding).decode(this.subarray(start, end));
}
},
toJSON: {
value: function() {
return { type: 'Buffer', data: Array.slice(this, 0) };
}
},
get: {
value: function(offset) {
return this[offset];
}
},
set: {
value: function(offset, value) { this[offset] = value; }
},
copy: {
value: function(target, offset, start, end) {
let length = this.length;
let targetLength = target.length;
offset = isNumber(offset) ? offset : 0;
start = isNumber(start) ? start : 0;
if (start < 0)
throw new RangeError('sourceStart is outside of valid range');
if (end < 0)
throw new RangeError('sourceEnd is outside of valid range');
// If sourceStart > sourceEnd, or targetStart > targetLength,
// zero bytes copied
if (start > end ||
offset > targetLength
)
return 0;
// If `end` is not defined, or if it is defined
// but would overflow `target`, redefine `end`
// so we can copy as much as we can
if (end - start > targetLength - offset ||
end == null) {
let remainingTarget = targetLength - offset;
let remainingSource = length - start;
if (remainingSource <= remainingTarget)
end = length;
else
end = start + remainingTarget;
}
Uint8ArraySet.call(target, this.subarray(start, end), offset);
return end - start;
}
},
slice: {
value: function(start, end) {
let length = this.length;
start = ~~start;
end = end != null ? end : length;
if (start < 0) {
start += length;
if (start < 0) start = 0;
} else if (start > length)
start = length;
if (end < 0) {
end += length;
if (end < 0) end = 0;
} else if (end > length)
end = length;
if (end < start)
end = start;
// This instantiation uses the new Uint8Array(buffer, offset, length) version
// of construction to share the same underling data structure
let buffer = new Buffer(this.buffer, start, end - start);
// If buffer has a value, assign its parent value to the
// buffer it shares its underlying structure with. If a slice of
// a slice, then use the root structure
if (buffer.length > 0)
parents.set(buffer, this.parent || this);
return buffer;
}
},
write: {
value: function(string, offset, length, encoding = 'utf8') {
// write(string, encoding);
if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) {
[offset, length, encoding] = [0, null, offset];
}
// write(string, offset, encoding);
else if (typeof(length) === 'string')
[length, encoding] = [null, length];
if (offset < 0 || offset > this.length)
throw new RangeError('offset is outside of valid range');
offset = ~~offset;
// Clamp length if it would overflow buffer, or if its
// undefined
if (length == null || length + offset > this.length)
length = this.length - offset;
let buffer = new TextEncoder(encoding).encode(string);
let result = Math.min(buffer.length, length);
if (buffer.length !== length)
buffer = buffer.subarray(0, length);
Uint8ArraySet.call(this, buffer, offset);
return result;
}
},
fill: {
value: function fill(value, start, end) {
let length = this.length;
value = value || 0;
start = start || 0;
end = end || length;
if (typeof(value) === 'string')
value = value.charCodeAt(0);
if (typeof(value) !== 'number' || isNaN(value))
throw TypeError('value is not a number');
if (end < start)
throw new RangeError('end < start');
// Fill 0 bytes; we're done
if (end === start)
return 0;
if (length == 0)
return 0;
if (start < 0 || start >= length)
throw RangeError('start out of bounds');
if (end < 0 || end > length)
throw RangeError('end out of bounds');
let index = start;
while (index < end) this[index++] = value;
}
}
});
// Define nodejs Buffer's getter and setter functions that just proxy
// to internal DataView's equivalent methods.
// TODO do we need to check architecture to see if it's default big/little endian?
[['readUInt16LE', 'getUint16', true],
['readUInt16BE', 'getUint16', false],
['readInt16LE', 'getInt16', true],
['readInt16BE', 'getInt16', false],
['readUInt32LE', 'getUint32', true],
['readUInt32BE', 'getUint32', false],
['readInt32LE', 'getInt32', true],
['readInt32BE', 'getInt32', false],
['readFloatLE', 'getFloat32', true],
['readFloatBE', 'getFloat32', false],
['readDoubleLE', 'getFloat64', true],
['readDoubleBE', 'getFloat64', false],
['readUInt8', 'getUint8'],
['readInt8', 'getInt8']].forEach(([alias, name, littleEndian]) => {
Object.defineProperty(Buffer.prototype, alias, {
value: function(offset) {
return this.view[name](offset, littleEndian);
}
});
});
[['writeUInt16LE', 'setUint16', true],
['writeUInt16BE', 'setUint16', false],
['writeInt16LE', 'setInt16', true],
['writeInt16BE', 'setInt16', false],
['writeUInt32LE', 'setUint32', true],
['writeUInt32BE', 'setUint32', false],
['writeInt32LE', 'setInt32', true],
['writeInt32BE', 'setInt32', false],
['writeFloatLE', 'setFloat32', true],
['writeFloatBE', 'setFloat32', false],
['writeDoubleLE', 'setFloat64', true],
['writeDoubleBE', 'setFloat64', false],
['writeUInt8', 'setUint8'],
['writeInt8', 'setInt8']].forEach(([alias, name, littleEndian]) => {
Object.defineProperty(Buffer.prototype, alias, {
value: function(value, offset) {
return this.view[name](offset, value, littleEndian);
}
});
});

Просмотреть файл

@ -0,0 +1,104 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
exports.ByteReader = ByteReader;
exports.ByteWriter = ByteWriter;
const {Cc, Ci} = require("chrome");
// This just controls the maximum number of bytes we read in at one time.
const BUFFER_BYTE_LEN = 0x8000;
function ByteReader(inputStream) {
const self = this;
let stream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(inputStream);
let manager = new StreamManager(this, stream);
this.read = function ByteReader_read(numBytes) {
manager.ensureOpened();
if (typeof(numBytes) !== "number")
numBytes = Infinity;
let data = "";
let read = 0;
try {
while (true) {
let avail = stream.available();
let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN);
if (toRead <= 0)
break;
data += stream.readBytes(toRead);
read += toRead;
}
}
catch (err) {
throw new Error("Error reading from stream: " + err);
}
return data;
};
}
function ByteWriter(outputStream) {
const self = this;
let stream = Cc["@mozilla.org/binaryoutputstream;1"].
createInstance(Ci.nsIBinaryOutputStream);
stream.setOutputStream(outputStream);
let manager = new StreamManager(this, stream);
this.write = function ByteWriter_write(str) {
manager.ensureOpened();
try {
stream.writeBytes(str, str.length);
}
catch (err) {
throw new Error("Error writing to stream: " + err);
}
};
}
// This manages the lifetime of stream, a ByteReader or ByteWriter. It defines
// closed and close() on stream and registers an unload listener that closes
// rawStream if it's still opened. It also provides ensureOpened(), which
// throws an exception if the stream is closed.
function StreamManager(stream, rawStream) {
const self = this;
this.rawStream = rawStream;
this.opened = true;
stream.__defineGetter__("closed", function stream_closed() {
return !self.opened;
});
stream.close = function stream_close() {
self.ensureOpened();
self.unload();
};
require("../system/unload").ensure(this);
}
StreamManager.prototype = {
ensureOpened: function StreamManager_ensureOpened() {
if (!this.opened)
throw new Error("The stream is closed and cannot be used.");
},
unload: function StreamManager_unload() {
this.rawStream.close();
this.opened = false;
}
};

Просмотреть файл

@ -0,0 +1,985 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci, CC } = require("chrome");
lazyRequire(this, "../timers", "setTimeout");
lazyRequire(this, "./stream", "Stream", "InputStream", "OutputStream");
lazyRequire(this, "../event/core", "emit", "on");
lazyRequire(this, "./buffer", "Buffer");
const { ns } = require("../core/namespace");
const { Class } = require("../core/heritage");
const LocalFile = CC("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1",
"nsIFileOutputStream", "init");
const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
"nsIFileInputStream", "init");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream", "setOutputStream");
const StreamPump = CC("@mozilla.org/network/input-stream-pump;1",
"nsIInputStreamPump", "init");
const { createOutputTransport, createInputTransport } =
Cc["@mozilla.org/network/stream-transport-service;1"].
getService(Ci.nsIStreamTransportService);
const { OPEN_UNBUFFERED } = Ci.nsITransport;
const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream;
const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile;
const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream;
const FILE_PERMISSION = 0o666;
const PR_UINT32_MAX = 0xfffffff;
// Values taken from:
// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615
const PR_RDONLY = 0x01;
const PR_WRONLY = 0x02;
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND = 0x10;
const PR_TRUNCATE = 0x20;
const PR_SYNC = 0x40;
const PR_EXCL = 0x80;
const FLAGS = {
"r": PR_RDONLY,
"r+": PR_RDWR,
"w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY,
"w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR,
"a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY,
"a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR
};
function accessor() {
let map = new WeakMap();
return function(fd, value) {
if (value === null) map.delete(fd);
if (value !== undefined) map.set(fd, value);
return map.get(fd);
}
}
var nsIFile = accessor();
var nsIFileInputStream = accessor();
var nsIFileOutputStream = accessor();
var nsIBinaryInputStream = accessor();
var nsIBinaryOutputStream = accessor();
// Just a contstant object used to signal that all of the file
// needs to be read.
const ALL = new String("Read all of the file");
function isWritable(mode) {
return !!(mode & PR_WRONLY || mode & PR_RDWR);
}
function isReadable(mode) {
return !!(mode & PR_RDONLY || mode & PR_RDWR);
}
function isString(value) {
return typeof(value) === "string";
}
function isFunction(value) {
return typeof(value) === "function";
}
function toArray(enumerator) {
let value = [];
while(enumerator.hasMoreElements())
value.push(enumerator.getNext())
return value
}
function getFileName(file) {
return file.QueryInterface(Ci.nsIFile).leafName;
}
function remove(path, recursive) {
let fd = new LocalFile(path)
if (fd.exists()) {
fd.remove(recursive || false);
}
else {
throw FSError("remove", "ENOENT", 34, path);
}
}
/**
* Utility function to convert either an octal number or string
* into an octal number
* 0777 => 0o777
* "0644" => 0o644
*/
function Mode(mode, fallback) {
return isString(mode) ? parseInt(mode, 8) : mode || fallback;
}
function Flags(flag) {
return !isString(flag) ? flag :
FLAGS[flag] || Error("Unknown file open flag: " + flag);
}
function FSError(op, code, errno, path, file, line) {
let error = Error(code + ", " + op + " " + path, file, line);
error.code = code;
error.path = path;
error.errno = errno;
return error;
}
const ReadStream = Class({
extends: InputStream,
initialize: function initialize(path, options) {
this.position = -1;
this.length = -1;
this.flags = "r";
this.mode = FILE_PERMISSION;
this.bufferSize = 64 * 1024;
options = options || {};
if ("flags" in options && options.flags)
this.flags = options.flags;
if ("bufferSize" in options && options.bufferSize)
this.bufferSize = options.bufferSize;
if ("length" in options && options.length)
this.length = options.length;
if ("position" in options && options.position !== undefined)
this.position = options.position;
let { flags, mode, position, length } = this;
let fd = isString(path) ? openSync(path, flags, mode) : path;
this.fd = fd;
let input = nsIFileInputStream(fd);
// Setting a stream position, unless it"s `-1` which means current position.
if (position >= 0)
input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
// We use `nsIStreamTransportService` service to transform blocking
// file input stream into a fully asynchronous stream that can be written
// without blocking the main thread.
let transport = createInputTransport(input, position, length, false);
// Open an input stream on a transport. We don"t pass flags to guarantee
// non-blocking stream semantics. Also we use defaults for segment size &
// count.
InputStream.prototype.initialize.call(this, {
asyncInputStream: transport.openInputStream(null, 0, 0)
});
// Close file descriptor on end and destroy the stream.
on(this, "end", _ => {
this.destroy();
emit(this, "close");
});
this.read();
},
destroy: function() {
closeSync(this.fd);
InputStream.prototype.destroy.call(this);
}
});
exports.ReadStream = ReadStream;
exports.createReadStream = function createReadStream(path, options) {
return new ReadStream(path, options);
};
const WriteStream = Class({
extends: OutputStream,
initialize: function initialize(path, options) {
this.drainable = true;
this.flags = "w";
this.position = -1;
this.mode = FILE_PERMISSION;
options = options || {};
if ("flags" in options && options.flags)
this.flags = options.flags;
if ("mode" in options && options.mode)
this.mode = options.mode;
if ("position" in options && options.position !== undefined)
this.position = options.position;
let { position, flags, mode } = this;
// If pass was passed we create a file descriptor out of it. Otherwise
// we just use given file descriptor.
let fd = isString(path) ? openSync(path, flags, mode) : path;
this.fd = fd;
let output = nsIFileOutputStream(fd);
// Setting a stream position, unless it"s `-1` which means current position.
if (position >= 0)
output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
// We use `nsIStreamTransportService` service to transform blocking
// file output stream into a fully asynchronous stream that can be written
// without blocking the main thread.
let transport = createOutputTransport(output, position, -1, false);
// Open an output stream on a transport. We don"t pass flags to guarantee
// non-blocking stream semantics. Also we use defaults for segment size &
// count.
OutputStream.prototype.initialize.call(this, {
asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0),
output: output
});
// For write streams "finish" basically means close.
on(this, "finish", _ => {
this.destroy();
emit(this, "close");
});
},
destroy: function() {
OutputStream.prototype.destroy.call(this);
closeSync(this.fd);
}
});
exports.WriteStream = WriteStream;
exports.createWriteStream = function createWriteStream(path, options) {
return new WriteStream(path, options);
};
const Stats = Class({
initialize: function initialize(path) {
let file = new LocalFile(path);
if (!file.exists()) throw FSError("stat", "ENOENT", 34, path);
nsIFile(this, file);
},
isDirectory: function() {
return nsIFile(this).isDirectory();
},
isFile: function() {
return nsIFile(this).isFile();
},
isSymbolicLink: function() {
return nsIFile(this).isSymlink();
},
get mode() {
return nsIFile(this).permissions;
},
get size() {
return nsIFile(this).fileSize;
},
get mtime() {
return nsIFile(this).lastModifiedTime;
},
isBlockDevice: function() {
return nsIFile(this).isSpecial();
},
isCharacterDevice: function() {
return nsIFile(this).isSpecial();
},
isFIFO: function() {
return nsIFile(this).isSpecial();
},
isSocket: function() {
return nsIFile(this).isSpecial();
},
// non standard
get exists() {
return nsIFile(this).exists();
},
get hidden() {
return nsIFile(this).isHidden();
},
get writable() {
return nsIFile(this).isWritable();
},
get readable() {
return nsIFile(this).isReadable();
}
});
exports.Stats = Stats;
const LStats = Class({
extends: Stats,
get size() {
return this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink :
nsIFile(this).fileSize;
},
get mtime() {
return this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink :
nsIFile(this).lastModifiedTime;
},
// non standard
get permissions() {
return this.isSymbolicLink() ? nsIFile(this).permissionsOfLink :
nsIFile(this).permissions;
}
});
const FStat = Class({
extends: Stats,
initialize: function initialize(fd) {
nsIFile(this, nsIFile(fd));
}
});
function noop() {}
function Async(wrapped) {
return function (path, callback) {
let args = Array.slice(arguments);
callback = args.pop();
// If node is not given a callback argument
// it just does not calls it.
if (typeof(callback) !== "function") {
args.push(callback);
callback = noop;
}
setTimeout(function() {
try {
var result = wrapped.apply(this, args);
if (result === undefined) callback(null);
else callback(null, result);
} catch (error) {
callback(error);
}
}, 0);
}
}
/**
* Synchronous rename(2)
*/
function renameSync(oldPath, newPath) {
let source = new LocalFile(oldPath);
let target = new LocalFile(newPath);
if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath);
return source.moveTo(target.parent, target.leafName);
};
exports.renameSync = renameSync;
/**
* Asynchronous rename(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var rename = Async(renameSync);
exports.rename = rename;
/**
* Test whether or not the given path exists by checking with the file system.
*/
function existsSync(path) {
return new LocalFile(path).exists();
}
exports.existsSync = existsSync;
var exists = Async(existsSync);
exports.exists = exists;
/**
* Synchronous ftruncate(2).
*/
function truncateSync(path, length) {
let fd = openSync(path, "w");
ftruncateSync(fd, length);
closeSync(fd);
}
exports.truncateSync = truncateSync;
/**
* Asynchronous ftruncate(2). No arguments other than a possible exception are
* given to the completion callback.
*/
function truncate(path, length, callback) {
open(path, "w", function(error, fd) {
if (error) return callback(error);
ftruncate(fd, length, function(error) {
if (error) {
closeSync(fd);
callback(error);
}
else {
close(fd, callback);
}
});
});
}
exports.truncate = truncate;
function ftruncate(fd, length, callback) {
write(fd, new Buffer(length), 0, length, 0, function(error) {
callback(error);
});
}
exports.ftruncate = ftruncate;
function ftruncateSync(fd, length = 0) {
writeSync(fd, new Buffer(length), 0, length, 0);
}
exports.ftruncateSync = ftruncateSync;
function chownSync(path, uid, gid) {
throw Error("Not implemented yet!!");
}
exports.chownSync = chownSync;
var chown = Async(chownSync);
exports.chown = chown;
function lchownSync(path, uid, gid) {
throw Error("Not implemented yet!!");
}
exports.lchownSync = chownSync;
var lchown = Async(lchown);
exports.lchown = lchown;
/**
* Synchronous chmod(2).
*/
function chmodSync (path, mode) {
let file;
try {
file = new LocalFile(path);
} catch(e) {
throw FSError("chmod", "ENOENT", 34, path);
}
file.permissions = Mode(mode);
}
exports.chmodSync = chmodSync;
/**
* Asynchronous chmod(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var chmod = Async(chmodSync);
exports.chmod = chmod;
/**
* Synchronous chmod(2).
*/
function fchmodSync(fd, mode) {
throw Error("Not implemented yet!!");
};
exports.fchmodSync = fchmodSync;
/**
* Asynchronous chmod(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var fchmod = Async(fchmodSync);
exports.fchmod = fchmod;
/**
* Synchronous stat(2). Returns an instance of `fs.Stats`
*/
function statSync(path) {
return new Stats(path);
};
exports.statSync = statSync;
/**
* Asynchronous stat(2). The callback gets two arguments (err, stats) where
* stats is a `fs.Stats` object. It looks like this:
*/
var stat = Async(statSync);
exports.stat = stat;
/**
* Synchronous lstat(2). Returns an instance of `fs.Stats`.
*/
function lstatSync(path) {
return new LStats(path);
};
exports.lstatSync = lstatSync;
/**
* Asynchronous lstat(2). The callback gets two arguments (err, stats) where
* stats is a fs.Stats object. lstat() is identical to stat(), except that if
* path is a symbolic link, then the link itself is stat-ed, not the file that
* it refers to.
*/
var lstat = Async(lstatSync);
exports.lstat = lstat;
/**
* Synchronous fstat(2). Returns an instance of `fs.Stats`.
*/
function fstatSync(fd) {
return new FStat(fd);
};
exports.fstatSync = fstatSync;
/**
* Asynchronous fstat(2). The callback gets two arguments (err, stats) where
* stats is a fs.Stats object.
*/
var fstat = Async(fstatSync);
exports.fstat = fstat;
/**
* Synchronous link(2).
*/
function linkSync(source, target) {
throw Error("Not implemented yet!!");
};
exports.linkSync = linkSync;
/**
* Asynchronous link(2). No arguments other than a possible exception are given
* to the completion callback.
*/
var link = Async(linkSync);
exports.link = link;
/**
* Synchronous symlink(2).
*/
function symlinkSync(source, target) {
throw Error("Not implemented yet!!");
};
exports.symlinkSync = symlinkSync;
/**
* Asynchronous symlink(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var symlink = Async(symlinkSync);
exports.symlink = symlink;
/**
* Synchronous readlink(2). Returns the resolved path.
*/
function readlinkSync(path) {
return new LocalFile(path).target;
};
exports.readlinkSync = readlinkSync;
/**
* Asynchronous readlink(2). The callback gets two arguments
* `(error, resolvedPath)`.
*/
var readlink = Async(readlinkSync);
exports.readlink = readlink;
/**
* Synchronous realpath(2). Returns the resolved path.
*/
function realpathSync(path) {
return new LocalFile(path).path;
};
exports.realpathSync = realpathSync;
/**
* Asynchronous realpath(2). The callback gets two arguments
* `(err, resolvedPath)`.
*/
var realpath = Async(realpathSync);
exports.realpath = realpath;
/**
* Synchronous unlink(2).
*/
var unlinkSync = remove;
exports.unlinkSync = unlinkSync;
/**
* Asynchronous unlink(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var unlink = Async(remove);
exports.unlink = unlink;
/**
* Synchronous rmdir(2).
*/
var rmdirSync = remove;
exports.rmdirSync = rmdirSync;
/**
* Asynchronous rmdir(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var rmdir = Async(rmdirSync);
exports.rmdir = rmdir;
/**
* Synchronous mkdir(2).
*/
function mkdirSync(path, mode) {
try {
return LocalFile(path).create(DIRECTORY_TYPE, Mode(mode));
} catch (error) {
// Adjust exception thorw to match ones thrown by node.
if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") {
let { fileName, lineNumber } = error;
error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber);
}
throw error;
}
};
exports.mkdirSync = mkdirSync;
/**
* Asynchronous mkdir(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var mkdir = Async(mkdirSync);
exports.mkdir = mkdir;
/**
* Synchronous readdir(3). Returns an array of filenames excluding `"."` and
* `".."`.
*/
function readdirSync(path) {
try {
return toArray(new LocalFile(path).directoryEntries).map(getFileName);
}
catch (error) {
// Adjust exception thorw to match ones thrown by node.
if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" ||
error.name === "NS_ERROR_FILE_NOT_FOUND")
{
let { fileName, lineNumber } = error;
error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber);
}
throw error;
}
};
exports.readdirSync = readdirSync;
/**
* Asynchronous readdir(3). Reads the contents of a directory. The callback
* gets two arguments `(error, files)` where `files` is an array of the names
* of the files in the directory excluding `"."` and `".."`.
*/
var readdir = Async(readdirSync);
exports.readdir = readdir;
/**
* Synchronous close(2).
*/
function closeSync(fd) {
let input = nsIFileInputStream(fd);
let output = nsIFileOutputStream(fd);
// Closing input stream and removing reference.
if (input) input.close();
// Closing output stream and removing reference.
if (output) output.close();
nsIFile(fd, null);
nsIFileInputStream(fd, null);
nsIFileOutputStream(fd, null);
nsIBinaryInputStream(fd, null);
nsIBinaryOutputStream(fd, null);
};
exports.closeSync = closeSync;
/**
* Asynchronous close(2). No arguments other than a possible exception are
* given to the completion callback.
*/
var close = Async(closeSync);
exports.close = close;
/**
* Synchronous open(2).
*/
function openSync(aPath, aFlag, aMode) {
let [ fd, flags, mode, file ] =
[ { path: aPath }, Flags(aFlag), Mode(aMode), LocalFile(aPath) ];
nsIFile(fd, file);
// If trying to open file for just read that does not exists
// need to throw exception as node does.
if (!file.exists() && !isWritable(flags))
throw FSError("open", "ENOENT", 34, aPath);
// If we want to open file in read mode we initialize input stream.
if (isReadable(flags)) {
let input = FileInputStream(file, flags, mode, DEFER_OPEN);
nsIFileInputStream(fd, input);
}
// If we want to open file in write mode we initialize output stream for it.
if (isWritable(flags)) {
let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
nsIFileOutputStream(fd, output);
}
return fd;
}
exports.openSync = openSync;
/**
* Asynchronous file open. See open(2). Flags can be
* `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`.
* The callback gets two arguments `(error, fd).
*/
var open = Async(openSync);
exports.open = open;
/**
* Synchronous version of buffer-based fs.write(). Returns the number of bytes
* written.
*/
function writeSync(fd, buffer, offset, length, position) {
if (length + offset > buffer.length) {
throw Error("Length is extends beyond buffer");
}
else if (length + offset !== buffer.length) {
buffer = buffer.slice(offset, offset + length);
}
let output = BinaryOutputStream(nsIFileOutputStream(fd));
nsIBinaryOutputStream(fd, output);
// We write content as a byte array as this will avoid any transcoding
// if content was a buffer.
output.writeByteArray(buffer.valueOf(), buffer.length);
output.flush();
};
exports.writeSync = writeSync;
/**
* Write buffer to the file specified by fd.
*
* `offset` and `length` determine the part of the buffer to be written.
*
* `position` refers to the offset from the beginning of the file where this
* data should be written. If `position` is `null`, the data will be written
* at the current position. See pwrite(2).
*
* The callback will be given three arguments `(error, written, buffer)` where
* written specifies how many bytes were written into buffer.
*
* Note that it is unsafe to use `fs.write` multiple times on the same file
* without waiting for the callback.
*/
function write(fd, buffer, offset, length, position, callback) {
if (!Buffer.isBuffer(buffer)) {
// (fd, data, position, encoding, callback)
let encoding = null;
[ position, encoding, callback ] = Array.slice(arguments, 1);
buffer = new Buffer(String(buffer), encoding);
offset = 0;
} else if (length + offset > buffer.length) {
throw Error("Length is extends beyond buffer");
} else if (length + offset !== buffer.length) {
buffer = buffer.slice(offset, offset + length);
}
let writeStream = new WriteStream(fd, { position: position,
length: length });
writeStream.on("error", callback);
writeStream.write(buffer, function onEnd() {
writeStream.destroy();
if (callback)
callback(null, buffer.length, buffer);
});
};
exports.write = write;
/**
* Synchronous version of string-based fs.read. Returns the number of
* bytes read.
*/
function readSync(fd, buffer, offset, length, position) {
let input = nsIFileInputStream(fd);
// Setting a stream position, unless it"s `-1` which means current position.
if (position >= 0)
input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
// We use `nsIStreamTransportService` service to transform blocking
// file input stream into a fully asynchronous stream that can be written
// without blocking the main thread.
let binaryInputStream = BinaryInputStream(input);
let count = length === ALL ? binaryInputStream.available() : length;
if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer);
else {
let chunk = new Buffer(count);
binaryInputStream.readArrayBuffer(count, chunk.buffer);
chunk.copy(buffer, offset);
}
return buffer.slice(offset, offset + count);
};
exports.readSync = readSync;
/**
* Read data from the file specified by `fd`.
*
* `buffer` is the buffer that the data will be written to.
* `offset` is offset within the buffer where writing will start.
*
* `length` is an integer specifying the number of bytes to read.
*
* `position` is an integer specifying where to begin reading from in the file.
* If `position` is `null`, data will be read from the current file position.
*
* The callback is given the three arguments, `(error, bytesRead, buffer)`.
*/
function read(fd, buffer, offset, length, position, callback) {
let bytesRead = 0;
let readStream = new ReadStream(fd, { position: position, length: length });
readStream.on("data", function onData(data) {
data.copy(buffer, offset + bytesRead);
bytesRead += data.length;
});
readStream.on("end", function onEnd() {
callback(null, bytesRead, buffer);
readStream.destroy();
});
};
exports.read = read;
/**
* Asynchronously reads the entire contents of a file.
* The callback is passed two arguments `(error, data)`, where data is the
* contents of the file.
*/
function readFile(path, encoding, callback) {
if (isFunction(encoding)) {
callback = encoding
encoding = null
}
let buffer = null;
try {
let readStream = new ReadStream(path);
readStream.on("data", function(data) {
if (!buffer) buffer = data;
else buffer = Buffer.concat([buffer, data], 2);
});
readStream.on("error", function onError(error) {
callback(error);
});
readStream.on("end", function onEnd() {
// Note: Need to destroy before invoking a callback
// so that file descriptor is released.
readStream.destroy();
callback(null, buffer);
});
}
catch (error) {
setTimeout(callback, 0, error);
}
};
exports.readFile = readFile;
/**
* Synchronous version of `fs.readFile`. Returns the contents of the path.
* If encoding is specified then this function returns a string.
* Otherwise it returns a buffer.
*/
function readFileSync(path, encoding) {
let fd = openSync(path, "r");
let size = fstatSync(fd).size;
let buffer = new Buffer(size);
try {
readSync(fd, buffer, 0, ALL, 0);
}
finally {
closeSync(fd);
}
return buffer;
};
exports.readFileSync = readFileSync;
/**
* Asynchronously writes data to a file, replacing the file if it already
* exists. data can be a string or a buffer.
*/
function writeFile(path, content, encoding, callback) {
if (!isString(path))
throw new TypeError('path must be a string');
try {
if (isFunction(encoding)) {
callback = encoding
encoding = null
}
if (isString(content))
content = new Buffer(content, encoding);
let writeStream = new WriteStream(path);
let error = null;
writeStream.end(content, function() {
writeStream.destroy();
callback(error);
});
writeStream.on("error", function onError(reason) {
error = reason;
writeStream.destroy();
});
} catch (error) {
callback(error);
}
};
exports.writeFile = writeFile;
/**
* The synchronous version of `fs.writeFile`.
*/
function writeFileSync(filename, data, encoding) {
// TODO: Implement this in bug 1148209 https://bugzilla.mozilla.org/show_bug.cgi?id=1148209
throw Error("Not implemented");
};
exports.writeFileSync = writeFileSync;
function utimesSync(path, atime, mtime) {
throw Error("Not implemented");
}
exports.utimesSync = utimesSync;
var utimes = Async(utimesSync);
exports.utimes = utimes;
function futimesSync(fd, atime, mtime, callback) {
throw Error("Not implemented");
}
exports.futimesSync = futimesSync;
var futimes = Async(futimesSync);
exports.futimes = futimes;
function fsyncSync(fd, atime, mtime, callback) {
throw Error("Not implemented");
}
exports.fsyncSync = fsyncSync;
var fsync = Async(fsyncSync);
exports.fsync = fsync;
/**
* Watch for changes on filename. The callback listener will be called each
* time the file is accessed.
*
* The second argument is optional. The options if provided should be an object
* containing two members a boolean, persistent, and interval, a polling value
* in milliseconds. The default is { persistent: true, interval: 0 }.
*/
function watchFile(path, options, listener) {
throw Error("Not implemented");
};
exports.watchFile = watchFile;
function unwatchFile(path, listener) {
throw Error("Not implemented");
}
exports.unwatchFile = unwatchFile;
function watch(path, options, listener) {
throw Error("Not implemented");
}
exports.watch = watch;

Просмотреть файл

@ -0,0 +1,441 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { CC, Cc, Ci, Cu, Cr, components } = require("chrome");
const { EventTarget } = require("../event/target");
const { Class } = require("../core/heritage");
lazyRequire(this, "../event/core", "emit");
lazyRequire(this, "./buffer", "Buffer");
lazyRequire(this, "../timers", "setTimeout");
const MultiplexInputStream = CC("@mozilla.org/io/multiplex-input-stream;1",
"nsIMultiplexInputStream");
const AsyncStreamCopier = CC("@mozilla.org/network/async-stream-copier;1",
"nsIAsyncStreamCopier", "init");
const StringInputStream = CC("@mozilla.org/io/string-input-stream;1",
"nsIStringInputStream");
const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
"nsIArrayBufferInputStream");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
const InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
"nsIInputStreamPump", "init");
const threadManager = Cc["@mozilla.org/thread-manager;1"].
getService(Ci.nsIThreadManager);
const eventTarget = Cc["@mozilla.org/network/stream-transport-service;1"].
getService(Ci.nsIEventTarget);
var isFunction = value => typeof(value) === "function"
function accessor() {
let map = new WeakMap();
return function(target, value) {
if (value)
map.set(target, value);
return map.get(target);
}
}
const Stream = Class({
extends: EventTarget,
initialize: function() {
this.readable = false;
this.writable = false;
this.encoding = null;
},
setEncoding: function setEncoding(encoding) {
this.encoding = String(encoding).toUpperCase();
},
pipe: function pipe(target, options) {
let source = this;
function onData(chunk) {
if (target.writable) {
if (false === target.write(chunk))
source.pause();
}
}
function onDrain() {
if (source.readable)
source.resume();
}
function onEnd() {
target.end();
}
function onPause() {
source.pause();
}
function onResume() {
if (source.readable)
source.resume();
}
function cleanup() {
source.removeListener("data", onData);
target.removeListener("drain", onDrain);
source.removeListener("end", onEnd);
target.removeListener("pause", onPause);
target.removeListener("resume", onResume);
source.removeListener("end", cleanup);
source.removeListener("close", cleanup);
target.removeListener("end", cleanup);
target.removeListener("close", cleanup);
}
if (!options || options.end !== false)
target.on("end", onEnd);
source.on("data", onData);
target.on("drain", onDrain);
target.on("resume", onResume);
target.on("pause", onPause);
source.on("end", cleanup);
source.on("close", cleanup);
target.on("end", cleanup);
target.on("close", cleanup);
emit(target, "pipe", source);
},
pause: function pause() {
emit(this, "pause");
},
resume: function resume() {
emit(this, "resume");
},
destroySoon: function destroySoon() {
this.destroy();
}
});
exports.Stream = Stream;
var nsIStreamListener = accessor();
var nsIInputStreamPump = accessor();
var nsIAsyncInputStream = accessor();
var nsIBinaryInputStream = accessor();
const StreamListener = Class({
initialize: function(stream) {
this.stream = stream;
},
// Next three methods are part of `nsIStreamListener` interface and are
// invoked by `nsIInputStreamPump.asyncRead`.
onDataAvailable: function(request, context, input, offset, count) {
let stream = this.stream;
let buffer = new ArrayBuffer(count);
nsIBinaryInputStream(stream).readArrayBuffer(count, buffer);
emit(stream, "data", new Buffer(buffer));
},
// Next two methods implement `nsIRequestObserver` interface and are invoked
// by `nsIInputStreamPump.asyncRead`.
onStartRequest: function() {},
// Called to signify the end of an asynchronous request. We only care to
// discover errors.
onStopRequest: function(request, context, status) {
let stream = this.stream;
stream.readable = false;
if (!components.isSuccessCode(status))
emit(stream, "error", status);
else
emit(stream, "end");
}
});
const InputStream = Class({
extends: Stream,
readable: false,
paused: false,
initialize: function initialize(options) {
let { asyncInputStream } = options;
this.readable = true;
let binaryInputStream = new BinaryInputStream(asyncInputStream);
let inputStreamPump = new InputStreamPump(asyncInputStream,
-1, -1, 0, 0, false);
let streamListener = new StreamListener(this);
nsIAsyncInputStream(this, asyncInputStream);
nsIInputStreamPump(this, inputStreamPump);
nsIBinaryInputStream(this, binaryInputStream);
nsIStreamListener(this, streamListener);
this.asyncInputStream = asyncInputStream;
this.inputStreamPump = inputStreamPump;
this.binaryInputStream = binaryInputStream;
},
get status() {
return nsIInputStreamPump(this).status;
},
read: function() {
nsIInputStreamPump(this).asyncRead(nsIStreamListener(this), null);
},
pause: function pause() {
this.paused = true;
nsIInputStreamPump(this).suspend();
emit(this, "paused");
},
resume: function resume() {
this.paused = false;
if (nsIInputStreamPump(this).isPending()) {
nsIInputStreamPump(this).resume();
emit(this, "resume");
}
},
close: function close() {
this.readable = false;
nsIInputStreamPump(this).cancel(Cr.NS_OK);
nsIBinaryInputStream(this).close();
nsIAsyncInputStream(this).close();
},
destroy: function destroy() {
this.close();
nsIInputStreamPump(this);
nsIAsyncInputStream(this);
nsIBinaryInputStream(this);
nsIStreamListener(this);
}
});
exports.InputStream = InputStream;
var nsIRequestObserver = accessor();
var nsIAsyncOutputStream = accessor();
var nsIAsyncStreamCopier = accessor();
var nsIMultiplexInputStream = accessor();
const RequestObserver = Class({
initialize: function(stream) {
this.stream = stream;
},
// Method is part of `nsIRequestObserver` interface that is
// invoked by `nsIAsyncStreamCopier.asyncCopy`.
onStartRequest: function() {},
// Method is part of `nsIRequestObserver` interface that is
// invoked by `nsIAsyncStreamCopier.asyncCopy`.
onStopRequest: function(request, context, status) {
let stream = this.stream;
stream.drained = true;
// Remove copied chunk.
let multiplexInputStream = nsIMultiplexInputStream(stream);
multiplexInputStream.removeStream(0);
// If there was an error report.
if (!components.isSuccessCode(status))
emit(stream, "error", status);
// If there more chunks in queue then flush them.
else if (multiplexInputStream.count)
stream.flush();
// If stream is still writable notify that queue has drained.
else if (stream.writable)
emit(stream, "drain");
// If stream is no longer writable close it.
else {
nsIAsyncStreamCopier(stream).cancel(Cr.NS_OK);
nsIMultiplexInputStream(stream).close();
nsIAsyncOutputStream(stream).close();
nsIAsyncOutputStream(stream).flush();
}
}
});
const OutputStreamCallback = Class({
initialize: function(stream) {
this.stream = stream;
},
// Method is part of `nsIOutputStreamCallback` interface that
// is invoked by `nsIAsyncOutputStream.asyncWait`. It is registered
// with `WAIT_CLOSURE_ONLY` flag that overrides the default behavior,
// causing the `onOutputStreamReady` notification to be suppressed until
// the stream becomes closed.
onOutputStreamReady: function(nsIAsyncOutputStream) {
emit(this.stream, "finish");
}
});
const OutputStream = Class({
extends: Stream,
writable: false,
drained: true,
get bufferSize() {
let multiplexInputStream = nsIMultiplexInputStream(this);
return multiplexInputStream && multiplexInputStream.available();
},
initialize: function initialize(options) {
let { asyncOutputStream, output } = options;
this.writable = true;
// Ensure that `nsIAsyncOutputStream` was provided.
asyncOutputStream.QueryInterface(Ci.nsIAsyncOutputStream);
// Create a `nsIMultiplexInputStream` and `nsIAsyncStreamCopier`. Former
// is used to queue written data chunks that `asyncStreamCopier` will
// asynchronously drain into `asyncOutputStream`.
let multiplexInputStream = MultiplexInputStream();
let asyncStreamCopier = AsyncStreamCopier(multiplexInputStream,
output || asyncOutputStream,
eventTarget,
// nsIMultiplexInputStream
// implemnts .readSegments()
true,
// nsIOutputStream may or
// may not implemnet
// .writeSegments().
false,
// Use default buffer size.
null,
// Should not close an input.
false,
// Should not close an output.
false);
// Create `requestObserver` implementing `nsIRequestObserver` interface
// in the constructor that's gonna be reused across several flushes.
let requestObserver = RequestObserver(this);
// Create observer that implements `nsIOutputStreamCallback` and register
// using `WAIT_CLOSURE_ONLY` flag. That way it will be notfied once
// `nsIAsyncOutputStream` is closed.
asyncOutputStream.asyncWait(OutputStreamCallback(this),
asyncOutputStream.WAIT_CLOSURE_ONLY,
0,
threadManager.currentThread);
nsIRequestObserver(this, requestObserver);
nsIAsyncOutputStream(this, asyncOutputStream);
nsIMultiplexInputStream(this, multiplexInputStream);
nsIAsyncStreamCopier(this, asyncStreamCopier);
this.asyncOutputStream = asyncOutputStream;
this.multiplexInputStream = multiplexInputStream;
this.asyncStreamCopier = asyncStreamCopier;
},
write: function write(content, encoding, callback) {
if (isFunction(encoding)) {
callback = encoding;
encoding = callback;
}
// If stream is not writable we throw an error.
if (!this.writable) throw Error("stream is not writable");
let chunk = null;
// If content is not a buffer then we create one out of it.
if (Buffer.isBuffer(content)) {
chunk = new ArrayBufferInputStream();
chunk.setData(content.buffer, 0, content.length);
}
else {
chunk = new StringInputStream();
chunk.setData(content, content.length);
}
if (callback)
this.once("drain", callback);
// Queue up chunk to be copied to output sync.
nsIMultiplexInputStream(this).appendStream(chunk);
this.flush();
return this.drained;
},
flush: function() {
if (this.drained) {
this.drained = false;
nsIAsyncStreamCopier(this).asyncCopy(nsIRequestObserver(this), null);
}
},
end: function end(content, encoding, callback) {
if (isFunction(content)) {
callback = content
content = callback
}
if (isFunction(encoding)) {
callback = encoding
encoding = callback
}
// Setting a listener to "finish" event if passed.
if (isFunction(callback))
this.once("finish", callback);
if (content)
this.write(content, encoding);
this.writable = false;
// Close `asyncOutputStream` only if output has drained. If it's
// not drained than `asyncStreamCopier` is busy writing, so let
// it finish. Note that since `this.writable` is false copier will
// close `asyncOutputStream` once output drains.
if (this.drained)
nsIAsyncOutputStream(this).close();
},
destroy: function destroy() {
nsIAsyncOutputStream(this).close();
nsIAsyncOutputStream(this);
nsIMultiplexInputStream(this);
nsIAsyncStreamCopier(this);
nsIRequestObserver(this);
}
});
exports.OutputStream = OutputStream;
const DuplexStream = Class({
extends: Stream,
implements: [InputStream, OutputStream],
allowHalfOpen: true,
initialize: function initialize(options) {
options = options || {};
let { readable, writable, allowHalfOpen } = options;
InputStream.prototype.initialize.call(this, options);
OutputStream.prototype.initialize.call(this, options);
if (readable === false)
this.readable = false;
if (writable === false)
this.writable = false;
if (allowHalfOpen === false)
this.allowHalfOpen = false;
// If in a half open state and it's disabled enforce end.
this.once("end", () => {
if (!this.allowHalfOpen && (!this.readable || !this.writable))
this.end();
});
},
destroy: function destroy(error) {
InputStream.prototype.destroy.call(this);
OutputStream.prototype.destroy.call(this);
}
});
exports.DuplexStream = DuplexStream;

Просмотреть файл

@ -0,0 +1,235 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci, Cu, components } = require("chrome");
const { ensure } = require("../system/unload");
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best
// performance we use it, too.
const BUFFER_BYTE_LEN = 0x8000;
const PR_UINT32_MAX = 0xffffffff;
const DEFAULT_CHARSET = "UTF-8";
/**
* An input stream that reads text from a backing stream using a given text
* encoding.
*
* @param inputStream
* The stream is backed by this nsIInputStream. It must already be
* opened.
* @param charset
* Text in inputStream is expected to be in this character encoding. If
* not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl for
* documentation on how to determine other valid values for this.
*/
function TextReader(inputStream, charset) {
charset = checkCharset(charset);
let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
stream.init(inputStream, charset, BUFFER_BYTE_LEN,
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
let manager = new StreamManager(this, stream);
/**
* Reads a string from the stream. If the stream is closed, an exception is
* thrown.
*
* @param numChars
* The number of characters to read. If not given, the remainder of
* the stream is read.
* @return The string read. If the stream is already at EOS, returns the
* empty string.
*/
this.read = function TextReader_read(numChars) {
manager.ensureOpened();
let readAll = false;
if (typeof(numChars) === "number")
numChars = Math.max(numChars, 0);
else
readAll = true;
let str = "";
let totalRead = 0;
let chunkRead = 1;
// Read in numChars or until EOS, whichever comes first. Note that the
// units here are characters, not bytes.
while (true) {
let chunk = {};
let toRead = readAll ?
PR_UINT32_MAX :
Math.min(numChars - totalRead, PR_UINT32_MAX);
if (toRead <= 0 || chunkRead <= 0)
break;
// The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call
// to readString, enough to fill its byte buffer. chunkRead will be the
// number of characters encoded by the bytes in that buffer.
chunkRead = stream.readString(toRead, chunk);
str += chunk.value;
totalRead += chunkRead;
}
return str;
};
}
exports.TextReader = TextReader;
/**
* A buffered output stream that writes text to a backing stream using a given
* text encoding.
*
* @param outputStream
* The stream is backed by this nsIOutputStream. It must already be
* opened.
* @param charset
* Text will be written to outputStream using this character encoding.
* If not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl
* for documentation on how to determine other valid values for this.
*/
function TextWriter(outputStream, charset) {
charset = checkCharset(charset);
let stream = outputStream;
// Buffer outputStream if it's not already.
let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
if (!ioUtils.outputStreamIsBuffered(outputStream)) {
stream = Cc["@mozilla.org/network/buffered-output-stream;1"].
createInstance(Ci.nsIBufferedOutputStream);
stream.init(outputStream, BUFFER_BYTE_LEN);
}
// I'd like to use nsIConverterOutputStream. But NetUtil.asyncCopy(), which
// we use below in writeAsync(), naturally expects its sink to be an instance
// of nsIOutputStream, which nsIConverterOutputStream's only implementation is
// not. So we use uconv and manually convert all strings before writing to
// outputStream.
let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
uconv.charset = charset;
let manager = new StreamManager(this, stream);
/**
* Flushes the backing stream's buffer.
*/
this.flush = function TextWriter_flush() {
manager.ensureOpened();
stream.flush();
};
/**
* Writes a string to the stream. If the stream is closed, an exception is
* thrown.
*
* @param str
* The string to write.
*/
this.write = function TextWriter_write(str) {
manager.ensureOpened();
let istream = uconv.convertToInputStream(str);
let len = istream.available();
while (len > 0) {
stream.writeFrom(istream, len);
len = istream.available();
}
istream.close();
};
/**
* Writes a string on a background thread. After the write completes, the
* backing stream's buffer is flushed, and both the stream and the backing
* stream are closed, also on the background thread. If the stream is already
* closed, an exception is thrown immediately.
*
* @param str
* The string to write.
* @param callback
* An optional function. If given, it's called as callback(error) when
* the write completes. error is an Error object or undefined if there
* was no error. Inside callback, |this| is the stream object.
*/
this.writeAsync = function TextWriter_writeAsync(str, callback) {
manager.ensureOpened();
let istream = uconv.convertToInputStream(str);
NetUtil.asyncCopy(istream, stream, (result) => {
let err = components.isSuccessCode(result) ? undefined :
new Error("An error occured while writing to the stream: " + result);
if (err)
console.error(err);
// asyncCopy() closes its output (and input) stream.
manager.opened = false;
if (typeof(callback) === "function") {
try {
callback.call(this, err);
}
catch (exc) {
console.exception(exc);
}
}
});
};
}
exports.TextWriter = TextWriter;
// This manages the lifetime of stream, a TextReader or TextWriter. It defines
// closed and close() on stream and registers an unload listener that closes
// rawStream if it's still opened. It also provides ensureOpened(), which
// throws an exception if the stream is closed.
function StreamManager(stream, rawStream) {
this.rawStream = rawStream;
this.opened = true;
/**
* True iff the stream is closed.
*/
stream.__defineGetter__("closed", () => !this.opened);
/**
* Closes both the stream and its backing stream. If the stream is already
* closed, an exception is thrown. For TextWriters, this first flushes the
* backing stream's buffer.
*/
stream.close = () => {
this.ensureOpened();
this.unload();
};
ensure(this);
}
StreamManager.prototype = {
ensureOpened: function StreamManager_ensureOpened() {
if (!this.opened)
throw new Error("The stream is closed and cannot be used.");
},
unload: function StreamManager_unload() {
// TextWriter.writeAsync() causes rawStream to close and therefore sets
// opened to false, so check that we're still opened.
if (this.opened) {
// Calling close() on both an nsIUnicharInputStream and
// nsIBufferedOutputStream closes their backing streams. It also forces
// nsIOutputStreams to flush first.
this.rawStream.close();
this.opened = false;
}
}
};
function checkCharset(charset) {
return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;
}

Просмотреть файл

@ -0,0 +1,110 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { observer: keyboardObserver } = require("./observer");
const { getKeyForCode, normalize, isFunctionKey,
MODIFIERS } = require("./utils");
/**
* Register a global `hotkey` that executes `listener` when the key combination
* in `hotkey` is pressed. If more then one `listener` is registered on the same
* key combination only last one will be executed.
*
* @param {string} hotkey
* Key combination in the format of 'modifier key'.
*
* Examples:
*
* "accel s"
* "meta shift i"
* "control alt d"
*
* Modifier keynames:
*
* - **shift**: The Shift key.
* - **alt**: The Alt key. On the Macintosh, this is the Option key. On
* Macintosh this can only be used in conjunction with another modifier,
* since `Alt+Letter` combinations are reserved for entering special
* characters in text.
* - **meta**: The Meta key. On the Macintosh, this is the Command key.
* - **control**: The Control key.
* - **accel**: The key used for keyboard shortcuts on the user's platform,
* which is Control on Windows and Linux, and Command on Mac. Usually, this
* would be the value you would use.
*
* @param {function} listener
* Function to execute when the `hotkey` is executed.
*/
exports.register = function register(hotkey, listener) {
hotkey = normalize(hotkey);
hotkeys[hotkey] = listener;
};
/**
* Unregister a global `hotkey`. If passed `listener` is not the one registered
* for the given `hotkey`, the call to this function will be ignored.
*
* @param {string} hotkey
* Key combination in the format of 'modifier key'.
* @param {function} listener
* Function that will be invoked when the `hotkey` is pressed.
*/
exports.unregister = function unregister(hotkey, listener) {
hotkey = normalize(hotkey);
if (hotkeys[hotkey] === listener)
delete hotkeys[hotkey];
};
/**
* Map of hotkeys and associated functions.
*/
const hotkeys = exports.hotkeys = {};
keyboardObserver.on("keydown", function onKeypress(event, window) {
let key, modifiers = [];
let isChar = "isChar" in event && event.isChar;
let which = "which" in event ? event.which : null;
let keyCode = "keyCode" in event ? event.keyCode : null;
if ("shiftKey" in event && event.shiftKey)
modifiers.push("shift");
if ("altKey" in event && event.altKey)
modifiers.push("alt");
if ("ctrlKey" in event && event.ctrlKey)
modifiers.push("control");
if ("metaKey" in event && event.metaKey)
modifiers.push("meta");
// If it's not a printable character then we fall back to a human readable
// equivalent of one of the following constants.
// http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
key = getKeyForCode(keyCode);
// If only non-function (f1 - f24) key or only modifiers are pressed we don't
// have a valid combination so we return immediately (Also, sometimes
// `keyCode` may be one for the modifier which means we do not have a
// modifier).
if (!key || (!isFunctionKey(key) && !modifiers.length) || key in MODIFIERS)
return;
let combination = normalize({ key: key, modifiers: modifiers });
let hotkey = hotkeys[combination];
if (hotkey) {
try {
hotkey();
} catch (exception) {
console.exception(exception);
} finally {
// Work around bug 582052 by preventing the (nonexistent) default action.
event.preventDefault();
}
}
});

Просмотреть файл

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Class } = require("../core/heritage");
const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { browserWindowIterator } = require('../deprecated/window-utils');
const { isBrowser } = require('../window/utils');
const { observer: windowObserver } = require("../windows/observer");
// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
implements: [DOMEventAssembler, EventTarget],
initialize() {
// Adding each opened window to a list of observed windows.
windowObserver.on("open", window => {
if (isBrowser(window))
this.observe(window);
});
// Removing each closed window form the list of observed windows.
windowObserver.on("close", window => {
if (isBrowser(window))
this.ignore(window);
});
// Making observer aware of already opened windows.
for (let window of browserWindowIterator()) {
this.observe(window);
}
},
/**
* Events that are supported and emitted by the module.
*/
supportedEventsTypes: [ "keydown", "keyup", "keypress" ],
/**
* Function handles all the supported events on all the windows that are
* observed. Method is used to proxy events to the listeners registered on
* this event emitter.
* @param {Event} event
* Keyboard event being emitted.
*/
handleEvent(event) {
emit(this, event.type, event, event.target.ownerGlobal || undefined);
}
});
exports.observer = new Observer();

Просмотреть файл

@ -0,0 +1,189 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Cc, Ci } = require("chrome");
const runtime = require("../system/runtime");
const { isString } = require("../lang/type");
const array = require("../util/array");
const SWP = "{{SEPARATOR}}";
const SEPARATOR = "-"
const INVALID_COMBINATION = "Hotkey key combination must contain one or more " +
"modifiers and only one key";
// Map of modifier key mappings.
const MODIFIERS = exports.MODIFIERS = {
'accel': runtime.OS === "Darwin" ? 'meta' : 'control',
'meta': 'meta',
'control': 'control',
'ctrl': 'control',
'option': 'alt',
'command': 'meta',
'alt': 'alt',
'shift': 'shift'
};
// Hash of key:code pairs for all the chars supported by `nsIDOMKeyEvent`.
// This is just a copy of the `nsIDOMKeyEvent` hash with normalized names.
// @See: http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
const CODES = exports.CODES = new function Codes() {
let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent;
// Names that will be substituted with a shorter analogs.
let aliases = {
'subtract': '-',
'add': '+',
'equals': '=',
'slash': '/',
'backslash': '\\',
'openbracket': '[',
'closebracket': ']',
'quote': '\'',
'backquote': '`',
'period': '.',
'semicolon': ';',
'comma': ','
};
// Normalizing keys and copying values to `this` object.
Object.keys(nsIDOMKeyEvent).filter(function(key) {
// Filter out only key codes.
return key.indexOf('DOM_VK') === 0;
}).map(function(key) {
// Map to key:values
return [ key, nsIDOMKeyEvent[key] ];
}).map(function([key, value]) {
return [ key.replace('DOM_VK_', '').replace('_', '').toLowerCase(), value ];
}).forEach(function ([ key, value ]) {
this[aliases[key] || key] = value;
}, this);
};
// Inverted `CODES` hash of `code:key`.
const KEYS = exports.KEYS = new function Keys() {
Object.keys(CODES).forEach(function(key) {
this[CODES[key]] = key;
}, this)
}
exports.getKeyForCode = function getKeyForCode(code) {
return (code in KEYS) && KEYS[code];
};
exports.getCodeForKey = function getCodeForKey(key) {
return (key in CODES) && CODES[key];
};
/**
* Utility function that takes string or JSON that defines a `hotkey` and
* returns normalized string version of it.
* @param {JSON|String} hotkey
* @param {String} [separator=" "]
* Optional string that represents separator used to concatenate keys in the
* given `hotkey`.
* @returns {String}
* @examples
*
* require("keyboard/hotkeys").normalize("b Shift accel");
* // 'control shift b' -> on windows & linux
* // 'meta shift b' -> on mac
* require("keyboard/hotkeys").normalize("alt-d-shift", "-");
* // 'alt shift d'
*/
var normalize = exports.normalize = function normalize(hotkey, separator) {
if (!isString(hotkey))
hotkey = toString(hotkey, separator);
return toString(toJSON(hotkey, separator), separator);
};
/*
* Utility function that splits a string of characters that defines a `hotkey`
* into modifier keys and the defining key.
* @param {String} hotkey
* @param {String} [separator=" "]
* Optional string that represents separator used to concatenate keys in the
* given `hotkey`.
* @returns {JSON}
* @examples
*
* require("keyboard/hotkeys").toJSON("accel shift b");
* // { key: 'b', modifiers: [ 'control', 'shift' ] } -> on windows & linux
* // { key: 'b', modifiers: [ 'meta', 'shift' ] } -> on mac
*
* require("keyboard/hotkeys").normalize("alt-d-shift", "-");
* // { key: 'd', modifiers: [ 'alt', 'shift' ] }
*/
var toJSON = exports.toJSON = function toJSON(hotkey, separator) {
separator = separator || SEPARATOR;
// Since default separator is `-`, combination may take form of `alt--`. To
// avoid misbehavior we replace `--` with `-{{SEPARATOR}}` where
// `{{SEPARATOR}}` can be swapped later.
hotkey = hotkey.toLowerCase().replace(separator + separator, separator + SWP);
let value = {};
let modifiers = [];
let keys = hotkey.split(separator);
keys.forEach(function(name) {
// If name is `SEPARATOR` than we swap it back.
if (name === SWP)
name = separator;
if (name in MODIFIERS) {
array.add(modifiers, MODIFIERS[name]);
} else {
if (!value.key)
value.key = name;
else
throw new TypeError(INVALID_COMBINATION);
}
});
if (!value.key)
throw new TypeError(INVALID_COMBINATION);
value.modifiers = modifiers.sort();
return value;
};
/**
* Utility function that takes object that defines a `hotkey` and returns
* string representation of it.
*
* _Please note that this function does not validates data neither it normalizes
* it, if you are unsure that data is well formed use `normalize` function
* instead.
*
* @param {JSON} hotkey
* @param {String} [separator=" "]
* Optional string that represents separator used to concatenate keys in the
* given `hotkey`.
* @returns {String}
* @examples
*
* require("keyboard/hotkeys").toString({
* key: 'b',
* modifiers: [ 'control', 'shift' ]
* }, '+');
* // 'control+shift+b
*
*/
var toString = exports.toString = function toString(hotkey, separator) {
let keys = hotkey.modifiers.slice();
keys.push(hotkey.key);
return keys.join(separator || SEPARATOR);
};
/**
* Utility function takes `key` name and returns `true` if it's function key
* (F1, ..., F24) and `false` if it's not.
*/
var isFunctionKey = exports.isFunctionKey = function isFunctionKey(key) {
var $
return key[0].toLowerCase() === 'f' &&
($ = parseInt(key.substr(1)), 0 < $ && $ < 25);
};

Просмотреть файл

@ -0,0 +1,94 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
lazyRequireModule(this, "./l10n/json/core", "json");
lazyRequire(this, "./l10n/core", {"get": "getKey"});
lazyRequireModule(this, "./l10n/properties/core", "properties");
lazyRequire(this, "./l10n/plural-rules", "getRulesForLocale");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
// Retrieve the plural mapping function
XPCOMUtils.defineLazyGetter(this, "pluralMappingFunction",
() => getRulesForLocale(json.language()) ||
getRulesForLocale("en"));
exports.get = function get(k) {
// For now, we only accept a "string" as first argument
// TODO: handle plural forms in gettext pattern
if (typeof k !== "string")
throw new Error("First argument of localization method should be a string");
let n = arguments[1];
// Get translation from big hashmap or default to hard coded string:
let localized = getKey(k, n) || k;
// # Simplest usecase:
// // String hard coded in source code:
// _("Hello world")
// // Identifier of a key stored in properties file
// _("helloString")
if (arguments.length <= 1)
return localized;
let args = Array.slice(arguments);
let placeholders = [null, ...args.slice(typeof(n) === "number" ? 2 : 1)];
if (typeof localized == "object" && "other" in localized) {
// # Plural form:
// // Strings hard coded in source code:
// _(["One download", "%d downloads"], 10);
// // Identifier of a key stored in properties file
// _("downloadNumber", 0);
let n = arguments[1];
// First handle simple universal forms that may not be mandatory
// for each language, (i.e. not different than 'other' form,
// but still usefull for better phrasing)
// For example 0 in english is the same form than 'other'
// but we accept 'zero' form if specified in localization file
if (n === 0 && "zero" in localized)
localized = localized["zero"];
else if (n === 1 && "one" in localized)
localized = localized["one"];
else if (n === 2 && "two" in localized)
localized = localized["two"];
else {
let pluralForm = pluralMappingFunction(n);
if (pluralForm in localized)
localized = localized[pluralForm];
else // Fallback in case of error: missing plural form
localized = localized["other"];
}
// Simulate a string with one placeholder:
args = [null, n];
}
// # String with placeholders:
// // Strings hard coded in source code:
// _("Hello %s", username)
// // Identifier of a key stored in properties file
// _("helloString", username)
// * We supports `%1s`, `%2s`, ... pattern in order to change arguments order
// in translation.
// * In case of plural form, we has `%d` instead of `%s`.
let offset = 1;
if (placeholders.length > 1) {
args = placeholders;
}
localized = localized.replace(/%(\d*)[sd]/g, (v, n) => {
let rv = args[n != "" ? n : offset];
offset++;
return rv;
});
return localized;
}

Просмотреть файл

@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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";
lazyRequireModule(this, "./json/core", "json");
lazyRequireModule(this, "./properties/core", "properties");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "get",
() => json.usingJSON ? json.get : properties.get);
module.exports = Object.freeze({
get get() { return get; }, // ... yeah.
});

Просмотреть файл

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { processes, remoteRequire } = require("../remote/parent");
remoteRequire("sdk/content/l10n-html");
var enabled = false;
function enable() {
if (!enabled) {
processes.port.emit("sdk/l10n/html/enable");
enabled = true;
}
}
exports.enable = enable;
function disable() {
if (enabled) {
processes.port.emit("sdk/l10n/html/disable");
enabled = false;
}
}
exports.disable = disable;
processes.forEvery(process => {
process.port.emit(enabled ? "sdk/l10n/html/enable" : "sdk/l10n/html/disable");
});

Просмотреть файл

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
var usingJSON = false;
var hash = {}, bestMatchingLocale = null;
try {
let data = require("@l10n/data");
hash = data.hash;
bestMatchingLocale = data.bestMatchingLocale;
usingJSON = true;
}
catch(e) {}
exports.usingJSON = usingJSON;
// Returns the translation for a given key, if available.
exports.get = function get(k) {
return k in hash ? hash[k] : null;
}
// Returns the full length locale code: ja-JP-mac, en-US or fr
exports.locale = function locale() {
return bestMatchingLocale;
}
// Returns the short locale code: ja, en, fr
exports.language = function language() {
return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
: "en";
}

Просмотреть файл

@ -0,0 +1,70 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Cc, Ci } = require("chrome");
lazyRequire(this, "./locale", "getPreferedLocales", "findClosestLocale");
lazyRequire(this, "../net/url", "readURI");
lazyRequire(this, "../core/promise", "resolve");
function parseJsonURI(uri) {
return readURI(uri).
then(JSON.parse).
catch(function (error) {
throw Error("Failed to parse locale file:\n" + uri + "\n" + error);
});
}
// Returns the array stored in `locales.json` manifest that list available
// locales files
function getAvailableLocales(rootURI) {
let uri = rootURI + "locales.json";
return parseJsonURI(uri).then(function (manifest) {
return "locales" in manifest &&
Array.isArray(manifest.locales) ?
manifest.locales : [];
});
}
// Returns URI of the best locales file to use from the XPI
function getBestLocale(rootURI) {
// Read localization manifest file that contains list of available languages
return getAvailableLocales(rootURI).then(function (availableLocales) {
// Retrieve list of prefered locales to use
let preferedLocales = getPreferedLocales();
// Compute the most preferable locale to use by using these two lists
return findClosestLocale(availableLocales, preferedLocales);
});
}
/**
* Read localization files and returns a promise of data to put in `@l10n/data`
* pseudo module, in order to allow l10n/json/core to fetch it.
*/
exports.load = function load(rootURI) {
// First, search for a locale file:
return getBestLocale(rootURI).then(function (bestMatchingLocale) {
// It may be null if the addon doesn't have any locale file
if (!bestMatchingLocale)
return resolve(null);
let localeURI = rootURI + "locale/" + bestMatchingLocale + ".json";
// Locale files only contains one big JSON object that is used as
// an hashtable of: "key to translate" => "translated key"
// TODO: We are likely to change this in order to be able to overload
// a specific key translation. For a specific package, module or line?
return parseJsonURI(localeURI).then(function (json) {
return {
hash: json,
bestMatchingLocale: bestMatchingLocale
};
});
});
}

Просмотреть файл

@ -0,0 +1,85 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const prefs = require("../preferences/service");
const { Cu, Cc, Ci } = require("chrome");
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
function getPreferedLocales(caseSensitve) {
const locales = Services.locale.getRequestedLocales();
// This API expects to always append en-US fallback, so for compatibility
// reasons, we're going to inject it here.
// See bug 1373061 for details.
if (!locales.includes('en-US')) {
locales.push('en-US');
}
return locales;
}
exports.getPreferedLocales = getPreferedLocales;
/**
* Selects the closest matching locale from a list of locales.
*
* @param aLocales
* An array of available locales
* @param aMatchLocales
* An array of prefered locales, ordered by priority. Most wanted first.
* Locales have to be in lowercase.
* If null, uses getPreferedLocales() results
* @return the best match for the currently selected locale
*
* Stolen from http://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm
*/
exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) {
aMatchLocales = aMatchLocales || getPreferedLocales();
// Holds the best matching localized resource
let bestmatch = null;
// The number of locale parts it matched with
let bestmatchcount = 0;
// The number of locale parts in the match
let bestpartcount = 0;
for (let locale of aMatchLocales) {
let lparts = locale.split("-");
for (let localized of aLocales) {
let found = localized.toLowerCase();
// Exact match is returned immediately
if (locale == found)
return localized;
let fparts = found.split("-");
/* If we have found a possible match and this one isn't any longer
then we dont need to check further. */
if (bestmatch && fparts.length < bestmatchcount)
continue;
// Count the number of parts that match
let maxmatchcount = Math.min(fparts.length, lparts.length);
let matchcount = 0;
while (matchcount < maxmatchcount &&
fparts[matchcount] == lparts[matchcount])
matchcount++;
/* If we matched more than the last best match or matched the same and
this locale is less specific than the last best match. */
if (matchcount > bestmatchcount ||
(matchcount == bestmatchcount && fparts.length < bestpartcount)) {
bestmatch = localized;
bestmatchcount = matchcount;
bestpartcount = fparts.length;
}
}
// If we found a valid match for this locale return it
if (bestmatch)
return bestmatch;
}
return null;
}

Просмотреть файл

@ -0,0 +1,407 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This file is automatically generated with /python-lib/plural-rules-generator.py
// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml
// Mapping of short locale name == to == > rule index in following list
module.metadata = {
"stability": "unstable"
};
const LOCALES_TO_RULES = {
"af": 3,
"ak": 4,
"am": 4,
"ar": 1,
"asa": 3,
"az": 0,
"be": 11,
"bem": 3,
"bez": 3,
"bg": 3,
"bh": 4,
"bm": 0,
"bn": 3,
"bo": 0,
"br": 20,
"brx": 3,
"bs": 11,
"ca": 3,
"cgg": 3,
"chr": 3,
"cs": 12,
"cy": 17,
"da": 3,
"de": 3,
"dv": 3,
"dz": 0,
"ee": 3,
"el": 3,
"en": 3,
"eo": 3,
"es": 3,
"et": 3,
"eu": 3,
"fa": 0,
"ff": 5,
"fi": 3,
"fil": 4,
"fo": 3,
"fr": 5,
"fur": 3,
"fy": 3,
"ga": 8,
"gd": 24,
"gl": 3,
"gsw": 3,
"gu": 3,
"guw": 4,
"gv": 23,
"ha": 3,
"haw": 3,
"he": 2,
"hi": 4,
"hr": 11,
"hu": 0,
"id": 0,
"ig": 0,
"ii": 0,
"is": 3,
"it": 3,
"iu": 7,
"ja": 0,
"jmc": 3,
"jv": 0,
"ka": 0,
"kab": 5,
"kaj": 3,
"kcg": 3,
"kde": 0,
"kea": 0,
"kk": 3,
"kl": 3,
"km": 0,
"kn": 0,
"ko": 0,
"ksb": 3,
"ksh": 21,
"ku": 3,
"kw": 7,
"lag": 18,
"lb": 3,
"lg": 3,
"ln": 4,
"lo": 0,
"lt": 10,
"lv": 6,
"mas": 3,
"mg": 4,
"mk": 16,
"ml": 3,
"mn": 3,
"mo": 9,
"mr": 3,
"ms": 0,
"mt": 15,
"my": 0,
"nah": 3,
"naq": 7,
"nb": 3,
"nd": 3,
"ne": 3,
"nl": 3,
"nn": 3,
"no": 3,
"nr": 3,
"nso": 4,
"ny": 3,
"nyn": 3,
"om": 3,
"or": 3,
"pa": 3,
"pap": 3,
"pl": 13,
"ps": 3,
"pt": 3,
"rm": 3,
"ro": 9,
"rof": 3,
"ru": 11,
"rwk": 3,
"sah": 0,
"saq": 3,
"se": 7,
"seh": 3,
"ses": 0,
"sg": 0,
"sh": 11,
"shi": 19,
"sk": 12,
"sl": 14,
"sma": 7,
"smi": 7,
"smj": 7,
"smn": 7,
"sms": 7,
"sn": 3,
"so": 3,
"sq": 3,
"sr": 11,
"ss": 3,
"ssy": 3,
"st": 3,
"sv": 3,
"sw": 3,
"syr": 3,
"ta": 3,
"te": 3,
"teo": 3,
"th": 0,
"ti": 4,
"tig": 3,
"tk": 3,
"tl": 4,
"tn": 3,
"to": 0,
"tr": 0,
"ts": 3,
"tzm": 22,
"uk": 11,
"ur": 3,
"ve": 3,
"vi": 0,
"vun": 3,
"wa": 4,
"wae": 3,
"wo": 0,
"xh": 3,
"xog": 3,
"yo": 0,
"zh": 0,
"zu": 3
};
// Utility functions for plural rules methods
function isIn(n, list) {
return list.indexOf(n) !== -1;
}
function isBetween(n, start, end) {
return start <= n && n <= end;
}
// List of all plural rules methods, that maps an integer to the plural form name to use
const RULES = {
"0": function (n) {
return "other"
},
"1": function (n) {
if ((isBetween((n % 100), 3, 10)))
return "few";
if (n == 0)
return "zero";
if ((isBetween((n % 100), 11, 99)))
return "many";
if (n == 2)
return "two";
if (n == 1)
return "one";
return "other"
},
"2": function (n) {
if (n != 0 && (n % 10) == 0)
return "many";
if (n == 2)
return "two";
if (n == 1)
return "one";
return "other"
},
"3": function (n) {
if (n == 1)
return "one";
return "other"
},
"4": function (n) {
if ((isBetween(n, 0, 1)))
return "one";
return "other"
},
"5": function (n) {
if ((isBetween(n, 0, 2)) && n != 2)
return "one";
return "other"
},
"6": function (n) {
if (n == 0)
return "zero";
if ((n % 10) == 1 && (n % 100) != 11)
return "one";
return "other"
},
"7": function (n) {
if (n == 2)
return "two";
if (n == 1)
return "one";
return "other"
},
"8": function (n) {
if ((isBetween(n, 3, 6)))
return "few";
if ((isBetween(n, 7, 10)))
return "many";
if (n == 2)
return "two";
if (n == 1)
return "one";
return "other"
},
"9": function (n) {
if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19)))
return "few";
if (n == 1)
return "one";
return "other"
},
"10": function (n) {
if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
return "few";
if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
return "one";
return "other"
},
"11": function (n) {
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
return "few";
if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14)))
return "many";
if ((n % 10) == 1 && (n % 100) != 11)
return "one";
return "other"
},
"12": function (n) {
if ((isBetween(n, 2, 4)))
return "few";
if (n == 1)
return "one";
return "other"
},
"13": function (n) {
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
return "few";
if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14)))
return "many";
if (n == 1)
return "one";
return "other"
},
"14": function (n) {
if ((isBetween((n % 100), 3, 4)))
return "few";
if ((n % 100) == 2)
return "two";
if ((n % 100) == 1)
return "one";
return "other"
},
"15": function (n) {
if (n == 0 || (isBetween((n % 100), 2, 10)))
return "few";
if ((isBetween((n % 100), 11, 19)))
return "many";
if (n == 1)
return "one";
return "other"
},
"16": function (n) {
if ((n % 10) == 1 && n != 11)
return "one";
return "other"
},
"17": function (n) {
if (n == 3)
return "few";
if (n == 0)
return "zero";
if (n == 6)
return "many";
if (n == 2)
return "two";
if (n == 1)
return "one";
return "other"
},
"18": function (n) {
if (n == 0)
return "zero";
if ((isBetween(n, 0, 2)) && n != 0 && n != 2)
return "one";
return "other"
},
"19": function (n) {
if ((isBetween(n, 2, 10)))
return "few";
if ((isBetween(n, 0, 1)))
return "one";
return "other"
},
"20": function (n) {
if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99)))
return "few";
if ((n % 1000000) == 0 && n != 0)
return "many";
if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
return "two";
if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
return "one";
return "other"
},
"21": function (n) {
if (n == 0)
return "zero";
if (n == 1)
return "one";
return "other"
},
"22": function (n) {
if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
return "one";
return "other"
},
"23": function (n) {
if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0)
return "one";
return "other"
},
"24": function (n) {
if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
return "few";
if (isIn(n, [2, 12]))
return "two";
if (isIn(n, [1, 11]))
return "one";
return "other"
},
};
/**
* Return a function that gives the plural form name for a given integer
* for the specified `locale`
* let fun = getRulesForLocale('en');
* fun(1) -> 'one'
* fun(0) -> 'other'
* fun(1000) -> 'other'
*/
exports.getRulesForLocale = function getRulesForLocale(locale) {
let index = LOCALES_TO_RULES[locale];
if (!(index in RULES)) {
console.warn('Plural form unknown for locale "' + locale + '"');
return function () { return "other"; };
}
return RULES[index];
}

Просмотреть файл

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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";
lazyRequire(this, "../system/events", "on");
lazyRequireModule(this, "./core", "core");
const { id: jetpackId } = require('../self');
const OPTIONS_DISPLAYED = "addon-options-displayed";
function enable() {
on(OPTIONS_DISPLAYED, onOptionsDisplayed);
}
exports.enable = enable;
function onOptionsDisplayed({ subject: document, data: addonId }) {
if (addonId !== jetpackId)
return;
localizeInlineOptions(document);
}
function localizeInlineOptions(document) {
let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' +
'button[data-jetpack-id="' + jetpackId + '"][pref-name]';
let nodes = document.querySelectorAll(query);
for (let node of nodes) {
let name = node.getAttribute("pref-name");
if (node.tagName == "setting") {
let desc = core.get(name + "_description");
if (desc)
node.setAttribute("desc", desc);
let title = core.get(name + "_title");
if (title)
node.setAttribute("title", title);
for (let item of node.querySelectorAll("menuitem, radio")) {
let key = name + "_options." + item.getAttribute("label");
let label = core.get(key);
if (label)
item.setAttribute("label", label);
}
}
else if (node.tagName == "button") {
let label = core.get(name + "_label");
if (label)
node.setAttribute("label", label);
}
}
}
exports.localizeInlineOptions = localizeInlineOptions;

Просмотреть файл

@ -0,0 +1,89 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cu } = require("chrome");
lazyRequire(this, '../../url/utils', 'newURI');
lazyRequire(this, "../plural-rules", 'getRulesForLocale');
lazyRequire(this, '../locale', 'getPreferedLocales');
const { rootURI } = require("@loader/options");
const { Services } = require("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const baseURI = rootURI + "locale/";
XPCOMUtils.defineLazyGetter(this, "preferedLocales", () => getPreferedLocales(true));
// Make sure we don't get stale data after an update
// (See Bug 1300735 for rationale).
Services.strings.flushBundles();
function getLocaleURL(locale) {
// if the locale is a valid chrome URI, return it
try {
let uri = newURI(locale);
if (uri.scheme == 'chrome')
return uri.spec;
}
catch(_) {}
// otherwise try to construct the url
return baseURI + locale + ".properties";
}
function getKey(locale, key) {
let bundle = Services.strings.createBundle(getLocaleURL(locale));
try {
return bundle.GetStringFromName(key) + "";
}
catch (_) {}
return undefined;
}
function get(key, n, locales) {
// try this locale
let locale = locales.shift();
let localized;
if (typeof n == 'number') {
if (n == 0) {
localized = getKey(locale, key + '[zero]');
}
else if (n == 1) {
localized = getKey(locale, key + '[one]');
}
else if (n == 2) {
localized = getKey(locale, key + '[two]');
}
if (!localized) {
// Retrieve the plural mapping function
let pluralForm = (getRulesForLocale(locale.split("-")[0].toLowerCase()) ||
getRulesForLocale("en"))(n);
localized = getKey(locale, key + '[' + pluralForm + ']');
}
if (!localized) {
localized = getKey(locale, key + '[other]');
}
}
if (!localized) {
localized = getKey(locale, key);
}
if (!localized) {
localized = getKey(locale, key + '[other]');
}
if (localized) {
return localized;
}
// try next locale
if (locales.length)
return get(key, n, locales);
return undefined;
}
exports.get = (k, n) => get(k, n, Array.slice(preferedLocales));

Просмотреть файл

@ -0,0 +1,102 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
// This module is manually loaded by bootstrap.js in a sandbox and immediatly
// put in module cache so that it is never loaded in any other way.
/* Workarounds to include dependencies in the manifest
require('chrome') // Otherwise CFX will complain about Components
require('toolkit/loader') // Otherwise CFX will stip out loader.js
require('sdk/addon/runner') // Otherwise CFX will stip out addon/runner.js
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
// `loadSandbox` is exposed by bootstrap.js
const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
"toolkit/loader.js");
const xulappURI = module.uri.replace("loader/cuddlefish.js",
"system/xul-app.jsm");
// We need to keep a reference to the sandbox in order to unload it in
// bootstrap.js
var loaderSandbox = loadSandbox(loaderURI);
const loaderModule = loaderSandbox.exports;
const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
const { override, load } = loaderModule;
function CuddlefishLoader(options) {
let { manifest } = options;
options = override(options, {
// Put `api-utils/loader` and `api-utils/cuddlefish` loaded as JSM to module
// cache to avoid subsequent loads via `require`.
modules: override({
'toolkit/loader': loaderModule,
'sdk/loader/cuddlefish': exports
}, options.modules),
resolve: function resolve(id, requirer) {
let entry = requirer && requirer in manifest && manifest[requirer];
let uri = null;
// If manifest entry for this requirement is present we follow manifest.
// Note: Standard library modules like 'panel' will be present in
// manifest unless they were moved to platform.
if (entry) {
let requirement = entry.requirements[id];
// If requirer entry is in manifest and it's requirement is not, than
// it has no authority to load since linker was not able to find it.
if (!requirement)
throw Error('Module: ' + requirer + ' has no authority to load: '
+ id, requirer);
uri = requirement;
} else {
// If requirer is off manifest than it's a system module and we allow it
// to go off manifest by resolving a relative path.
uri = loaderModule.resolve(id, requirer);
}
return uri;
},
load: function(loader, module) {
let result;
let error;
// In order to get the module's metadata, we need to load the module.
// if an exception is raised here, it could be that is due to application
// incompatibility. Therefore the exception is stored, and thrown again
// only if the module seems be compatible with the application currently
// running. Otherwise the incompatibility message takes the precedence.
try {
result = load(loader, module);
}
catch (e) {
error = e;
}
error = incompatibility(module) || error;
if (error)
throw error;
return result;
}
});
let loader = loaderModule.Loader(options);
// Hack to allow loading from `toolkit/loader`.
loader.modules[loaderURI] = loaderSandbox;
return loader;
}
exports = override(loaderModule, {
Loader: CuddlefishLoader
});

Просмотреть файл

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cc, Ci, CC, Cu } = require('chrome');
const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
getService(Ci.mozIJSSubScriptLoader);
const self = require('sdk/self');
const { getTabId } = require('../tabs/utils');
const { getInnerId } = require('../window/utils');
/**
* Make a new sandbox that inherits given `source`'s principals. Source can be
* URI string, DOMWindow or `null` for system principals.
*/
function sandbox(target, options) {
options = options || {};
options.metadata = options.metadata ? options.metadata : {};
options.metadata.addonID = options.metadata.addonID ?
options.metadata.addonID : self.id;
let sandbox = Cu.Sandbox(target || systemPrincipal, options);
Cu.setSandboxMetadata(sandbox, options.metadata);
return sandbox;
}
exports.sandbox = sandbox;
/**
* Evaluates given `source` in a given `sandbox` and returns result.
*/
function evaluate(sandbox, code, uri, line, version) {
return Cu.evalInSandbox(code, sandbox, version || '1.8', uri || '', line || 1);
}
exports.evaluate = evaluate;
/**
* Evaluates code under the given `uri` in the given `sandbox`.
*
* @param {String} uri
* The URL pointing to the script to load.
* It must be a local chrome:, resource:, file: or data: URL.
*/
function load(sandbox, uri) {
if (uri.indexOf('data:') === 0) {
let source = uri.substr(uri.indexOf(',') + 1);
return evaluate(sandbox, decodeURIComponent(source), '1.8', uri, 0);
} else {
return scriptLoader.loadSubScriptWithOptions(uri, {target: sandbox,
charset: 'UTF-8',
wantReturnValue: true});
}
}
exports.load = load;
/**
* Forces the given `sandbox` to be freed immediately.
*/
exports.nuke = Cu.nukeSandbox

Просмотреть файл

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { window } = require("sdk/addon/window");
exports.MessageChannel = window.MessageChannel;
exports.MessagePort = window.MessagePort;

Просмотреть файл

@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { dispatcher } = require("../util/dispatcher");
// Define `modelFor` accessor function that can be implemented
// for different types of views. Since view's we'll be dealing
// with types that don't really play well with `instanceof`
// operator we're gonig to use `dispatcher` that is slight
// extension over polymorphic dispatch provided by method.
// This allows models to extend implementations of this by
// providing predicates:
//
// modelFor.when($ => $ && $.nodeName === "tab", findTabById($.id))
const modelFor = dispatcher("modelFor");
exports.modelFor = modelFor;

Просмотреть файл

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
const { deprecateFunction } = require("../util/deprecate");
const { Ci, Cu } = require("chrome");
Cu.importGlobalProperties(["XMLHttpRequest"]);
Object.defineProperties(XMLHttpRequest.prototype, {
mozBackgroundRequest: {
value: true,
},
forceAllowThirdPartyCookie: {
configurable: true,
value: deprecateFunction(function() {
forceAllowThirdPartyCookie(this);
}, "`xhr.forceAllowThirdPartyCookie()` is deprecated, please use" +
"`require('sdk/net/xhr').forceAllowThirdPartyCookie(request)` instead")
}
});
exports.XMLHttpRequest = XMLHttpRequest;
function forceAllowThirdPartyCookie(xhr) {
if (xhr.channel instanceof Ci.nsIHttpChannelInternal)
xhr.channel.forceAllowThirdPartyCookie = true;
}
exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie;
// No need to handle add-on unloads as addon/window is closed at unload
// and it will take down all the associated requests.

Просмотреть файл

@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
const { Cc, Ci, Cr } = require("chrome");
const apiUtils = require("./deprecated/api-utils");
const { isString, isUndefined, instanceOf } = require('./lang/type');
const { URL, isLocalURL } = require('./url');
const { data } = require('./self');
const NOTIFICATION_DIRECTIONS = ["auto", "ltr", "rtl"];
try {
let alertServ = Cc["@mozilla.org/alerts-service;1"].
getService(Ci.nsIAlertsService);
// The unit test sets this to a mock notification function.
var notify = alertServ.showAlertNotification.bind(alertServ);
}
catch (err) {
// An exception will be thrown if the platform doesn't provide an alert
// service, e.g., if Growl is not installed on OS X. In that case, use a
// mock notification function that just logs to the console.
notify = notifyUsingConsole;
}
exports.notify = function notifications_notify(options) {
let valOpts = validateOptions(options);
let clickObserver = !valOpts.onClick ? null : {
observe: (subject, topic, data) => {
if (topic === "alertclickcallback") {
try {
valOpts.onClick.call(exports, valOpts.data);
}
catch(e) {
console.exception(e);
}
}
}
};
function notifyWithOpts(notifyFn) {
let { iconURL } = valOpts;
iconURL = iconURL && isLocalURL(iconURL) ? data.url(iconURL) : iconURL;
notifyFn(iconURL, valOpts.title, valOpts.text, !!clickObserver,
valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang);
}
try {
notifyWithOpts(notify);
}
catch (err) {
if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
console.warn("The notification icon named by " + iconURL +
" does not exist. A default icon will be used instead.");
delete valOpts.iconURL;
notifyWithOpts(notify);
}
else {
notifyWithOpts(notifyUsingConsole);
}
}
};
function notifyUsingConsole(iconURL, title, text) {
title = title ? "[" + title + "]" : "";
text = text || "";
let str = [title, text].filter(s => s).join(" ");
console.log(str);
}
function validateOptions(options) {
return apiUtils.validateOptions(options, {
data: {
is: ["string", "undefined"]
},
iconURL: {
is: ["string", "undefined", "object"],
ok: function(value) {
return isUndefined(value) || isString(value) || (value instanceof URL);
},
msg: "`iconURL` must be a string or an URL instance."
},
onClick: {
is: ["function", "undefined"]
},
text: {
is: ["string", "undefined", "number"]
},
title: {
is: ["string", "undefined", "number"]
},
tag: {
is: ["string", "undefined", "number"]
},
dir: {
is: ["string", "undefined"],
ok: function(value) {
return isUndefined(value) || ~NOTIFICATION_DIRECTIONS.indexOf(value);
},
msg: '`dir` option must be one of: "auto", "ltr" or "rtl".'
},
lang: {
is: ["string", "undefined"]
}
});
}

Просмотреть файл

@ -0,0 +1,71 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cr } = require("chrome");
const { Input, start, stop, receive, outputs } = require("../event/utils");
const { id: addonID } = require("../self");
const { setImmediate } = require("../timers");
const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
getService(Ci.nsIObserverService);
const NOT_AN_INPUT = "OutputPort can be used only for sending messages";
// `OutputPort` creates a port to which messages can be send. Those
// messages are actually disptached as `subject`'s of the observer
// notifications. This is handy for communicating between different
// components of the SDK. By default messages are dispatched
// asynchronously, although `options.sync` can be used to make them
// synchronous. If `options.id` is given `topic` for observer
// notifications is generated by namespacing it, to avoid spamming
// other SDK add-ons. It's also possible to provide `options.topic`
// to use excat `topic` without namespacing it.
//
// Note: Symmetric `new InputPort({ id: "x" })` instances can be used to
// receive messages send to the instances of `new OutputPort({ id: "x" })`.
const OutputPort = function({id, topic, sync}) {
this.id = id || topic;
this.sync = !!sync;
this.topic = topic || "sdk:" + addonID + ":" + id;
};
// OutputPort extends base signal type to implement same message
// receiving interface.
OutputPort.prototype = new Input();
OutputPort.constructor = OutputPort;
// OutputPort can not be consumed there for starting or stopping it
// is not supported.
OutputPort.prototype[start] = _ => { throw TypeError(NOT_AN_INPUT); };
OutputPort.prototype[stop] = _ => { throw TypeError(NOT_AN_INPUT); };
// Port reecives message send to it, which will be dispatched via
// observer notification service.
OutputPort.receive = ({topic, sync}, message) => {
const type = typeof(message);
const supported = message === null ||
type === "object" ||
type === "function";
// There is no sensible way to wrap JS primitives that would make sense
// for general observer notification users. It's also probably not very
// useful to dispatch JS primitives as subject of observer service, there
// for we do not support those use cases.
if (!supported)
throw new TypeError("Unsupproted message type: `" + type + "`");
// Normalize `message` to create a valid observer notification `subject`.
// If `message` is `null`, implements `nsISupports` interface or already
// represents wrapped JS object use it as is. Otherwise create a wrapped
// object so that observers could receive it.
const subject = message === null ? null :
message instanceof Ci.nsISupports ? message :
message.wrappedJSObject ? message :
{wrappedJSObject: message};
if (sync)
notifyObservers(subject, topic, null);
else
setImmediate(notifyObservers, subject, topic, null);
};
OutputPort.prototype[receive] = OutputPort.receive;
exports.OutputPort = OutputPort;

Просмотреть файл

@ -0,0 +1,190 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
const { contract: loaderContract } = require('./content/loader');
const { contract } = require('./util/contract');
const { WorkerHost, connect } = require('./content/utils');
const { Class } = require('./core/heritage');
const { Disposable } = require('./core/disposable');
lazyRequire(this, './content/worker', "Worker");
const { EventTarget } = require('./event/target');
lazyRequire(this, './event/core', "on", "emit", "once", "setListeners");
lazyRequire(this, './lang/type', "isRegExp", "isUndefined");
const { merge, omit } = require('./util/object');
lazyRequire(this, "./util/array", "remove", "has", "hasAny");
lazyRequire(this, "./util/rules", "Rules");
const { processes, frames, remoteRequire } = require('./remote/parent');
remoteRequire('sdk/content/page-mod');
const pagemods = new Map();
const workers = new Map();
const models = new WeakMap();
var modelFor = (mod) => models.get(mod);
var workerFor = (mod) => workers.get(mod)[0];
// Helper functions
var isRegExpOrString = (v) => isRegExp(v) || typeof v === 'string';
var PAGEMOD_ID = 0;
// Validation Contracts
const modOptions = {
// contentStyle* / contentScript* are sharing the same validation constraints,
// so they can be mostly reused, except for the messages.
contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
msg: 'The `contentStyle` option must be a string or an array of strings.'
}),
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
}),
include: {
is: ['string', 'array', 'regexp'],
ok: (rule) => {
if (isRegExpOrString(rule))
return true;
if (Array.isArray(rule) && rule.length > 0)
return rule.every(isRegExpOrString);
return false;
},
msg: 'The `include` option must always contain atleast one rule as a string, regular expression, or an array of strings and regular expressions.'
},
exclude: {
is: ['string', 'array', 'regexp', 'undefined'],
ok: (rule) => {
if (isRegExpOrString(rule) || isUndefined(rule))
return true;
if (Array.isArray(rule) && rule.length > 0)
return rule.every(isRegExpOrString);
return false;
},
msg: 'If set, the `exclude` option must always contain at least one ' +
'rule as a string, regular expression, or an array of strings and ' +
'regular expressions.'
},
attachTo: {
is: ['string', 'array', 'undefined'],
map: function (attachTo) {
if (!attachTo) return ['top', 'frame'];
if (typeof attachTo === 'string') return [attachTo];
return attachTo;
},
ok: function (attachTo) {
return hasAny(attachTo, ['top', 'frame']) &&
attachTo.every(has.bind(null, ['top', 'frame', 'existing']));
},
msg: 'The `attachTo` option must be a string or an array of strings. ' +
'The only valid options are "existing", "top" and "frame", and must ' +
'contain at least "top" or "frame" values.'
},
};
const modContract = contract(merge({}, loaderContract.rules, modOptions));
/**
* PageMod constructor (exported below).
* @constructor
*/
const PageMod = Class({
implements: [
modContract.properties(modelFor),
EventTarget,
Disposable,
],
extends: WorkerHost(workerFor),
setup: function PageMod(options) {
let mod = this;
let model = modContract(options);
models.set(this, model);
model.id = PAGEMOD_ID++;
let include = model.include;
model.include = Rules();
model.include.add.apply(model.include, [].concat(include));
let exclude = isUndefined(model.exclude) ? [] : model.exclude;
model.exclude = Rules();
model.exclude.add.apply(model.exclude, [].concat(exclude));
// Set listeners on {PageMod} itself, not the underlying worker,
// like `onMessage`, as it'll get piped.
setListeners(this, options);
pagemods.set(model.id, this);
workers.set(this, []);
function* serializeRules(rules) {
for (let rule of rules) {
yield isRegExp(rule) ? { type: "regexp", pattern: rule.source, flags: rule.flags }
: { type: "string", value: rule };
}
}
model.childOptions = omit(model, ["include", "exclude", "contentScriptOptions"]);
model.childOptions.include = [...serializeRules(model.include)];
model.childOptions.exclude = [...serializeRules(model.exclude)];
model.childOptions.contentScriptOptions = model.contentScriptOptions ?
JSON.stringify(model.contentScriptOptions) :
null;
processes.port.emit('sdk/page-mod/create', model.childOptions);
},
dispose: function(reason) {
processes.port.emit('sdk/page-mod/destroy', modelFor(this).id);
pagemods.delete(modelFor(this).id);
workers.delete(this);
},
destroy: function(reason) {
// Explicit destroy call, i.e. not via unload so destroy the workers
let list = workers.get(this);
if (!list)
return;
// Triggers dispose which will cause the child page-mod to be destroyed
Disposable.prototype.destroy.call(this, reason);
// Destroy any active workers
for (let worker of list)
worker.destroy(reason);
}
});
exports.PageMod = PageMod;
// Whenever a new process starts send over the list of page-mods
processes.forEvery(process => {
for (let mod of pagemods.values())
process.port.emit('sdk/page-mod/create', modelFor(mod).childOptions);
});
frames.port.on('sdk/page-mod/worker-create', (frame, modId, workerOptions) => {
let mod = pagemods.get(modId);
if (!mod)
return;
// Attach the parent side of the worker to the child
let worker = Worker();
workers.get(mod).unshift(worker);
worker.on('*', (event, ...args) => {
// page-mod's "attach" event needs to be passed a worker
if (event === 'attach')
emit(mod, event, worker)
else
emit(mod, event, ...args);
});
worker.on('detach', () => {
let array = workers.get(mod);
if (array)
remove(array, worker);
});
connect(worker, frame, workerOptions);
});

Просмотреть файл

@ -0,0 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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 { deprecateUsage } = require("../util/deprecate");
deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
module.exports = require("../util/match-pattern");

Просмотреть файл

@ -0,0 +1,194 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "stable"
};
const { Class } = require('./core/heritage');
const { ns } = require('./core/namespace');
lazyRequire(this, './event/utils', "pipe", "stripListeners");
const { connect, destroy, WorkerHost } = require('./content/utils');
lazyRequire(this, './content/worker', "Worker");
const { Disposable } = require('./core/disposable');
const { EventTarget } = require('./event/target');
lazyRequire(this, './event/core', "setListeners");
lazyRequire(this, './addon/window', "window");
lazyRequire(this, './frame/utils', { "create": "makeFrame" }, "getDocShell");
const { contract } = require('./util/contract');
const { contract: loaderContract } = require('./content/loader');
lazyRequire(this, './util/rules', "Rules");
const { merge } = require('./util/object');
lazyRequire(this, './util/uuid', "uuid");
const { useRemoteProcesses, remoteRequire, frames } = require("./remote/parent");
remoteRequire("sdk/content/page-worker");
const workers = new WeakMap();
const pages = new Map();
const internal = ns();
let workerFor = (page) => workers.get(page);
let isDisposed = (page) => !pages.has(internal(page).id);
// The frame is used to ensure we have a remote process to load workers in
let remoteFrame = null;
let framePromise = null;
function getFrame() {
if (framePromise)
return framePromise;
framePromise = new Promise(resolve => {
let view = makeFrame(window.document, {
namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
nodeName: "iframe",
type: "content",
remote: useRemoteProcesses,
uri: "about:blank"
});
// Wait for the remote side to connect
let listener = (frame) => {
if (frame.frameElement != view)
return;
frames.off("attach", listener);
remoteFrame = frame;
resolve(frame);
}
frames.on("attach", listener);
});
return framePromise;
}
var pageContract = contract(merge({
allow: {
is: ['object', 'undefined', 'null'],
map: function (allow) { return { script: !allow || allow.script !== false }}
},
onMessage: {
is: ['function', 'undefined']
},
include: {
is: ['string', 'array', 'regexp', 'undefined']
},
contentScriptWhen: {
is: ['string', 'undefined'],
map: (when) => when || "end"
}
}, loaderContract.rules));
function enableScript (page) {
getDocShell(viewFor(page)).allowJavascript = true;
}
function disableScript (page) {
getDocShell(viewFor(page)).allowJavascript = false;
}
function Allow (page) {
return {
get script() {
return internal(page).options.allow.script;
},
set script(value) {
internal(page).options.allow.script = value;
if (isDisposed(page))
return;
remoteFrame.port.emit("sdk/frame/set", internal(page).id, { allowScript: value });
}
};
}
function isValidURL(page, url) {
return !page.rules || page.rules.matchesAny(url);
}
const Page = Class({
implements: [
EventTarget,
Disposable
],
extends: WorkerHost(workerFor),
setup: function Page(options) {
options = pageContract(options);
// Sanitize the options
if ("contentScriptOptions" in options)
options.contentScriptOptions = JSON.stringify(options.contentScriptOptions);
internal(this).id = uuid().toString();
internal(this).options = options;
for (let prop of ['contentScriptFile', 'contentScript', 'contentScriptWhen']) {
this[prop] = options[prop];
}
pages.set(internal(this).id, this);
// Set listeners on the {Page} object itself, not the underlying worker,
// like `onMessage`, as it gets piped
setListeners(this, options);
let worker = new Worker(stripListeners(options));
workers.set(this, worker);
pipe(worker, this);
if (options.include) {
this.rules = Rules();
this.rules.add.apply(this.rules, [].concat(options.include));
}
getFrame().then(frame => {
if (isDisposed(this))
return;
frame.port.emit("sdk/frame/create", internal(this).id, stripListeners(options));
});
},
get allow() { return Allow(this); },
set allow(value) {
if (isDisposed(this))
return;
this.allow.script = pageContract({ allow: value }).allow.script;
},
get contentURL() {
return internal(this).options.contentURL;
},
set contentURL(value) {
if (!isValidURL(this, value))
return;
internal(this).options.contentURL = value;
if (isDisposed(this))
return;
remoteFrame.port.emit("sdk/frame/set", internal(this).id, { contentURL: value });
},
dispose: function () {
if (isDisposed(this))
return;
pages.delete(internal(this).id);
let worker = workerFor(this);
if (worker)
destroy(worker);
remoteFrame.port.emit("sdk/frame/destroy", internal(this).id);
// Destroy the remote frame if all the pages have been destroyed
if (pages.size == 0) {
framePromise = null;
remoteFrame.frameElement.remove();
remoteFrame = null;
}
},
toString: function () { return '[object Page]' }
});
exports.Page = Page;
frames.port.on("sdk/frame/connect", (frame, id, params) => {
let page = pages.get(id);
if (!page)
return;
connect(workerFor(page), frame, params);
});

Просмотреть файл

@ -0,0 +1,436 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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";
// The panel module currently supports only Firefox and SeaMonkey.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
module.metadata = {
"stability": "stable",
"engines": {
"Firefox": "*",
"SeaMonkey": "*"
}
};
const { Cu, Ci } = require("chrome");
lazyRequire(this, './timers', "setTimeout");
const { Class } = require("./core/heritage");
const { DefaultWeakMap, merge } = require("./util/object");
const { WorkerHost } = require("./content/utils");
lazyRequire(this, "./deprecated/sync-worker", "Worker");
const { Disposable } = require("./core/disposable");
const { WeakReference } = require('./core/reference');
const { contract: loaderContract } = require("./content/loader");
const { contract } = require("./util/contract");
lazyRequire(this, "./event/core", "on", "off", "emit", "setListeners");
const { EventTarget } = require("./event/target");
lazyRequireModule(this, "./panel/utils", "domPanel");
lazyRequire(this, './frame/utils', "getDocShell");
const { events } = require("./panel/events");
const { filter, pipe, stripListeners } = require("./event/utils");
lazyRequire(this, "./view/core", "getNodeView", "getActiveView");
lazyRequire(this, "./lang/type", "isNil", "isObject", "isNumber");
lazyRequire(this, "./content/utils", "getAttachEventType");
const { number, boolean, object } = require('./deprecated/api-utils');
lazyRequire(this, "./stylesheet/style", "Style");
lazyRequire(this, "./content/mod", "attach", "detach");
var isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
some(value => isNumber(value) && !isNaN(value));
var isSDKObj = obj => obj instanceof Class;
var rectContract = contract({
top: number,
right: number,
bottom: number,
left: number
});
var position = {
is: object,
map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
msg: 'The option "position" must be a SDK object registered as anchor; ' +
'or an object with one or more of the following keys set to numeric ' +
'values: top, right, bottom, left.'
}
var displayContract = contract({
width: number,
height: number,
focus: boolean,
position: position
});
var panelContract = contract(merge({
// contentStyle* / contentScript* are sharing the same validation constraints,
// so they can be mostly reused, except for the messages.
contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
msg: 'The `contentStyle` option must be a string or an array of strings.'
}),
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
}),
contextMenu: boolean,
allow: {
is: ['object', 'undefined', 'null'],
map: function (allow) { return { script: !allow || allow.script !== false }}
},
}, displayContract.rules, loaderContract.rules));
function Allow(panel) {
return {
get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; },
set script(value) { return setScriptState(panel, value); },
};
}
function setScriptState(panel, value) {
let view = viewFor(panel);
getDocShell(view.backgroundFrame).allowJavascript = value;
getDocShell(view.viewFrame).allowJavascript = value;
view.setAttribute("sdkscriptenabled", "" + value);
}
function isDisposed(panel) {
return !views.has(panel);
}
var optionsMap = new WeakMap();
var panels = new WeakMap();
var models = new WeakMap();
var views = new DefaultWeakMap(panel => {
let model = models.get(panel);
// Setup view
let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)};
let view = domPanel.make(null, viewOptions);
panels.set(view, panel);
// Load panel content.
domPanel.setURL(view, model.contentURL);
// Allow context menu
domPanel.allowContextMenu(view, model.contextMenu);
return view;
});
var workers = new DefaultWeakMap(panel => {
let options = optionsMap.get(panel);
let worker = new Worker(stripListeners(options));
workers.set(panel, worker);
// pipe events from worker to a panel.
pipe(worker, panel);
return worker;
});
var styles = new WeakMap();
const viewFor = (panel) => views.get(panel);
const modelFor = (panel) => models.get(panel);
const panelFor = (view) => panels.get(view);
const workerFor = (panel) => workers.get(panel);
const styleFor = (panel) => styles.get(panel);
function getPanelFromWeakRef(weakRef) {
if (!weakRef) {
return null;
}
let panel = weakRef.get();
if (!panel) {
return null;
}
if (isDisposed(panel)) {
return null;
}
return panel;
}
var SinglePanelManager = {
visiblePanel: null,
enqueuedPanel: null,
enqueuedPanelCallback: null,
// Calls |callback| with no arguments when the panel may be shown.
requestOpen: function(panelToOpen, callback) {
let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
if (currentPanel || SinglePanelManager.enqueuedPanel) {
SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen);
SinglePanelManager.enqueuedPanelCallback = callback;
if (currentPanel && currentPanel.isShowing) {
currentPanel.hide();
}
} else {
SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback);
}
},
notifyPanelCanOpen: function(panel, callback) {
let view = viewFor(panel);
// Can't pass an arrow function as the event handler because we need to be
// able to call |removeEventListener| later.
view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown);
SinglePanelManager.enqueuedPanel = null;
SinglePanelManager.enqueuedPanelCallback = null;
SinglePanelManager.visiblePanel = Cu.getWeakReference(panel);
callback();
},
onVisiblePanelShown: function(event) {
let panel = panelFor(event.target);
if (SinglePanelManager.enqueuedPanel) {
// Another panel started waiting for |panel| to close before |panel| was
// even done opening.
panel.hide();
}
},
onVisiblePanelHidden: function(event) {
let view = event.target;
let panel = panelFor(view);
let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
if (currentPanel && currentPanel != panel) {
return;
}
SinglePanelManager.visiblePanel = null;
view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown);
let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel);
let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback;
if (nextPanel) {
SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback);
}
}
};
const Panel = Class({
implements: [
// Generate accessors for the validated properties that update model on
// set and return values from model on get.
panelContract.properties(modelFor),
EventTarget,
Disposable,
WeakReference
],
extends: WorkerHost(workerFor),
setup: function setup(options) {
let model = merge({
defaultWidth: 320,
defaultHeight: 240,
focus: true,
position: Object.freeze({}),
contextMenu: false
}, panelContract(options));
model.ready = false;
models.set(this, model);
if (model.contentStyle || model.contentStyleFile) {
styles.set(this, Style({
uri: model.contentStyleFile,
source: model.contentStyle
}));
}
optionsMap.set(this, options);
// Setup listeners.
setListeners(this, options);
},
dispose: function dispose() {
if (views.has(this))
this.hide();
off(this);
workerFor(this).destroy();
detach(styleFor(this));
if (views.has(this))
domPanel.dispose(viewFor(this));
views.delete(this);
},
/* Public API: Panel.width */
get width() {
return modelFor(this).width;
},
set width(value) {
this.resize(value, this.height);
},
/* Public API: Panel.height */
get height() {
return modelFor(this).height;
},
set height(value) {
this.resize(this.width, value);
},
/* Public API: Panel.focus */
get focus() {
return modelFor(this).focus;
},
/* Public API: Panel.position */
get position() {
return modelFor(this).position;
},
/* Public API: Panel.contextMenu */
get contextMenu() {
return modelFor(this).contextMenu;
},
set contextMenu(allow) {
let model = modelFor(this);
model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
domPanel.allowContextMenu(viewFor(this), model.contextMenu);
},
get contentURL() {
return modelFor(this).contentURL;
},
set contentURL(value) {
let model = modelFor(this);
model.contentURL = panelContract({ contentURL: value }).contentURL;
domPanel.setURL(viewFor(this), model.contentURL);
// Detach worker so that messages send will be queued until it's
// reatached once panel content is ready.
workerFor(this).detach();
},
get allow() { return Allow(this); },
set allow(value) {
let allowJavascript = panelContract({ allow: value }).allow.script;
return setScriptState(this, value);
},
/* Public API: Panel.isShowing */
get isShowing() {
return !isDisposed(this) && domPanel.isOpen(viewFor(this));
},
/* Public API: Panel.show */
show: function show(options={}, anchor) {
let view = viewFor(this);
SinglePanelManager.requestOpen(this, () => {
if (options instanceof Ci.nsIDOMElement) {
[anchor, options] = [options, null];
}
if (anchor instanceof Ci.nsIDOMElement) {
console.warn(
"Passing a DOM node to Panel.show() method is an unsupported " +
"feature that will be soon replaced. " +
"See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
);
}
let model = modelFor(this);
let anchorView = getNodeView(anchor || options.position || model.position);
options = merge({
position: model.position,
width: model.width,
height: model.height,
defaultWidth: model.defaultWidth,
defaultHeight: model.defaultHeight,
focus: model.focus,
contextMenu: model.contextMenu
}, displayContract(options));
if (!isDisposed(this)) {
domPanel.show(view, options, anchorView);
}
});
return this;
},
/* Public API: Panel.hide */
hide: function hide() {
// Quit immediately if panel is disposed or there is no state change.
domPanel.close(viewFor(this));
return this;
},
/* Public API: Panel.resize */
resize: function resize(width, height) {
let model = modelFor(this);
let view = viewFor(this);
let change = panelContract({
width: width || model.width || model.defaultWidth,
height: height || model.height || model.defaultHeight
});
model.width = change.width
model.height = change.height
domPanel.resize(view, model.width, model.height);
return this;
}
});
exports.Panel = Panel;
// Note must be defined only after value to `Panel` is assigned.
getActiveView.define(Panel, viewFor);
// Filter panel events to only panels that are create by this module.
var panelEvents = filter(events, ({target}) => panelFor(target));
// Panel events emitted after panel has being shown.
var shows = filter(panelEvents, ({type}) => type === "popupshown");
// Panel events emitted after panel became hidden.
var hides = filter(panelEvents, ({type}) => type === "popuphidden");
// Panel events emitted after content inside panel is ready. For different
// panels ready may mean different state based on `contentScriptWhen` attribute.
// Weather given event represents readyness is detected by `getAttachEventType`
// helper function.
var ready = filter(panelEvents, ({type, target}) =>
getAttachEventType(modelFor(panelFor(target))) === type);
// Panel event emitted when the contents of the panel has been loaded.
var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded");
// Styles should be always added as soon as possible, and doesn't makes them
// depends on `contentScriptWhen`
var start = filter(panelEvents, ({type}) => type === "document-element-inserted");
// Forward panel show / hide events to panel's own event listeners.
on(shows, "data", ({target}) => {
let panel = panelFor(target);
if (modelFor(panel).ready)
emit(panel, "show");
});
on(hides, "data", ({target}) => {
let panel = panelFor(target);
if (modelFor(panel).ready)
emit(panel, "hide");
});
on(ready, "data", ({target}) => {
let panel = panelFor(target);
let window = domPanel.getContentDocument(target).defaultView;
workerFor(panel).attach(window);
});
on(readyToShow, "data", ({target}) => {
let panel = panelFor(target);
if (!modelFor(panel).ready) {
modelFor(panel).ready = true;
if (viewFor(panel).state == "open")
emit(panel, "show");
}
});
on(start, "data", ({target}) => {
let panel = panelFor(target);
let window = domPanel.getContentDocument(target).defaultView;
attach(styleFor(panel), window);
});

Просмотреть файл

@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.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";
// This module basically translates system/events to a SDK standard events
// so that `map`, `filter` and other utilities could be used with them.
module.metadata = {
"stability": "experimental"
};
const events = require("../system/events");
lazyRequire(this, "../event/core", "emit");
var channel = {};
function forward({ subject, type, data }) {
return emit(channel, "data", { target: subject, type: type, data: data });
}
["popupshowing", "popuphiding", "popupshown", "popuphidden",
"document-element-inserted", "DOMContentLoaded", "load"
].forEach(type => events.on(type, forward));
exports.events = channel;

Просмотреть файл

@ -0,0 +1,451 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { Cc, Ci } = require("chrome");
const { Services } = require("resource://gre/modules/Services.jsm");
lazyRequire(this, "../timers", "setTimeout");
lazyRequire(this, "../system", "platform");
lazyRequire(this, "../window/utils", "getMostRecentBrowserWindow", "getOwnerBrowserWindow",
"getScreenPixelsPerCSSPixel");
lazyRequire(this, "../frame/utils", { "create": "createFrame" }, "swapFrameLoaders", "getDocShell");
lazyRequire(this, "../addon/window", { "window": "addonWindow" });
lazyRequire(this, "../lang/type", "isNil");
lazyRequire(this, '../self', "data");
lazyRequireModule(this, "../system/events", "events");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
position = position || {};
let x, y;
let hasTop = !isNil(position.top);
let hasRight = !isNil(position.right);
let hasBottom = !isNil(position.bottom);
let hasLeft = !isNil(position.left);
let hasWidth = !isNil(width);
let hasHeight = !isNil(height);
// if width is not specified by constructor or show's options, then get
// the default width
if (!hasWidth)
width = defaultWidth;
// if height is not specified by constructor or show's options, then get
// the default height
if (!hasHeight)
height = defaultHeight;
// default position is centered
x = (rect.right - width) / 2;
y = (rect.top + rect.bottom - height) / 2;
if (hasTop) {
y = rect.top + position.top;
if (hasBottom && !hasHeight)
height = rect.bottom - position.bottom - y;
}
else if (hasBottom) {
y = rect.bottom - position.bottom - height;
}
if (hasLeft) {
x = position.left;
if (hasRight && !hasWidth)
width = rect.right - position.right - x;
}
else if (hasRight) {
x = rect.right - width - position.right;
}
return {x: x, y: y, width: width, height: height};
}
function open(panel, options, anchor) {
// Wait for the XBL binding to be constructed
if (!panel.openPopup) setTimeout(open, 50, panel, options, anchor);
else display(panel, options, anchor);
}
exports.open = open;
function isOpen(panel) {
return panel.state === "open"
}
exports.isOpen = isOpen;
function isOpening(panel) {
return panel.state === "showing"
}
exports.isOpening = isOpening
function close(panel) {
// Sometimes "TypeError: panel.hidePopup is not a function" is thrown
// when quitting the host application while a panel is visible. To suppress
// these errors, check for "hidePopup" in panel before calling it.
// It's not clear if there's an issue or it's expected behavior.
// See Bug 1151796.
return panel.hidePopup && panel.hidePopup();
}
exports.close = close
function resize(panel, width, height) {
// Resize the iframe instead of using panel.sizeTo
// because sizeTo doesn't work with arrow panels
if (panel.firstChild) {
panel.firstChild.style.width = width + "px";
panel.firstChild.style.height = height + "px";
}
}
exports.resize = resize
function display(panel, options, anchor) {
let document = panel.ownerDocument;
let x, y;
let { width, height, defaultWidth, defaultHeight } = options;
let popupPosition = null;
// Panel XBL has some SDK incompatible styling decisions. We shim panel
// instances until proper fix for Bug 859504 is shipped.
shimDefaultStyle(panel);
if (!anchor) {
// The XUL Panel doesn't have an arrow, so the margin needs to be reset
// in order to, be positioned properly
panel.style.margin = "0";
let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
({x, y, width, height} = calculateRegion(options, viewportRect));
}
else {
// The XUL Panel has an arrow, so the margin needs to be reset
// to the default value.
panel.style.margin = "";
let { CustomizableUI, window } = anchor.ownerGlobal;
// In Australis, widgets may be positioned in an overflow panel or the
// menu panel.
// In such cases clicking this widget will hide the overflow/menu panel,
// and the widget's panel will show instead.
// If `CustomizableUI` is not available, it means the anchor is not in a
// chrome browser window, and therefore there is no need for this check.
if (CustomizableUI) {
let node = anchor;
({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window));
// if `node` is not the `anchor` itself, it means the widget is
// positioned in a panel, therefore we have to hide it before show
// the widget's panel in the same anchor
if (node !== anchor)
CustomizableUI.hidePanelForNode(anchor);
}
width = width || defaultWidth;
height = height || defaultHeight;
// Open the popup by the anchor.
let rect = anchor.getBoundingClientRect();
let zoom = getScreenPixelsPerCSSPixel(window);
let screenX = rect.left + window.mozInnerScreenX * zoom;
let screenY = rect.top + window.mozInnerScreenY * zoom;
// Set up the vertical position of the popup relative to the anchor
// (always display the arrow on anchor center)
let horizontal, vertical;
if (screenY > window.screen.availHeight / 2 + height)
vertical = "top";
else
vertical = "bottom";
if (screenY > window.screen.availWidth / 2 + width)
horizontal = "left";
else
horizontal = "right";
let verticalInverse = vertical == "top" ? "bottom" : "top";
popupPosition = vertical + "center " + verticalInverse + horizontal;
// Allow panel to flip itself if the panel can't be displayed at the
// specified position (useful if we compute a bad position or if the
// user moves the window and panel remains visible)
panel.setAttribute("flip", "both");
}
if (!panel.viewFrame) {
panel.viewFrame = document.importNode(panel.backgroundFrame, false);
panel.appendChild(panel.viewFrame);
let {privateBrowsingId} = getDocShell(panel.viewFrame).getOriginAttributes();
let principal = Services.scriptSecurityManager.createNullPrincipal({privateBrowsingId});
getDocShell(panel.viewFrame).createAboutBlankContentViewer(principal);
}
// Resize the iframe instead of using panel.sizeTo
// because sizeTo doesn't work with arrow panels
panel.firstChild.style.width = width + "px";
panel.firstChild.style.height = height + "px";
panel.openPopup(anchor, popupPosition, x, y);
}
exports.display = display;
// This utility function is just a workaround until Bug 859504 has shipped.
function shimDefaultStyle(panel) {
let document = panel.ownerDocument;
// Please note that `panel` needs to be part of document in order to reach
// it's anonymous nodes. One of the anonymous node has a big padding which
// doesn't work well since panel frame needs to fill all of the panel.
// XBL binding is a not the best option as it's applied asynchronously, and
// makes injected frames behave in strange way. Also this feels a lot
// cheaper to do.
["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) {
let node = document.getAnonymousElementByAttribute(panel, "class", value);
if (node) node.style.padding = 0;
});
}
function show(panel, options, anchor) {
// Prevent the panel from getting focus when showing up
// if focus is set to false
panel.setAttribute("noautofocus", !options.focus);
let window = anchor && getOwnerBrowserWindow(anchor);
let { document } = window ? window : getMostRecentBrowserWindow();
attach(panel, document);
open(panel, options, anchor);
}
exports.show = show
function onPanelClick(event) {
let { target, metaKey, ctrlKey, shiftKey, button } = event;
let accel = platform === "darwin" ? metaKey : ctrlKey;
let isLeftClick = button === 0;
let isMiddleClick = button === 1;
if ((isLeftClick && (accel || shiftKey)) || isMiddleClick) {
let link = target.closest('a');
if (link && link.href)
getMostRecentBrowserWindow().openUILink(link.href, event)
}
}
function setupPanelFrame(frame) {
frame.setAttribute("flex", 1);
frame.setAttribute("transparent", "transparent");
frame.setAttribute("autocompleteenabled", true);
frame.setAttribute("tooltip", "aHTMLTooltip");
if (platform === "darwin") {
frame.style.borderRadius = "var(--arrowpanel-border-radius, 3.5px)";
frame.style.padding = "1px";
}
}
function make(document, options) {
document = document || getMostRecentBrowserWindow().document;
let panel = document.createElementNS(XUL_NS, "panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("sdkscriptenabled", options.allowJavascript);
// The panel needs to be attached to a browser window in order for us
// to copy browser styles to the content document when it loads.
attach(panel, document);
let frameOptions = {
allowJavascript: options.allowJavascript,
allowPlugins: true,
allowAuth: true,
allowWindowControl: false,
// Need to override `nodeName` to use `iframe` as `browsers` save session
// history and in consequence do not dispatch "inner-window-destroyed"
// notifications.
browser: false,
};
let backgroundFrame = createFrame(addonWindow, frameOptions);
setupPanelFrame(backgroundFrame);
getDocShell(backgroundFrame).inheritPrivateBrowsingId = false;
function onPopupShowing({type, target}) {
if (target === this) {
let attrs = getDocShell(backgroundFrame).getOriginAttributes();
getDocShell(panel.viewFrame).setOriginAttributes(attrs);
swapFrameLoaders(backgroundFrame, panel.viewFrame);
}
}
function onPopupHiding({type, target}) {
if (target === this) {
swapFrameLoaders(backgroundFrame, panel.viewFrame);
panel.viewFrame.remove();
panel.viewFrame = null;
}
}
function onContentReady({target, type}) {
if (target === getContentDocument(panel)) {
style(panel);
events.emit(type, { subject: panel });
}
}
function onContentLoad({target, type}) {
if (target === getContentDocument(panel))
events.emit(type, { subject: panel });
}
function onContentChange({subject: document, type}) {
if (document === getContentDocument(panel) && document.defaultView)
events.emit(type, { subject: panel });
}
function onPanelStateChange({target, type}) {
if (target === this)
events.emit(type, { subject: panel })
}
panel.addEventListener("popupshowing", onPopupShowing);
panel.addEventListener("popuphiding", onPopupHiding);
for (let event of ["popupshowing", "popuphiding", "popupshown", "popuphidden"])
panel.addEventListener(event, onPanelStateChange);
panel.addEventListener("click", onPanelClick);
// Panel content document can be either in panel `viewFrame` or in
// a `backgroundFrame` depending on panel state. Listeners are set
// on both to avoid setting and removing listeners on panel state changes.
panel.addEventListener("DOMContentLoaded", onContentReady, true);
backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);
panel.addEventListener("load", onContentLoad, true);
backgroundFrame.addEventListener("load", onContentLoad, true);
events.on("document-element-inserted", onContentChange);
panel.backgroundFrame = backgroundFrame;
panel.viewFrame = null;
// Store event listener on the panel instance so that it won't be GC-ed
// while panel is alive.
panel.onContentChange = onContentChange;
return panel;
}
exports.make = make;
function attach(panel, document) {
document = document || getMostRecentBrowserWindow().document;
let container = document.getElementById("mainPopupSet");
if (container !== panel.parentNode) {
detach(panel);
document.getElementById("mainPopupSet").appendChild(panel);
}
}
exports.attach = attach;
function detach(panel) {
if (panel.parentNode) panel.remove();
}
exports.detach = detach;
function dispose(panel) {
panel.backgroundFrame.remove();
panel.backgroundFrame = null;
events.off("document-element-inserted", panel.onContentChange);
panel.onContentChange = null;
detach(panel);
}
exports.dispose = dispose;
function style(panel) {
/**
Injects default OS specific panel styles into content document that is loaded
into given panel. Optionally `document` of the browser window can be
given to inherit styles from it, by default it will use either panel owner
document or an active browser's document. It should not matter though unless
Firefox decides to style windows differently base on profile or mode like
chrome for example.
**/
try {
let document = panel.ownerDocument;
let contentDocument = getContentDocument(panel);
let window = document.defaultView;
let node = document.getAnonymousElementByAttribute(panel, "class",
"panel-arrowcontent");
let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
let style = contentDocument.createElement("style");
style.id = "sdk-panel-style";
style.textContent = "body { " +
"color: " + color + ";" +
"font-family: " + fontFamily + ";" +
"font-weight: " + fontWeight + ";" +
"font-size: " + fontSize + ";" +
"}";
let container = contentDocument.head ? contentDocument.head :
contentDocument.documentElement;
if (container.firstChild)
container.insertBefore(style, container.firstChild);
else
container.appendChild(style);
}
catch (error) {
console.error("Unable to apply panel style");
console.exception(error);
}
}
exports.style = style;
var getContentFrame = panel => panel.viewFrame || panel.backgroundFrame;
exports.getContentFrame = getContentFrame;
function getContentDocument(panel) {
return getContentFrame(panel).contentDocument;
}
exports.getContentDocument = getContentDocument;
function setURL(panel, url) {
let frame = getContentFrame(panel);
let webNav = getDocShell(frame).QueryInterface(Ci.nsIWebNavigation);
webNav.loadURI(url ? data.url(url) : "about:blank", 0, null, null, null);
}
exports.setURL = setURL;
function allowContextMenu(panel, allow) {
if (allow) {
panel.setAttribute("context", "contentAreaContextMenu");
}
else {
panel.removeAttribute("context");
}
}
exports.allowContextMenu = allowContextMenu;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше